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' ) 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')]) if sepa_payment_method: id=int(sepa_payment_method.id) return 4 else: raise Warning('veuillez configurer la méthode de paiment SEPA') 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('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_todaydate_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 " "%s 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 " "%s 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, } # 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