You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

623 lines
23 KiB

from odoo import models, fields, api,_
from odoo.exceptions import UserError, ValidationError,Warning
from psycopg2 import sql, DatabaseError
from datetime import datetime,timedelta,date
from dateutil.relativedelta import relativedelta
from werkzeug import utils
import base64
class opendons_recurring_donation(models.Model):
_name = 'opendons.recurring_donation'
_description = 'recurring donation management'
last_execution_date=fields.Date('Last execution date')
class DonationDonation(models.Model):
_inherit = 'donation.donation'
donor_id=fields.Char(related='partner_id.donor_id')
operation_id = fields.Many2one(
'opendons.operation',
string='Operation',
track_visibility='onchange',
ondelete='restrict',
required=True,
domain=[('state', '=', 'exported')]
)
# operation_state= fields.Char(related='operation_id.state')
segment_id = fields.Many2one(
'opendons.segment',
string='Segment',
track_visibility='onchange',
required=True,
ondelete='restrict'
)
recurring_template = fields.Selection(
[("active", "Active"), ("suspended", "Suspended"),("stopped", "Stopped")],
string="Recurring Template",
copy=False,
index=True,
tracking=True,
)
start_date= fields.Date(
string='Start Date',
index=True,
required=True,
track_visibility='onchange'
)
end_date = fields.Date(
string='End Date',
index=True,
track_visibility='onchange'
)
stopped_date = fields.Date(
string='Stopped Date',
index=True,
readonly=True,
track_visibility='onchange'
)
stopped_reason=fields.Selection(string='Stop reason',selection=[('motif1', 'Motif 1'), ('motif2', 'Motif 2')],default='motif1',track_visibility='onchange')
suspended_date = fields.Date(
string='Suspended Date',
index=True,
readonly=True,
track_visibility='onchange'
)
lastexecution_date = fields.Date(
string='Last execution Date',
index=True,
readonly=True,
track_visibility='onchange'
)
frequency =fields.Selection(
[('monthly','Monthly'), ('bimonthly','Bimonthly'),('quarterly','Quarterly'),('half-yearly','Half-yearly'),('annually','Annually')],
default='monthly'
)
payment_batch_id = fields.Many2one(
'opendons_payment_batch',
string='Payment Batch',
ondelete='set null'
)
year_donation_date=fields.Integer('Year donation date',compute='_compute_year_donation_date',store=True)
print_email_history_ids=fields.One2many(
'opendons.donation.print_email_history',
'donation_id',
string='print or email history ',
readonly=True
)
def _compute_year_donation_date(self):
for rec in self:
rec.year_donation_date=rec.donation_date.year
def _default_payment_mode_id(self):
#raise Warning(self.recurring_template)
if self.recurring_template=='active':
sepa_payment_method=self.env['account.payment.method'].search([('code','=','sepa_direct_debit')],limit=1)
if sepa_payment_method:
sepa_payment_mode=self.env['account.payment.mode'].search([('payment_method_id','=',int(sepa_payment_method.id))],limit=1)
if sepa_payment_mode:
self.payment_mode_id=sepa_payment_mode.id
else:
raise Warning(_('Please configure a SEPA payment mode'))
payment_mode_id = fields.Many2one(
"account.payment.mode",
string="Payment Mode",
domain="[('company_id', '=', company_id), ('donation', '=', True)]",
copy=False,
tracking=True,
check_company=True,
default=_default_payment_mode_id,
states={"done": [("readonly", True)]}
)
@api.model
def create(self, vals):
vals['tax_receipt_option']='annual'
res = super(DonationDonation, self).create(vals)
return res
@api.onchange("partner_id")
def donation_partner_direct_debit_change(self):
if not self.payment_mode_id and self.recurring_template=='active':
self.mandate_id=False
sepa_payment_method=self.env['account.payment.method'].search([('code','=','sepa_direct_debit')],limit=1)
if sepa_payment_method:
sepa_payment_mode=self.env['account.payment.mode'].search([('payment_method_id','=',int(sepa_payment_method.id))],limit=1)
if sepa_payment_mode:
self.payment_mode_id=sepa_payment_mode.id
else:
raise Warning(_('Please configure a SEPA payment mode'))
else:
raise Warning(_('Please configure a SEPA payment method'))
if (
self.partner_id
and self.payment_mode_id
and self.payment_mode_id.payment_method_id.mandate_required
):
mandate = self.env["account.banking.mandate"].search(
[
("state", "=", "valid"),
("partner_id", "=", int(self.partner_id)),
],
limit=1,
)
if mandate:
self.mandate_id = mandate
else:
self.mandate_id=False
@api.onchange("payment_mode_id")
def donation_payment_mode_id_change(self):
if self.recurring_template=='active' and self.payment_mode_id:
sepa_payment_method=self.env['account.payment.method'].search([('code','=','sepa_direct_debit')],limit=1)
if sepa_payment_method:
sepa_payment_mode=self.env['account.payment.mode'].search([('payment_method_id','=',int(sepa_payment_method.id))],limit=1)
if sepa_payment_mode:
if self.payment_mode_id!=sepa_payment_mode.id :
self.payment_mode_id=sepa_payment_mode.id
else:
raise ValidationError(_('Please configure SEPA Payment'))
@api.onchange('operation_id')
def _onchange_operation_id(self):
res = {}
res['domain']={'segment_id':[('operation_id', '=', self.operation_id.id)]}
return res
def generate_recurring_payment(self):
#self.ensure_one()
generation_day = self.env['ir.config_parameter'].get_param('opendons.generation_day', '')
limit_days_before = int(self.env['ir.config_parameter'].get_param('opendons.limit_days_before'))
debug_mode = self.env['ir.config_parameter'].get_param('opendons.pa_debug_mode')
r=self.env['opendons.recurring_donation'].search([])
if r.last_execution_date and not debug_mode:
month_last_exe=r.last_execution_date.month
month_today=datetime.now().date().month
#si le mois en cours a déjà été executé, l'execution est donc pour le mois suivant :
if month_last_exe==month_today:
date_generation=r.last_execution_date+relativedelta(months=1)
date_before=date_generation-timedelta(days=limit_days_before)
date_today=datetime.now().date()
#si la date du jour est antérieur à la date limite alors message
if date_today<date_before: raise Warning('you can\'t generate recurring payment before the '+str(date_before))
elif debug_mode :
#en mode debug on génère les dons même si déjà généré pour le mois
date_generation=datetime(datetime.now().date().year,datetime.now().date().month,int(generation_day))
else:
#si aucune génération de don effectuée, la date de génération est le mois en cours
date_generation=datetime(datetime.now().date().year,datetime.now().date().month,int(generation_day))
#si le mois en cours a déjà été executé, l'execution est donc pour le mois suivant :
#si la date < date limite inférieur du mois suivant : message d'erreur
#la date de dernière execution sera alors celle du mois suivant
#si le mois en cours n'a pas été executé alors execution
#la date de dernière execution sera alors celle du mois actuel
#if date_today<date_before and date_today>date_after:raise Warning('today you can\'t generate recurring payment')
doo = self.env["donation.donation"]
#chercher les templates de dons récurrents actifs
donations = doo.search(
[
("recurring_template", "=", "active")
]
)
new_donation_ids = []
#pour chaque template de don récurrent
#création d'un don si les dates sont bonnes
for donation in donations:
if not donation.start_date: continue
generate=True
existing_recur_donations = doo.search([("source_recurring_id", "=",int(donation.id))])
if donation.start_date>fields.Date.context_today(self):
generate=False
delta=0
if donation.frequency=='annually':delta=365
if donation.frequency=='half-yearly':delta=365/2
if donation.frequency=='quarterly':delta=365/4
if donation.frequency=='bimonthly':delta=365/6
if donation.frequency=='monthly':delta=365/12
if existing_recur_donations:
for d in existing_recur_donations:
days_diff=(d.donation_date-fields.Date.context_today(self)).days
if days_diff<=delta:
generate=False
break
#si tout est ok pour la date, génération du don
if generate==True:
default = {
"recurring_template":'',
"donation_date": date_generation,
"source_recurring_id": donation.id,
"payment_ref": '',
"payment_mode_id":donation.payment_mode_id.id,
"mandate_required":'True',
"mandate_id":donation.mandate_id.id,
"state":"draft"
}
#creation du don à partir du template de don récurrent
new_donation = donation.copy(default=default)
#ajout du don à la collection des dons générés pour affichage
new_donation_ids.append(new_donation.id)
#mise à jour de la date de dernière génération
donation.lastexecution_date=date_generation
payment_mode_id=donation.payment_mode_id.id
if not new_donation_ids : raise Warning ("aucun don n'a été généré")
#on teste l'existence d'un ordre de paiement en status brouillon
#si plusieurs, c'est le dernier créé qui sera alimenté
#payorder=self.env['account.payment.order'].search([('state','=','draft')],order='create_date desc', limit=2)
#if payorder[0]:
# payorder_id=self.update_direct_debit_payment_order(new_donation_ids,payment_mode_id,payorder[0])
#sinon création de l'ordre de paiement SEPA avec tous les dons générés
#else:
payorder_id=self.create_direct_debit_payment_order(new_donation_ids,payment_mode_id)
#affichage des dons générés
# action = self.env.ref("donation.donation_action").sudo().read([])[0]
# action.update(
# {
# "domain": [("id", "in", new_donation_ids)],
# "limit": 500,
# }
# )
if not r:r.create({'last_execution_date':date_generation})
if r:r.write({'last_execution_date':date_generation})
r=self.env['opendons.recurring_donation'].search([])
action = self.env.ref("account_payment_order.account_payment_order_inbound_action").sudo().read([])[0]
action.update(
{
"view_mode": "form",
"view_type":"list",
"res_id": payorder_id if payorder_id else False
}
)
#affichage de l'ordre de prélèvement
#action = self.env.ref("account_payment_order.account_payment_order_inbound_action").sudo().read([])[0]
return action
def create_direct_debit_payment_order(self,donation_ids,payment_mode_id):
"""Create Direct debit payment order on donation validation or update
an existing draft Direct Debit pay order"""
apoo = self.env["account.payment.order"].sudo()
vals={}
vals['payment_mode_id']=payment_mode_id
payorder = apoo.create(vals)
msg = _(
"A new draft direct debit order "
"<a href=# data-oe-model=account.payment.order "
"data-oe-id=%d>%s</a> has been automatically created"
) % (payorder.id, payorder.name)
# add payment lines
donations=self.env["donation.donation"].search([("id", "in",donation_ids)])
for donation in donations:
#validation du don et génération des écritures comptables
self.validate_donation(donation)
if donation.payment_mode_id.payment_type == "inbound":
#génération de la ligne de paiement dans l'ordre de prélèvement
payment_account_id = (
donation.payment_mode_id.fixed_journal_id.payment_debit_account_id.id
)
for mline in donation.move_id.line_ids:
if mline.account_id.id == payment_account_id:
mline.sudo().create_payment_line_from_move_line(payorder)
break
if donations:donation.message_post(body=msg)
return int(payorder.id)
def update_direct_debit_payment_order(self,donation_ids,payment_mode_id,payorder):
payorder_vals = {"payment_mode_id": payment_mode_id}
msg = _(
"A new draft direct debit order "
"<a href=# data-oe-model=account.payment.order "
"data-oe-id=%d>%s</a> has been automatically created"
) % (payorder.id, payorder.name)
# add payment lines
donations=self.env["donation.donation"].search([("id", "in",donation_ids)])
for donation in donations:
#validation du don et génération des écritures comptables
self.validate_donation(donation)
if donation.payment_mode_id.payment_type == "inbound":
#génération de la ligne de paiement dans l'ordre de prélèvement
payment_account_id = (
donation.payment_mode_id.fixed_journal_id.payment_debit_account_id.id
)
for mline in donation.move_id.line_ids:
if mline.account_id.id == payment_account_id:
mline.sudo().create_payment_line_from_move_line(payorder)
break
if donations:donation.message_post(body=msg)
return int(payorder.id)
def validate_donation(self,donations):
check_total = self.env["res.users"].has_group(
"donation.group_donation_check_total"
)
for donation in donations:
if donation.donation_date > fields.Date.context_today(self):
raise UserError(
_(
"The date of donation %s should be today "
"or in the past, not in the future!"
)
% donation.number
)
if not donation.line_ids:
raise UserError(
_(
"Cannot validate donation %s because it doesn't "
"have any lines!"
)
% donation.number
)
if donation.currency_id.is_zero(donation.amount_total):
raise UserError(
_("Cannot validate donation %s because the " "total amount is 0!")
% donation.number
)
if donation.state != "draft":
raise UserError(
_(
"Cannot validate donation %s because it is not "
"in draft state."
)
% donation.number
)
if check_total and donation.currency_id.compare_amounts(
donation.check_total, donation.amount_total
):
raise UserError(
_(
"The amount of donation %s (%s) is different "
"from the sum of the donation lines (%s)."
)
% (
donation.number,
format_amount(
self.env, donation.check_total, donation.currency_id
),
format_amount(
self.env, donation.amount_total, donation.currency_id
),
)
)
full_in_kind = all([line.in_kind for line in donation.line_ids])
if not donation.payment_mode_id and not full_in_kind:
raise UserError(
_(
"Payment Mode is not set on donation %s (only fully "
"in-kind donations don't require a payment mode)."
)
% donation.number
)
vals = {"state": "done"}
if full_in_kind and donation.payment_mode_id:
vals["payment_mode_id"] = False
if not full_in_kind:
move_vals = donation._prepare_donation_move()
# when we have a full in-kind donation: no account move
if move_vals:
move = self.env["account.move"].create(move_vals)
move.action_post()
vals["move_id"] = move.id
else:
donation.message_post(
body=_("Full in-kind donation: no account move generated")
)
receipt = donation.generate_each_tax_receipt()
if receipt:
vals["tax_receipt_id"] = receipt.id
donation.write(vals)
if donation.bank_statement_line_id:
donation._reconcile_donation_from_bank_statement()
donation.partner_id._update_donor_rank()
return
def recurring_donation_action(self):
sepa_payment_method=self.env['account.payment.method'].search([('code','=','sepa_direct_debit')])
if sepa_payment_method:
sepa_payment_mode=self.env['account.payment.mode'].search([('payment_method_id','=',int( sepa_payment_method))])
if sepa_payment_mode:
payment_mode_id=sepa_payment_mode.id
else:
raise Warning('Please configure mode sepa payment')
else:
raise Warning('Please configure method sepa payment')
action = self.env.ref("opendons.donation_recurring_action").sudo().read([])[0]
action.update(
{
"res_model": 'donation.donation',
"view_mode": 'tree,form,pivot,graph',
"context": {'default_recurring_template': 'active', 'recurring_view': True,'default_payment_mode_id':payment_mode_id,
'default_tax_receipt_option':'annual'},
"domain": [('recurring_template', '!=', False)]
}
)
return action
def payment_order_action(self):
#payorder=self.env['account.payment.order'].search([('state','=','draft')],order='create_date desc', limit=2)
action = self.env.ref("account_payment_order.account_payment_order_inbound_action").sudo().read([])[0]
# action.update(
# {
# "view_mode": "tree",
# "view_type":"list",
# #"res_id": payorder[0].id if payorder[0].id else False
# }
# )
return action
def active2suspended(self):
self.ensure_one()
assert self.recurring_template == "active"
self.write({"recurring_template": "suspended"})
self.write({"suspended_date": fields.Date.context_today(self)})
def suspended2active(self):
self.ensure_one()
assert self.recurring_template == "suspended"
self.write({"recurring_template": "active"})
self.write({"suspended_date": False})
def active2stopped(self):
self.ensure_one()
assert self.recurring_template == "active"
view=self.env.ref('opendons.recurring_donation_view')
wiz=self.env['opendons.recurringdonation.wizard'].create({'stopped_reason':'motif1','donation_id':self.id})
return {
'name': _('Stop recurring donation'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'opendons.recurringdonation.wizard',
'views': [(view.id, 'form')],
'view_id': view.id,
'target': 'new',
'res_id': wiz.id,
'context': self.env.context,
}
def show_print_wizard(self):
self.ensure_one()
assert self.recurring_template == "active"
view=self.env.ref('opendons.recurring_donation_view')
wiz=self.env['opendons.recurringdonationprint.wizard'].create({'donation_id':self.id})
return {
'name': _('Print letter'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'opendons.recurringdonationprint.wizard',
'views': [(view.id, 'form')],
'view_id': view.id,
'target': 'new',
'res_id': wiz.id,
'context': self.env.context,
}
# self.write({"recurring_template": "stopped"})
# self.write({"stopped_date": fields.Date.context_today(self)})
@api.depends("state", "partner_id", "move_id", "recurring_template")
def name_get(self):
res = []
for donation in self:
if donation.recurring_template == "active":
name = _("Recurring Donation %s") % (donation.number)
elif donation.recurring_template == "suspended":
name = _("Suspended Recurring Donation %s") % (donation.number)
elif donation.recurring_template == "stopped":
name = _("Stopped Recurring Donation %s") % (donation.number)
else:
name = super(DonationDonation, donation).name_get()[0][1]
res.append((donation.id, name))
return res