Merge pull request #3989 from expsa/dev_odex25_donation

Dev odex25 donation
This commit is contained in:
kchyounes19 2025-07-22 16:03:31 +01:00 committed by GitHub
commit b2df2b0040
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 731 additions and 41 deletions

View File

@ -12,6 +12,7 @@
'data/sequence.xml', 'data/sequence.xml',
'data/donation_stage_data.xml', 'data/donation_stage_data.xml',
'data/donation_priority_data.xml', 'data/donation_priority_data.xml',
'data/donation_recurring_cron.xml',
'views/donation_request_views.xml', 'views/donation_request_views.xml',
'views/donation_stage_views.xml', 'views/donation_stage_views.xml',
'views/donation_priority_views.xml', 'views/donation_priority_views.xml',
@ -19,6 +20,7 @@
'views/res_config_settings_views.xml', 'views/res_config_settings_views.xml',
'report/campaign_report.xml', 'report/campaign_report.xml',
'views/product_public_category.xml', 'views/product_public_category.xml',
'views/donation_recurring_views.xml',
'views/menus.xml', 'views/menus.xml',
], ],
'application': True, 'application': True,

View File

@ -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>

View File

@ -7,4 +7,11 @@
<field name="padding">5</field> <field name="padding">5</field>
<field name="company_id" eval="False"/> <field name="company_id" eval="False"/>
</record> </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> </odoo>

View File

@ -1,4 +1,6 @@
from . import donation_request from . import donation_request
from . import donation_recurring
from . import sale_order
from . import donation_stage from . import donation_stage
from . import donation_priority from . import donation_priority
from . import product_template from . import product_template

View File

@ -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

View File

@ -57,7 +57,7 @@ class DonationRequest(models.Model):
@api.depends('remaining_amount') @api.depends('remaining_amount')
def _compute_stage_id(self): def _compute_stage_id(self):
for rec in 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') new_stage = self.env.ref('ensan_donation_request.stage_done')
else: else:
new_stage = rec.stage_id new_stage = rec.stage_id

View File

@ -5,3 +5,7 @@ class ProductTemplate(models.Model):
donation_request_id = fields.Many2one('donation.request', ondelete='restrict') donation_request_id = fields.Many2one('donation.request', ondelete='restrict')
hide_from_shop_front = fields.Boolean() 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.'
)

View File

@ -1,12 +1,39 @@
from odoo import models, fields from odoo import models, fields
class ResConfigSettings(models.TransientModel): class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings' _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): class ResCompany(models.Model):
_inherit = 'res.company' _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')

View File

@ -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")

View File

@ -1,5 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 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_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_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_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 access_donation_stage,access_donation_stage,model_donation_stage,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_donation_request access_donation_request model_donation_request base.group_user 1 1 1 1
3 access_donation_recurring access_donation_recurring model_donation_recurring base.group_user 1 1 1 1
4 access_donation_recurring_line access_donation_recurring_line model_donation_recurring_line base.group_user 1 1 1 1
5 access_donation_request_public access_donation_request_public model_donation_request base.group_public 1 0 0 0
6 access_donation_request_portal access_donation_request_portal model_donation_request base.group_portal 1 0 0 0
7 access_donation_stage access_donation_stage model_donation_stage base.group_user 1 1 1 1

View File

@ -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>

View File

@ -1,31 +1,36 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<menuitem id="menu_donation_root" <menuitem id="menu_donation_root"
name="Donation Requests" name="Donation Requests"
web_icon="ensan_donation_request,static/description/icon.png" web_icon="ensan_donation_request,static/description/icon.png"
groups="ensan_donation_request.group_donations_user" groups="ensan_donation_request.group_donations_user"
sequence="10"> sequence="10">
<menuitem <menuitem
id="menu_donation_request" id="menu_donation_request"
name="Requests" name="Requests"
action="action_donation_request" action="action_donation_request"
sequence="10" /> sequence="10"/>
<menuitem <menuitem
id="menu_donation_configuration" id="menu_donation_recurring"
name="Configuration" name="Recurring Donations"
groups="ensan_donation_request.group_donations_manager" action="action_donation_recurring"
sequence="30"> 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" <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" <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" <menuitem id="menu_donation_priority" name="Donation Priorities"
action="action_donation_priority" sequence="30" /> action="action_donation_priority" sequence="30"/>
<menuitem <menuitem
id="public_category_donation_menu" id="public_category_donation_menu"
name="Categories" name="Categories"
action="product_public_category_donation_action" action="product_public_category_donation_action"
sequence="40" /> sequence="40"/>
</menuitem> </menuitem>
</menuitem> </menuitem>

