Initial commit: Odoomates Odoo packages (12 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:49:21 +02:00
commit 3b38c49bf0
526 changed files with 34983 additions and 0 deletions

View file

@ -0,0 +1,47 @@
# Customer Follow Up Management
Customer FollowUp Management
## Installation
```bash
pip install odoo-bringout-odoomates-om_account_followup
```
## Dependencies
This addon depends on:
- account
- mail
## Manifest Information
- **Name**: Customer Follow Up Management
- **Version**: 16.0.1.0.1
- **Category**: Accounting
- **License**: LGPL-3
- **Installable**: True
## Source
Custom addon from bringout-odoomates vendor, addon `om_account_followup`.
## License
This package maintains the original LGPL-3 license from the addon.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph Om_account_followup Module - om_account_followup
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for om_account_followup. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

View file

@ -0,0 +1,6 @@
# Dependencies
This addon depends on:
- [account](../../odoo-bringout-oca-ocb-account)
- [mail](../../odoo-bringout-oca-ocb-mail)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon om_account_followup or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-odoomates-om_account_followup"
# or
uv pip install odoo-bringout-odoomates-om_account_followup"
```

View file

@ -0,0 +1,17 @@
# Models
Detected core models and extensions in om_account_followup.
```mermaid
classDiagram
class followup_followup
class followup_line
class followup_stat_by_partner
class account_move_line
class res_config_settings
class res_partner
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: om_account_followup. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon om_account_followup
- License: LGPL-3

View file

@ -0,0 +1,30 @@
# Reports
Report definitions and templates in om_account_followup.
```mermaid
classDiagram
class ReportFollowup
AbstractModel <|-- ReportFollowup
class AccountFollowupStat
Model <|-- AccountFollowupStat
```
## Available Reports
### Analytical/Dashboard Reports
- **Follow-ups Analysis** (Analysis/Dashboard)
## Report Files
- **followup_print.py** (Python logic)
- **followup_report.py** (Python logic)
- **followup_report.xml** (XML template/definition)
- **__init__.py** (Python logic)
## Notes
- Named reports above are accessible through Odoo's reporting menu
- Python files define report logic and data processing
- XML files contain report templates, definitions, and formatting
- Reports are integrated with Odoo's printing and email systems

View file

@ -0,0 +1,41 @@
# Security
Access control and security definitions in om_account_followup.
## Access Control Lists (ACLs)
Model access permissions defined in:
- **[ir.model.access.csv](../om_account_followup/security/ir.model.access.csv)**
- 9 model access rules
## Record Rules
Row-level security rules defined in:
## Security Groups & Configuration
Security groups and permissions defined in:
- **[security.xml](../om_account_followup/security/security.xml)**
```mermaid
graph TB
subgraph "Security Layers"
A[Users] --> B[Groups]
B --> C[Access Control Lists]
C --> D[Models]
B --> E[Record Rules]
E --> F[Individual Records]
end
```
Security files overview:
- **[ir.model.access.csv](../om_account_followup/security/ir.model.access.csv)**
- Model access permissions (CRUD rights)
- **[security.xml](../om_account_followup/security/security.xml)**
- Security groups, categories, and XML-based rules
Notes
- Access Control Lists define which groups can access which models
- Record Rules provide row-level security (filter records by user/group)
- Security groups organize users and define permission sets
- All security is enforced at the ORM level by Odoo

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon om_account_followup
```

View file

@ -0,0 +1,9 @@
# Wizards
Transient models exposed as UI wizards in om_account_followup.
```mermaid
classDiagram
class FollowupPrint
class FollowupSendingResults
```

View file

@ -0,0 +1,45 @@
=============================
Customer Follow Up Management
=============================
This Module will add customer follow up management in Odoo 16 Community Edition
Installation
============
To install this module, you need to:
Download the module and add it to your Odoo addons folder. Afterward, log on to
your Odoo server and go to the Apps menu. Trigger the debug mode and update the
list by clicking on the "Update Apps List" link. Now install the module by
clicking on the install button.
Upgrade
============
To upgrade this module, you need to:
Download the module and add it to your Odoo addons folder. Restart the server
and log on to your Odoo server. Select the Apps menu and upgrade the module by
clicking on the upgrade button.
Configuration
=============
Configure follow up levels
Credits
=======
Contributors
------------
* Odoo Mates <odoomates@gmail.com>
Author & Maintainer
-------------------
This module is maintained by the Odoo Mates

View file

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from . import wizard
from . import models
from . import report

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
{
'name': 'Customer Follow Up Management',
'version': '16.0.1.0.1',
'category': 'Accounting',
'description': """Customer FollowUp Management""",
'summary': """Customer FollowUp Management""",
'author': 'Odoo Mates, Odoo S.A',
'license': 'LGPL-3',
'website': 'https://www.odoomates.tech',
'depends': ['account', 'mail'],
'data': [
'security/security.xml',
'security/ir.model.access.csv',
'data/data.xml',
'wizard/followup_print_view.xml',
'wizard/followup_results_view.xml',
'views/followup_view.xml',
'views/account_move.xml',
'views/partners.xml',
'views/report_followup.xml',
'views/reports.xml',
'views/followup_partner_view.xml',
'report/followup_report.xml',
],
'demo': ['demo/demo.xml'],
'images': ['static/description/banner.png'],
'installable': True,
'auto_install': False,
}

View file

@ -0,0 +1,243 @@
<?xml version="1.0"?>
<odoo>
<data noupdate="1">
<record id="email_template_om_account_followup_level0" model="mail.template">
<field name="name">First polite payment follow-up reminder email</field>
<field name="email_from">${(user.email or '')|safe}</field>
<field name="subject">${user.company_id.name} Payment Reminder</field>
<field name="email_to">${object.email|safe}</field>
<field name="lang">${object.lang}</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="auto_delete" eval="True"/>
<field name="body_html"><![CDATA[
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
<p>Dear ${object.name},</p>
<p>
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.
</p>
<br/>
Best Regards,
<br/>
<br/>
${user.name}
<br/>
<br/>
${object.get_followup_table_html() | safe}
<br/>
</div>
]]></field>
</record>
<!--Mail template level 1 -->
<record id="email_template_om_account_followup_level1" model="mail.template">
<field name="name">A bit urging second payment follow-up reminder email</field>
<field name="email_from">${(user.email or '')|safe}</field>
<field name="subject">${user.company_id.name} Payment Reminder</field>
<field name="email_to">${object.email|safe}</field>
<field name="lang">${object.lang}</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="auto_delete" eval="True"/>
<field name="body_html"><![CDATA[
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
<p>Dear ${object.name},</p>
<p>
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.
</p>
<br/>
Best Regards,
<br/>
<br/>
${user.name}
<br/>
<br/>
${object.get_followup_table_html() | safe}
<br/>
</div>
]]></field>
</record>
<!--Mail template level 2 -->
<record id="email_template_om_account_followup_level2" model="mail.template">
<field name="name">Urging payment follow-up reminder email</field>
<field name="email_from">${(user.email or '')|safe}</field>
<field name="subject">${user.company_id.name} Payment Reminder</field>
<field name="email_to">${object.email|safe}</field>
<field name="lang">${object.lang}</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="auto_delete" eval="True"/>
<field name="body_html"><![CDATA[
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
<p>Dear ${object.name},</p>
<p>
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.
</p>
<br/>
Best Regards,
<br/>
<br/>
${user.name}
<br/>
<br/>
${object.get_followup_table_html() | safe}
<br/>
</div>
]]></field>
</record>
<!-- Default follow up message -->
<record id="email_template_om_account_followup_default" model="mail.template">
<field name="name">Default payment follow-up reminder e-mail</field>
<field name="email_from">${(user.email or '')|safe}</field>
<field name="subject">${user.company_id.name} Payment Reminder</field>
<field name="email_to">${object.email|safe}</field>
<field name="lang">${object.lang}</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="auto_delete" eval="True"/>
<field name="body_html"><![CDATA[
<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, 255, 255); ">
<p>Dear ${object.name},</p>
<p>
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.
</p>
<br/>
Best Regards,
<br/>
<br/>
${user.name}
<br/>
<br/>
${object.get_followup_table_html() | safe}
<br/>
</div>
]]></field>
</record>
<record id="demo_followup1" model="followup.followup" forcecreate="False">
<field name="company_id" ref="base.main_company"/>
</record>
<record id="demo_followup_line1" model="followup.line" forcecreate="False">
<field name="name">Send first reminder email</field>
<field name="sequence">0</field>
<field name="delay">15</field>
<field name="followup_id" ref="demo_followup1"/>
<field name="send_email">True</field>
<field name="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,
</field>
<field name="email_template_id" ref="email_template_om_account_followup_level0"/>
</record>
<record id="demo_followup_line2" model="followup.line" forcecreate="False">
<field name="name">Send reminder letter and email</field>
<field name="sequence">1</field>
<field name="delay">30</field>
<field name="followup_id" ref="demo_followup1"/>
<field name="email_template_id"
ref="email_template_om_account_followup_level1"/>
<field name="send_email">True</field>
<field name="send_letter">True</field>
<field name="description">
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,
</field>
</record>
<record id="demo_followup_line3" model="followup.line" forcecreate="False">
<field name="name">Call the customer on the phone</field>
<field name="sequence">3</field>
<field name="delay">40</field>
<field name="followup_id" ref="demo_followup1"/>
<field name="email_template_id"
ref="email_template_om_account_followup_level2"/>
<field eval="False" name="send_email"/>
<field name="manual_action">True</field>
<field name="manual_action_note">Call the customer on the phone!</field>
<field name="description">
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,
</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="demo_followup_line4" model="followup.line">
<field name="name">Urging reminder email</field>
<field name="sequence">4</field>
<field name="delay">50</field>
<field name="followup_id" ref="demo_followup1"/>
<field name="send_email">True</field>
<field name="email_template_id" ref="email_template_om_account_followup_level2"/>
<field name="description">
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,
</field>
</record>
<record id="demo_followup_line5" model="followup.line">
<field name="name">Urging reminder letter</field>
<field name="sequence">5</field>
<field name="delay">60</field>
<field name="followup_id" ref="demo_followup1"/>
<field eval="False" name="send_email"/>
<field name="send_letter">True</field>
<field name="email_template_id" ref="email_template_om_account_followup_level2"/>
<field name="description">
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,
</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,9 @@
## Module <om_account_followup>
#### 22.07.2022
#### Version 16.0.1.0.0
##### ADD
- initial release

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from . import account_move
from . import followup
from . import followup_partner
from . import partner
from . import settings

View file

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
class AccountMoveLine(models.Model):
_inherit = 'account.move.line'
followup_line_id = fields.Many2one('followup.line', 'Follow-up Level')
followup_date = fields.Date('Latest Follow-up')
result = fields.Float(compute='_get_result', string="Balance Amount")
def _get_result(self):
for aml in self:
aml.result = aml.debit - aml.credit

View file

@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
class FollowupFollowup(models.Model):
_name = 'followup.followup'
_description = 'Account Follow-up'
_rec_name = 'name'
name = fields.Char(string="Name", related='company_id.name', readonly=True)
followup_line = fields.One2many('followup.line', 'followup_id', 'Follow-up', copy=True)
company_id = fields.Many2one('res.company', 'Company', required=True, default=lambda self: self.env.company)
_sql_constraints = [('company_uniq', 'unique(company_id)',
'Only one follow-up per company is allowed')]
class FollowupLine(models.Model):
_name = 'followup.line'
_description = 'Follow-up Criteria'
_order = 'delay'
def _compute_sequence(self):
delays = [line.delay for line in self.followup_id.followup_line]
delays.sort()
for line in self.followup_id.followup_line:
sequence = delays.index(line.delay)
line.sequence = sequence+1
@api.model
def default_get(self, default_fields):
values = super(FollowupLine, self).default_get(default_fields)
if self.env.ref('om_account_followup.email_template_om_account_followup_default'):
values['email_template_id'] = self.env.ref('om_account_followup.email_template_om_account_followup_default').id
return values
name = fields.Char('Follow-Up Action', required=True)
sequence = fields.Integer('Sequence', compute='_compute_sequence',
store=False,
help="Gives the sequence order when displaying a list of follow-up lines.")
followup_id = fields.Many2one('followup.followup', 'Follow Ups',
required=True, ondelete="cascade")
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)
description = fields.Text('Printed Message', translate=True, default="""
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_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('Manual Action', 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')
manual_action_responsible_id = fields.Many2one('res.users',
string='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')]
@api.constrains('description')
def _check_description(self):
for line in self:
if line.description:
try:
line.description % {'partner_name': '', 'date': '',
'user_signature': '',
'company_name': ''}
except ValidationError:
raise ValidationError(
_('Your description is invalid, use the right legend '
'or %% if you want to use the percent character.'))

View file

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
from odoo import tools
class FollowupStatByPartner(models.Model):
_name = "followup.stat.by.partner"
_description = "Follow-up Statistics by Partner"
_rec_name = 'partner_id'
_auto = False
def _get_invoice_partner_id(self):
for rec in self:
rec.invoice_partner_id = rec.partner_id.address_get(
adr_pref=['invoice']).get('invoice', rec.partner_id.id)
partner_id = fields.Many2one('res.partner', '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 follow-up', readonly=True)
max_followup_id = fields.Many2one('followup.line', 'Max Follow Up Level', readonly=True, ondelete="cascade")
balance = fields.Float('Balance', readonly=True)
company_id = fields.Many2one('res.company', 'Company', readonly=True)
invoice_partner_id = fields.Many2one('res.partner', compute='_get_invoice_partner_id', string='Invoice Address')
@api.model
def init(self):
tools.drop_view_if_exists(self._cr, 'followup_stat_by_partner')
self._cr.execute("""
create view followup_stat_by_partner as (
SELECT
l.partner_id * 10000::bigint + l.company_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 max_followup_id,
sum(l.debit - l.credit) AS balance,
l.company_id as company_id
FROM
account_move_line l
LEFT JOIN account_account a ON (l.account_id = a.id)
WHERE
a.account_type = 'asset_receivable' AND
l.full_reconcile_id is NULL AND
l.partner_id IS NOT NULL
GROUP BY
l.partner_id, l.company_id
)""")

View file

@ -0,0 +1,374 @@
# -*- coding: utf-8 -*-
from functools import reduce
from lxml import etree
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from odoo.tools.misc import formatLang
class ResPartner(models.Model):
_inherit = "res.partner"
def fields_view_get(self, view_id=None, view_type='form', toolbar=False,
submenu=False):
res = super(ResPartner, self).fields_view_get(
view_id=view_id, view_type=view_type, toolbar=toolbar,
submenu=submenu)
if view_type == 'form' and self.env.context.get('Followupfirst'):
doc = etree.XML(res['arch'], parser=None, base_url=None)
first_node = doc.xpath("//page[@name='followup_tab']")
root = first_node[0].getparent()
root.insert(0, first_node[0])
res['arch'] = etree.tostring(doc, encoding="utf-8")
return res
def _get_latest(self):
company = self.env.user.company_id
for partner in self:
amls = partner.unreconciled_aml_ids
latest_date = False
latest_level = False
latest_days = False
latest_level_without_lit = False
latest_days_without_lit = False
for aml in amls:
aml_followup = aml.followup_line_id
if (aml.company_id == company) and aml_followup and \
(not latest_days or latest_days < aml_followup.delay):
latest_days = aml_followup.delay
latest_level = aml_followup.id
if (aml.company_id == company) and aml.followup_date and (
not latest_date or latest_date < aml.followup_date):
latest_date = aml.followup_date
if (aml.company_id == company) and not aml.blocked and \
(aml_followup and (not latest_days_without_lit or
latest_days_without_lit < aml_followup.delay)):
latest_days_without_lit = aml_followup.delay
latest_level_without_lit = aml_followup.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
def do_partner_manual_action_dermanord(self, followup_line):
action_text = followup_line.manual_action_note or ''
action_date = self.payment_next_action_date or \
fields.Date.today()
if self.payment_responsible_id:
responsible_id = self.payment_responsible_id.id
else:
p = followup_line.manual_action_responsible_id
responsible_id = p and p.id or False
self.write({'payment_next_action_date': action_date,
'payment_next_action': action_text,
'payment_responsible_id': responsible_id})
def do_partner_manual_action(self, partner_ids):
for partner in self.browse(partner_ids):
followup_without_lit = partner.latest_followup_level_id_without_lit
if partner.payment_next_action:
action_text = \
(partner.payment_next_action or '') + "\n" + \
(followup_without_lit.manual_action_note or '')
else:
action_text = followup_without_lit.manual_action_note or ''
action_date = partner.payment_next_action_date or \
fields.Date.today()
if partner.payment_responsible_id:
responsible_id = partner.payment_responsible_id.id
else:
p = followup_without_lit.manual_action_responsible_id
responsible_id = p and p.id or False
partner.write({'payment_next_action_date': action_date,
'payment_next_action': action_text,
'payment_responsible_id': responsible_id})
def do_partner_print(self, wizard_partner_ids, data):
if not wizard_partner_ids:
return {}
data['partner_ids'] = wizard_partner_ids
datas = {
'ids': wizard_partner_ids,
'model': 'followup.followup',
'form': data
}
return self.env.ref(
'om_account_followup.action_report_followup').report_action(
self, data=datas)
def do_partner_mail(self):
ctx = self.env.context.copy()
ctx['followup'] = True
template = 'om_account_followup.email_template_om_account_followup_default'
unknown_mails = 0
for partner in self:
partners_to_email = [child for child in partner.child_ids if
child.type == 'invoice' and child.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.with_context(ctx).send_mail(
partner_to_email.id)
else:
mail_template_id = self.env.ref(template)
mail_template_id.with_context(ctx).send_mail(
partner_to_email.id)
if partner not in partners_to_email:
partner.message_post(body=_(
'Overdue email sent to %s' % ', '.join(
['%s <%s>' % (partner.name, partner.email) for
partner in partners_to_email])))
else:
unknown_mails = unknown_mails + 1
action_text = _("Email not sent because of email address "
"of partner not filled in")
if partner.payment_next_action_date:
payment_action_date = min(
fields.Date.today(),
partner.payment_next_action_date)
else:
payment_action_date = fields.Date.today()
if partner.payment_next_action:
payment_next_action = \
partner.payment_next_action + " \n " + action_text
else:
payment_next_action = action_text
partner.with_context(ctx).write(
{'payment_next_action_date': payment_action_date,
'payment_next_action': payment_next_action})
return unknown_mails
def get_followup_table_html(self):
self.ensure_one()
partner = self.commercial_partner_id
followup_table = ''
if partner.unreconciled_aml_ids:
company = self.env.user.company_id
current_date = fields.Date.today()
report = self.env['report.om_account_followup.report_followup']
final_res = report._lines_get_with_partner(partner, company.id)
for currency_dict in final_res:
currency = currency_dict.get('line', [
{'currency_id': company.currency_id}])[0]['currency_id']
followup_table += '''
<table border="2" width=100%%>
<tr>
<td>''' + _("Invoice Date") + '''</td>
<td>''' + _("Description") + '''</td>
<td>''' + _("Reference") + '''</td>
<td>''' + _("Due Date") + '''</td>
<td>''' + _("Amount") + " (%s)" % (
currency.symbol) + '''</td>
<td>''' + _("Lit.") + '''</td>
</tr>
'''
total = 0
for aml in currency_dict['line']:
block = aml['blocked'] and 'X' or ' '
total += aml['balance']
strbegin = "<TD>"
strend = "</TD>"
date = aml['date_maturity'] or aml['date']
if date <= current_date and aml['balance'] > 0:
strbegin = "<TD><B>"
strend = "</B></TD>"
followup_table += "<TR>" + strbegin + str(aml['date']) + \
strend + strbegin + aml['name'] + \
strend + strbegin + \
(aml['ref'] or '') + strend + \
strbegin + str(date) + strend + \
strbegin + str(aml['balance']) + \
strend + strbegin + block + \
strend + "</TR>"
total = reduce(lambda x, y: x + y['balance'],
currency_dict['line'], 0.00)
total = formatLang(self.env, total, currency_obj=currency)
followup_table += '''<tr> </tr>
</table>
<center>''' + _(
"Amount due") + ''' : %s </center>''' % (total)
return followup_table
def write(self, vals):
if vals.get("payment_responsible_id", False):
for part in self:
if part.payment_responsible_id != \
self.env['res.users'].browse(vals["payment_responsible_id"]):
# Find partner_id of user put as responsible
responsible_partner_id = self.env["res.users"].browse(
vals['payment_responsible_id']).partner_id.id
part.message_post(
body=_("You became responsible to do the next action "
"for the payment follow-up of") +
" <b><a href='#id=" + str(part.id) +
"&view_type=form&model=res.partner'> " + part.name +
" </a></b>",
type='comment',
context=self.env.context,
partner_ids=[responsible_partner_id])
return super(ResPartner, self).write(vals)
def action_done(self):
return self.write({'payment_next_action_date': False,
'payment_next_action': '',
'payment_responsible_id': False})
def do_button_print(self):
self.ensure_one()
company_id = self.env.user.company_id.id
if not self.env['account.move.line'].search(
[('partner_id', '=', self.id),
('account_id.account_type', '=', 'asset_receivable'),
('full_reconcile_id', '=', False),
('company_id', '=', company_id),
'|', ('date_maturity', '=', False),
('date_maturity', '<=', fields.Date.today())]):
raise ValidationError(
_("The partner does not have any accounting entries to "
"print in the overdue report for the current company."))
self.message_post(body=_('Printed overdue payments report'))
self.message_post(body=_('Printed overdue payments report'))
wizard_partner_ids = [self.id * 10000 + company_id]
followup_ids = self.env['followup.followup'].search(
[('company_id', '=', company_id)])
if not followup_ids:
raise ValidationError(_(
"There is no followup plan defined for the current company."))
data = {
'date': fields.date.today(),
'followup_id': followup_ids[0].id,
}
return self.do_partner_print(wizard_partner_ids, data)
def _get_amounts_and_date(self):
company = self.env.user.company_id
current_date = fields.Date.today()
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
def _get_followup_overdue_query(self, args, overdue_only=False):
company_id = self.env.user.company_id.id
having_where_clause = ' AND '.join(
map(lambda x: '(SUM(bal2) %s %%s)' % (x[1]), args))
having_values = [x[2] for x in args]
having_where_clause = having_where_clause % (having_values[0])
overdue_only_str = overdue_only 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
LEFT JOIN account_account a ON a.id = l.account_id
WHERE a.account_type = 'asset_receivable'
%s AND full_reconcile_id IS NULL
AND l.company_id = %s) AS l
RIGHT JOIN res_partner p
ON p.id = partner_id ) AS pl
GROUP BY pid HAVING %s''') % (
overdue_only_str, company_id, having_where_clause)
def _payment_overdue_search(self, operator, operand):
args = [('payment_amount_overdue', operator, operand)]
query = self._get_followup_overdue_query(args, overdue_only=True)
self._cr.execute(query)
res = self._cr.fetchall()
if not res:
return [('id', '=', '0')]
return [('id', 'in', [x[0] for x in res])]
def _payment_earliest_date_search(self, operator, operand):
args = [('payment_earliest_due_date', operator, operand)]
company_id = self.env.user.company_id.id
having_where_clause = ' AND '.join(
map(lambda x: "(MIN(l.date_maturity) %s '%%s')" % (x[1]), args))
having_values = [x[2] for x in args]
having_where_clause = having_where_clause % (having_values[0])
query = """SELECT partner_id FROM account_move_line l
LEFT JOIN account_account a ON a.id = l.account_id
WHERE a.account_type = 'asset_receivable'
AND l.company_id = %s
AND l.full_reconcile_id IS NULL
AND partner_id IS NOT NULL GROUP BY partner_id"""
query = query % (company_id)
if having_where_clause:
query += ' HAVING %s ' % (having_where_clause)
self._cr.execute(query)
res = self._cr.fetchall()
if not res:
return [('id', '=', '0')]
return [('id', 'in', [x[0] for x in res])]
def _payment_due_search(self, operator, operand):
args = [('payment_amount_due', operator, operand)]
query = self._get_followup_overdue_query(args, overdue_only=False)
self._cr.execute(query)
res = self._cr.fetchall()
if not res:
return [('id', '=', '0')]
return [('id', 'in', [x[0] for x in res])]
def _get_partners(self):
partners = set()
for aml in self:
if aml.partner_id:
partners.add(aml.partner_id.id)
return list(partners)
payment_responsible_id = fields.Many2one('res.users', ondelete='set null',
string='Follow-up Responsible',
tracking=True, copy=False,
help="Optionally you can assign a user to this field, which will make "
"him responsible for the action.", )
payment_note = fields.Text('Customer Payment Promise', help="Payment Note", copy=False)
payment_next_action = fields.Text('Next Action', copy=False, tracking=True,
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. ")
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',
domain=[('full_reconcile_id', '=', False),
('account_id.account_type', '=', 'asset_receivable')])
latest_followup_date = fields.Date(compute='_get_latest', string="Latest Follow-up Date", compute_sudo=True,
help="Latest date that the follow-up level of the partner was changed")
latest_followup_level_id = fields.Many2one('followup.line', compute='_get_latest', compute_sudo=True,
string="Latest Follow-up Level", help="The maximum follow-up level")
latest_followup_sequence = fields.Integer('Sequence', help="Gives the sequence order when displaying a list of follow-up lines.", default=0)
latest_followup_level_id_without_lit = fields.Many2one('followup.line',
compute='_get_latest', store=True, compute_sudo=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='_get_amounts_and_date',
string="Amount Due", search='_payment_due_search')
payment_amount_overdue = fields.Float(compute='_get_amounts_and_date',
string="Amount Overdue", search='_payment_overdue_search')
payment_earliest_due_date = fields.Date(compute='_get_amounts_and_date', string="Worst Due Date",
search='_payment_earliest_date_search')

View file

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
class AccountConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
def open_followup_level_form(self):
res_ids = self.env['followup.followup'].search([], limit=1)
return {
'type': 'ir.actions.act_window',
'name': 'Follow-up Levels',
'res_model': 'followup.followup',
'res_id': res_ids and res_ids.id or False,
'view_mode': 'form,tree',
}

View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import followup_print
from . import followup_report

View file

@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
import time
from collections import defaultdict
from odoo.exceptions import ValidationError
from odoo import api, fields, models, _
from odoo.tools import format_date
class ReportFollowup(models.AbstractModel):
_name = 'report.om_account_followup.report_followup'
_description = 'Report Followup'
@api.model
def _get_report_values(self, docids, data=None):
model = self.env['followup.sending.results']
ids = self.env.context.get('active_ids') or False
docs = model.browse(ids)
return {
'docs': docs,
'doc_ids': docids,
'doc_model': model,
'time': time,
'ids_to_objects': self._ids_to_objects,
'getLines': self._lines_get,
'get_text': self._get_text,
'data': data and data['form'] or {}}
def _ids_to_objects(self, ids):
all_lines = []
for line in self.env['followup.stat.by.partner'].browse(ids):
if line not in all_lines:
all_lines.append(line)
return all_lines
def _lines_get(self, stat_by_partner_line):
return self._lines_get_with_partner(stat_by_partner_line.partner_id,
stat_by_partner_line.company_id.id)
def _lines_get_with_partner(self, partner, company_id):
moveline_obj = self.env['account.move.line']
moveline_ids = moveline_obj.search(
[('partner_id', '=', partner.id),
('account_id.account_type', '=', 'asset_receivable'),
('full_reconcile_id', '=', False),
('company_id', '=', company_id),
'|', ('date_maturity', '=', False),
('date_maturity', '<=', fields.Date.today())])
lines_per_currency = defaultdict(list)
total = 0
for line in moveline_ids:
currency = line.currency_id or line.company_id.currency_id
balance = line.debit - line.credit
if currency != line.company_id.currency_id:
balance = line.amount_currency
line_data = {
'name': line.move_id.name,
'ref': line.ref,
'date': format_date(self.env, line.date),
'date_maturity': format_date(self.env, line.date_maturity),
'balance': balance,
'blocked': line.blocked,
'currency_id': currency,
}
total = total + line_data['balance']
lines_per_currency[currency].append(line_data)
return [{'total': total, 'line': lines, 'currency': currency} for
currency, lines in
lines_per_currency.items()]
def _get_text(self, stat_line, followup_id, context=None):
fp_obj = self.env['followup.followup']
fp_line = fp_obj.browse(followup_id).followup_line
if not fp_line:
raise ValidationError(
_("The followup plan defined for the current company does not "
"have any followup action."))
default_text = ''
li_delay = []
for line in fp_line:
if not default_text and line.description:
default_text = line.description
li_delay.append(line.delay)
li_delay.sort(reverse=True)
partner_line_ids = self.env['account.move.line'].search(
[('partner_id', '=', stat_line.partner_id.id),
('full_reconcile_id', '=', False),
('company_id', '=', stat_line.company_id.id),
('blocked', '=', False),
('debit', '!=', False),
('account_id.account_type', '=', 'asset_receivable'),
('followup_line_id', '!=', False)])
partner_max_delay = 0
partner_max_text = ''
for i in partner_line_ids:
if i.followup_line_id.delay > partner_max_delay and \
i.followup_line_id.description:
partner_max_delay = i.followup_line_id.delay
partner_max_text = i.followup_line_id.description
text = partner_max_delay and partner_max_text or default_text
if text:
lang_obj = self.env['res.lang']
lang_ids = lang_obj.search(
[('code', '=', stat_line.partner_id.lang)], limit=1)
date_format = lang_ids and lang_ids.date_format or '%Y-%m-%d'
text = text % {
'partner_name': stat_line.partner_id.name,
'date': time.strftime(date_format),
'company_name': stat_line.company_id.name,
'user_signature': self.env.user.signature or '',
}
return text

View file

@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models
from odoo import tools
class AccountFollowupStat(models.Model):
_name = "followup.stat"
_description = "Follow-up Statistics"
_rec_name = 'partner_id'
_order = 'date_move'
_auto = False
partner_id = fields.Many2one('res.partner', '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('followup.line', 'Follow Ups', readonly=True, ondelete="cascade")
balance = fields.Float('Balance', readonly=True)
debit = fields.Float('Debit', readonly=True)
credit = fields.Float('Credit', readonly=True)
company_id = fields.Many2one('res.company', 'Company', readonly=True)
blocked = fields.Boolean('Blocked', readonly=True)
@api.model
def init(self):
tools.drop_view_if_exists(self._cr, 'followup_stat')
self._cr.execute("""
create or replace view 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.account_type = 'asset_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
)""")

View file

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_om_account_followup_stat_graph" model="ir.ui.view">
<field name="name">followup.stat.graph</field>
<field name="model">followup.stat</field>
<field name="arch" type="xml">
<graph string="Follow-up lines">
<field name="followup_id" type="row"/>
<field name="date_followup" type="col"/>
<field name="balance" type="measure"/>
</graph>
</field>
</record>
<record id="view_om_account_followup_stat_search" model="ir.ui.view">
<field name="name">followup.stat.search</field>
<field name="model">followup.stat</field>
<field name="arch" type="xml">
<search string="Follow-ups Sent">
<field name="date_move"/>
<field name="date_move_last"/>
<separator/>
<filter string="Not Litigation" name="not_litigation"
domain="[('blocked','=', False)]"
help="Including journal entries marked as a litigation"/>
<field name="partner_id"/>
<field name="balance"/>
<group expand="1" string="Group By">
<filter string="Partner" name="partner"
context="{'group_by':'partner_id'}"/>
<filter string="Litigation" name="litigation"
context="{'group_by':'blocked'}"/>
<filter string="Follow-up Level" name="followup_level"
context="{'group_by':'followup_id'}"/>
<filter string="Company" name="company"
groups="base.group_multi_company"
context="{'group_by':'company_id'}"/>
<separator/>
<filter string="Latest Follow-up Month" name="lastest_month"
context="{'group_by':'date_followup:month'}"/>
</group>
</search>
</field>
</record>
<record id="action_followup_stat" model="ir.actions.act_window">
<field name="name">Follow-ups Analysis</field>
<field name="res_model">followup.stat</field>
<field name="view_mode">graph</field>
<field name="context">{'search_default_followup_level':1}</field>
<field name="search_view_id" ref="view_om_account_followup_stat_search"/>
</record>
<menuitem action="action_followup_stat"
id="menu_action_followup_stat_follow"
parent="om_account_followup.menu_finance_followup"
groups="account.group_account_invoice"
name="Follow-ups Analysis"
sequence="20"/>
</data>
</odoo>

View file

@ -0,0 +1,11 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_followup_followup_line,followup.followup.line,model_followup_line,account.group_account_invoice,1,0,0,0
access_followup_followup_line_manager,followup.followup.line.manager,model_followup_line,account.group_account_manager,1,1,1,1
access_followup_followup_accountant,followup.followup.user,model_followup_followup,account.group_account_invoice,1,0,0,0
access_followup_followup_manager,followup.followup.manager,model_followup_followup,account.group_account_manager,1,1,1,1
access_followup_stat_invoice,followup.stat.invoice,model_followup_stat,account.group_account_invoice,1,1,0,0
access_followup_stat_by_partner_manager,followup.stat.by.partner,model_followup_stat_by_partner,account.group_account_user,1,1,0,0
access_followup_stat_user,followup.stat.user,model_followup_stat,account.group_account_user,1,1,0,0
access_followup_stat_manager,followup.stat.manager,model_followup_stat,account.group_account_manager,1,1,1,1
access_followup_print,access_followup_print,model_followup_print,base.group_user,1,1,1,1
access_followup_sending_results,access_followup_sending_results,model_followup_sending_results,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_followup_followup_line followup.followup.line model_followup_line account.group_account_invoice 1 0 0 0
3 access_followup_followup_line_manager followup.followup.line.manager model_followup_line account.group_account_manager 1 1 1 1
4 access_followup_followup_accountant followup.followup.user model_followup_followup account.group_account_invoice 1 0 0 0
5 access_followup_followup_manager followup.followup.manager model_followup_followup account.group_account_manager 1 1 1 1
6 access_followup_stat_invoice followup.stat.invoice model_followup_stat account.group_account_invoice 1 1 0 0
7 access_followup_stat_by_partner_manager followup.stat.by.partner model_followup_stat_by_partner account.group_account_user 1 1 0 0
8 access_followup_stat_user followup.stat.user model_followup_stat account.group_account_user 1 1 0 0
9 access_followup_stat_manager followup.stat.manager model_followup_stat account.group_account_manager 1 1 1 1
10 access_followup_print access_followup_print model_followup_print base.group_user 1 1 1 1
11 access_followup_sending_results access_followup_sending_results model_followup_sending_results base.group_user 1 1 1 1

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="om_account_followup_comp_rule" model="ir.rule">
<field name="name">Account Follow-up multi company rule</field>
<field name="model_id" ref="model_followup_followup"/>
<field eval="True" name="global"/>
<field name="domain_force">['|',('company_id','=',False),
('company_id','child_of',[user.company_id.id])]</field>
</record>
<record id="om_account_followup_stat_by_partner_comp_rule" model="ir.rule">
<field name="name">Account Follow-up Statistics by Partner Rule</field>
<field ref="model_followup_stat_by_partner" name="model_id"/>
<field eval="True" name="global"/>
<field name="domain_force">['|',('company_id','=',False),
('company_id','child_of',[user.company_id.id])]</field>
</record>
</data>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -0,0 +1,81 @@
<section class="oe_container oe_dark">
<div class="col-md-12">
<h2 class="oe_slogan" style="font-size: 35px;color:#2C0091"><b>Customer Follow Up Management</b></h2>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan" style="color:olive;">Follow-Up Levels</h2>
<div class="oe_demo oe_picture oe_screenshot">
<img src="follow_up_level.png">
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan" style="color:olive;">Send Follow-Ups</h2>
<div class="oe_demo oe_picture oe_screenshot">
<img src="followup.png">
</div>
</div>
</section>
<br/>
<section class="oe_container">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan" style="color:olive;">Manual Follow-Ups</h2>
<div class="oe_demo oe_picture oe_screenshot">
<img src="manual_followup.png">
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan" style="color:olive;">Follow-ups Analysis</h2>
<div class="oe_demo oe_picture oe_screenshot">
<img src="follow_up_analysis.png">
</div>
</div>
</section>
<br/>
<br/>
<br/>
<hr style="width: 100%;height: 4px;background: #2C0091;margin: 0px 0px;">
<hr style="width: 100%;height: 4px;background: #148963;margin: 0px 0px;">
<section class="oe_container oe_dark">
<div class="oe_row ">
<div class="oe_slogan text-center">
<img src="odoo_mates.png"/>
<div style="color:#269900;">
<h3 style="color:#2C0091;font-size: 25px;">If you need any support or want more features, just contact us:</h3><br>
<h3 style="color:#2C0091;font-size: 20px;">Email: <a href="odoomates@gmail.com">odoomates@gmail.com</a> <br></h3>
</div>
<div class="oe_slogan">
<h2>
<a target="_blank" href="https://www.facebook.com/odoomate/" target="new">
<i class="fa fa-facebook-square" style="font-size:38px;"></i>
</a>
<a target="_blank" href="https://twitter.com/odoomates/" target="new">
<i class="fa fa-twitter" style="font-size:38px;"></i>
</a>
<a href="#" target="_blank">
<i class="fa fa-linkedin" style="font-size:38px;"></i>
</a>
<a target="_blank" href="https://www.youtube.com/channel/UCVKlUZP7HAhdQgs-9iTJklQ">
<i class="fa fa-youtube-play" style="font-size:38px;"></i>
</a>
</h2>
</div>
</div>
</div>
</section>
<hr style="width: 100%;height: 4px;background: #148963;margin: 0px 0px;">
<hr style="width: 100%;height: 4px;background: #2C0091;margin: 0px 0px;">

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View file

@ -0,0 +1,72 @@
<odoo>
<data>
<record id="view_move_line_reconcile_tree" model="ir.ui.view">
<field name="name">account.move.line.tree</field>
<field name="model">account.move.line</field>
<field name="arch" type="xml">
<tree string="Journal Items to Reconcile" create="false">
<field name="date"/>
<field name="move_id"/>
<field name="ref"/>
<field name="name"/>
<field name="partner_id"/>
<field name="account_id"/>
<field name="journal_id" invisible="1"/>
<field name="full_reconcile_id"/>
<field name="debit" sum="Total debit"/>
<field name="credit" sum="Total credit"/>
</tree>
</field>
</record>
<record id="account_manual_reconcile_action" model="ir.actions.act_window">
<field name="context">{'search_default_unreconciled': 1,'view_mode':True}</field>
<field name="name">Journal Items to Reconcile</field>
<field name="res_model">account.move.line</field>
<field name="view_id" ref="view_move_line_reconcile_tree"/>
<field name="view_mode">tree</field>
<field name="help" type="html">
<p>No journal items found.</p>
</field>
</record>
<record id="account_move_line_partner_tree" model="ir.ui.view">
<field name="name">account.move.line.partner.tree</field>
<field name="model">account.move.line</field>
<field eval="32" name="priority"/>
<field name="arch" type="xml">
<tree editable="bottom" string="Partner Entries">
<field name="date"/>
<field name="move_id"/>
<field name="ref"/>
<field name="name"/>
<field name="partner_id"/>
<field name="company_id" invisible="1"/>
<field name="company_id" groups="base.group_multi_company" readonly="1"/>
<field name="account_id" options="{'no_open': True, 'no_create': True}"
domain="[('company_id', '=', company_id)]" groups="account.group_account_readonly"/>
<field name="followup_line_id"/>
<field name="followup_date"/>
<field name="debit" sum="Total debit"/>
<field name="credit" sum="Total credit"/>
<field name="date_maturity"/>
</tree>
</field>
</record>
<record id="view_move_line_form" model="ir.ui.view">
<field name="name">account.move.line.form.followup</field>
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_form"/>
<field name="arch" type="xml">
<field name="date_maturity" position="after">
<field name="followup_line_id"/>
<field name="followup_date"/>
</field>
</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="om_account_followup_stat_by_partner_search" model="ir.ui.view">
<field name="name">followup.stat.by.partner.search</field>
<field name="model">followup.stat.by.partner</field>
<field name="arch" type="xml">
<search string="Partner to Remind">
<field name="date_followup"/>
<filter string="Balance > 0"
domain="[('balance','&gt;',0)]" icon="terp-dolar"
name="balance_positive"/>
<field name="partner_id"/>
<field name="max_followup_id"/>
<field name="company_id"
groups="base.group_multi_company"/>
</search>
</field>
</record>
<record id="om_account_followup_stat_by_partner_tree" model="ir.ui.view">
<field name="name">followup.stat.by.partner.tree</field>
<field name="model">followup.stat.by.partner</field>
<field name="arch" type="xml">
<tree string="Partner to Remind">
<field name="partner_id"/>
<field name="balance"/>
<field name="max_followup_id"/>
<field name="date_followup"/>
<field name="date_move_last"/>
<field name="company_id" groups="base.group_multi_company"/>
</tree>
</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,216 @@
<odoo>
<data>
<record id="view_om_account_followup_followup_line_tree" model="ir.ui.view">
<field name="name">followup.line.tree</field>
<field name="model">followup.line</field>
<field name="arch" type="xml">
<tree string="Follow-up Steps">
<field name="name"/>
<field name="delay"/>
<field name="send_email"/>
<field name="send_letter"/>
<field name="manual_action"/>
</tree>
</field>
</record>
<record id="view_om_account_followup_followup_line_form" model="ir.ui.view">
<field name="name">followup.line.form</field>
<field name="model">followup.line</field>
<field name="arch" type="xml">
<form string="Follow-up Steps">
<label for="name" class="oe_edit_only"/>
<h1>
<field name="name"/>
</h1>
<div class="oe_inline">
After
<field name="delay" class="oe_inline"/>
days overdue, do the following actions:
</div>
<div>
<field name="manual_action" class="oe_inline"/>
<label for="manual_action"/>
</div>
<div>
<field name="send_email" class="oe_inline"/>
<label for="send_email"/>
</div>
<div>
<field name="send_letter" class="oe_inline"/>
<label for="send_letter"/>
</div>
<group string="Manual Action"
attrs="{'invisible': [('manual_action', '=', False)]}">
<field name="manual_action_responsible_id"/>
<field name="manual_action_note"
attrs="{'required': [('manual_action', '&lt;&gt;', False)]}"
placeholder="e.g. Call the customer, check if it's paid, ..."/>
</group>
<group string="Send an Email"
attrs="{'invisible': [('send_email', '=', False)]}">
<field name="email_template_id"
attrs="{'required': [('send_email', '&lt;&gt;', False)]}"/>
</group>
<group string="Send a Letter or Email"
attrs="{'invisible': [('send_email', '=', False), ('send_letter', '=', False)]}">
<p colspan="2" class="oe_grey">
Write here the introduction in the letter,
according to the level of the follow-up. You can
use the following keywords in the text. Don't
forget to translate in all languages you installed
using to top right icon.
<table>
<tr>
<td t-translation="off">%%(partner_name)s
</td>
<td>: Partner Name</td>
</tr>
<tr>
<td t-translation="off">%%(date)s</td>
<td>: Current Date</td>
</tr>
<tr>
<td t-translation="off">
%%(user_signature)s
</td>
<td>: User Name</td>
</tr>
<tr>
<td t-translation="off">%%(company_name)s
</td>
<td>: User's Company Name</td>
</tr>
</table>
</p>
<field name="description" nolabel="1" colspan="2"/>
</group>
</form>
</field>
</record>
<record id="view_om_account_followup_followup_form" model="ir.ui.view">
<field name="name">followup.followup.form</field>
<field name="model">followup.followup</field>
<field name="arch" type="xml">
<form string="Follow-up">
<h1>
<field name="name"/>
</h1>
<label for="company_id" groups="base.group_multi_company"/>
<field name="company_id" widget="selection"
class="oe_inline"
groups="base.group_multi_company"/>
<p class="oe_grey">
To remind customers of paying their invoices, you can
define different actions depending on how severely
overdue the customer is. These actions are bundled
into follow-up levels that are triggered when the due
date of an invoice has passed a certain
number of days. If there are other overdue invoices for
the
same customer, the actions of the most
overdue invoice will be executed.
</p>
<field name="followup_line"/>
</form>
</field>
</record>
<record id="view_om_account_followup_followup_tree" model="ir.ui.view">
<field name="name">followup.followup.tree</field>
<field name="model">followup.followup</field>
<field name="arch" type="xml">
<tree string="Follow-up">
<field name="company_id"/>
</tree>
</field>
</record>
<record id="view_om_account_followup_filter" model="ir.ui.view">
<field name="name">account.followup.select</field>
<field name="model">followup.followup</field>
<field name="arch" type="xml">
<search string="Search Follow-up">
<field name="company_id" groups="base.group_multi_company"/>
</search>
</field>
</record>
<record id="action_om_account_followup_definition_form" model="ir.actions.act_window">
<field name="name">Follow-up Levels</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">followup.followup</field>
<field name="search_view_id" ref="view_om_account_followup_filter"/>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to define follow-up levels and their related actions.
</p>
<p>
For each step, specify the actions to be taken and delay in
days. It is
possible to use print and e-mail templates to send specific
messages to
the customer.
</p>
</field>
</record>
<record id="view_move_line_reconcile_tree" model="ir.ui.view">
<field name="name">account.move.line.tree</field>
<field name="model">account.move.line</field>
<field name="arch" type="xml">
<tree string="Journal Items to Reconcile" create="false">
<field name="date"/>
<field name="move_id"/>
<field name="ref"/>
<field name="name"/>
<field name="partner_id"/>
<field name="account_id"/>
<field name="journal_id" invisible="1"/>
<field name="full_reconcile_id"/>
<field name="debit" sum="Total debit"/>
<field name="credit" sum="Total credit"/>
</tree>
</field>
</record>
<record id="account_manual_reconcile_action" model="ir.actions.act_window">
<field name="context">{'search_default_unreconciled': 1,'view_mode':True}</field>
<field name="name">Journal Items to Reconcile</field>
<field name="res_model">account.move.line</field>
<field name="view_id" ref="view_move_line_reconcile_tree"/>
<field name="view_mode">tree</field>
<field name="help" type="html">
<p>
No journal items found.
</p>
</field>
</record>
<menuitem id="om_account_followup_main_menu"
parent="account.menu_finance_configuration"
name="Follow-up"/>
<menuitem id="om_account_followup_menu"
name="Follow-up Levels"
action="action_om_account_followup_definition_form"
parent="om_account_followup_main_menu" />
<menuitem id="om_account_followup_main_menu"
parent="account.menu_finance_configuration"
name="Follow-up"/>
<menuitem id="om_account_followup_menu"
name="Follow-up Levels"
action="action_om_account_followup_definition_form"
parent="om_account_followup_main_menu"/>
</data>
</odoo>

View file

@ -0,0 +1,193 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="customer_followup_tree" model="ir.ui.view">
<field name="name">res.partner.followup.inherit.tree</field>
<field name="model">res.partner</field>
<field name="priority" eval="20"/>
<field name="arch" type="xml">
<tree string="Customer Followup" create="false" delete="false">
<field name="display_name"/>
<field name="payment_next_action_date"/>
<field name="payment_next_action"/>
<field name="user_id" invisible="1"/>
<field name="country_id" invisible="1"/>
<field name="parent_id" invisible="1"/>
<field name="payment_responsible_id"/>
<field name="payment_earliest_due_date"/>
<field name="latest_followup_level_id"/>
<field name="payment_amount_overdue"/>
<field name="payment_amount_due"/>
</tree>
</field>
</record>
<record id="view_partner_inherit_customer_followup_tree" model="ir.ui.view">
<field name="name">res.partner.followup.inherit.tree</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_tree"/>
<field name="arch" type="xml">
<field name="display_name" position="after">
<field name="payment_responsible_id" invisible="1"/>
</field>
</field>
</record>
<record id="customer_followup_search_view" model="ir.ui.view">
<field name="name">Search</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_res_partner_filter"/>
<field name="arch" type="xml">
<xpath expr="//group[1]" position="after">
<group string="Follow-up">
<filter string="Partners with Overdue Credits" domain="[('payment_amount_overdue', '>', 0.0)]"
name="credits"/>
<separator/>
<filter string="Follow-ups To Do"
domain="[('payment_next_action_date', '&lt;=', time.strftime('%%Y-%%m-%%d')), ('payment_amount_overdue', '>', 0.0)]"
name="todo"/>
<separator/>
<filter string="No Responsible" name="no_responsibe" domain="[('payment_responsible_id', '=', False)]"/>
<filter string="My Follow-ups" domain="[('payment_responsible_id','=', uid)]" name="my"/>
</group>
</xpath>
<xpath expr="//group[1]" position="inside">
<filter string="Follow-up Responsible" name="responsibe"
context="{'group_by':'payment_responsible_id'}"/>
<filter string="Followup Level" name="followup_level"
context="{'group_by':'latest_followup_level_id'}"/>
</xpath>
</field>
</record>
<record id="action_customer_followup" model="ir.actions.act_window">
<field name="name">Manual Follow-Ups</field>
<field name="view_id" ref="customer_followup_tree"/>
<field name="res_model">res.partner</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('payment_amount_due', '>', 0.0)]</field>
<field name="context">{'Followupfirst':True, 'search_default_todo': True}</field>
<field name="search_view_id" ref="customer_followup_search_view"/>
</record>
<record id="view_partner_inherit_followup_form" model="ir.ui.view">
<field name="name">res.partner.followup.form.inherit</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="model">res.partner</field>
<field name="arch" type="xml">
<xpath expr="//page[@name='sales_purchases']" position="after">
<page string="Payment Follow-up"
groups="account.group_account_invoice"
name="followup_tab">
<div class="oe_right"
name="followup_button">
<button name="do_button_print" type="object"
string="Print Overdue Payments"
groups="account.group_account_user"
help="Print overdue payments report independent of follow-up line"
attrs="{'invisible':[('payment_amount_due', '&lt;=', 0.0)]}"/>
<button name="do_partner_mail" type="object"
string="Send Overdue Email"
groups="account.group_account_user"
help="If not specified by the latest follow-up level, it will send from the default email template"
attrs="{'invisible':[('payment_amount_due', '&lt;=', 0.0)]}"/>
</div>
<p attrs="{'invisible':[('latest_followup_date','=', False)]}">
The
<field name="latest_followup_date"
class="oe_inline"/>
, the latest payment follow-up was:
<field name="latest_followup_level_id"
class="oe_inline"/>
</p>
<group>
<field name="payment_responsible_id"
placeholder="Responsible of credit collection"
class="oe_inline"/>
<label for="payment_next_action"/>
<div>
<field name="payment_next_action_date"
class="oe_inline"/>
<button name="action_done" type="object"
string="⇾ Mark as Done"
help="Click to mark the action as done."
class="oe_link"
attrs="{'invisible':[('payment_next_action_date','=', False)]}"
groups="account.group_account_user"/>
<field name="payment_next_action"
placeholder="Action to be taken e.g. Give a phonecall, Check if it's paid, ..."/>
</div>
</group>
<label for="payment_note" class="oe_edit_only"/>
<field name="payment_note"
placeholder="He said the problem was temporary and promised to pay 50%% before 15th of May, balance before 1st of July."/>
<p class="oe_grey">
Below is the history of the transactions of this
customer. You can check "No Follow-up" in
order to exclude it from the next follow-up
actions.
</p>
<field name="unreconciled_aml_ids">
<tree string="Account Move line" editable="bottom"
create="false" delete="false"
colors="red:(not date_maturity or date_maturity&lt;=current_date) and result&gt;0">
<field name="date" readonly="True"/>
<field name="company_id" readonly="True"
groups="base.group_multi_company"/>
<field name="move_id" readonly="True"/>
<field name="blocked"/>
<field name="date_maturity" readonly="True"/>
<field name="result" readonly="True"/>
<field name="followup_line_id" invisible='1'/>
</tree>
</field>
<group class="oe_subtotal_footer oe_right">
<field name="payment_amount_due"/>
</group>
<div class="oe_clear"/>
</page>
</xpath>
</field>
</record>
<record id="action_view_customer_followup_form" model="ir.actions.act_window.view">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_partner_inherit_followup_form"/>
<field name="act_window_id" ref="action_customer_followup"/>
</record>
<record id="action_view_customer_followup_tree" model="ir.actions.act_window.view">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="customer_followup_tree"/>
<field name="act_window_id" ref="action_customer_followup"/>
</record>
<menuitem id="om_account_followup_s"
action="action_customer_followup"
parent="menu_finance_followup"
name="Do Manual Follow-Ups"
sequence="3"/>
<record id="action_customer_my_followup" model="ir.actions.act_window">
<field name="name">My Follow-Ups</field>
<field name="view_id" ref="customer_followup_tree"/>
<field name="res_model">res.partner</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('payment_amount_due', '>', 0.0)]</field>
<field name="context">{'Followupfirst':True, 'search_default_todo': True, 'search_default_my': True}</field>
<field name="search_view_id" ref="customer_followup_search_view"/>
</record>
<menuitem id="menu_sale_followup"
parent="menu_finance_followup"
sequence="10"
action="action_customer_my_followup"
groups="account.group_account_invoice"/>
</data>
</odoo>

View file

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<template id="report_followup">
<t t-call="web.html_container">
<t t-foreach="ids_to_objects(data['partner_ids'])" t-as="o">
<t t-set="o" t-value="o.with_context({'lang':o.partner_id.lang})"/>
<t t-call="web.external_layout">
<div class="page">
<p>
<span t-field="o.invoice_partner_id"/>
<br/>
<t t-if="o.partner_id.vat">
<span t-field="o.partner_id.vat"/>
<br/>
</t>
Document: Customer account statement
<br/>
Date:
<span t-esc="data['date']"/>
<br/>
Customer ref:
<span t-field="o.partner_id.ref"/>
</p>
<p t-raw="get_text(o,data['followup_id']).replace('\n', '&lt;br&gt;')"/>
<t t-foreach="getLines(o)" t-as="cur_lines">
<table class="table table-condensed"
style="margin-top: 50px;">
<thead>
<tr>
<th>Invoice Date</th>
<th>Description</th>
<th class="text-center">Ref</th>
<th class="text-center">Maturity Date</th>
<th class="text-end">Amount</th>
<th class="text-center">Due</th>
</tr>
</thead>
<tbody>
<tr t-foreach="cur_lines['line']"
t-as="line">
<td>
<span t-esc="line['date']"/>
</td>
<td>
<span t-esc="line['name']"/>
</td>
<td>
<span t-esc="line['ref']"/>
</td>
<td class="text-center">
<span t-esc="line['date_maturity']"/>
</td>
<td class="text-end">
<span t-esc="line['balance']"/>
</td>
<td>
<span t-esc="line['blocked'] and 'X' or ''"/>
</td>
</tr>
</tbody>
</table>
<p>Total:
<span t-esc="cur_lines['total']"/>
</p>
</t>
</div>
</t>
</t>
</t>
</template>
</data>
</odoo>

View file

@ -0,0 +1,14 @@
<?xml version="1.0"?>
<odoo>
<data>
<record id="action_report_followup" model="ir.actions.report">
<field name="name">Follow-up Report</field>
<field name="model">followup.followup</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">om_account_followup.report_followup</field>
<field name="report_file">om_account_followup.report_followup</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from . import followup_print
from . import followup_results

View file

@ -0,0 +1,227 @@
# -*- coding: utf-8 -*-
import datetime
import time
from odoo import api, fields, models, _
class FollowupPrint(models.TransientModel):
_name = 'followup.print'
_description = 'Print Follow-up & Send Mail to Customers'
def _get_followup(self):
if self.env.context.get('active_model',
'ir.ui.menu') == 'followup.followup':
return self.env.context.get('active_id', False)
company_id = self.env.user.company_id.id
followp_id = self.env['followup.followup'].search(
[('company_id', '=', company_id)], limit=1)
return followp_id or False
date = fields.Date('Follow-up Sending Date', required=True,
help="This field allow you to select a forecast date "
"to plan your follow-ups",
default=lambda *a: time.strftime('%Y-%m-%d'))
followup_id = fields.Many2one('followup.followup', 'Follow-Up',
required=True, readonly=True,
default=_get_followup)
partner_ids = fields.Many2many('followup.stat.by.partner',
'partner_stat_rel', 'osv_memory_id',
'partner_id', 'Partners', required=True)
company_id = fields.Many2one('res.company', readonly=True,
related='followup_id.company_id')
email_conf = fields.Boolean('Send Email Confirmation')
email_subject = fields.Char('Email Subject', size=64,
default=_('Invoices Reminder'))
partner_lang = fields.Boolean(
'Send Email in Partner Language', default=True,
help='Do not change message text, if you want to send email in '
'partner language, or configure from company')
email_body = fields.Text('Email Body', default='')
summary = fields.Text('Summary', readonly=True)
test_print = fields.Boolean(
'Test Print', help='Check if you want to print follow-ups without '
'changing follow-up level.')
def process_partners(self, partner_ids, data):
partner_obj = self.env['res.partner']
partner_ids_to_print = []
nbmanuals = 0
manuals = {}
nbmails = 0
nbunknownmails = 0
nbprints = 0
resulttext = " "
for partner in self.env['followup.stat.by.partner'].browse(
partner_ids):
if partner.max_followup_id.manual_action:
partner_obj.do_partner_manual_action([partner.partner_id.id])
nbmanuals = nbmanuals + 1
key = partner.partner_id.payment_responsible_id.name or _(
"Anybody")
if key not in manuals.keys():
manuals[key] = 1
else:
manuals[key] = manuals[key] + 1
if partner.max_followup_id.send_email:
nbunknownmails += partner.partner_id.do_partner_mail()
nbmails += 1
if partner.max_followup_id.send_letter:
partner_ids_to_print.append(partner.id)
nbprints += 1
followup_without_lit = \
partner.partner_id.latest_followup_level_id_without_lit
message = "%s<I> %s </I>%s" % (_("Follow-up letter of "),
followup_without_lit.name,
_(" will be sent"))
partner.partner_id.message_post(body=message)
if nbunknownmails == 0:
resulttext += str(nbmails) + _(" email(s) sent")
else:
resulttext += str(nbmails) + _(
" email(s) should have been sent, but ") + str(
nbunknownmails) + _(
" had unknown email address(es)") + "\n <BR/> "
resulttext += "<BR/>" + str(nbprints) + _(
" letter(s) in report") + " \n <BR/>" + str(nbmanuals) + _(
" manual action(s) assigned:")
needprinting = False
if nbprints > 0:
needprinting = True
resulttext += "<p align=\"center\">"
for item in manuals:
resulttext = resulttext + "<li>" + item + ":" + str(
manuals[item]) + "\n </li>"
resulttext += "</p>"
result = {}
action = partner_obj.do_partner_print(partner_ids_to_print, data)
result['needprinting'] = needprinting
result['resulttext'] = resulttext
result['action'] = action or {}
return result
def do_update_followup_level(self, to_update, partner_list, date):
for id in to_update.keys():
if to_update[id]['partner_id'] in partner_list:
self.env['account.move.line'].browse([int(id)]).write(
{'followup_line_id': to_update[id]['level'],
'followup_date': date})
def clear_manual_actions(self, partner_list):
partner_list_ids = [partner.partner_id.id for partner in self.env[
'followup.stat.by.partner'].browse(partner_list)]
ids = self.env['res.partner'].search(
['&', ('id', 'not in', partner_list_ids), '|',
('payment_responsible_id', '!=', False),
('payment_next_action_date', '!=', False)])
partners_to_clear = []
for part in ids:
if not part.unreconciled_aml_ids:
partners_to_clear.append(part.id)
part.action_done()
return len(partners_to_clear)
def do_process(self):
context = dict(self.env.context or {})
tmp = self._get_partners_followp()
partner_list = tmp['partner_ids']
to_update = tmp['to_update']
date = self.date
data = self.read()[0]
data['followup_id'] = data['followup_id'][0]
self.do_update_followup_level(to_update, partner_list, date)
restot_context = context.copy()
restot = self.with_context(restot_context).process_partners(
partner_list, data)
context.update(restot_context)
nbactionscleared = self.clear_manual_actions(partner_list)
if nbactionscleared > 0:
restot['resulttext'] = restot['resulttext'] + "<li>" + _(
"%s partners have no credits and as such the "
"action is cleared") % (str(nbactionscleared)) + "</li>"
resource_id = self.env.ref(
'om_account_followup.view_om_account_followup_sending_results')
context.update({'description': restot['resulttext'],
'needprinting': restot['needprinting'],
'report_data': restot['action']})
return {
'name': _('Send Letters and Emails: Actions Summary'),
'view_type': 'form',
'context': context,
'view_mode': 'tree,form',
'res_model': 'followup.sending.results',
'views': [(resource_id.id, 'form')],
'type': 'ir.actions.act_window',
'target': 'new',
}
def _get_msg(self):
return self.env.user.company_id.follow_up_msg
def _get_partners_followp(self):
data = self
company_id = data.company_id.id
context = self.env.context
self._cr.execute(
'''SELECT
l.partner_id,
l.followup_line_id,
l.date_maturity,
l.date, l.id
FROM account_move_line AS l
LEFT JOIN account_account AS a
ON (l.account_id=a.id)
WHERE (l.full_reconcile_id IS NULL)
AND a.account_type = 'asset_receivable'
AND (l.partner_id is NOT NULL)
AND (l.debit > 0)
AND (l.company_id = %s)
AND (l.blocked = False)
ORDER BY l.date''' % (company_id))
move_lines = self._cr.fetchall()
old = None
fups = {}
fup_id = 'followup_id' in context and context[
'followup_id'] or data.followup_id.id
date = 'date' in context and context['date'] or data.date
date = fields.Date.to_string(date)
current_date = datetime.date(*time.strptime(date, '%Y-%m-%d')[:3])
self._cr.execute(
'''SELECT *
FROM followup_line
WHERE followup_id=%s
ORDER BY delay''' % (fup_id,))
for result in self._cr.dictfetchall():
delay = datetime.timedelta(days=result['delay'])
fups[old] = (current_date - delay, result['id'])
old = result['id']
partner_list = []
to_update = {}
for partner_id, followup_line_id, date_maturity, date, id in \
move_lines:
if not partner_id:
continue
if followup_line_id not in fups:
continue
stat_line_id = partner_id * 10000 + company_id
if date_maturity:
date_maturity = fields.Date.to_string(date_maturity)
if date_maturity <= fups[followup_line_id][0].strftime(
'%Y-%m-%d'):
if stat_line_id not in partner_list:
partner_list.append(stat_line_id)
to_update[str(id)] = {'level': fups[followup_line_id][1],
'partner_id': stat_line_id}
elif date and date <= fups[followup_line_id][0].strftime(
'%Y-%m-%d'):
if stat_line_id not in partner_list:
partner_list.append(stat_line_id)
to_update[str(id)] = {'level': fups[followup_line_id][1],
'partner_id': stat_line_id}
return {'partner_ids': partner_list, 'to_update': to_update}

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_om_account_followup_print" model="ir.ui.view">
<field name="name">account.followup.print.form</field>
<field name="model">followup.print</field>
<field name="arch" type="xml">
<form string="Send follow-ups">
<group col="4">
<field name="date" groups="base.group_no_one"/>
<field name="followup_id"
groups="base.group_multi_company"/>
</group>
<p class="oe_grey">
This action will send follow-up emails, print the
letters and
set the manual actions per customer, according to the
follow-up levels defined.
</p>
<footer>
<button name="do_process"
string="Send emails and generate letters"
type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link"
special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_om_account_followup_print" model="ir.actions.act_window">
<field name="name">Send Follow-Ups</field>
<field name="res_model">followup.print</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<menuitem id="menu_finance_followup" parent="account.menu_finance"
name="Follow-Ups"
groups="account.group_account_invoice"/>
<menuitem action="action_om_account_followup_print"
id="om_account_followup_print_menu"
parent="menu_finance_followup"
name="Send Letters and Emails"
groups="account.group_account_user,account.group_account_manager"
sequence="2"/>
</data>
</odoo>

View file

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
class FollowupSendingResults(models.TransientModel):
_name = 'followup.sending.results'
_description = 'Results from the sending of the different letters and emails'
def do_report(self):
return self.env.context.get('report_data')
def do_done(self):
return {}
def _get_description(self):
return self.env.context.get('description')
def _get_need_printing(self):
return self.env.context.get('needprinting')
description = fields.Text("Description", readonly=True,default=_get_description)
needprinting = fields.Boolean("Needs Printing", default=_get_need_printing)

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_om_account_followup_sending_results" model="ir.ui.view">
<field name="name">followup.sending.results.form</field>
<field name="model">followup.sending.results</field>
<field name="arch" type="xml">
<form string="Summary of actions">
<field name="description" widget="html"
class="oe_view_only"/>
<footer>
<field name="needprinting" invisible="1"/>
<div attrs="{'invisible':[('needprinting','=', False)]}">
<button name="do_report" string="Download Letters"
type="object" class="oe_highlight"/>
</div>
<div attrs="{'invisible':[('needprinting','!=', False)]}">
<button name="do_done" string="Close" type="object"
class="oe_highlight"/>
</div>
</footer>
</form>
</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,43 @@
[project]
name = "odoo-bringout-odoomates-om_account_followup"
version = "16.0.0"
description = "Customer Follow Up Management - Customer FollowUp Management"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-oca-ocb-account>=16.0.0",
"odoo-bringout-oca-ocb-mail>=16.0.0",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["om_account_followup"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]