Merge pull request #3988 from expsa/younes_dev_odex25_donation
Add full Recurring Donation automation with SMS alerts
This commit is contained in:
commit
a43e3665c8
|
|
@ -12,6 +12,7 @@
|
|||
'data/sequence.xml',
|
||||
'data/donation_stage_data.xml',
|
||||
'data/donation_priority_data.xml',
|
||||
'data/donation_recurring_cron.xml',
|
||||
'views/donation_request_views.xml',
|
||||
'views/donation_stage_views.xml',
|
||||
'views/donation_priority_views.xml',
|
||||
|
|
@ -19,6 +20,7 @@
|
|||
'views/res_config_settings_views.xml',
|
||||
'report/campaign_report.xml',
|
||||
'views/product_public_category.xml',
|
||||
'views/donation_recurring_views.xml',
|
||||
'views/menus.xml',
|
||||
],
|
||||
'application': True,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo noupdate="1">
|
||||
cron_recurring_create_invoice()
|
||||
<record id="cron_process_recurring_donations" model="ir.cron">
|
||||
<field name="name">Process Recurring Donations</field>
|
||||
<field name="model_id" ref="model_donation_recurring"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.cron_recurring_create_donations()</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -7,4 +7,11 @@
|
|||
<field name="padding">5</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
<record id="seq_donation_recurring" model="ir.sequence">
|
||||
<field name="name">Donation Recurring Sequence</field>
|
||||
<field name="code">donation.recurring</field>
|
||||
<field name="prefix">DR/</field>
|
||||
<field name="padding">5</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
from . import donation_request
|
||||
from . import donation_recurring
|
||||
from . import sale_order
|
||||
from . import donation_stage
|
||||
from . import donation_priority
|
||||
from . import product_template
|
||||
|
|
|
|||
|
|
@ -0,0 +1,366 @@
|
|||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import ValidationError,UserError
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
|
||||
class DonationRecurring(models.Model):
|
||||
_name = "donation.recurring"
|
||||
_description = "Donation Recurring"
|
||||
_rec_name = "name"
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
|
||||
name = fields.Char(string="Name", required=True, tracking=True, copy=False, default=_('New'))
|
||||
partner_id = fields.Many2one(comodel_name="res.partner", required=True, tracking=True)
|
||||
partner_mobile = fields.Char(string="Mobile Number", related='partner_id.mobile')
|
||||
currency_id = fields.Many2one('res.currency', related='partner_id.currency_id', readonly=True)
|
||||
total_amount = fields.Monetary(string="Total Amount", compute='_compute_total_amount', store=True,
|
||||
currency_field='currency_id', tracking=True,
|
||||
help="Total amount")
|
||||
date_start = fields.Date(string="Date Start", default=lambda self: fields.Date.context_today(self), tracking=True)
|
||||
date_end = fields.Date(string="Date End", tracking=True)
|
||||
recurring_next_date = fields.Date(
|
||||
string="Next Donation Date", tracking=True,
|
||||
help="The date when the next donation will be processed.", default=lambda self: fields.Date.context_today(self)
|
||||
)
|
||||
recurring_interval = fields.Integer(
|
||||
string="Repeat Every",
|
||||
default=1,
|
||||
required=True, tracking=True
|
||||
)
|
||||
frequency = fields.Selection(
|
||||
[('daily', 'Day(s)'),
|
||||
('weekly', 'Week(s)'),
|
||||
('monthly', 'Month(s)'),
|
||||
],
|
||||
string='Recurrence Frequency',
|
||||
default='monthly',
|
||||
required=True,
|
||||
help='How often the donation should recur.', tracking=True
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
state = fields.Selection([
|
||||
('active', 'Active'),
|
||||
('paused', 'Paused'),
|
||||
('cancel', 'Cancelled'),
|
||||
], default='active', string='Status', tracking=True)
|
||||
recurring_line_ids = fields.One2many(
|
||||
'donation.recurring.line',
|
||||
'recurring_id',
|
||||
string="Donation Lines"
|
||||
)
|
||||
sale_order_ids = fields.One2many(
|
||||
'sale.order', 'donation_recurring_id',
|
||||
string="Related Sale Orders"
|
||||
)
|
||||
sale_order_count = fields.Integer(string="Sale Orders", compute="_compute_sale_order_count", store=True)
|
||||
invoice_count = fields.Integer(string="Invoices", compute="_compute_invoice_count", store=True)
|
||||
|
||||
@api.depends('sale_order_ids')
|
||||
def _compute_sale_order_count(self):
|
||||
for rec in self:
|
||||
rec.sale_order_count = len(rec.sale_order_ids)
|
||||
|
||||
@api.depends('sale_order_ids.invoice_ids')
|
||||
def _compute_invoice_count(self):
|
||||
for rec in self:
|
||||
invoices = rec.sale_order_ids.mapped('invoice_ids')
|
||||
rec.invoice_count = len(invoices)
|
||||
|
||||
def action_view_sale_orders(self):
|
||||
self.ensure_one()
|
||||
sale_orders = self.env['sale.order'].search([('donation_recurring_id', '=', self.id)])
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Sales Orders'),
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'sale.order',
|
||||
'domain': [('id', 'in', sale_orders.ids)],
|
||||
'context': {'default_partner_id': self.partner_id.id, 'create': False,
|
||||
'edit': False, },
|
||||
}
|
||||
|
||||
def action_view_invoices(self):
|
||||
self.ensure_one()
|
||||
invoices = self.sale_order_ids.mapped('invoice_ids')
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Invoices'),
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'account.move',
|
||||
'domain': [('id', 'in', invoices.ids)],
|
||||
'context': {
|
||||
'default_move_type': 'out_invoice',
|
||||
'create': False,
|
||||
'edit': False,
|
||||
},
|
||||
}
|
||||
|
||||
@api.depends('recurring_line_ids', 'recurring_line_ids.total_amount')
|
||||
def _compute_total_amount(self):
|
||||
for rec in self:
|
||||
rec.total_amount = sum(line.total_amount for line in rec.recurring_line_ids)
|
||||
|
||||
@api.onchange('recurring_interval', 'frequency')
|
||||
def _onchange_frequency_or_interval(self):
|
||||
for rec in self:
|
||||
last_line = rec.recurring_line_ids.sorted('date')[-1] if rec.recurring_line_ids else None
|
||||
if last_line:
|
||||
interval = rec.recurring_interval or 1
|
||||
if rec.frequency == 'daily':
|
||||
rec.recurring_next_date = last_line.date + relativedelta(days=interval)
|
||||
elif rec.frequency == 'weekly':
|
||||
rec.recurring_next_date = last_line.date + relativedelta(weeks=interval)
|
||||
elif rec.frequency == 'monthly':
|
||||
rec.recurring_next_date = last_line.date + relativedelta(months=interval)
|
||||
|
||||
def _create_donation_line(self):
|
||||
for rec in self:
|
||||
last_line = rec.recurring_line_ids.sorted('date')[-1] if rec.recurring_line_ids else None
|
||||
if not last_line:
|
||||
continue
|
||||
|
||||
new_line = self.env['donation.recurring.line'].create({
|
||||
'recurring_id': rec.id,
|
||||
'product_id': last_line.product_id.id,
|
||||
'quantity': last_line.quantity,
|
||||
'price_unit': last_line.price_unit,
|
||||
'date': fields.Date.context_today(self),
|
||||
})
|
||||
return new_line
|
||||
|
||||
def _create_sale_order(self, line):
|
||||
self.ensure_one()
|
||||
|
||||
if not self.partner_id or not line or line.sale_order_id:
|
||||
return
|
||||
|
||||
# 1. Prepare Sale Order vals
|
||||
sale_vals = {
|
||||
'partner_id': self.partner_id.id,
|
||||
'currency_id': self.currency_id.id,
|
||||
'date_order': line.date,
|
||||
'donation_recurring_id': self.id,
|
||||
}
|
||||
|
||||
# 2. Simulate sale.order with .new() and trigger onchanges
|
||||
sale_draft = self.env['sale.order'].new(sale_vals)
|
||||
sale_draft.onchange_partner_id()
|
||||
|
||||
# 3. Convert to create dict
|
||||
sale_final_vals = sale_draft._convert_to_write(sale_draft._cache)
|
||||
sale_order = self.env['sale.order'].sudo().create(sale_final_vals)
|
||||
|
||||
# 4. Prepare Sale Order Line vals
|
||||
line_vals = {
|
||||
'order_id': sale_order.id,
|
||||
'product_id': line.product_id.id,
|
||||
'product_uom_qty': line.quantity,
|
||||
'qty_invoiced': line.quantity,
|
||||
'qty_delivered': line.quantity,
|
||||
'price_unit': line.price_unit,
|
||||
'name': line.product_id.name or 'Donation Item',
|
||||
}
|
||||
|
||||
# 5. Simulate sale.order.line with .new() and trigger onchanges
|
||||
line_draft = self.env['sale.order.line'].new(line_vals)
|
||||
|
||||
# 6. Convert to create dict
|
||||
line_final_vals = line_draft._convert_to_write(line_draft._cache)
|
||||
self.env['sale.order.line'].sudo().create(line_final_vals)
|
||||
|
||||
# 7. Confirm the order
|
||||
sale_order.action_confirm()
|
||||
|
||||
# 8. Link the recurring line
|
||||
line.sale_order_id = sale_order.id
|
||||
|
||||
self.message_post(
|
||||
body=_("✅ Sale Order <b>%s</b> created for donation dated %s.") % (sale_order.name, line.date)
|
||||
)
|
||||
|
||||
return sale_order
|
||||
|
||||
# def process_delivery_from_order(self, order, quantity_done=None):
|
||||
# picking = order.picking_ids.filtered(lambda p: p.state not in ('done', 'cancel'))
|
||||
# if not picking:
|
||||
# return False
|
||||
#
|
||||
# for move in picking.move_lines:
|
||||
# move.quantity_done = quantity_done or move.product_uom_qty
|
||||
#
|
||||
# result = picking.button_validate()
|
||||
# if isinstance(result, dict) and result.get('res_model'):
|
||||
# wizard = self.env[result['res_model']].browse(result['res_id'])
|
||||
# wizard.process()
|
||||
#
|
||||
# self.message_post(body=_("📦 Delivery validated for Sale Order <b>%s</b>.") % order.name)
|
||||
# return picking
|
||||
|
||||
def create_invoice_from_order(self, order):
|
||||
invoice = order._create_invoices()
|
||||
invoice.action_post()
|
||||
self.message_post(body=_("🧾 Invoice <b>%s</b> posted for Sale Order <b>%s</b>.") % (invoice.name, order.name))
|
||||
return invoice
|
||||
|
||||
def action_pause(self):
|
||||
for record in self:
|
||||
record.state = 'paused'
|
||||
template = self.env.company.donation_recurring_paused_sms_template_id
|
||||
if not template:
|
||||
raise ValidationError(
|
||||
_("⚠️ SMS template for 'Paused Recurring Donation' is not configured in Company settings."))
|
||||
record._message_sms_with_template(
|
||||
template=template,
|
||||
partner_ids=record.partner_id.ids,
|
||||
put_in_queue=True
|
||||
)
|
||||
|
||||
def action_resume(self):
|
||||
today = fields.Date.context_today(self)
|
||||
for record in self:
|
||||
if record.state != 'active':
|
||||
old_date = record.recurring_next_date
|
||||
record.state = 'active'
|
||||
|
||||
if old_date and old_date < today:
|
||||
record.recurring_next_date = today
|
||||
record.message_post(
|
||||
body=_(
|
||||
"🔁 Recurring profile resumed. "
|
||||
"Next donation date was in the past (%s) and has been reset to today (%s)."
|
||||
) % (old_date, today)
|
||||
)
|
||||
else:
|
||||
record.message_post(
|
||||
body=_("🔁 Recurring profile resumed. Next donation scheduled on %s.") % old_date
|
||||
)
|
||||
template = self.env.company.donation_recurring_resumed_sms_template_id
|
||||
if not template:
|
||||
raise ValidationError(
|
||||
_("⚠️ SMS template for 'Resumed Recurring Donation' is not configured in Company settings.")
|
||||
)
|
||||
record._message_sms_with_template(
|
||||
template=template,
|
||||
partner_ids=record.partner_id.ids,
|
||||
put_in_queue=True
|
||||
)
|
||||
|
||||
def action_cancel(self):
|
||||
for record in self:
|
||||
record.state = 'cancel'
|
||||
record.active = False
|
||||
|
||||
def action_reset_to_active(self):
|
||||
for record in self:
|
||||
record.state = 'active'
|
||||
|
||||
@api.model
|
||||
def cron_recurring_create_donations(self, date_ref=None):
|
||||
return self._cron_recurring_create(date_ref)
|
||||
|
||||
@api.model
|
||||
def _cron_recurring_create(self, date_ref=None):
|
||||
if not date_ref:
|
||||
date_ref = fields.Date.context_today(self)
|
||||
domain = self._get_donations_to_process_domain(date_ref)
|
||||
|
||||
records = self.search(domain)
|
||||
for rec in records:
|
||||
try:
|
||||
unprocessed_lines = rec.recurring_line_ids.filtered(lambda l: not l.sale_order_id)
|
||||
if unprocessed_lines:
|
||||
for line in unprocessed_lines:
|
||||
order = rec._create_sale_order(line)
|
||||
if order:
|
||||
rec.create_invoice_from_order(order)
|
||||
new_line = rec._create_donation_line()
|
||||
if not new_line:
|
||||
continue
|
||||
order = rec._create_sale_order(new_line)
|
||||
if order:
|
||||
rec.create_invoice_from_order(order)
|
||||
rec._advance_next_date()
|
||||
except Exception as e:
|
||||
rec.message_post(body=_("⛔ Unexpected error:<br/><pre>%s</pre>") % str(e))
|
||||
continue
|
||||
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def _get_donations_to_process_domain(self, date_ref=None):
|
||||
if not date_ref:
|
||||
date_ref = fields.Date.context_today(self)
|
||||
return [
|
||||
('recurring_next_date', '<=', date_ref),
|
||||
('state', '=', 'active'),
|
||||
('active', '=', True),
|
||||
]
|
||||
|
||||
def _advance_next_date(self):
|
||||
for rec in self:
|
||||
interval = rec.recurring_interval or 1
|
||||
if rec.frequency == 'daily':
|
||||
rec.recurring_next_date += relativedelta(days=interval)
|
||||
elif rec.frequency == 'weekly':
|
||||
rec.recurring_next_date += relativedelta(weeks=interval)
|
||||
elif rec.frequency == 'monthly':
|
||||
rec.recurring_next_date += relativedelta(months=interval)
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if vals.get('name', _('New')) == _('New'):
|
||||
vals['name'] = self.env['ir.sequence'].next_by_code('donation.recurring') or _('New')
|
||||
res = super(DonationRecurring, self).create(vals)
|
||||
for line in res.recurring_line_ids:
|
||||
order = res._create_sale_order(line)
|
||||
if not order:
|
||||
continue
|
||||
res.sudo().create_invoice_from_order(order)
|
||||
res._advance_next_date()
|
||||
template = self.env.company.donation_recurring_created_sms_template_id
|
||||
if not template:
|
||||
raise ValidationError(_("⚠️ SMS template for 'Send When Created' is not configured in Company settings."))
|
||||
|
||||
res._message_sms_with_template(
|
||||
template=template,
|
||||
partner_ids=res.partner_id.ids,
|
||||
put_in_queue=True
|
||||
)
|
||||
return res
|
||||
|
||||
def unlink(self):
|
||||
for rec in self:
|
||||
if rec.state == 'active':
|
||||
raise UserError(
|
||||
_("❌ Cannot delete a profile while it is in 'Active' state. Please cancel or pause it first."))
|
||||
|
||||
if rec.sale_order_ids:
|
||||
raise UserError(_("❌ Cannot delete a profile that has related Sale Orders. Archive it instead."))
|
||||
template = self.env.company.donation_recurring_deleted_sms_template_id
|
||||
if template:
|
||||
rec._message_sms_with_template(
|
||||
template=template,
|
||||
partner_ids=rec.partner_id.ids,
|
||||
put_in_queue=True
|
||||
)
|
||||
return super(DonationRecurring, self).unlink()
|
||||
|
||||
|
||||
class DonationRecurringLine(models.Model):
|
||||
_name = 'donation.recurring.line'
|
||||
_description = 'Recurring Donation Line'
|
||||
|
||||
recurring_id = fields.Many2one('donation.recurring', string="Recurring Profile")
|
||||
product_id = fields.Many2one('product.product', string="Product", domain=[('is_recurring_donation', '=', True)],
|
||||
required=True)
|
||||
quantity = fields.Float(string="Quantity", default=1.0, digits='Product Unit of Measure')
|
||||
price_unit = fields.Float(string="Unit Price", digits='Product Price')
|
||||
total_amount = fields.Monetary(string="Total Amount", compute="_compute_total_amount", store=True)
|
||||
currency_id = fields.Many2one('res.currency', related='recurring_id.partner_id.currency_id', readonly=True)
|
||||
date = fields.Date(string="Donation Date", default=fields.Date.context_today)
|
||||
sale_order_id = fields.Many2one('sale.order', string="Related Sale Order", readonly=True)
|
||||
|
||||
@api.depends('quantity', 'price_unit')
|
||||
def _compute_total_amount(self):
|
||||
for line in self:
|
||||
line.total_amount = line.quantity * line.price_unit
|
||||
|
|
@ -57,7 +57,7 @@ class DonationRequest(models.Model):
|
|||
@api.depends('remaining_amount')
|
||||
def _compute_stage_id(self):
|
||||
for rec in self:
|
||||
if rec.remaining_amount <= 0:
|
||||
if rec.product_id and rec.remaining_amount <= 0:
|
||||
new_stage = self.env.ref('ensan_donation_request.stage_done')
|
||||
else:
|
||||
new_stage = rec.stage_id
|
||||
|
|
|
|||
|
|
@ -5,3 +5,7 @@ class ProductTemplate(models.Model):
|
|||
|
||||
donation_request_id = fields.Many2one('donation.request', ondelete='restrict')
|
||||
hide_from_shop_front = fields.Boolean()
|
||||
is_recurring_donation = fields.Boolean(
|
||||
string='Is Recurring Donation Product',
|
||||
help='Enable this if the product can be used in recurring donations.'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,39 @@
|
|||
from odoo import models, fields
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
donation_request_confirmation_sms_template_id = fields.Many2one('sms.template', required=True, related='company_id.donation_request_confirmation_sms_template_id', readonly=False)
|
||||
donation_request_confirmation_sms_template_id = fields.Many2one('sms.template', required=True,
|
||||
related='company_id.donation_request_confirmation_sms_template_id',
|
||||
readonly=False)
|
||||
donation_recurring_created_sms_template_id = fields.Many2one(
|
||||
'sms.template', related='company_id.donation_recurring_created_sms_template_id', readonly=False
|
||||
)
|
||||
donation_recurring_paused_sms_template_id = fields.Many2one(
|
||||
'sms.template', related='company_id.donation_recurring_paused_sms_template_id', readonly=False
|
||||
)
|
||||
donation_recurring_resumed_sms_template_id = fields.Many2one(
|
||||
'sms.template', related='company_id.donation_recurring_resumed_sms_template_id', readonly=False
|
||||
)
|
||||
donation_recurring_cancelled_sms_template_id = fields.Many2one(
|
||||
'sms.template', related='company_id.donation_recurring_cancelled_sms_template_id', readonly=False
|
||||
)
|
||||
donation_recurring_charged_sms_template_id = fields.Many2one(
|
||||
'sms.template', related='company_id.donation_recurring_charged_sms_template_id', readonly=False
|
||||
)
|
||||
donation_recurring_deleted_sms_template_id = fields.Many2one(
|
||||
'sms.template', related='company_id.donation_recurring_deleted_sms_template_id', readonly=False
|
||||
)
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = 'res.company'
|
||||
|
||||
donation_request_confirmation_sms_template_id = fields.Many2one('sms.template', required=True)
|
||||
donation_request_confirmation_sms_template_id = fields.Many2one('sms.template', required=True)
|
||||
donation_recurring_created_sms_template_id = fields.Many2one('sms.template')
|
||||
donation_recurring_paused_sms_template_id = fields.Many2one('sms.template')
|
||||
donation_recurring_resumed_sms_template_id = fields.Many2one('sms.template')
|
||||
donation_recurring_cancelled_sms_template_id = fields.Many2one('sms.template')
|
||||
donation_recurring_charged_sms_template_id = fields.Many2one('sms.template')
|
||||
donation_recurring_deleted_sms_template_id = fields.Many2one('sms.template')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api,_
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
donation_recurring_id = fields.Many2one('donation.recurring', string="Recurring Donation")
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_donation_request,access_donation_request,model_donation_request,base.group_user,1,1,1,1
|
||||
access_donation_recurring,access_donation_recurring,model_donation_recurring,base.group_user,1,1,1,1
|
||||
access_donation_recurring_line,access_donation_recurring_line,model_donation_recurring_line,base.group_user,1,1,1,1
|
||||
access_donation_request_public,access_donation_request_public,model_donation_request,base.group_public,1,0,0,0
|
||||
access_donation_request_portal,access_donation_request_portal,model_donation_request,base.group_portal,1,0,0,0
|
||||
access_donation_stage,access_donation_stage,model_donation_stage,base.group_user,1,1,1,1
|
||||
|
|
|
|||
|
|
|
@ -0,0 +1,193 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_donation_recurring_form" model="ir.ui.view">
|
||||
<field name="name">donation.recurring.form</field>
|
||||
<field name="model">donation.recurring</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Recurring Donations">
|
||||
<field name="active" invisible="1"/>
|
||||
<header>
|
||||
<button name="action_pause"
|
||||
type="object"
|
||||
string="Pause"
|
||||
class="oe_highlight"
|
||||
attrs="{'invisible': [('state', '!=', 'active')]}"/>
|
||||
|
||||
<button name="action_resume"
|
||||
type="object"
|
||||
string="Resume"
|
||||
class="oe_highlight"
|
||||
attrs="{'invisible': [('state', '!=', 'paused')]}"/>
|
||||
|
||||
<button name="action_cancel"
|
||||
type="object"
|
||||
string="Cancel"
|
||||
confirm="Are you sure you want to cancel this recurring profile?"
|
||||
attrs="{'invisible': [('state', '=', 'cancel')]}"/>
|
||||
|
||||
<!-- <button name="action_reset_to_active"-->
|
||||
<!-- type="object"-->
|
||||
<!-- string="Reactivate"-->
|
||||
<!-- class="oe_highlight"-->
|
||||
<!-- confirm="Are you sure you want to reactivate this cancelled profile?"-->
|
||||
<!-- attrs="{'invisible': [('state', '!=', 'cancel')]}"/>-->
|
||||
<field name="state" readonly="1" widget="statusbar"
|
||||
statusbar_visible="active,cancel"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-shopping-cart"
|
||||
name="action_view_sale_orders"
|
||||
attrs="{'invisible': [('sale_order_count', '=', 0)]}">
|
||||
<div class="o_form_field o_stat_info">
|
||||
<span class="o_stat_value">
|
||||
<field name="sale_order_count"/>
|
||||
</span>
|
||||
<span class="o_stat_text">Sale Orders</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-file-text-o"
|
||||
name="action_view_invoices"
|
||||
attrs="{'invisible': [('invoice_count', '=', 0)]}">
|
||||
<div class="o_form_field o_stat_info">
|
||||
<span class="o_stat_value">
|
||||
<field name="invoice_count"/>
|
||||
</span>
|
||||
<span class="o_stat_text">Invoices</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<widget name="web_ribbon" title="Archived" bg_color="bg-danger"
|
||||
attrs="{'invisible': [('active', '=', True)]}"/>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" default_focus="1" widget="text" class="text-break"
|
||||
options="{'line_breaks': False}" placeholder="Name"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="sale_order_ids" invisible="1"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="partner_mobile"/>
|
||||
<label for="recurring_interval"/>
|
||||
<div class="o_row">
|
||||
<field name="recurring_interval"
|
||||
class="oe_inline"
|
||||
nolabel="1"
|
||||
/>
|
||||
<field name="frequency" class="oe_inline"
|
||||
nolabel="1"/>
|
||||
</div>
|
||||
</group>
|
||||
<group>
|
||||
<field name="date_start"/>
|
||||
<field name="date_end"/>
|
||||
<field name="recurring_next_date"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Donation Lines">
|
||||
<field name="recurring_line_ids"
|
||||
attrs="{'readonly': [('state', '!=', 'active')]}">
|
||||
<tree string="Donation Lines"
|
||||
editable="bottom">
|
||||
<field name="recurring_id" invisible="1"/>
|
||||
<field name="sale_order_id" invisible="1"/>
|
||||
<field name="product_id"/>
|
||||
<field name="quantity"/>
|
||||
<field name="price_unit"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="total_amount"/>
|
||||
<field name="date" readonly="1" force_save="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<group class="oe_subtotal_footer oe_right">
|
||||
<label for="total_amount"/>
|
||||
<field name="total_amount" nolabel="1" widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="activity_ids"/>
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_donation_recurring_kanban" model="ir.ui.view">
|
||||
<field name="name">donation.recurring.kanban</field>
|
||||
<field name="model">donation.recurring</field>
|
||||
<field name="arch" type="xml">
|
||||
<!-- <kanban default_group_by="stage_id" class="o_kanban_small_column" sample="1">-->
|
||||
<kanban>
|
||||
<!-- <field name="stage_id"/>-->
|
||||
<field name="name"/>
|
||||
<field name="partner_id"/>
|
||||
<!-- <field name="beneficiary_name"/> -->
|
||||
<field name="partner_mobile"/>
|
||||
<field name="total_amount"/>
|
||||
<!-- <field name="image_128"/> -->
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click">
|
||||
<div class="oe_kanban_details">
|
||||
<strong class="o_kanban_record_title">
|
||||
<field name="name"/>
|
||||
</strong>
|
||||
<div class="o_kanban_record_subtitle">
|
||||
<field name="partner_id"/>
|
||||
</div>
|
||||
<!-- <div>
|
||||
<field name="beneficiary_name"/>
|
||||
</div> -->
|
||||
|
||||
<div class="text-muted">
|
||||
Amount:
|
||||
<field name="partner_mobile"/>
|
||||
</div>
|
||||
<div class="text-muted" t-if="record.remaining_amount.value > 0">
|
||||
Remaining:
|
||||
<field name="total_amount"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_donation_recurring_tree" model="ir.ui.view">
|
||||
<field name="name">donation.recurring.tree</field>
|
||||
<field name="model">donation.recurring</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Recurring Donations">
|
||||
<field name="name"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="partner_mobile"/>
|
||||
<field name="frequency"/>
|
||||
<field name="date_start"/>
|
||||
<field name="date_end"/>
|
||||
<field name="recurring_next_date"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_donation_recurring" model="ir.actions.act_window">
|
||||
<field name="name">Recurring Donations</field>
|
||||
<field name="res_model">donation.recurring</field>
|
||||
<field name="view_mode">tree,kanban,form</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -1,31 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<menuitem id="menu_donation_root"
|
||||
name="Donation Requests"
|
||||
web_icon="ensan_donation_request,static/description/icon.png"
|
||||
groups="ensan_donation_request.group_donations_user"
|
||||
sequence="10">
|
||||
name="Donation Requests"
|
||||
web_icon="ensan_donation_request,static/description/icon.png"
|
||||
groups="ensan_donation_request.group_donations_user"
|
||||
sequence="10">
|
||||
<menuitem
|
||||
id="menu_donation_request"
|
||||
name="Requests"
|
||||
action="action_donation_request"
|
||||
sequence="10" />
|
||||
id="menu_donation_request"
|
||||
name="Requests"
|
||||
action="action_donation_request"
|
||||
sequence="10"/>
|
||||
<menuitem
|
||||
id="menu_donation_configuration"
|
||||
name="Configuration"
|
||||
groups="ensan_donation_request.group_donations_manager"
|
||||
sequence="30">
|
||||
id="menu_donation_recurring"
|
||||
name="Recurring Donations"
|
||||
action="action_donation_recurring"
|
||||
sequence="20"/>
|
||||
<menuitem
|
||||
id="menu_donation_configuration"
|
||||
name="Configuration"
|
||||
groups="ensan_donation_request.group_donations_manager"
|
||||
sequence="30">
|
||||
<menuitem id="menu_donation_config" name="Settings"
|
||||
action="action_donation_request_config" sequence="0" />
|
||||
action="action_donation_request_config" sequence="0"/>
|
||||
<menuitem id="menu_donation_stage" name="Donation Stages"
|
||||
action="action_donation_stage" sequence="10" />
|
||||
action="action_donation_stage" sequence="10"/>
|
||||
<menuitem id="menu_donation_priority" name="Donation Priorities"
|
||||
action="action_donation_priority" sequence="30" />
|
||||
action="action_donation_priority" sequence="30"/>
|
||||
<menuitem
|
||||
id="public_category_donation_menu"
|
||||
name="Categories"
|
||||
action="product_public_category_donation_action"
|
||||
sequence="40" />
|
||||
id="public_category_donation_menu"
|
||||
name="Categories"
|
||||
action="product_public_category_donation_action"
|
||||
sequence="40"/>
|
||||
|
||||
</menuitem>
|
||||
</menuitem>
|
||||
|
|
|
|||
|
|
@ -7,13 +7,16 @@
|
|||
<field name="inherit_id" ref="product.product_template_only_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="sale_ok" position="before">
|
||||
<div>
|
||||
<field name="is_recurring_donation"/>
|
||||
<label for="is_recurring_donation"/>
|
||||
</div>
|
||||
<div>
|
||||
<field name="hide_from_shop_front"/>
|
||||
<label for="hide_from_shop_front"/>
|
||||
</div>
|
||||
|
||||
|
||||
</field>
|
||||
|
||||
|
||||
|
||||
</field>
|
||||
|
|
|
|||
|
|
@ -4,38 +4,90 @@
|
|||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.donation</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="priority" eval="40" />
|
||||
<field name="inherit_id" ref="base.res_config_settings_view_form" />
|
||||
<field name="priority" eval="40"/>
|
||||
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('settings')]" position="inside">
|
||||
<field name="country_code" invisible="1" />
|
||||
<field name="country_code" invisible="1"/>
|
||||
<div class="app_settings_block" data-string="Donation Requests" string="Donation Requests"
|
||||
data-key="d_requests" groups="ensan_donation_request.group_donations_manager">
|
||||
data-key="d_requests" groups="ensan_donation_request.group_donations_manager">
|
||||
<h2>SMS</h2>
|
||||
<div class="row mt16 o_settings_container"
|
||||
name="donation_sms_settings">
|
||||
name="donation_sms_settings">
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<div class="o_setting_left_pane" />
|
||||
<div class="o_setting_left_pane"/>
|
||||
<div class="o_setting_right_pane">
|
||||
<span class="o_form_label">Confirmation SMS Template</span>
|
||||
<span class="fa fa-lg fa-building-o"
|
||||
title="Values set here are company-specific."
|
||||
aria-label="Values set here are company-specific."
|
||||
groups="base.group_multi_company" role="img" />
|
||||
title="Values set here are company-specific."
|
||||
aria-label="Values set here are company-specific."
|
||||
groups="base.group_multi_company" role="img"/>
|
||||
<div class="text-muted">
|
||||
Template used to send confirmation sms to donation request submitter.
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<div class="row mt16">
|
||||
<label for="donation_request_confirmation_sms_template_id" string="SMS Template"
|
||||
class="col-lg-3 o_light_label" />
|
||||
<field name="donation_request_confirmation_sms_template_id" />
|
||||
class="col-lg-3 o_light_label"/>
|
||||
<field name="donation_request_confirmation_sms_template_id"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app_settings_block" data-string="Recurring Donations" string="Recurring Donations"
|
||||
data-key="recurring_sms">
|
||||
<!-- groups="ensan_donation_request.group_donations_manager"-->
|
||||
<h2>Recurring Donation SMS</h2>
|
||||
<div class="row mt16 o_settings_container" name="donation_sms_settings">
|
||||
<div class="col-12 col-lg-6 o_setting_box">
|
||||
<div class="o_setting_left_pane"/>
|
||||
<div class="o_setting_right_pane">
|
||||
<span class="o_form_label">SMS Templates</span>
|
||||
<div class="text-muted">Templates used for SMS notifications.</div>
|
||||
<div class="content-group">
|
||||
<div class="row mt16">
|
||||
<label for="donation_recurring_created_sms_template_id"
|
||||
string="Send When Created"
|
||||
class="col-lg-3 o_light_label"/>
|
||||
<field name="donation_recurring_created_sms_template_id"/>
|
||||
</div>
|
||||
<div class="row mt16">
|
||||
<label for="donation_recurring_paused_sms_template_id" string="Send When Paused"
|
||||
class="col-lg-3 o_light_label"/>
|
||||
<field name="donation_recurring_paused_sms_template_id"/>
|
||||
</div>
|
||||
<div class="row mt16">
|
||||
<label for="donation_recurring_resumed_sms_template_id"
|
||||
string="Send When Resumed"
|
||||
class="col-lg-3 o_light_label"/>
|
||||
<field name="donation_recurring_resumed_sms_template_id"/>
|
||||
</div>
|
||||
<div class="row mt16">
|
||||
<label for="donation_recurring_cancelled_sms_template_id"
|
||||
string="Send When Cancelled"
|
||||
class="col-lg-3 o_light_label"/>
|
||||
<field name="donation_recurring_cancelled_sms_template_id"/>
|
||||
</div>
|
||||
<div class="row mt16">
|
||||
<label for="donation_recurring_charged_sms_template_id"
|
||||
string="Send When Charged"
|
||||
class="col-lg-3 o_light_label"/>
|
||||
<field name="donation_recurring_charged_sms_template_id"/>
|
||||
</div>
|
||||
<div class="row mt16">
|
||||
<label for="donation_recurring_deleted_sms_template_id"
|
||||
string="Send When Deleted"
|
||||
class="col-lg-3 o_light_label"/>
|
||||
<field name="donation_recurring_deleted_sms_template_id"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class ProductTemplate_Inherit(models.Model):
|
|||
# logger.info("Done Percentage Change %s",self.done_percentage)
|
||||
# if self.done_percentage and self.done_percentage == 100:
|
||||
# self.sale_ok = False
|
||||
|
||||
|
||||
@api.depends('end_donation_date')
|
||||
def _get_date_remaining(self):
|
||||
for rec in self:
|
||||
|
|
@ -91,13 +91,15 @@ class ProductTemplate_Inherit(models.Model):
|
|||
@api.depends('target_amount', 'donated_amount')
|
||||
def _get_remaining_amount(self):
|
||||
for rec in self:
|
||||
rec.remaining_amount = rec.target_amount - rec.donated_amount
|
||||
remaining = rec.target_amount - rec.donated_amount
|
||||
rec.remaining_amount = remaining if remaining > 0 else 0.0
|
||||
|
||||
@api.depends('target_amount', 'donated_amount')
|
||||
def get_done_percentage(self):
|
||||
for rec in self:
|
||||
if rec.target_amount:
|
||||
rec.done_percentage = (rec.donated_amount / rec.target_amount) * 100
|
||||
raw_percentage = (rec.donated_amount / rec.target_amount) * 100
|
||||
rec.done_percentage = min(raw_percentage, 100)
|
||||
# When the product is 100% complete the product is deleted
|
||||
if rec.done_percentage >= 100 and rec.target_amount != 1:
|
||||
rec.sale_ok = False
|
||||
|
|
@ -182,12 +184,15 @@ class ProductTemplate_Inherit(models.Model):
|
|||
|
||||
for product in products:
|
||||
donated_amount = donated_amounts.get(product.id, 0)
|
||||
new_remaining_amount = product.target_amount - donated_amount
|
||||
# new_remaining_amount = product.target_amount - donated_amount
|
||||
new_remaining_amount = max(product.target_amount - donated_amount, 0.0)
|
||||
|
||||
product.remaining_amount = new_remaining_amount
|
||||
product.sale_ok = not (new_remaining_amount <= 0 and product.target_amount != 0)
|
||||
product.done_percentage = (donated_amount / product.target_amount) * 100 if product.target_amount else 0
|
||||
|
||||
# product.done_percentage = (donated_amount / product.target_amount) * 100 if product.target_amount else 0
|
||||
product.done_percentage = min((donated_amount / product.target_amount) * 100,
|
||||
100) if product.target_amount else 0
|
||||
|
||||
class ProductProduct_Inherit(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue