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.
 
 

816 lines
31 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,
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'
)
accountingdeposit_id = fields.Many2one(
'opendons.accountingdeposit',
string='accounting deposit',
ondelete='set null'
)
#bank_deposit_date=fields.Datetime(related='payment_batch_id.bank_deposit_date')
payment_state=fields.Selection(string='payment state',selection=[
('draft', 'Draft'),
('validated', 'Validated'),
('deposited_in_bank', 'Deposited in bank'),
('deposited_in_accounting', 'Deposited in accounting')
],
compute='_compute_payment_state', store=True)
@api.onchange("recurring_template")
def recurring_template_change(self):
res = {"warning": {}}
if self.recurring_template and self.tax_receipt_option == "each":
self.tax_receipt_option = "annual"
if not self.recurring_template and self.commercial_partner_id:
if self.commercial_partner_id.tax_receipt_option != self.tax_receipt_option:
self.tax_receipt_option = self.commercial_partner_id.tax_receipt_option
return res
def create_mandate(self):
return True
def generate_each_tax_receipt(self):
#pas de création du RF à la validation du don, mais génération des RF ponctuels en mode batch
return False
def generate_each_tax_receipt_batch(self):
#on générère tous les dons avec taxreceipt_option= qui n'on pas de RF rattaché
donations=self.env['donation.donation'].search([('tax_receipt_option','=','each'),('tax_receipt_id','=',False)])
for d in donations:
if not d.company_currency_id.is_zero(d.tax_receipt_total):
receipt_vals = d._prepare_each_tax_receipt()
receipt = self.env["donation.tax.receipt"].create(receipt_vals)
d.tax_receipt_id=receipt.id
return True
def _compute_payment_state(self):
for rec in self:
rec.payment_state='draft'
if rec.payment_batch_id:
rec.payment_state=rec.payment_batch_id.state
if rec.source_recurring_id:
rec.payment_state='deposited_in_bank'
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
)
html_content_print=fields.Html('html content print')
cancel_reason=fields.Selection(string='cancel reason',selection=[
('debit error','Debit error'),
('donor error','Donor error'),
('amount error','Amount error'),
('invalid check','invalid check'),
('affectation error','Affectation error'),
('date error','Date error'),
('other','Other'),
])
def done2cancel(self):
"""from Done state to Cancel state"""
for donation in self:
if donation.tax_receipt_id:
raise UserError(
_(
"You cannot cancel this donation because "
"it is linked to the tax receipt %s. You should first "
"delete this tax receipt (but it may not be legally "
"allowed)."
)
% donation.tax_receipt_id.number
)
view=self.env.ref('opendons.donation_cancel_view')
wiz=self.env['opendons.donation_cancel.wizard'].create({'donation_id':self.id})
return {
'name': _('Cancel donation'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'opendons.donation_cancel.wizard',
'views': [(view.id, 'form')],
'view_id': view.id,
'target': 'new',
'res_id': wiz.id,
'context': self.env.context,
}
def reverse_accounting_entries(self):
move=self.env['account.move'].search([('id','=',int(self.move_id))])
#création de la pièce comptable d'extourne
vals={}
vals['ref']='Extourne de '+ move.name
vals['date']=datetime.now()
vals['journal_id']=int(move.journal_id)
vals['reversed_entry_id']=int(move.id)
move_r=self.env['account.move'].create(vals)
#création des écritures comptables d'extourne
for line in move.line_ids:
vals={}
vals['move_id']=int(move_r.id)
vals['partner_id']=int(line.partner_id)
vals['account_id']=int(line.account_id)
vals['debit']=line.credit
vals['credit']=line.debit
vals['quantity']=line.quantity
vals['date']=move_r.date
line_r=self.env['account.move.line'].with_context(check_move_validity=False).create(vals)
move_r._post()
def _compute_year_donation_date(self):
for rec in self:
rec.year_donation_date=rec.donation_date.year
def _default_payment_mode_id(self):
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)]}
)
bank_deposit_date=fields.Date('bank deposit date')
accounting_deposit_date=fields.Date('bank deposit date')
@api.model
def create(self, vals):
# #vals['tax_receipt_option']='annual'
# if vals['tax_receipt_option']!='annual': raise Warning('The tax receipt option must be annual')
# #si montant du PA=0 => message
# total=0
# if not vals['line_ids']:
# total=0
# #raise Warning('please add a donation line')
# else:
# for line_d in vals['line_ids']:
# total=total+float(line_d[2]['unit_price'])
# if total==0:raise Warning('The total amount is null')
res = super(DonationDonation, self).create(vals)
#si don hors lot de paiement,ne provenant pas d'un PA : création auto du lot de paiement
if not res.payment_batch_id and res.state!='draft' and res.source_recurring_id==False :
vals={}
vals['payment_mode_id']=int(res.payment_mode_id)
batch=self.env['opendons_payment_batch'].create(vals)
batch.write({'donation_ids':[(4,int(res.id))]})
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)
new_donation.payment_state='deposited_in_bank'
#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')
today=fields.Date.context_today(self)
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',
'default_donation_date':today},
"domain": [('recurring_template', '!=', False)]
}
)
return action
def recurring_donation_action_partner(self):
partner_id = self._context.get('active_id')
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]
today=fields.Date.context_today(self)
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_partner_id':partner_id,
'default_tax_receipt_option':'annual',
'default_donation_date':today},
"domain": [('recurring_template', '!=', False),('partner_id','=',partner_id)]
}
)
return action
def payment_order_action(self):
action = self.env.ref("account_payment_order.account_payment_order_inbound_action").sudo().read([])[0]
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,
}
@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
def print_thanks(self):
self.ensure_one()
self.write({"thanks_printed": True})
html_content_print=self.thanks_template_id.html_content
html_content_print=html_content_print.replace('{{partner_id.name}}',self.partner_id.name)
html_content_print=html_content_print.replace('{{partner_id.firstname}}',self.partner_id.firstname)
html_content_print=html_content_print.replace('{{adresse}}',self.env['donation.tax.receipt'].update_adresse())
html_content_print=html_content_print.replace('{{donor_id}}',self.partner_id.donor_id)
self.html_content_print=html_content_print
return self.env.ref("opendons.report_donation_thanks").report_action(self)
class DonationLine(models.Model):
_inherit = 'donation.line'
donation_date=fields.Date(related='donation_id.donation_date')
partner_id=fields.Many2one(related='donation_id.partner_id')
@api.model
def create(self, values):
res = super(DonationLine, self).create(values)
res.analytic_account_id=res.product_id.analytic_account_id
return res