price_update_notifications module added
This commit is contained in:
parent
c8bbeac919
commit
a968b64e80
11 changed files with 474 additions and 0 deletions
2
price_update_notifications/__init__.py
Normal file
2
price_update_notifications/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
from . import models
|
||||
from . import wizard
|
||||
39
price_update_notifications/__manifest__.py
Normal file
39
price_update_notifications/__manifest__.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#
|
||||
# Bemade Inc.
|
||||
#
|
||||
# Copyright (C) 2023-June Bemade Inc. (<https://www.bemade.org>).
|
||||
# Author: Marc Durepos (Contact : marc@bemade.org)
|
||||
#
|
||||
# This program is under the terms of the GNU Lesser General Public License,
|
||||
# version 3.
|
||||
#
|
||||
# For full license details, see https://www.gnu.org/licenses/lgpl-3.0.en.html.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
{
|
||||
"name": "Price Update Notifications",
|
||||
"version": "17.0.1.0.0",
|
||||
"summary": "Send a price update notification to clients",
|
||||
"category": "Sale",
|
||||
"author": "Bemade Inc.",
|
||||
"website": "http://www.bemade.org",
|
||||
"license": "LGPL-3",
|
||||
"depends": ["sale_management", "sales_team", "mail"],
|
||||
"data": [
|
||||
"security/ir.model.access.csv",
|
||||
"data/ir_cron_data.xml",
|
||||
"data/email_templates.xml",
|
||||
"views/price_notice_mailing_views.xml",
|
||||
"wizard/send_price_update_notice_views.xml",
|
||||
],
|
||||
"assets": {},
|
||||
"installable": True,
|
||||
"auto_install": False,
|
||||
}
|
||||
63
price_update_notifications/data/email_templates.xml
Normal file
63
price_update_notifications/data/email_templates.xml
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="mail_template_price_update" model="mail.template">
|
||||
<field name="name">Product Price Update</field>
|
||||
<field name="model_id"
|
||||
ref="price_update_notifications.model_price_notice_mailing"/>
|
||||
<field name="subject">
|
||||
{{ object.partner_id.company_id.name }} Price Update
|
||||
Notification -
|
||||
Effective {{ object.effective_date.strftime("%Y-%m-%d") }}
|
||||
</field>
|
||||
<field name="description">Sent using the send price update
|
||||
letter action on partners.
|
||||
</field>
|
||||
<field name="email_from">
|
||||
{{ object.partner_id.company_id.email}}
|
||||
</field>
|
||||
<field name="email_to">{{ object.email }}</field>
|
||||
<field name="lang">{{ object.partner_id.lang }}</field>
|
||||
<field name="body_html" type="html">
|
||||
<div style="margin: 0px; padding: 0px" id="intro">
|
||||
<p>Dear <span t-field="object.partner_id.name"/>,</p>
|
||||
<p>We have recently updated the pricing for the following
|
||||
products you have purchased or requested pricing for in the past
|
||||
year. Please take note that these prices will be effective as of
|
||||
<span t-out="object.effective_date.strftime('%Y-%m-%d')"/>.
|
||||
</p>
|
||||
</div>
|
||||
<t t-set="company"
|
||||
t-value="object.partner_id.company_id or object.env.company"/>
|
||||
<div id="table">
|
||||
<table class="table" style="width: 100%; border-collapse: collapse; font-family: Arial, sans-serif;">
|
||||
<thead style="background-color: #f2f2f2; text-align: left;">
|
||||
<tr>
|
||||
<th style="padding: 8px; border-bottom: 2px solid #ddd;">Product</th>
|
||||
<th style="padding: 8px; border-bottom: 2px solid #ddd;">Unit</th>
|
||||
<th style="padding: 8px; border-bottom: 2px solid #ddd; text-align: right;">Unit Price</th>
|
||||
<th style="padding: 8px; border-bottom: 2px solid #ddd;">Currency</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="object.line_ids" t-as="line" t-attf-style="background-color: {{ line_index % 2 == 0 and '#f9f9f9' or '#ffffff' }};">
|
||||
<td style="padding: 8px; border-bottom: 1px solid #ddd;">
|
||||
<span t-field="line.product_id.display_name"/>
|
||||
</td>
|
||||
<td style="padding: 8px; border-bottom: 1px solid #ddd;">
|
||||
<span t-field="line.product_id.uom_id.display_name"/>
|
||||
</td>
|
||||
<td style="padding: 8px; border-bottom: 1px solid #ddd; text-align: right;">
|
||||
<span t-field="line.price" t-options="{'widget': 'monetary', 'display_currency': line.currency_id}"/>
|
||||
</td>
|
||||
<td style="padding: 8px; border-bottom: 1px solid #ddd;">
|
||||
<span t-field="line.currency_id.name"/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Best regards,</p>
|
||||
<p>The <span t-out="company.name"/> team.</p>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
18
price_update_notifications/data/ir_cron_data.xml
Normal file
18
price_update_notifications/data/ir_cron_data.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="send_pricing_update_mails" model="ir.cron">
|
||||
<field name="name">Price Update Notifications: Send
|
||||
Notifications</field>
|
||||
<field name="model_id"
|
||||
ref="price_update_notifications.model_price_notice_mailing"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.action_send_all()</field>
|
||||
<field name="active" eval="True"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
<field name="nextcall"
|
||||
eval="(DateTime.now() + timedelta(days=1))"/>
|
||||
</record>
|
||||
</odoo>
|
||||
1
price_update_notifications/models/__init__.py
Normal file
1
price_update_notifications/models/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import price_notice_mailing
|
||||
132
price_update_notifications/models/price_notice_mailing.py
Normal file
132
price_update_notifications/models/price_notice_mailing.py
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
from odoo import models, fields, api
|
||||
from odoo.exceptions import ValidationError
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
class PriceNoticeMailing(models.Model):
|
||||
_name = "price.notice.mailing"
|
||||
_description = "Price Notice Mailing"
|
||||
_inherit = ["mail.thread"]
|
||||
|
||||
partner_id = fields.Many2one("res.partner", required=True, readonly=True)
|
||||
email = fields.Char(readonly=True)
|
||||
product_template_ids = fields.Many2many("product.template")
|
||||
line_ids = fields.One2many(
|
||||
comodel_name="price.notice.mailing.line",
|
||||
inverse_name="mailing_id",
|
||||
)
|
||||
effective_date = fields.Date(
|
||||
required=True,
|
||||
string="Effective Pricing Date",
|
||||
)
|
||||
send_date = fields.Date(required=True)
|
||||
sent = fields.Boolean(default=False, readonly=True)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
def _compute_display_name(self):
|
||||
for rec in self:
|
||||
rec.display_name = f"{rec.send_date} - {rec.partner_id.name}"
|
||||
|
||||
def create(self, vals_list):
|
||||
res = super().create(vals_list)
|
||||
for mailing in res:
|
||||
email = mailing.partner_id.email
|
||||
if not email:
|
||||
raise ValidationError(
|
||||
f"Partner email not found for price "
|
||||
f"notice "
|
||||
"mailing to {partner.display_name}."
|
||||
)
|
||||
mailing.email = mailing.partner_id.email
|
||||
mailing._create_lines()
|
||||
if not mailing.line_ids:
|
||||
mailing.active = False
|
||||
return res
|
||||
|
||||
def _create_lines(self):
|
||||
line_vals = []
|
||||
for mailing in self:
|
||||
products = mailing._get_purchased_products()
|
||||
for product in products:
|
||||
pricelist = mailing.partner_id.property_product_pricelist
|
||||
price = pricelist._get_product_price(
|
||||
product,
|
||||
quantity=1.0,
|
||||
date=mailing.effective_date,
|
||||
)
|
||||
currency = (
|
||||
pricelist.currency_id
|
||||
or mailing.partner_id.company_id.currency_id
|
||||
or self.env.company.currency_id
|
||||
)
|
||||
line_vals.append(
|
||||
{
|
||||
"mailing_id": mailing.id,
|
||||
"product_id": product.id,
|
||||
"currency_id": currency.id,
|
||||
"price": price,
|
||||
}
|
||||
)
|
||||
self.env["price.notice.mailing.line"].create(line_vals)
|
||||
|
||||
def _get_purchased_products(self):
|
||||
self.ensure_one()
|
||||
# Retrieve partner purchase history
|
||||
so_domain = [
|
||||
("date_order", ">=", fields.Date.today() - timedelta(days=365)),
|
||||
"|",
|
||||
"|",
|
||||
("partner_id", "=", self.partner_id.id),
|
||||
("partner_id", "=", self.partner_id.commercial_partner_id.id),
|
||||
(
|
||||
"partner_id.commercial_partner_id",
|
||||
"=",
|
||||
self.partner_id.commercial_partner_id.id,
|
||||
),
|
||||
]
|
||||
if self.product_template_ids:
|
||||
so_domain = [
|
||||
"order_line.product_tmpl_id",
|
||||
"in",
|
||||
self.product_template_ids.ids,
|
||||
] + so_domain
|
||||
sale_ids = self.env["sale.order"].search(so_domain)
|
||||
|
||||
# Retrieve purchased products and limit to the selected product
|
||||
# templates
|
||||
product_domain = [
|
||||
("id", "in", sale_ids.order_line.mapped("product_id").ids),
|
||||
]
|
||||
if self.product_template_ids:
|
||||
product_domain = product_domain + [
|
||||
("product_tmpl_id", "in", self.product_template_ids.ids)
|
||||
]
|
||||
product_ids = self.env["product.product"].search(product_domain)
|
||||
return product_ids
|
||||
|
||||
@api.model
|
||||
def action_send_all(self):
|
||||
due_mailings = self.search(
|
||||
[("sent", "=", False), ("send_date", "<=", fields.Date.today())]
|
||||
)
|
||||
due_mailings._action_send()
|
||||
|
||||
def _action_send(self):
|
||||
template = self.env.ref("price_update_notifications.mail_template_price_update")
|
||||
for mailing in self:
|
||||
template.send_mail(mailing.id)
|
||||
mailing.sent = True
|
||||
|
||||
|
||||
class PriceNoticeMailingLine(models.Model):
|
||||
_name = "price.notice.mailing.line"
|
||||
_description = "Price Notice Mailing Line"
|
||||
|
||||
mailing_id = fields.Many2one(
|
||||
"price.notice.mailing",
|
||||
required=True,
|
||||
ondelete="cascade",
|
||||
)
|
||||
product_id = fields.Many2one("product.product", required=True)
|
||||
currency_id = fields.Many2one("res.currency", required=True)
|
||||
price = fields.Monetary(required=True)
|
||||
7
price_update_notifications/security/ir.model.access.csv
Normal file
7
price_update_notifications/security/ir.model.access.csv
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_wizard_price_update_notice_manager,access_wizard_price_update_notice_manager,model_wizard_price_update_notice,sales_team.group_sale_manager,1,1,1,1
|
||||
access_price_notice_mailing_manager,access_price_notice_mailing_manager,model_price_notice_mailing,sales_team.group_sale_manager,1,1,1,1
|
||||
access_price_notice_mailing_line_manager,access_price_notice_mailing_line_manager,model_price_notice_mailing_line,sales_team.group_sale_manager,1,1,1,1
|
||||
access_wizard_price_update_notice_user,access_wizard_price_update_notice_user,model_wizard_price_update_notice,base.group_user,1,0,0,0
|
||||
access_price_notice_mailing_user,access_price_notice_mailing_user,model_price_notice_mailing,base.group_user,1,0,0,0
|
||||
access_price_notice_mailing_line_user,access_price_notice_mailing_line_user,model_price_notice_mailing_line,base.group_user,1,0,0,0
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="price_notice_mailing_view_form" model="ir.ui.view">
|
||||
<field name="name">price.notice.mailing.view.form</field>
|
||||
<field name="model">price.notice.mailing</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<group colspan="2">
|
||||
<field name="partner_id"/>
|
||||
<field name="send_date"/>
|
||||
<field name="effective_date"/>
|
||||
<field name="sent"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Notification Lines">
|
||||
<field name="line_ids">
|
||||
<tree>
|
||||
<field name="product_id"/>
|
||||
<field name="price"/>
|
||||
<field name="currency_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Applicable Products">
|
||||
<field name="product_template_ids">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="price_notice_mailing_view_list" model="ir.ui.view">
|
||||
<field name="name">price.notice.mailing.view.list</field>
|
||||
<field name="model">price.notice.mailing</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="send_date"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="effective_date"/>
|
||||
<field name="send_date"/>
|
||||
<field name="sent"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_view_price_notice_mailings" model="ir.actions.act_window">
|
||||
<field name="name">Price Notice Mailings</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">price.notice.mailing</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No mailings yet. Create one!
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_send_price_update_notice_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Send Price Update Notice</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">wizard.price.update.notice</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<record id="partner_action_send_price_update_notice" model="ir.actions.server">
|
||||
<field name="name">Send Price Update Notice(s)</field>
|
||||
<field name="model_id" ref="base.model_res_partner"/>
|
||||
<field name="binding_model_id" ref="base.model_res_partner"/>
|
||||
<field name="binding_type">action</field>
|
||||
<field name="state">code</field>
|
||||
<field name="code">
|
||||
action = {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Send Price Update Notice',
|
||||
'res_model': 'wizard.price.update.notice',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
}
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="sale_menu_price_notice_mailing"
|
||||
parent="sale.sale_menu_root"
|
||||
action="action_view_price_notice_mailings"
|
||||
sequence="20"
|
||||
name="Price Update Notices"/>
|
||||
</odoo>
|
||||
1
price_update_notifications/wizard/__init__.py
Normal file
1
price_update_notifications/wizard/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import send_price_update_notice
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
""" Send a price update notice to selected partners """
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
class SendPriceUpdateNotice(models.TransientModel):
|
||||
_name = "wizard.price.update.notice"
|
||||
_description = "Price Update Notice Wizard"
|
||||
|
||||
partner_ids = fields.Many2many(comodel_name="res.partner")
|
||||
product_template_ids = fields.Many2many(
|
||||
string="Applicable Products",
|
||||
comodel_name="product.template",
|
||||
)
|
||||
pricing_date = fields.Date(
|
||||
string="Effective Pricing Date",
|
||||
help="The date at which the prices are valid.",
|
||||
)
|
||||
send_date = fields.Date(
|
||||
default=fields.Date.today() + timedelta(days=1),
|
||||
)
|
||||
warning_msg = fields.Html()
|
||||
|
||||
@api.depends_context("active_ids")
|
||||
def default_get(self, fields):
|
||||
vals = {}
|
||||
if "partner_ids" in fields:
|
||||
partners = self.env["res.partner"].search(
|
||||
[("id", "in", self.env.context.get("active_ids"))]
|
||||
)
|
||||
if no_email_partners := partners.filtered(lambda p: not p.email):
|
||||
warning_msg = self._get_warning_message(no_email_partners)
|
||||
else:
|
||||
warning_msg = None
|
||||
valid_partners = partners.filtered("email")
|
||||
if not valid_partners:
|
||||
raise UserError(_("No valid partners were selected."))
|
||||
vals.update(
|
||||
partner_ids=partners.filtered("email").ids,
|
||||
warning_msg=warning_msg,
|
||||
)
|
||||
if "product_template_ids" in fields:
|
||||
vals.update(product_template_ids=[])
|
||||
return vals
|
||||
|
||||
def action_create(self):
|
||||
self.ensure_one()
|
||||
vals_list = []
|
||||
for partner in self.partner_ids:
|
||||
vals_list.append(
|
||||
{
|
||||
"partner_id": partner.id,
|
||||
"product_template_ids": self.product_template_ids.ids,
|
||||
"effective_date": self.pricing_date,
|
||||
"send_date": self.send_date,
|
||||
}
|
||||
)
|
||||
mailings = self.env["price.notice.mailing"].create(vals_list)
|
||||
return {
|
||||
"name": "Price Notice Mailings",
|
||||
"type": "ir.actions.act_window",
|
||||
"view_mode": "tree,form",
|
||||
"res_model": "price.notice.mailing",
|
||||
"res_id": False,
|
||||
"domain": [["id", "in", mailings.ids]],
|
||||
"target": "current",
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _get_warning_message(self, partner_ids):
|
||||
msg = _("<p>The following partners do not have emails configured:</p>")
|
||||
msg += "<ul>"
|
||||
for partner in partner_ids:
|
||||
msg += f"<li>{partner.display_name}</li>"
|
||||
msg += "</ul>"
|
||||
return msg
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="send_price_update_notice_view_form" model="ir.ui.view">
|
||||
<field name="name">send.price.update.notice.view.form</field>
|
||||
<field name="model">wizard.price.update.notice</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<div invisible="not warning_msg" class="alert alert-warning"
|
||||
role="alert">
|
||||
<field name="warning_msg" readonly="True"/>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="partner_ids" invisible="True"/>
|
||||
<field name="pricing_date"/>
|
||||
<field name="send_date"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Applicable Products">
|
||||
<field name="product_template_ids"/>
|
||||
</page>
|
||||
</notebook>
|
||||
<footer>
|
||||
<button special="cancel" string="Cancel"
|
||||
class="btn btn-secondary"/>
|
||||
<button type="object" name="action_create"
|
||||
string="Create Notifications"
|
||||
class="btn btn-primary"/>
|
||||
</footer>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Loading…
Reference in a new issue