This commit is contained in:
gsinghpal
2026-02-25 09:40:41 -05:00
parent 0e1aebe60b
commit e71bc503f9
69 changed files with 7537 additions and 82 deletions

View File

@@ -0,0 +1 @@
from . import main

View File

@@ -0,0 +1,305 @@
import logging
from odoo import _, fields, http
from odoo.http import request
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
class FusionRentalController(http.Controller):
# =================================================================
# Cancellation (from renewal reminder email)
# =================================================================
@http.route(
'/rental/cancel/<string:token>',
type='http',
auth='public',
website=True,
methods=['GET', 'POST'],
)
def rental_cancel(self, token, **kwargs):
"""Handle rental cancellation requests from email links."""
cancel_request = request.env['rental.cancellation.request'].sudo().search([
('token', '=', token),
('state', '=', 'new'),
], limit=1)
if not cancel_request:
return request.render(
'fusion_rental.cancellation_invalid_page',
{'error': _("This cancellation link is invalid or has already been used.")},
)
if request.httprequest.method == 'POST':
reason = kwargs.get('reason', '')
cancel_request.write({'reason': reason})
cancel_request.action_confirm()
return request.render(
'fusion_rental.cancellation_success_page',
{
'order': cancel_request.order_id,
'partner': cancel_request.partner_id,
},
)
return request.render(
'fusion_rental.cancellation_form_page',
{
'order': cancel_request.order_id,
'partner': cancel_request.partner_id,
'token': token,
},
)
# =================================================================
# Rental Agreement Signing
# =================================================================
@http.route(
'/rental/agreement/<int:order_id>/<string:token>',
type='http',
auth='public',
website=True,
methods=['GET'],
)
def rental_agreement_page(self, order_id, token, **kwargs):
"""Render the rental agreement signing page."""
order = request.env['sale.order'].sudo().browse(order_id)
if (
not order.exists()
or order.rental_agreement_token != token
or order.rental_agreement_signed
):
return request.render(
'fusion_rental.cancellation_invalid_page',
{'error': _("This agreement link is invalid or has already been signed.")},
)
return request.render(
'fusion_rental.agreement_signing_page',
{
'order': order,
'partner': order.partner_id,
'token': token,
'pdf_preview_url': f'/rental/agreement/{order_id}/{token}/pdf',
},
)
@http.route(
'/rental/agreement/<int:order_id>/<string:token>/pdf',
type='http',
auth='public',
methods=['GET'],
)
def rental_agreement_pdf(self, order_id, token, **kwargs):
"""Serve the rental agreement PDF for preview (token-protected)."""
order = request.env['sale.order'].sudo().browse(order_id)
if not order.exists() or order.rental_agreement_token != token:
return request.not_found()
report_name = 'fusion_rental.report_rental_agreement'
report = request.env['ir.actions.report'].sudo()._get_report_from_name(report_name)
if not report:
report = request.env['ir.actions.report'].sudo()._get_report_from_name(
'fusion_claims.report_rental_agreement'
)
report_name = 'fusion_claims.report_rental_agreement'
if not report:
return request.not_found()
pdf_content, _report_type = report.sudo()._render_qweb_pdf(
report_name, [order.id]
)
return request.make_response(
pdf_content,
headers=[
('Content-Type', 'application/pdf'),
('Content-Disposition', f'inline; filename="Rental Agreement - {order.name}.pdf"'),
],
)
@http.route(
'/rental/agreement/<int:order_id>/<string:token>/sign',
type='json',
auth='public',
methods=['POST'],
)
def rental_agreement_sign(self, order_id, token, **kwargs):
"""Process the agreement signing: save signature and tokenize card."""
order = request.env['sale.order'].sudo().browse(order_id)
if (
not order.exists()
or order.rental_agreement_token != token
or order.rental_agreement_signed
):
return {'success': False, 'error': 'Invalid or expired agreement link.'}
signer_name = kwargs.get('signer_name', '').strip()
signature_data = kwargs.get('signature_data', '')
card_number = kwargs.get('card_number', '').replace(' ', '')
exp_month = kwargs.get('exp_month', '')
exp_year = kwargs.get('exp_year', '')
cvv = kwargs.get('cvv', '')
cardholder_name = kwargs.get('cardholder_name', '').strip()
if not signer_name:
return {'success': False, 'error': 'Full name is required.'}
if not signature_data:
return {'success': False, 'error': 'Signature is required.'}
if not card_number or len(card_number) < 13:
return {'success': False, 'error': 'Valid card number is required.'}
if not exp_month or not exp_year:
return {'success': False, 'error': 'Card expiry is required.'}
if not cvv:
return {'success': False, 'error': 'CVV is required.'}
sig_binary = signature_data
if ',' in sig_binary:
sig_binary = sig_binary.split(',')[1]
try:
payment_token = self._tokenize_card_via_poynt(
order, card_number, exp_month, exp_year, cvv, cardholder_name,
)
except (UserError, Exception) as e:
_logger.error("Card tokenization failed for %s: %s", order.name, e)
return {'success': False, 'error': str(e)}
order.write({
'rental_agreement_signed': True,
'rental_agreement_signature': sig_binary,
'rental_agreement_signer_name': signer_name,
'rental_agreement_signed_date': fields.Datetime.now(),
'rental_payment_token_id': payment_token.id if payment_token else False,
})
order.message_post(
body=_("Rental agreement signed by %s.", signer_name),
)
return {
'success': True,
'message': 'Agreement signed successfully. Thank you!',
}
def _tokenize_card_via_poynt(
self, order, card_number, exp_month, exp_year, cvv, cardholder_name,
):
"""Tokenize a card through the Poynt API and create a payment.token."""
provider = request.env['payment.provider'].sudo().search([
('code', '=', 'poynt'),
('state', '!=', 'disabled'),
], limit=1)
if not provider:
raise UserError(_("Poynt payment provider is not configured."))
from odoo.addons.fusion_poynt import utils as poynt_utils
funding_source = {
'type': 'CREDIT_DEBIT',
'card': {
'number': card_number,
'expirationMonth': int(exp_month),
'expirationYear': int(exp_year),
'cardHolderFullName': cardholder_name,
},
'verificationData': {
'cvData': cvv,
},
'entryDetails': {
'customerPresenceStatus': 'MOTO',
'entryMode': 'KEYED',
},
}
minor_amount = poynt_utils.format_poynt_amount(0.00, order.currency_id)
txn_payload = {
'action': 'VERIFY',
'amounts': {
'transactionAmount': minor_amount,
'orderAmount': minor_amount,
'currency': order.currency_id.name,
},
'fundingSource': funding_source,
'context': {
'source': 'WEB',
'sourceApp': 'odoo.fusion_rental',
'businessId': provider.poynt_business_id,
},
'notes': f"Card tokenization for {order.name}",
}
result = provider._poynt_make_request('POST', 'transactions', payload=txn_payload)
card_data = result.get('fundingSource', {}).get('card', {})
card_id = card_data.get('cardId', '')
last_four = card_data.get('numberLast4', card_number[-4:])
card_type = card_data.get('type', 'UNKNOWN')
payment_method = request.env['payment.method'].sudo().search(
[('code', '=', 'card')], limit=1,
)
if not payment_method:
payment_method = request.env['payment.method'].sudo().search(
[('code', 'in', ('visa', 'mastercard'))], limit=1,
)
token = request.env['payment.token'].sudo().create({
'provider_id': provider.id,
'payment_method_id': payment_method.id if payment_method else False,
'partner_id': order.partner_id.id,
'poynt_card_id': card_id,
'payment_details': f"{card_type} ending in {last_four}",
})
return token
# =================================================================
# Purchase Interest (from marketing email)
# =================================================================
@http.route(
'/rental/purchase-interest/<int:order_id>/<string:token>',
type='http',
auth='public',
website=True,
methods=['GET'],
)
def rental_purchase_interest(self, order_id, token, **kwargs):
"""Handle customer expressing purchase interest from marketing email."""
order = request.env['sale.order'].sudo().browse(order_id)
if (
not order.exists()
or order.rental_agreement_token != token
):
return request.render(
'fusion_rental.cancellation_invalid_page',
{'error': _("This link is invalid.")},
)
if not order.rental_purchase_interest:
order.rental_purchase_interest = True
order.activity_schedule(
'mail.mail_activity_data_todo',
date_deadline=fields.Date.today(),
summary=_("Customer interested in purchasing rental product"),
note=_(
"Customer %s expressed interest in purchasing the rental "
"product from order %s. Please follow up.",
order.partner_id.name, order.name,
),
user_id=order.user_id.id or request.env.uid,
)
order.message_post(
body=_("Customer expressed purchase interest via marketing email."),
)
return request.render(
'fusion_rental.purchase_interest_success_page',
{'order': order, 'partner': order.partner_id},
)