from odoo import models, fields, api,_ from odoo.exceptions import UserError, ValidationError, Warning from psycopg2 import sql, DatabaseError from dateutil.relativedelta import relativedelta from datetime import datetime from werkzeug import utils class EventRegistration(models.Model): _inherit = 'event.registration' room_id = fields.Many2one( 'booking.room', String='Room', index=True, group_expand='_read_group_room_ids', track_visibility='onchange', ) booking_event=fields.Boolean(related='event_id.booking_event',store=True) individual_booking_event=fields.Boolean(related='event_id.individual_booking_event',store=True) event_end=fields.Boolean(related='event_id.stage_id.pipe_end') medical_concern=fields.Selection( [('have medical concern','I have a medical concern to report'), ('have no medical concern','I have no medical concern to report'), ('no answer','I don\'t want to answer')], string='Medical concern', ) medical_information=fields.Char('Medical informations') medical_contact_name=fields.Char('Name and Firstname of the contact') medical_contact_phone=fields.Char('Phone of the contact') questionnaire_ids=fields.One2many( 'event.registration_questionnaire', 'event_registration_id', String='Questionnaire', readonly=True ) option_ids=fields.One2many( 'event.registration_option', 'event_registration_id', String='Options', readonly=True ) donation_ids=fields.One2many( 'donation.donation', 'event_registration_id', String='Donations', readonly=True ) @api.model def _default_currency(self): company = self.env['res.company']._company_default_get( 'event.registration') return company.currency_id currency_id = fields.Many2one( 'res.currency', string='Currency', required=True, track_visibility='onchange', ondelete='restrict', default=_default_currency ) down_payment=fields.Boolean('down payment',default=False) firstname=fields.Char('Firstname') age=fields.Integer('Age') gender=fields.Selection([('male','Male'),('femelle','Femelle')],string='gender') mobile=fields.Char('Mobile') city=fields.Char('City') image_right_clearance=fields.Boolean('image right clearance') train_arrival_time=fields.Float(string="train arrival time") train_arrival_date=fields.Date(string="train arrival date") image_permission=fields.Boolean("Image permission") kanban_color=fields.Char('kanban color',compute='_compute_kanban_color') invoice_state=fields.Selection(related='invoice_id.payment_state') down_payment_invoice_state=fields.Selection(related='down_payment_invoice_id.payment_state') balance_invoice_state=fields.Selection(related='balance_invoice_id.payment_state') invoice_id=fields.Many2one('account.move', readonly=True, ondelete="cascade") #invoice_state=fields.Selection(related='invoice_id.state') down_payment_invoice_id=fields.Many2one('account.move', ondelete="cascade") #down_payment_invoice_state=fields.Selection(related='down_payment_order_id.state') balance_invoice_id=fields.Many2one('account.move' ,ondelete="cascade") #balance_invoice_state=fields.Selection(related='balance_order_id.state') end_of_stay_invoice_id=fields.Many2one('account.move' ,string='end of stay invoice',ondelete="cascade") payment_status=fields.Selection(string='payment status',selection=[('paid','Paid'),('not paid','Not paid'),('down payment not paid','down payment not paid'),('down payment paid','down payment paid')],compute='_compute_payment_status',translate=True) to_be_paid_amount=fields.Monetary('to be paid amount',compute='_compute_to_be_paid_amount',currency_field='currency_id') payment_adjustement=fields.Monetary('payment adjustement',currency_field='currency_id') individual_room=fields.Boolean('Individual room',compute='compute_individual_room') #individual booking fields start_day_individual_booking=fields.Date('Start day') end_day_individual_booking=fields.Date('End day') days_duration=fields.Integer('Duration in days') price_individual_booking=fields.Monetary('price individual booking',currency_field='currency_id') exported_state=fields.Char('accounting exported state') def compute_individual_room(self): for rec in self: prd=self.env['product.product'].search([('individual_room','=',True)]) rec.individual_room=False for opt in rec.option_ids: if prd.id==opt.booking_option_id.id : rec.individual_room=True def _compute_to_be_paid_amount(self): for rec in self: rec.to_be_paid_amount=0 if rec.invoice_id : rec.to_be_paid_amount=rec.invoice_id.amount_residual else: if rec.down_payment_invoice_id and rec.balance_invoice_id: rec.to_be_paid_amount=rec.down_payment_invoice_id.amount_residual+rec.balance_invoice_id.amount_residual def action_cancel(self): self.write({'state': 'cancel'}) #annulation de la facture liée à l'événement if self.invoice_id: self.invoice_id.state='cancel' #annulation des factures de retraites if self.down_payment_invoice_id: self.down_payment_invoice_id.state='cancel' if self.balance_invoice_id: self.balance_invoice_id.state='cancel' def unlink(self): if self.state!='cancel':raise UserError(_('This action is not allowed, please cancel the registration')) if self.state=='cancel':raise UserError(_('This action is not allowed,the registration is already canceled')) def _compute_payment_status(self): for rec in self: rec.payment_status='not paid' if rec.invoice_id: if rec.invoice_state not in ('paid'):rec.payment_status='not paid' if rec.invoice_state=='paid':rec.payment_status='paid' else: if rec.down_payment_invoice_id and rec.balance_invoice_id: if rec.down_payment_invoice_state!='paid':rec.payment_status='down payment not paid' if rec.down_payment_invoice_state=='paid' and rec.balance_invoice_state=='paid' :rec.payment_status='paid' if rec.down_payment_invoice_state=='paid' and rec.balance_invoice_state!='paid':rec.payment_status='down payment paid' if rec.payment_status in('paid','down payment paid') and rec.state!='cancel': rec.state='open' def _compute_kanban_color(self): for rec in self: rec.kanban_color='' if rec.gender=='male':rec.kanban_color="50aaf3" if rec.gender=='femelle':rec.kanban_color="ffffff" @api.model def _read_group_room_ids(self, stages, domain, order): return self.env['booking.room'].search([]) @api.model def create(self, vals_list): reg = super(EventRegistration, self).create(vals_list) return reg def action_event_registration_order(self): return True def create_donation(self,event_registration_id,partner_id,product_id,price_unit,invoice_id): vals={} vals['partner_id']=int(partner_id) vals['invoice_id']=int(invoice_id) vals['donation_date']=datetime.now() vals['tax_receipt_option']='annual' #mode de paiement CB par defaut electronic_method=self.env['account.payment.method'].search([('code','=','electronic')],limit=1) if electronic_method: cb_mode=self.env['account.payment.mode'].search([('payment_method_id','=',int(electronic_method.id))],limit=1) if cb_mode: vals['payment_mode_id']=cb_mode.id else: raise Warning('please configure credit card mode') #tant que le devis n'est pas validé (paiment effectué), le don est en brouillon vals['state']='draft' vals['payment_ref']='internet' donation_draft=self.env['donation.donation'].sudo().create(vals) #ajout du don au niveau de l'inscription reg=self.env['event.registration'].search([('id','=',int(event_registration_id))]) if reg: reg.write({'donation_ids':[(4,int(donation_draft.id))]}) vals={} #create line donation vals['donation_id']=donation_draft.id product=self.env['product.product'].search([('id','=',int(product_id))]) vals['product_id']=int(product_id) vals['display_name']=product.name vals['quantity']=1 vals['unit_price']=price_unit vals['tax_receipt_ok']=product.tax_receipt_ok donation_line=self.env['donation.line'].sudo().create(vals) donation_draft.state='draft' def remove_invoice(self): invoice=self.env['account.move'].sudo().search([('id','=',int(self.invoice_id))]) if invoice: if invoice.payment_state!='paid': invoice.state='draft' invoice.posted_before=False invoice_id=self.invoice_id self.invoice_id=False #suppression des lignes de paiements liés à la facture move_line=self.env['account.move.line'].sudo().search([('move_id','=',int(invoice_id))]) if move_line: for m in move_line: self.env['account.payment.line'].sudo().search([('move_line_id','=',int(m.id))]).unlink() #suppression des paiements liés à la facture self.env['account.payment'].sudo().search([('move_id','=',int(invoice_id))]).unlink() #suppression des écritures comptables lié à la facture self.env['account.move.line'].sudo().search([('move_id','=',int(invoice_id))]).unlink() #suppression de la facture self.env['account.move'].sudo().search([('id','=',int(invoice_id))]).unlink() if self.down_payment_invoice_id: invoice=self.env['account.move'].sudo().search([('id','=',int(self.down_payment_invoice_id))]) if invoice: if invoice.payment_state!='paid': invoice.state='draft' invoice.posted_before=False invoice_id=self.down_payment_invoice_id self.down_payment_invoice_id=False #suppression des lignes de paiements liés à la facture move_line=self.env['account.move.line'].sudo().search([('move_id','=',int(invoice_id))]) if move_line: for m in move_line: self.env['account.payment.line'].sudo().search([('move_line_id','=',int(m.id))]).unlink() #suppression des paiements liés à la facture self.env['account.payment'].sudo().search([('move_id','=',int(invoice_id))]).unlink() #suppression des écritures comptables lié à la facture self.env['account.move.line'].sudo().search([('move_id','=',int(invoice_id))]).unlink() #suppression de la facture self.env['account.move'].sudo().search([('id','=',int(invoice_id))]).unlink() if self.balance_invoice_id: invoice=self.env['account.move'].sudo().search([('id','=',int(self.balance_invoice_id))]) if invoice: if invoice.payment_state!='paid': invoice.state='draft' invoice.posted_before=False invoice_id=self.balance_invoice_id self.balance_invoice_id=False #suppression des lignes de paiements liés à la facture move_line=self.env['account.move.line'].sudo().search([('move_id','=',int(invoice_id))]) if move_line: for m in move_line: self.env['account.payment.line'].sudo().search([('move_line_id','=',int(m.id))]).unlink() #suppression des paiements liés à la facture self.env['account.payment'].sudo().search([('move_id','=',int(invoice_id))]).unlink() #suppression des écritures comptables lié à la facture self.env['account.move.line'].sudo().search([('move_id','=',int(invoice_id))]).unlink() #suppression de la facture self.env['account.move'].sudo().search([('id','=',int(invoice_id))]).unlink() return True def remove_donation(self): #suppression des dons existants lié à l'inscription et aux options donation=self.env['donation.donation'].sudo().search([('id','in',self.donation_ids.ids)]) for d in donation: d.state='draft' self.env['donation.donation'].sudo().search([('id','in',self.donation_ids.ids)]).unlink() self.donation_ids=False return True def action_event_registration_generate_end_of_stay_invoice(self): return self.action_event_registration_generate_invoice(None,True,True) def action_event_registration_generate_invoice(self,id_registration=None,backoffice=True,end_of_stay_invoice=False): if not end_of_stay_invoice: #suppression des factures existantes #si les factures sont payés on les supprime pas self.remove_invoice() #suppression des dons existants lié aux options de l'inscription self.remove_donation() else: end_invoice=self.env['account.move'].sudo().search([('id','=',int(self.end_of_stay_invoice_id))]) if end_invoice: if end_invoice.payment_state!='paid': invoice_id=self.end_of_stay_invoice_id self.end_of_stay_invoice_id=False self.env['account.move'].sudo().search([('id','=',int(invoice_id))]).unlink() if id_registration:reg=self.env['event.registration'].search([('id','=',int(id_registration))]) else : reg=self event=self.env['event.event'].search([('id','=',int(reg.event_id))]) selected_registrant_options=self.env['event.registration_option'].search([ ('event_registration_id','=',int(reg.id))]) #controles avant traitement : un produit doit être associé à l'événement if not event.booking_product_id: raise Warning('product missing for event booking') #Prix à appliquer au produit en fonction du statut de l'inscrit if reg.partner_id.email==reg.email: status=reg.partner_id.member_status #le participant n'est pas la personne qui s'est connecté else: participant=self.env['res.partner'].sudo().search([('email','=',reg.email)],limit=1) if participant: status=participant.member_status else: status='not member' if event.individual_booking_event: product_price=reg.price_individual_booking membership_option=False else: product_price=event.booking_price if status=='not member':product_price=event.booking_price if status=='member':product_price=event.booking_member_price if status=='super member':product_price=event.booking_super_member_price #Prix à appliquer au produit si paiement de l'adhésion membership_option=False #membership_product=self.env['event.membership_product'].search([]) membership_product=self.env['product.product'].sudo().search([('membership_product','=',True)],limit=1) if membership_product: if selected_registrant_options: for opt in selected_registrant_options: #raise Warning("opt.booking_option_id="+str(opt.booking_option_id.id)+" membership_product.id="+str(membership_product.id)) if opt.booking_option_id.id==membership_product.id: membership_option=True if status!="super member": product_price=event.booking_member_price if self.payment_adjustement!=0: product_price=product_price+self.payment_adjustement #calcul du montant total à régler if end_of_stay_invoice:product_price=0 #création du devis sans acompte if (not reg.down_payment and reg.invoice_state!='paid') or (end_of_stay_invoice): if not selected_registrant_options and end_of_stay_invoice : return False #création de la facture vals={} vals['partner_id']=int(reg.partner_id) vals['invoice_date']=datetime.now() #mode de paiement CB par defaut si frontoffice if not backoffice: electronic_method=self.env['account.payment.method'].sudo().search([('code','=','electronic')],limit=1) if electronic_method: cb_mode=self.env['account.payment.mode'].sudo().search([('payment_method_id','=',int(electronic_method.id))],limit=1) if cb_mode: vals['payment_mode_id']=cb_mode.id else: raise Warning('please configure credit card mode') else: check_mode=self.env['account.payment.mode'].sudo().search([('name','=','chèque')]) if check_mode: vals['payment_mode_id']=check_mode.id else: raise Warning('please configure check mode') vals['move_type']='out_invoice' vals['state']='draft' invoice=self.env['account.move'].sudo().create(vals) invoice.state='posted' invoice.name='REC'+str(invoice.id) invoice.payment_reference=invoice.name vals={} account_credit=self.env['account.account'].sudo().search([('code','=','707100')]) account_debit=self.env['account.account'].sudo().search([('code','=','411100')]) vals['move_id']=invoice.id vals['product_id']=int(event.booking_product_id) vals['quantity']=1 vals['price_unit']=product_price if event.individual_booking_event: vals['name']=event.booking_product_id.name+' du '+str(reg.start_day_individual_booking)+' au '+str(reg.end_day_individual_booking)+ ' - '+str(reg.days_duration)+' jours' else: vals['name']=event.booking_product_id.name+ ' du '+ str(event.date_begin)+' au '+str(event.date_begin) vals['account_id']=int(account_credit.id) invoice_line=self.env['account.move.line'].with_context(check_move_validity=False).sudo().create(vals) # #debit_line vals_d={} vals_d['move_id']=invoice.id vals_d['debit']=product_price vals_d['credit']=0 vals_d['date']=datetime.now() vals_d['partner_id']=int(reg.partner_id) vals_d['product_id']=int(event.booking_product_id) vals_d['name']=event.booking_product_id.name + ' du '+ str(event.date_begin)+' au '+str(event.date_begin) vals_d['account_id']=int(account_debit.id) vals_d['quantity']=1 vals_d['price_unit']=product_price vals_d['exclude_from_invoice_tab']=True #statut payment=paid invoice_line=self.env['account.move.line'].with_context(check_move_validity=False).sudo().create(vals_d) #statut payment=not_paid l=self.env['account.move.line'].sudo().search([('move_id','=',invoice.id),('balance','<',0)]) l.partner_id=int(reg.partner_id) #ajout des options if selected_registrant_options: for option in selected_registrant_options: prd=self.env['product.product'].sudo().search([('id','=',int(option.booking_option_id.id))]) #prix à appliquer aux options en fonction du statut ou de la présence de l'option d'adhesion event_option=self.env['booking.option'].sudo().search(['&',('event_id','=',int(reg.event_id)),('booking_option_id','=',int(option.booking_option_id.id))],limit=1) price_unit=0 if event_option: if status=="super member":price_unit=event_option.booking_option_super_member_price if status=="member":price_unit=event_option.booking_option_member_price if status=="not member" and membership_option and event_option.booking_option_id.membership_product:price_unit=event_option.booking_option_price if status=="not member" and membership_option and not event_option.booking_option_id.membership_product:price_unit=event_option.booking_option_member_price if status=="not member" and not membership_option:price_unit=event_option.booking_option_price if backoffice: if option.booking_option_id.booking_option_product_backoffice: price_unit=option.booking_option_price vals={} vals['move_id']=invoice.id #ajout du produit vals['product_id']=int(option.booking_option_id.id) vals['quantity']=1 vals['price_unit']=price_unit vals['name']=prd.name vals['account_id']=int(account_credit.id) invoice_line=self.env['account.move.line'].with_context(check_move_validity=False).sudo().create(vals) #debit_line vals_d={} vals_d['move_id']=invoice.id if price_unit>=0: vals_d['debit']=price_unit vals_d['credit']=0 if price_unit<0: vals_d['debit']=0 vals_d['credit']=-price_unit vals_d['date']=datetime.now() vals_d['partner_id']=int(reg.partner_id) vals_d['product_id']=int(option.booking_option_id.id) vals_d['name']=prd.name vals_d['account_id']=int(account_debit.id) vals_d['quantity']=1 vals_d['price_unit']=price_unit vals_d['exclude_from_invoice_tab']=True invoice_line=self.env['account.move.line'].with_context(check_move_validity=True).sudo().create([vals_d]) l=self.env['account.move.line'].sudo().search([('move_id','=',invoice.id),('balance','<',0)]) l.partner_id=int(reg.partner_id) #si l'option est un produit de don on créé un don pour la personne if prd.donation: self.sudo().create_donation(reg.id,reg.partner_id,prd.id,vals['price_unit'],invoice.id) if not end_of_stay_invoice: reg.invoice_id=invoice.id else: reg.end_of_stay_invoice_id=invoice.id user=self.env['res.users'].search([('email','ilike',reg.partner_id.email)],limit=1) invoice.invoice_user_id=user.id invoice.user_id=user.id # if event.individual_booking_event: # mail_template = self.env['mail.template'].search([('name','=','individual_booking_confirmation')]) # mail_template.email_to = self.partner_id.email # if mail_template: # mail_template.with_context({'down_payment_invoice_id':self.down_payment_invoice_id}).send_mail(self.id,False) #mail_template.send_mail(self.id,False) return invoice #2 factures si paiement avec acompte else: if reg.down_payment_invoice_state!='paid' and reg.down_payment and not end_of_stay_invoice: #création de la 1ère facture d'acompte vals={} vals['partner_id']=int(reg.partner_id) vals['invoice_date']=datetime.now() #mode de paiement CB par defaut if not backoffice: electronic_method=self.env['account.payment.method'].sudo().search([('code','=','electronic')],limit=1) if electronic_method: cb_mode=self.env['account.payment.mode'].sudo().search([('payment_method_id','=',int(electronic_method.id))],limit=1) if cb_mode: vals['payment_mode_id']=cb_mode.id else: raise Warning('please configure credit card mode') else: check_mode=self.env['account.payment.mode'].sudo().search([('name','=','chèque')]) if check_mode: vals['payment_mode_id']=check_mode.id else: raise Warning('please configure check mode') vals['move_type']='out_invoice' vals['state']='draft' #vals['currency_id']=self.currency_id.id invoice=self.env['account.move'].sudo().create(vals) invoice.state='posted' invoice.name='REC'+str(invoice.id) invoice.payment_reference=invoice.name user=self.env['res.users'].search([('email','ilike',reg.partner_id.email)],limit=1) invoice.invoice_user_id=user.id invoice.user_id=user.id vals={} account_credit=self.env['account.account'].sudo().search([('code','=','707100')]) account_debit=self.env['account.account'].sudo().search([('code','=','411100')]) vals['move_id']=invoice.id vals['product_id']=int(event.booking_product_id) vals['quantity']=1 if event.individual_booking_event: if product_price=0: vals_d['debit']=price_unit vals_d['credit']=0 if price_unit<0: vals_d['debit']=0 vals_d['credit']=-price_unit vals_d['date']=datetime.now() vals_d['partner_id']=int(reg.partner_id) vals_d['product_id']=int(option.booking_option_id.id) vals_d['name']=prd.name vals_d['account_id']=int(account_debit.id) vals_d['quantity']=1 vals_d['price_unit']=price_unit vals_d['exclude_from_invoice_tab']=True invoice_line=self.env['account.move.line'].with_context(check_move_validity=True).sudo().create([vals_d]) l=self.env['account.move.line'].search([('move_id','=',invoice.id),('balance','<',0)]) l.partner_id=int(reg.partner_id) #si l'option est un produit de don on créé un don pour la personne if prd.donation: self.sudo().create_donation(reg.id,reg.partner_id,prd.id,vals['price_unit'],invoice.id) reg.balance_invoice_id=invoice.id if event.individual_booking_event: mail_template = self.env['mail.template'].search([('name','=','individual_booking_confirmation')]) mail_template.email_to = self.partner_id.email if mail_template: mail_template.with_context({'down_payment_invoice_id':int(self.down_payment_invoice_id)}).send_mail(self.id,False) #mail_template.send_mail(self.id,False) return reg.down_payment_invoice_id def info_objet(self,model_id,objet_id): field_list=request.env[model_id].sudo().fields_get() result=[] for key in field_list: result.append((key,key)) result.sort() return result def create_payment(self,invoice_id,code_payment_method): invoice=self.env['account.move'].search([('id','=',int(invoice_id))]) payment_method=self.env['account.payment.method'].search([('code','=',code_payment_method),('payment_type','=','inbound')]) bank=self.env['account.journal'].search([('type','=','bank')]) if not payment_method : raise Warning ('please set up the '+code_payment_method+ ' method') if not bank : raise Warning ('please set up the bank journal !') Payment = self.env['account.payment'].with_context(default_invoice_ids=[(4, invoice_id, False)]) payment = Payment.create({ 'date': datetime.now(), 'payment_method_id': payment_method.id, 'payment_type': 'inbound', 'partner_type': 'customer', 'partner_id': invoice.partner_id.id, 'amount': invoice.amount_residual, 'journal_id': int(bank.id), 'company_id': self.env.company, 'currency_id': int(self.currency_id) #'payment_difference_handling': 'reconcile', #'writeoff_account_id': self.diff_income_account.id, }) payment.state='posted' invoice.payment_id=payment.id invoice.payment_order_ok=True def invoice_without_registration(self,account_description): move=self.env['account.move'].search([]) result='' for mv in move: description='' for line in mv.line_ids: if line.product_id.name==account_description: #recherche de l'inscription reg=self.env['event.registration'].search([('invoice_id','=',int(mv.id))]) if not reg: result=result+mv.name+':'+mv.partner_id.name+'\nr' break raise UserError(result) class EventRegistration_questionnaire(models.Model): _name = 'event.registration_questionnaire' _description = 'event registration questionnaire' sequence = fields.Integer(string="sequence", default=10) question=fields.Text(string='question',readonly=True) answer=fields.Text(string='answer') event_registration_id=fields.Many2one( 'event.registration', String='Event registration', index=True, readonly=True, track_visibility='onchange', ondelete='cascade' ) class EventRegistration_option(models.Model): _name = 'event.registration_option' _description = 'event registration option' #booking_option_id=fields.Many2one('product.product',string='booking options',domain="[('id','in',selectable_option_ids)]") booking_option_id=fields.Many2one('product.product',string='booking options', domain="['|',('booking_option_product_backoffice','=','True'),('booking_option_product','=','True')]") booking_option_price=fields.Monetary('Price',currency_field='currency_id') booking_option_backoffice=fields.Boolean(related='booking_option_id.booking_option_product_backoffice',store=True) selectable_option_ids = fields.Many2many('product.product', compute='_compute_selectable_option') event_registration_id=fields.Many2one( 'event.registration', String='Event registration', index=True, readonly=True, track_visibility='onchange', ) def _compute_selectable_option(self): selectable_options=self.env['booking.option'].search([('event_id','=',int(self.event_registration_id.event_id))]) if selectable_options: self.selectable_option_ids=selectable_options.booking_option_id.ids @api.model def _default_currency(self): company = self.env['res.company']._company_default_get( 'event.event') return company.currency_id currency_id = fields.Many2one( 'res.currency', string='Currency', required=True, states={'done': [('readonly', True)]}, track_visibility='onchange', ondelete='restrict', default=_default_currency )