View File

@ -7,13 +7,16 @@
<field name="inherit_id" ref="product.product_template_only_form_view"/> <field name="inherit_id" ref="product.product_template_only_form_view"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="sale_ok" position="before"> <field name="sale_ok" position="before">
<div>
<field name="is_recurring_donation"/>
<label for="is_recurring_donation"/>
</div>
<div> <div>
<field name="hide_from_shop_front"/> <field name="hide_from_shop_front"/>
<label for="hide_from_shop_front"/> <label for="hide_from_shop_front"/>
</div> </div>
</field> </field>
</field> </field>

View File

@ -4,38 +4,90 @@
<record id="res_config_settings_view_form" model="ir.ui.view"> <record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.donation</field> <field name="name">res.config.settings.view.form.inherit.donation</field>
<field name="model">res.config.settings</field> <field name="model">res.config.settings</field>
<field name="priority" eval="40" /> <field name="priority" eval="40"/>
<field name="inherit_id" ref="base.res_config_settings_view_form" /> <field name="inherit_id" ref="base.res_config_settings_view_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//div[hasclass('settings')]" position="inside"> <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" <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> <h2>SMS</h2>
<div class="row mt16 o_settings_container" <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="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"> <div class="o_setting_right_pane">
<span class="o_form_label">Confirmation SMS Template</span> <span class="o_form_label">Confirmation SMS Template</span>
<span class="fa fa-lg fa-building-o" <span class="fa fa-lg fa-building-o"
title="Values set here are company-specific." title="Values set here are company-specific."
aria-label="Values set here are company-specific." aria-label="Values set here are company-specific."
groups="base.group_multi_company" role="img" /> groups="base.group_multi_company" role="img"/>
<div class="text-muted"> <div class="text-muted">
Template used to send confirmation sms to donation request submitter. Template used to send confirmation sms to donation request submitter.
</div> </div>
<div class="content-group"> <div class="content-group">
<div class="row mt16"> <div class="row mt16">
<label for="donation_request_confirmation_sms_template_id" string="SMS Template" <label for="donation_request_confirmation_sms_template_id" string="SMS Template"
class="col-lg-3 o_light_label" /> class="col-lg-3 o_light_label"/>
<field name="donation_request_confirmation_sms_template_id" /> <field name="donation_request_confirmation_sms_template_id"/>
</div> </div>
</div> </div>
</div> </div>
</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> </xpath>
</field> </field>
</record> </record>

View File

@ -68,7 +68,7 @@ class ProductTemplate_Inherit(models.Model):
# logger.info("Done Percentage Change %s",self.done_percentage) # logger.info("Done Percentage Change %s",self.done_percentage)
# if self.done_percentage and self.done_percentage == 100: # if self.done_percentage and self.done_percentage == 100:
# self.sale_ok = False # self.sale_ok = False
@api.depends('end_donation_date') @api.depends('end_donation_date')
def _get_date_remaining(self): def _get_date_remaining(self):
for rec in self: for rec in self:
@ -91,13 +91,15 @@ class ProductTemplate_Inherit(models.Model):
@api.depends('target_amount', 'donated_amount') @api.depends('target_amount', 'donated_amount')
def _get_remaining_amount(self): def _get_remaining_amount(self):
for rec in 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') @api.depends('target_amount', 'donated_amount')
def get_done_percentage(self): def get_done_percentage(self):
for rec in self: for rec in self:
if rec.target_amount: 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 # When the product is 100% complete the product is deleted
if rec.done_percentage >= 100 and rec.target_amount != 1: if rec.done_percentage >= 100 and rec.target_amount != 1:
rec.sale_ok = False rec.sale_ok = False
@ -182,12 +184,15 @@ class ProductTemplate_Inherit(models.Model):
for product in products: for product in products:
donated_amount = donated_amounts.get(product.id, 0) 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.remaining_amount = new_remaining_amount
product.sale_ok = not (new_remaining_amount <= 0 and product.target_amount != 0) 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): class ProductProduct_Inherit(models.Model):
_inherit = 'product.product' _inherit = 'product.product'