import logging from datetime import timedelta from odoo import _, api, fields, models from odoo.exceptions import UserError _logger = logging.getLogger(__name__) class ManualRenewalWizard(models.TransientModel): _name = 'manual.renewal.wizard' _description = 'Manual Rental Renewal Wizard' order_id = fields.Many2one( 'sale.order', string="Rental Order", required=True, readonly=True, ) partner_id = fields.Many2one( related='order_id.partner_id', string="Customer", ) current_start_date = fields.Datetime( string="Current Start Date", readonly=True, ) current_return_date = fields.Datetime( string="Current Return Date", readonly=True, ) new_start_date = fields.Datetime( string="New Start Date", required=True, ) new_return_date = fields.Datetime( string="New Return Date", required=True, ) amount_preview = fields.Float( string="Estimated Renewal Amount", compute='_compute_amount_preview', ) payment_token_id = fields.Many2one( 'payment.token', string="Card on File", domain="[('partner_id', '=', partner_id)]", help="Select a stored card to charge automatically. " "Leave empty to open the manual payment wizard.", ) use_card_on_file = fields.Boolean( string="Charge Card on File", default=False, compute='_compute_use_card_on_file', store=True, readonly=False, ) previous_start_date = fields.Datetime( string="Previous Start (rollback)", readonly=True, ) previous_return_date = fields.Datetime( string="Previous Return (rollback)", readonly=True, ) renewal_invoice_id = fields.Many2one( 'account.move', string="Renewal Invoice", readonly=True, ) renewal_log_id = fields.Many2one( 'rental.renewal.log', string="Renewal Log", readonly=True, ) @api.depends('order_id', 'new_start_date', 'new_return_date') def _compute_amount_preview(self): for wizard in self: if wizard.order_id: wizard.amount_preview = wizard.order_id._get_renewal_amount() else: wizard.amount_preview = 0.0 @api.depends('payment_token_id') def _compute_use_card_on_file(self): for wizard in self: wizard.use_card_on_file = bool(wizard.payment_token_id) def action_confirm_renewal(self): """Confirm the manual renewal: extend dates, create invoice, and collect payment.""" self.ensure_one() order = self.order_id if not order.is_rental_order: raise UserError(_("This is not a rental order.")) if self.new_return_date <= self.new_start_date: raise UserError(_("New return date must be after the new start date.")) old_start = order.rental_start_date old_return = order.rental_return_date self.write({ 'previous_start_date': old_start, 'previous_return_date': old_return, }) order.write({ 'rental_start_date': self.new_start_date, 'rental_return_date': self.new_return_date, }) order._recompute_rental_prices() invoice = order._create_renewal_invoice() if not invoice: raise UserError(_("Could not create renewal invoice.")) renewal_log = self.env['rental.renewal.log'].create({ 'order_id': order.id, 'renewal_number': order.rental_renewal_count + 1, 'previous_start_date': old_start, 'previous_return_date': old_return, 'new_start_date': self.new_start_date, 'new_return_date': self.new_return_date, 'invoice_id': invoice.id, 'renewal_type': 'manual', 'state': 'draft', 'payment_status': 'pending', }) self.write({ 'renewal_invoice_id': invoice.id, 'renewal_log_id': renewal_log.id, }) order.write({ 'rental_renewal_count': order.rental_renewal_count + 1, 'rental_reminder_sent': False, }) if self.use_card_on_file and self.payment_token_id: invoice.action_post() ok = order._collect_token_payment_for_invoice(invoice) if ok: renewal_log.write({ 'state': 'done', 'payment_status': 'paid', }) order._send_invoice_with_receipt(invoice, 'renewal') order._send_renewal_confirmation_email(renewal_log, True) return {'type': 'ir.actions.act_window_close'} else: renewal_log.write({ 'state': 'done', 'payment_status': 'failed', }) order._send_renewal_confirmation_email(renewal_log, False) order._notify_staff_manual_payment(invoice) return {'type': 'ir.actions.act_window_close'} invoice.action_post() renewal_log.write({'state': 'done'}) order._send_renewal_confirmation_email(renewal_log, False) inv = invoice.with_user(self.env.user) return inv.action_open_poynt_payment_wizard() def action_cancel_renewal(self): """Cancel the renewal: revert dates and void the invoice.""" self.ensure_one() order = self.order_id if self.renewal_invoice_id: inv = self.renewal_invoice_id if inv.state == 'posted' and inv.payment_state in ('not_paid', 'partial'): inv.button_draft() if inv.state == 'draft': inv.button_cancel() if self.previous_start_date and self.previous_return_date: order.write({ 'rental_start_date': self.previous_start_date, 'rental_return_date': self.previous_return_date, }) order._recompute_rental_prices() if self.renewal_log_id: self.renewal_log_id.write({ 'state': 'failed', 'payment_status': 'failed', 'notes': 'Cancelled by user.', }) if order.rental_renewal_count > 0: order.rental_renewal_count -= 1 order.message_post(body=_("Manual renewal cancelled and reverted.")) return {'type': 'ir.actions.act_window_close'}