Rental orders no longer show the "Authorizer Required?" question or the Authorizer field. The sale type is automatically set to 'Rentals' when creating or confirming a rental order. Validation logic also skips authorizer checks for rental sale type. Made-with: Cursor
1389 lines
105 KiB
XML
1389 lines
105 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<odoo>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- 1. Renewal Reminder - 3 days before expiry -->
|
|
<!-- ============================================================ -->
|
|
<record id="mail_template_rental_reminder" model="mail.template">
|
|
<field name="name">Rental: Renewal Reminder</field>
|
|
<field name="model_id" ref="sale.model_sale_order"/>
|
|
<field name="subject">{{ object.company_id.name }} - Rental {{ object.name }} Renews Soon</field>
|
|
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
|
<field name="partner_to">{{ object.partner_id.id }}</field>
|
|
<field name="body_html" type="html">
|
|
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;max-width:600px;margin:0 auto;color:#2d3748;">
|
|
<div style="height:4px;background-color:#D69E2E;"></div>
|
|
<div style="background:#ffffff;padding:32px 28px;border:1px solid #e2e8f0;border-top:none;">
|
|
<p style="color:#D69E2E;font-size:13px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;margin:0 0 24px 0;">
|
|
<t t-out="object.company_id.name"/>
|
|
</p>
|
|
<h2 style="color:#1a202c;font-size:22px;font-weight:700;margin:0 0 6px 0;line-height:1.3;">Rental Renewal Notice</h2>
|
|
<p style="color:#718096;font-size:15px;line-height:1.5;margin:0 0 24px 0;">
|
|
Your rental order <strong style="color:#2d3748;"><t t-out="object.name"/></strong> is scheduled for automatic renewal on <strong style="color:#2d3748;"><t t-out="object.rental_return_date and object.rental_return_date.strftime('%B %d, %Y') or ''"/></strong>.
|
|
</p>
|
|
<table style="width:100%;border-collapse:collapse;margin:0 0 24px 0;">
|
|
<tr><td colspan="2" style="padding:10px 14px;font-size:12px;font-weight:600;color:#718096;text-transform:uppercase;letter-spacing:0.5px;border-bottom:2px solid #e2e8f0;">Renewal Details</td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;width:35%;">Order</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.name"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;">Renewal Date</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.rental_return_date and object.rental_return_date.strftime('%B %d, %Y') or ''"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;font-weight:600;border-top:2px solid #e2e8f0;">Renewal Amount</td><td style="padding:10px 14px;color:#D69E2E;font-size:14px;font-weight:700;border-top:2px solid #e2e8f0;"><t t-out="object.amount_total" t-options="{'widget': 'monetary', 'display_currency': object.currency_id}"/></td></tr>
|
|
</table>
|
|
<div style="border-left:3px solid #D69E2E;padding:12px 16px;margin:0 0 24px 0;background:#f7fafc;">
|
|
<p style="margin:0;font-size:14px;line-height:1.5;color:#2d3748;">If you would like to continue your rental, no action is needed. If you wish to cancel and schedule a pickup, click the button below.</p>
|
|
</div>
|
|
<div style="text-align:center;margin:0 0 24px 0;">
|
|
<a t-attf-href="{{ (object.get_base_url() or '') }}/rental/cancel/{{ ctx.get('cancel_token', '') }}" style="background-color:#E53E3E;color:#fff;padding:12px 30px;text-decoration:none;border-radius:4px;font-size:14px;font-weight:600;display:inline-block;">Request Cancellation & Pickup</a>
|
|
</div>
|
|
<t t-if="not is_html_empty(object.user_id.signature)" data-o-mail-quote-container="1">
|
|
<div data-o-mail-quote="1">--<br data-o-mail-quote="1"/><t t-out="object.user_id.signature or ''" data-o-mail-quote="1"/></div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</field>
|
|
<field name="lang">{{ object.partner_id.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
</record>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- 2. Renewal Confirmation -->
|
|
<!-- ============================================================ -->
|
|
<record id="mail_template_rental_renewed" model="mail.template">
|
|
<field name="name">Rental: Renewal Confirmation</field>
|
|
<field name="model_id" ref="sale.model_sale_order"/>
|
|
<field name="subject">{{ object.company_id.name }} - Rental {{ object.name }} Renewed</field>
|
|
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
|
<field name="partner_to">{{ object.partner_id.id }}</field>
|
|
<field name="body_html" type="html">
|
|
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;max-width:600px;margin:0 auto;color:#2d3748;">
|
|
<div style="height:4px;background-color:#38a169;"></div>
|
|
<div style="background:#ffffff;padding:32px 28px;border:1px solid #e2e8f0;border-top:none;">
|
|
<p style="color:#38a169;font-size:13px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;margin:0 0 24px 0;">
|
|
<t t-out="object.company_id.name"/>
|
|
</p>
|
|
<h2 style="color:#1a202c;font-size:22px;font-weight:700;margin:0 0 6px 0;line-height:1.3;">Rental Renewed</h2>
|
|
<p style="color:#718096;font-size:15px;line-height:1.5;margin:0 0 24px 0;">
|
|
Your rental <strong style="color:#2d3748;"><t t-out="object.name"/></strong> has been successfully renewed.
|
|
</p>
|
|
<table style="width:100%;border-collapse:collapse;margin:0 0 24px 0;">
|
|
<tr><td colspan="2" style="padding:10px 14px;font-size:12px;font-weight:600;color:#718096;text-transform:uppercase;letter-spacing:0.5px;border-bottom:2px solid #e2e8f0;">New Rental Period</td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;width:35%;">New Start</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.rental_start_date and object.rental_start_date.strftime('%B %d, %Y') or ''"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;">New Return</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.rental_return_date and object.rental_return_date.strftime('%B %d, %Y') or ''"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;font-weight:600;border-top:2px solid #e2e8f0;">Renewal #</td><td style="padding:10px 14px;color:#38a169;font-size:14px;font-weight:700;border-top:2px solid #e2e8f0;"><t t-out="object.rental_renewal_count"/></td></tr>
|
|
</table>
|
|
<div style="border-left:3px solid #38a169;padding:12px 16px;margin:0 0 24px 0;background:#f7fafc;">
|
|
<p style="margin:0;font-size:14px;line-height:1.5;color:#2d3748;" t-if="ctx.get('payment_ok')">Payment has been collected from your card on file. No further action is required.</p>
|
|
<p style="margin:0;font-size:14px;line-height:1.5;color:#2d3748;" t-if="not ctx.get('payment_ok')">Our team will be in touch regarding payment for this renewal period.</p>
|
|
</div>
|
|
<t t-if="not is_html_empty(object.user_id.signature)" data-o-mail-quote-container="1">
|
|
<div data-o-mail-quote="1">--<br data-o-mail-quote="1"/><t t-out="object.user_id.signature or ''" data-o-mail-quote="1"/></div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</field>
|
|
<field name="lang">{{ object.partner_id.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
</record>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- 3. Payment Receipt -->
|
|
<!-- ============================================================ -->
|
|
<record id="mail_template_rental_payment_receipt" model="mail.template">
|
|
<field name="name">Rental: Payment Receipt</field>
|
|
<field name="model_id" ref="sale.model_sale_order"/>
|
|
<field name="subject">{{ object.company_id.name }} - Payment Receipt {{ object.name }}</field>
|
|
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
|
<field name="partner_to">{{ object.partner_id.id }}</field>
|
|
<field name="body_html" type="html">
|
|
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;max-width:600px;margin:0 auto;color:#2d3748;">
|
|
<div style="height:4px;background-color:#2B6CB0;"></div>
|
|
<div style="background:#ffffff;padding:32px 28px;border:1px solid #e2e8f0;border-top:none;">
|
|
<p style="color:#2B6CB0;font-size:13px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;margin:0 0 24px 0;">
|
|
<t t-out="object.company_id.name"/>
|
|
</p>
|
|
<h2 style="color:#1a202c;font-size:22px;font-weight:700;margin:0 0 6px 0;line-height:1.3;">Payment Receipt</h2>
|
|
<p style="color:#718096;font-size:15px;line-height:1.5;margin:0 0 24px 0;">
|
|
Payment has been collected for rental <strong style="color:#2d3748;"><t t-out="object.name"/></strong>.
|
|
</p>
|
|
<table style="width:100%;border-collapse:collapse;margin:0 0 24px 0;">
|
|
<tr><td colspan="2" style="padding:10px 14px;font-size:12px;font-weight:600;color:#718096;text-transform:uppercase;letter-spacing:0.5px;border-bottom:2px solid #e2e8f0;">Payment Details</td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;width:35%;">Reference</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.name"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;">Rental Period</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.rental_start_date and object.rental_start_date.strftime('%B %d, %Y') or ''"/> to <t t-out="object.rental_return_date and object.rental_return_date.strftime('%B %d, %Y') or ''"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;font-weight:600;border-top:2px solid #e2e8f0;">Amount Paid</td><td style="padding:10px 14px;color:#2B6CB0;font-size:14px;font-weight:700;border-top:2px solid #e2e8f0;"><t t-out="ctx.get('invoice') and ctx['invoice'].amount_total or object.amount_total" t-options="{'widget': 'monetary', 'display_currency': object.currency_id}"/></td></tr>
|
|
</table>
|
|
<div style="border-left:3px solid #2B6CB0;padding:12px 16px;margin:0 0 24px 0;background:#f7fafc;">
|
|
<p style="margin:0;font-size:14px;line-height:1.5;color:#2d3748;">Thank you for your continued business. If you have any questions about this payment, please do not hesitate to contact us.</p>
|
|
</div>
|
|
<t t-if="not is_html_empty(object.user_id.signature)" data-o-mail-quote-container="1">
|
|
<div data-o-mail-quote="1">--<br data-o-mail-quote="1"/><t t-out="object.user_id.signature or ''" data-o-mail-quote="1"/></div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</field>
|
|
<field name="lang">{{ object.partner_id.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
</record>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- 4. Cancellation Confirmation -->
|
|
<!-- ============================================================ -->
|
|
<record id="mail_template_rental_cancellation_confirmed" model="mail.template">
|
|
<field name="name">Rental: Cancellation Confirmed</field>
|
|
<field name="model_id" ref="sale.model_sale_order"/>
|
|
<field name="subject">{{ object.company_id.name }} - Cancellation Received {{ object.name }}</field>
|
|
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
|
<field name="partner_to">{{ object.partner_id.id }}</field>
|
|
<field name="body_html" type="html">
|
|
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;max-width:600px;margin:0 auto;color:#2d3748;">
|
|
<div style="height:4px;background-color:#D69E2E;"></div>
|
|
<div style="background:#ffffff;padding:32px 28px;border:1px solid #e2e8f0;border-top:none;">
|
|
<p style="color:#D69E2E;font-size:13px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;margin:0 0 24px 0;">
|
|
<t t-out="object.company_id.name"/>
|
|
</p>
|
|
<h2 style="color:#1a202c;font-size:22px;font-weight:700;margin:0 0 6px 0;line-height:1.3;">Cancellation Request Received</h2>
|
|
<p style="color:#718096;font-size:15px;line-height:1.5;margin:0 0 24px 0;">
|
|
Your cancellation request for rental <strong style="color:#2d3748;"><t t-out="object.name"/></strong> has been received. Your rental will <strong style="color:#2d3748;">no longer auto-renew</strong>.
|
|
</p>
|
|
<div style="border-left:3px solid #D69E2E;padding:12px 16px;margin:0 0 24px 0;background:#f7fafc;">
|
|
<p style="margin:0;font-size:14px;line-height:1.5;color:#2d3748;">Our team will contact you shortly to schedule a pickup. If you have any questions in the meantime, please do not hesitate to reach out.</p>
|
|
</div>
|
|
<t t-if="not is_html_empty(object.user_id.signature)" data-o-mail-quote-container="1">
|
|
<div data-o-mail-quote="1">--<br data-o-mail-quote="1"/><t t-out="object.user_id.signature or ''" data-o-mail-quote="1"/></div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</field>
|
|
<field name="lang">{{ object.partner_id.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
</record>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- 5. Send Agreement for Signing -->
|
|
<!-- ============================================================ -->
|
|
<record id="mail_template_rental_agreement" model="mail.template">
|
|
<field name="name">Rental: Agreement for Signing</field>
|
|
<field name="model_id" ref="sale.model_sale_order"/>
|
|
<field name="subject">{{ object.company_id.name }} - Rental Agreement {{ object.name }}</field>
|
|
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
|
<field name="partner_to">{{ object.partner_id.id }}</field>
|
|
<field name="body_html" type="html">
|
|
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;max-width:600px;margin:0 auto;color:#2d3748;">
|
|
<div style="height:4px;background-color:#2B6CB0;"></div>
|
|
<div style="background:#ffffff;padding:32px 28px;border:1px solid #e2e8f0;border-top:none;">
|
|
<p style="color:#2B6CB0;font-size:13px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;margin:0 0 24px 0;">
|
|
<t t-out="object.company_id.name"/>
|
|
</p>
|
|
<h2 style="color:#1a202c;font-size:22px;font-weight:700;margin:0 0 6px 0;line-height:1.3;">Rental Agreement</h2>
|
|
<p style="color:#718096;font-size:15px;line-height:1.5;margin:0 0 24px 0;">
|
|
Your rental order <strong style="color:#2d3748;"><t t-out="object.name"/></strong> is ready. Please review and sign the rental agreement and provide your payment details.
|
|
</p>
|
|
<table style="width:100%;border-collapse:collapse;margin:0 0 24px 0;">
|
|
<tr><td colspan="2" style="padding:10px 14px;font-size:12px;font-weight:600;color:#718096;text-transform:uppercase;letter-spacing:0.5px;border-bottom:2px solid #e2e8f0;">Agreement Details</td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;width:35%;">Order</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.name"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;">Rental Period</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.rental_start_date and object.rental_start_date.strftime('%B %d, %Y') or ''"/> to <t t-out="object.rental_return_date and object.rental_return_date.strftime('%B %d, %Y') or ''"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;font-weight:600;border-top:2px solid #e2e8f0;">Total</td><td style="padding:10px 14px;color:#2B6CB0;font-size:14px;font-weight:700;border-top:2px solid #e2e8f0;"><t t-out="object.amount_total" t-options="{'widget': 'monetary', 'display_currency': object.currency_id}"/></td></tr>
|
|
</table>
|
|
<div style="text-align:center;margin:0 0 24px 0;">
|
|
<a t-attf-href="{{ (object.get_base_url() or '') }}/rental/agreement/{{ object.id }}/{{ object.rental_agreement_token or '' }}" style="background-color:#2B6CB0;color:#fff;padding:12px 30px;text-decoration:none;border-radius:4px;font-size:14px;font-weight:600;display:inline-block;">Review & Sign Agreement</a>
|
|
</div>
|
|
<div style="border-left:3px solid #2B6CB0;padding:12px 16px;margin:0 0 24px 0;background:#f7fafc;">
|
|
<p style="margin:0;font-size:14px;line-height:1.5;color:#2d3748;">Click the button above to review your rental agreement, provide your payment card details, and sign digitally. If you have any questions, please contact us.</p>
|
|
</div>
|
|
<t t-if="not is_html_empty(object.user_id.signature)" data-o-mail-quote-container="1">
|
|
<div data-o-mail-quote="1">--<br data-o-mail-quote="1"/><t t-out="object.user_id.signature or ''" data-o-mail-quote="1"/></div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</field>
|
|
<field name="lang">{{ object.partner_id.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
</record>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- 5b. Signed Agreement Copy to Customer -->
|
|
<!-- ============================================================ -->
|
|
<record id="mail_template_rental_signed_agreement" model="mail.template">
|
|
<field name="name">Rental: Signed Agreement Copy</field>
|
|
<field name="model_id" ref="sale.model_sale_order"/>
|
|
<field name="subject">{{ object.company_id.name }} - Your Signed Rental Agreement {{ object.name }}</field>
|
|
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
|
<field name="partner_to">{{ object.partner_id.id }}</field>
|
|
<field name="body_html" type="html">
|
|
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;max-width:600px;margin:0 auto;color:#2d3748;">
|
|
<div style="height:4px;background-color:#2B6CB0;"></div>
|
|
<div style="background:#ffffff;padding:32px 28px;border:1px solid #e2e8f0;border-top:none;">
|
|
<p style="color:#2B6CB0;font-size:13px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;margin:0 0 24px 0;">
|
|
<t t-out="object.company_id.name"/>
|
|
</p>
|
|
<h2 style="color:#1a202c;font-size:22px;font-weight:700;margin:0 0 6px 0;line-height:1.3;">Your Signed Rental Agreement</h2>
|
|
<p style="color:#718096;font-size:15px;line-height:1.5;margin:0 0 24px 0;">
|
|
Thank you for signing the rental agreement for order <strong style="color:#2d3748;"><t t-out="object.name"/></strong>. A copy of your signed agreement is attached to this email for your records.
|
|
</p>
|
|
<table style="width:100%;border-collapse:collapse;margin:0 0 24px 0;">
|
|
<tr><td colspan="2" style="padding:10px 14px;font-size:12px;font-weight:600;color:#718096;text-transform:uppercase;letter-spacing:0.5px;border-bottom:2px solid #e2e8f0;">Agreement Summary</td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;width:35%;">Order</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.name"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;">Signed By</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.rental_agreement_signer_name or object.partner_id.name"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;">Signed On</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.rental_agreement_signed_date and object.rental_agreement_signed_date.strftime('%B %d, %Y at %I:%M %p') or ''"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;">Rental Period</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.rental_start_date and object.rental_start_date.strftime('%B %d, %Y') or ''"/> to <t t-out="object.rental_return_date and object.rental_return_date.strftime('%B %d, %Y') or ''"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;font-weight:600;border-top:2px solid #e2e8f0;">Total</td><td style="padding:10px 14px;color:#2B6CB0;font-size:14px;font-weight:700;border-top:2px solid #e2e8f0;"><t t-out="object.amount_total" t-options="{'widget': 'monetary', 'display_currency': object.currency_id}"/></td></tr>
|
|
</table>
|
|
<div style="border-left:3px solid #38A169;padding:12px 16px;margin:0 0 24px 0;background:#f0fff4;">
|
|
<p style="margin:0;font-size:14px;line-height:1.5;color:#2d3748;">Please keep this email and attachment for your records. Our team will be in touch to schedule your delivery.</p>
|
|
</div>
|
|
<t t-if="not is_html_empty(object.user_id.signature)" data-o-mail-quote-container="1">
|
|
<div data-o-mail-quote="1">--<br data-o-mail-quote="1"/><t t-out="object.user_id.signature or ''" data-o-mail-quote="1"/></div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</field>
|
|
<field name="lang">{{ object.partner_id.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
</record>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- 6. Day-7 Marketing Email - Purchase Conversion -->
|
|
<!-- ============================================================ -->
|
|
<record id="mail_template_rental_marketing" model="mail.template">
|
|
<field name="name">Rental: Purchase Conversion Offer</field>
|
|
<field name="model_id" ref="sale.model_sale_order"/>
|
|
<field name="subject">{{ object.company_id.name }} - Make Your Rental Yours {{ object.name }}</field>
|
|
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
|
<field name="partner_to">{{ object.partner_id.id }}</field>
|
|
<field name="body_html" type="html">
|
|
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;max-width:600px;margin:0 auto;color:#2d3748;">
|
|
<div style="height:4px;background-color:#319795;"></div>
|
|
<div style="background:#ffffff;padding:32px 28px;border:1px solid #e2e8f0;border-top:none;">
|
|
<p style="color:#319795;font-size:13px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;margin:0 0 24px 0;">
|
|
<t t-out="object.company_id.name"/>
|
|
</p>
|
|
<h2 style="color:#1a202c;font-size:22px;font-weight:700;margin:0 0 6px 0;line-height:1.3;">Love Your Rental? Make It Yours!</h2>
|
|
<p style="color:#718096;font-size:15px;line-height:1.5;margin:0 0 24px 0;">
|
|
We hope you are enjoying your rental! Did you know you can purchase the product and apply your <strong style="color:#2d3748;">first month's rental as a discount</strong>?
|
|
</p>
|
|
<t t-if="object.rental_purchase_coupon_id">
|
|
<div style="padding:16px 20px;border:2px dashed #319795;border-radius:6px;text-align:center;margin:0 0 24px 0;background:#f7fafc;">
|
|
<p style="margin:0 0 4px 0;font-size:12px;font-weight:600;color:#718096;text-transform:uppercase;letter-spacing:0.5px;">Your Exclusive Coupon</p>
|
|
<p style="margin:0;font-size:24px;font-weight:700;color:#319795;letter-spacing:2px;" t-out="object.rental_purchase_coupon_id.code or ''">CODE</p>
|
|
</div>
|
|
</t>
|
|
<p style="color:#718096;font-size:13px;line-height:1.5;margin:0 0 24px 0;">Delivery fee is not included in this offer.</p>
|
|
<div style="text-align:center;margin:0 0 24px 0;">
|
|
<a t-attf-href="{{ (object.get_base_url() or '') }}/rental/purchase-interest/{{ object.id }}/{{ object.rental_agreement_token or '' }}" style="background-color:#38a169;color:#fff;padding:12px 30px;text-decoration:none;border-radius:4px;font-size:14px;font-weight:600;display:inline-block;">I'm Interested!</a>
|
|
</div>
|
|
<div style="border-left:3px solid #319795;padding:12px 16px;margin:0 0 24px 0;background:#f7fafc;">
|
|
<p style="margin:0;font-size:14px;line-height:1.5;color:#2d3748;">Click the button above to let us know, and a member of our team will follow up to discuss the details.</p>
|
|
</div>
|
|
<t t-if="not is_html_empty(object.user_id.signature)" data-o-mail-quote-container="1">
|
|
<div data-o-mail-quote="1">--<br data-o-mail-quote="1"/><t t-out="object.user_id.signature or ''" data-o-mail-quote="1"/></div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</field>
|
|
<field name="lang">{{ object.partner_id.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
</record>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- 7a. Security Deposit Refund Initiated -->
|
|
<!-- ============================================================ -->
|
|
<record id="mail_template_rental_deposit_refund_initiated" model="mail.template">
|
|
<field name="name">Rental: Security Deposit Refund Initiated</field>
|
|
<field name="model_id" ref="sale.model_sale_order"/>
|
|
<field name="subject">{{ object.company_id.name }} - Security Deposit Refund Processing {{ object.name }}</field>
|
|
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
|
<field name="partner_to">{{ object.partner_id.id }}</field>
|
|
<field name="body_html" type="html">
|
|
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;max-width:600px;margin:0 auto;color:#2d3748;">
|
|
<div style="height:4px;background-color:#2B6CB0;"></div>
|
|
<div style="background:#ffffff;padding:32px 28px;border:1px solid #e2e8f0;border-top:none;">
|
|
<p style="color:#2B6CB0;font-size:13px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;margin:0 0 24px 0;">
|
|
<t t-out="object.company_id.name"/>
|
|
</p>
|
|
<h2 style="color:#1a202c;font-size:22px;font-weight:700;margin:0 0 6px 0;line-height:1.3;">Security Deposit Refund in Progress</h2>
|
|
<p style="color:#718096;font-size:15px;line-height:1.5;margin:0 0 24px 0;">
|
|
Your security deposit for rental order <strong style="color:#2d3748;"><t t-out="object.name"/></strong> is being processed for refund.
|
|
</p>
|
|
<t t-if="object.rental_deposit_invoice_id">
|
|
<table style="width:100%;border-collapse:collapse;margin:0 0 24px 0;">
|
|
<tr><td colspan="2" style="padding:10px 14px;font-size:12px;font-weight:600;color:#718096;text-transform:uppercase;letter-spacing:0.5px;border-bottom:2px solid #e2e8f0;">Refund Details</td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;width:35%;">Deposit Amount</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.rental_deposit_invoice_id.amount_total" t-options="{'widget': 'monetary', 'display_currency': object.currency_id}"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;font-weight:600;border-top:2px solid #e2e8f0;">Refund To</td><td style="padding:10px 14px;color:#2B6CB0;font-size:14px;font-weight:700;border-top:2px solid #e2e8f0;">**** **** **** <t t-out="object._get_card_last_four() or '****'"/></td></tr>
|
|
</table>
|
|
</t>
|
|
<div style="border-left:3px solid #2B6CB0;padding:12px 16px;margin:0 0 24px 0;background:#f7fafc;">
|
|
<p style="margin:0;font-size:14px;line-height:1.5;color:#2d3748;">Your refund is being processed and will be released within <strong>1 to 3 business days</strong>. You will receive a confirmation email once the refund is complete.</p>
|
|
</div>
|
|
<t t-if="not is_html_empty(object.user_id.signature)" data-o-mail-quote-container="1">
|
|
<div data-o-mail-quote="1">--<br data-o-mail-quote="1"/><t t-out="object.user_id.signature or ''" data-o-mail-quote="1"/></div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</field>
|
|
<field name="lang">{{ object.partner_id.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
</record>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- 7b. Security Deposit Refund Completed -->
|
|
<!-- ============================================================ -->
|
|
<record id="mail_template_rental_deposit_refund" model="mail.template">
|
|
<field name="name">Rental: Security Deposit Refund Complete</field>
|
|
<field name="model_id" ref="sale.model_sale_order"/>
|
|
<field name="subject">{{ object.company_id.name }} - Security Deposit Refunded {{ object.name }}</field>
|
|
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
|
<field name="partner_to">{{ object.partner_id.id }}</field>
|
|
<field name="body_html" type="html">
|
|
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;max-width:600px;margin:0 auto;color:#2d3748;">
|
|
<div style="height:4px;background-color:#38a169;"></div>
|
|
<div style="background:#ffffff;padding:32px 28px;border:1px solid #e2e8f0;border-top:none;">
|
|
<p style="color:#38a169;font-size:13px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;margin:0 0 24px 0;">
|
|
<t t-out="object.company_id.name"/>
|
|
</p>
|
|
<h2 style="color:#1a202c;font-size:22px;font-weight:700;margin:0 0 6px 0;line-height:1.3;">Security Deposit Refunded</h2>
|
|
<p style="color:#718096;font-size:15px;line-height:1.5;margin:0 0 24px 0;">
|
|
Your security deposit for rental order <strong style="color:#2d3748;"><t t-out="object.name"/></strong> has been successfully refunded.
|
|
</p>
|
|
<t t-if="object.rental_deposit_invoice_id">
|
|
<table style="width:100%;border-collapse:collapse;margin:0 0 24px 0;">
|
|
<tr><td colspan="2" style="padding:10px 14px;font-size:12px;font-weight:600;color:#718096;text-transform:uppercase;letter-spacing:0.5px;border-bottom:2px solid #e2e8f0;">Refund Receipt</td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;width:35%;">Order</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.name"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;">Refund Amount</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.rental_deposit_invoice_id.amount_total" t-options="{'widget': 'monetary', 'display_currency': object.currency_id}"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;font-weight:600;border-top:2px solid #e2e8f0;">Refunded To</td><td style="padding:10px 14px;color:#38a169;font-size:14px;font-weight:700;border-top:2px solid #e2e8f0;">**** **** **** <t t-out="object._get_card_last_four() or '****'"/></td></tr>
|
|
</table>
|
|
</t>
|
|
<div style="border-left:3px solid #38a169;padding:12px 16px;margin:0 0 24px 0;background:#f7fafc;">
|
|
<p style="margin:0;font-size:14px;line-height:1.5;color:#2d3748;">Your refund has been processed. Please allow <strong>1 to 3 business days</strong> for the refund to appear on your statement, depending on your bank.</p>
|
|
</div>
|
|
<t t-if="not is_html_empty(object.user_id.signature)" data-o-mail-quote-container="1">
|
|
<div data-o-mail-quote="1">--<br data-o-mail-quote="1"/><t t-out="object.user_id.signature or ''" data-o-mail-quote="1"/></div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</field>
|
|
<field name="lang">{{ object.partner_id.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
</record>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- 9. Invoice with Payment Receipt -->
|
|
<!-- ============================================================ -->
|
|
<record id="mail_template_rental_invoice_receipt" model="mail.template">
|
|
<field name="name">Rental: Invoice + Payment Receipt</field>
|
|
<field name="model_id" ref="sale.model_sale_order"/>
|
|
<field name="subject">{{ object.company_id.name }} - Invoice & Payment Confirmation {{ object.name }}</field>
|
|
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
|
<field name="partner_to">{{ object.partner_id.id }}</field>
|
|
<field name="body_html" type="html">
|
|
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;max-width:600px;margin:0 auto;color:#2d3748;">
|
|
<div style="height:4px;background-color:#2B6CB0;"></div>
|
|
<div style="background:#ffffff;padding:32px 28px;border:1px solid #e2e8f0;border-top:none;">
|
|
<p style="color:#2B6CB0;font-size:13px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;margin:0 0 24px 0;">
|
|
<t t-out="object.company_id.name"/>
|
|
</p>
|
|
<h2 style="color:#1a202c;font-size:22px;font-weight:700;margin:0 0 6px 0;line-height:1.3;">Invoice & Payment Confirmation</h2>
|
|
<p style="color:#718096;font-size:15px;line-height:1.5;margin:0 0 24px 0;">
|
|
Payment has been successfully processed for your rental order <strong style="color:#2d3748;"><t t-out="object.name"/></strong>.
|
|
</p>
|
|
<t t-set="inv" t-value="ctx.get('rental_invoice')"/>
|
|
<t t-set="inv_type" t-value="ctx.get('rental_invoice_type', '')"/>
|
|
<table style="width:100%;border-collapse:collapse;margin:0 0 24px 0;">
|
|
<tr><td colspan="2" style="padding:10px 14px;font-size:12px;font-weight:600;color:#718096;text-transform:uppercase;letter-spacing:0.5px;border-bottom:2px solid #e2e8f0;">Payment Receipt</td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;width:35%;">Order</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.name"/></td></tr>
|
|
<t t-if="inv">
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;">Invoice</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="inv.name or 'Draft'"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;">Description</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;">
|
|
<t t-if="inv_type == 'rental_charges'">Rental Charges, Delivery & Services</t>
|
|
<t t-elif="inv_type == 'security_deposit'">Security Deposit (Refundable)</t>
|
|
<t t-elif="inv_type == 'renewal'">Rental Renewal</t>
|
|
<t t-elif="inv_type == 'damage'">Damage Assessment Charges</t>
|
|
<t t-else="">Rental Payment</t>
|
|
</td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;font-weight:600;border-top:2px solid #e2e8f0;">Amount Paid</td><td style="padding:10px 14px;color:#2B6CB0;font-size:14px;font-weight:700;border-top:2px solid #e2e8f0;"><t t-out="inv.amount_total" t-options="{'widget': 'monetary', 'display_currency': object.currency_id}"/></td></tr>
|
|
</t>
|
|
<t t-else="">
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;font-weight:600;border-top:2px solid #e2e8f0;">Amount</td><td style="padding:10px 14px;color:#2B6CB0;font-size:14px;font-weight:700;border-top:2px solid #e2e8f0;"><t t-out="object.amount_total" t-options="{'widget': 'monetary', 'display_currency': object.currency_id}"/></td></tr>
|
|
</t>
|
|
</table>
|
|
<div style="border-left:3px solid #2B6CB0;padding:12px 16px;margin:0 0 24px 0;background:#f7fafc;">
|
|
<p style="margin:0;font-size:14px;line-height:1.5;color:#2d3748;">Your payment has been received and applied. If you have any questions about this charge, please do not hesitate to contact us.</p>
|
|
</div>
|
|
<t t-if="not is_html_empty(object.user_id.signature)" data-o-mail-quote-container="1">
|
|
<div data-o-mail-quote="1">--<br data-o-mail-quote="1"/><t t-out="object.user_id.signature or ''" data-o-mail-quote="1"/></div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</field>
|
|
<field name="lang">{{ object.partner_id.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
</record>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- 10. Damage Notification to Customer -->
|
|
<!-- ============================================================ -->
|
|
<record id="mail_template_rental_damage_notification" model="mail.template">
|
|
<field name="name">Rental: Damage Notification</field>
|
|
<field name="model_id" ref="sale.model_sale_order"/>
|
|
<field name="subject">{{ object.company_id.name }} - Rental Inspection Update {{ object.name }}</field>
|
|
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
|
<field name="partner_to">{{ object.partner_id.id }}</field>
|
|
<field name="body_html" type="html">
|
|
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;max-width:600px;margin:0 auto;color:#2d3748;">
|
|
<div style="height:4px;background-color:#E53E3E;"></div>
|
|
<div style="background:#ffffff;padding:32px 28px;border:1px solid #e2e8f0;border-top:none;">
|
|
<p style="color:#E53E3E;font-size:13px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;margin:0 0 24px 0;">
|
|
<t t-out="object.company_id.name"/>
|
|
</p>
|
|
<h2 style="color:#1a202c;font-size:22px;font-weight:700;margin:0 0 6px 0;line-height:1.3;">Rental Inspection Update</h2>
|
|
<p style="color:#718096;font-size:15px;line-height:1.5;margin:0 0 24px 0;">
|
|
During the pickup inspection of your rental order <strong style="color:#2d3748;"><t t-out="object.name"/></strong>, our technician noted a condition that requires review.
|
|
</p>
|
|
<table style="width:100%;border-collapse:collapse;margin:0 0 24px 0;">
|
|
<tr><td colspan="2" style="padding:10px 14px;font-size:12px;font-weight:600;color:#718096;text-transform:uppercase;letter-spacing:0.5px;border-bottom:2px solid #e2e8f0;">Inspection Summary</td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;width:35%;">Order</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.name"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;font-weight:600;border-top:2px solid #e2e8f0;">Status</td><td style="padding:10px 14px;color:#E53E3E;font-size:14px;font-weight:700;border-top:2px solid #e2e8f0;">Flagged for Review</td></tr>
|
|
</table>
|
|
<div style="border-left:3px solid #E53E3E;padding:12px 16px;margin:0 0 24px 0;background:#f7fafc;">
|
|
<p style="margin:0;font-size:14px;line-height:1.5;color:#2d3748;">Our team is reviewing the inspection findings and will reach out to you shortly with an update. Your security deposit is on hold pending the outcome of this review.</p>
|
|
</div>
|
|
<t t-if="not is_html_empty(object.user_id.signature)" data-o-mail-quote-container="1">
|
|
<div data-o-mail-quote="1">--<br data-o-mail-quote="1"/><t t-out="object.user_id.signature or ''" data-o-mail-quote="1"/></div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</field>
|
|
<field name="lang">{{ object.partner_id.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
</record>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- 8. Thank You + Google Review -->
|
|
<!-- ============================================================ -->
|
|
<record id="mail_template_rental_thank_you" model="mail.template">
|
|
<field name="name">Rental: Thank You + Review</field>
|
|
<field name="model_id" ref="sale.model_sale_order"/>
|
|
<field name="subject">{{ object.company_id.name }} - Thank You {{ object.name }}</field>
|
|
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
|
<field name="partner_to">{{ object.partner_id.id }}</field>
|
|
<field name="body_html" type="html">
|
|
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;max-width:600px;margin:0 auto;color:#2d3748;">
|
|
<div style="height:4px;background-color:#2B6CB0;"></div>
|
|
<div style="background:#ffffff;padding:32px 28px;border:1px solid #e2e8f0;border-top:none;">
|
|
<p style="color:#2B6CB0;font-size:13px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;margin:0 0 24px 0;">
|
|
<t t-out="object.company_id.name"/>
|
|
</p>
|
|
<h2 style="color:#1a202c;font-size:22px;font-weight:700;margin:0 0 6px 0;line-height:1.3;">Thank You!</h2>
|
|
<p style="color:#718096;font-size:15px;line-height:1.5;margin:0 0 24px 0;">
|
|
Thank you for choosing <strong style="color:#2d3748;"><t t-out="object.company_id.name or ''"/></strong> for your rental needs. Your rental order <strong style="color:#2d3748;"><t t-out="object.name"/></strong> has been completed and closed.
|
|
</p>
|
|
<t t-if="ctx.get('google_review_url')">
|
|
<div style="border-left:3px solid #2B6CB0;padding:12px 16px;margin:0 0 24px 0;background:#f7fafc;">
|
|
<p style="margin:0;font-size:14px;line-height:1.5;color:#2d3748;">We would greatly appreciate your feedback. A review helps us serve you and others better!</p>
|
|
</div>
|
|
<div style="text-align:center;margin:0 0 24px 0;">
|
|
<a t-att-href="ctx.get('google_review_url', '#')" style="background-color:#2B6CB0;color:#fff;padding:12px 30px;text-decoration:none;border-radius:4px;font-size:14px;font-weight:600;display:inline-block;">Leave a Google Review</a>
|
|
</div>
|
|
</t>
|
|
<t t-else="">
|
|
<div style="border-left:3px solid #2B6CB0;padding:12px 16px;margin:0 0 24px 0;background:#f7fafc;">
|
|
<p style="margin:0;font-size:14px;line-height:1.5;color:#2d3748;">We hope you had a great experience. If you need anything in the future, do not hesitate to reach out.</p>
|
|
</div>
|
|
</t>
|
|
<t t-if="not is_html_empty(object.user_id.signature)" data-o-mail-quote-container="1">
|
|
<div data-o-mail-quote="1">--<br data-o-mail-quote="1"/><t t-out="object.user_id.signature or ''" data-o-mail-quote="1"/></div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</field>
|
|
<field name="lang">{{ object.partner_id.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
</record>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- 12. Card Reauthorization Request -->
|
|
<!-- ============================================================ -->
|
|
<record id="mail_template_rental_card_reauthorization" model="mail.template">
|
|
<field name="name">Rental: Card Reauthorization Request</field>
|
|
<field name="model_id" ref="sale.model_sale_order"/>
|
|
<field name="subject">{{ object.company_id.name }} - Update Payment Card {{ object.name }}</field>
|
|
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
|
<field name="partner_to">{{ object.partner_id.id }}</field>
|
|
<field name="body_html" type="html">
|
|
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;max-width:600px;margin:0 auto;color:#2d3748;">
|
|
<div style="height:4px;background-color:#2B6CB0;"></div>
|
|
<div style="background:#ffffff;padding:32px 28px;border:1px solid #e2e8f0;border-top:none;">
|
|
<p style="color:#2B6CB0;font-size:13px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;margin:0 0 24px 0;">
|
|
<t t-out="object.company_id.name"/>
|
|
</p>
|
|
<h2 style="color:#1a202c;font-size:22px;font-weight:700;margin:0 0 6px 0;line-height:1.3;">Update Payment Card</h2>
|
|
<p style="color:#718096;font-size:15px;line-height:1.5;margin:0 0 24px 0;">
|
|
We need to update the payment card on file for your rental order <strong style="color:#2d3748;"><t t-out="object.name"/></strong>. Please click the button below to authorize a new card.
|
|
</p>
|
|
<div style="text-align:center;margin:0 0 24px 0;">
|
|
<a t-attf-href="{{ (object.get_base_url() or '') }}/rental/reauthorize/{{ object.id }}/{{ object.rental_agreement_token or '' }}" style="background-color:#2B6CB0;color:#fff;padding:12px 30px;text-decoration:none;border-radius:4px;font-size:14px;font-weight:600;display:inline-block;">Authorize New Card</a>
|
|
</div>
|
|
<div style="border-left:3px solid #2B6CB0;padding:12px 16px;margin:0 0 24px 0;background:#f7fafc;">
|
|
<p style="margin:0;font-size:14px;line-height:1.5;color:#2d3748;">Click the button above to securely provide your new payment card details. Your card information is encrypted and securely stored.</p>
|
|
</div>
|
|
<t t-if="not is_html_empty(object.user_id.signature)" data-o-mail-quote-container="1">
|
|
<div data-o-mail-quote="1">--<br data-o-mail-quote="1"/><t t-out="object.user_id.signature or ''" data-o-mail-quote="1"/></div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</field>
|
|
<field name="lang">{{ object.partner_id.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
</record>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- 13. Card Reauthorization Confirmation -->
|
|
<!-- ============================================================ -->
|
|
<record id="mail_template_rental_card_reauth_confirmation" model="mail.template">
|
|
<field name="name">Rental: Card Updated Confirmation</field>
|
|
<field name="model_id" ref="sale.model_sale_order"/>
|
|
<field name="subject">{{ object.company_id.name }} - Payment Card Updated {{ object.name }}</field>
|
|
<field name="email_from">{{ (object.user_id.email_formatted or object.company_id.email_formatted or user.email_formatted) }}</field>
|
|
<field name="partner_to">{{ object.partner_id.id }}</field>
|
|
<field name="body_html" type="html">
|
|
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;max-width:600px;margin:0 auto;color:#2d3748;">
|
|
<div style="height:4px;background-color:#38A169;"></div>
|
|
<div style="background:#ffffff;padding:32px 28px;border:1px solid #e2e8f0;border-top:none;">
|
|
<p style="color:#2B6CB0;font-size:13px;font-weight:600;letter-spacing:0.5px;text-transform:uppercase;margin:0 0 24px 0;">
|
|
<t t-out="object.company_id.name"/>
|
|
</p>
|
|
<h2 style="color:#1a202c;font-size:22px;font-weight:700;margin:0 0 6px 0;line-height:1.3;">Payment Card Updated</h2>
|
|
<p style="color:#718096;font-size:15px;line-height:1.5;margin:0 0 24px 0;">
|
|
The payment card on file for your rental order <strong style="color:#2d3748;"><t t-out="object.name"/></strong> has been updated successfully.
|
|
</p>
|
|
<table style="width:100%;border-collapse:collapse;margin:0 0 24px 0;">
|
|
<tr><td colspan="2" style="padding:10px 14px;font-size:12px;font-weight:600;color:#718096;text-transform:uppercase;letter-spacing:0.5px;border-bottom:2px solid #e2e8f0;">Card Details</td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;width:35%;">Cardholder</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="ctx.get('cardholder_name', '')"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;">Card</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="ctx.get('card_details', '')"/></td></tr>
|
|
<tr><td style="padding:10px 14px;color:#718096;font-size:14px;border-bottom:1px solid #f0f0f0;">Order</td><td style="padding:10px 14px;color:#2d3748;font-size:14px;border-bottom:1px solid #f0f0f0;"><t t-out="object.name"/></td></tr>
|
|
</table>
|
|
<div style="border-left:3px solid #38A169;padding:12px 16px;margin:0 0 24px 0;background:#f0fff4;">
|
|
<p style="margin:0;font-size:14px;line-height:1.5;color:#2d3748;">This card will be used for future rental charges and renewals. If you did not authorize this change, please contact us immediately.</p>
|
|
</div>
|
|
<t t-if="not is_html_empty(object.user_id.signature)" data-o-mail-quote-container="1">
|
|
<div data-o-mail-quote="1">--<br data-o-mail-quote="1"/><t t-out="object.user_id.signature or ''" data-o-mail-quote="1"/></div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</field>
|
|
<field name="lang">{{ object.partner_id.lang }}</field>
|
|
<field name="auto_delete" eval="True"/>
|
|
</record>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- QWeb Pages -->
|
|
<!-- ============================================================ -->
|
|
|
|
<!-- Cancellation Form -->
|
|
<record id="cancellation_form_page" model="ir.ui.view">
|
|
<field name="name">Rental Cancellation Form</field>
|
|
<field name="type">qweb</field>
|
|
<field name="key">fusion_rental.cancellation_form_page</field>
|
|
<field name="arch" type="xml">
|
|
<t t-call="web.frontend_layout">
|
|
<div class="container py-5">
|
|
<div class="row justify-content-center">
|
|
<div class="col-md-8 col-lg-6">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-warning text-white"><h3 class="mb-0">Cancel Rental & Request Pickup</h3></div>
|
|
<div class="card-body">
|
|
<p>Cancellation for <strong t-out="order.name">SO001</strong>.</p>
|
|
<p><strong>Customer:</strong> <t t-out="partner.name">Name</t><br/><strong>Period Ends:</strong> <t t-out="order.rental_return_date and order.rental_return_date.strftime('%B %d, %Y') or ''">Date</t></p>
|
|
<form t-attf-action="/rental/cancel/{{ token }}" method="post">
|
|
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
|
<div class="mb-3"><label for="reason" class="form-label">Reason (optional)</label><textarea name="reason" id="reason" class="form-control" rows="3" placeholder="Let us know why..."/></div>
|
|
<button type="submit" class="btn btn-danger w-100">Confirm Cancellation & Request Pickup</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</t>
|
|
</field>
|
|
</record>
|
|
|
|
<!-- Cancellation Success -->
|
|
<record id="cancellation_success_page" model="ir.ui.view">
|
|
<field name="name">Rental Cancellation Success</field>
|
|
<field name="type">qweb</field>
|
|
<field name="key">fusion_rental.cancellation_success_page</field>
|
|
<field name="arch" type="xml">
|
|
<t t-call="web.frontend_layout">
|
|
<div class="container py-5"><div class="row justify-content-center"><div class="col-md-8 col-lg-6"><div class="card shadow-sm">
|
|
<div class="card-header bg-success text-white"><h3 class="mb-0">Cancellation Request Received</h3></div>
|
|
<div class="card-body text-center">
|
|
<i class="fa fa-check-circle text-success" style="font-size:48px;"/>
|
|
<p class="lead mt-3">Your cancellation for <strong t-out="order.name">SO001</strong> has been received.</p>
|
|
<p>Our team will contact you to schedule a pickup.</p>
|
|
</div>
|
|
</div></div></div></div>
|
|
</t>
|
|
</field>
|
|
</record>
|
|
|
|
<!-- Invalid Link -->
|
|
<record id="cancellation_invalid_page" model="ir.ui.view">
|
|
<field name="name">Rental Invalid Link</field>
|
|
<field name="type">qweb</field>
|
|
<field name="key">fusion_rental.cancellation_invalid_page</field>
|
|
<field name="arch" type="xml">
|
|
<t t-call="web.frontend_layout">
|
|
<div class="container py-5"><div class="row justify-content-center"><div class="col-md-8 col-lg-6"><div class="card shadow-sm">
|
|
<div class="card-header bg-danger text-white"><h3 class="mb-0">Invalid Request</h3></div>
|
|
<div class="card-body text-center">
|
|
<i class="fa fa-times-circle text-danger" style="font-size:48px;"/>
|
|
<p class="lead mt-3" t-out="error">This link is invalid or has already been used.</p>
|
|
<p>Please contact our office for assistance.</p>
|
|
</div>
|
|
</div></div></div></div>
|
|
</t>
|
|
</field>
|
|
</record>
|
|
|
|
<!-- Agreement Signing Page -->
|
|
<record id="agreement_signing_page" model="ir.ui.view">
|
|
<field name="name">Rental Agreement Signing</field>
|
|
<field name="type">qweb</field>
|
|
<field name="key">fusion_rental.agreement_signing_page</field>
|
|
<field name="arch" type="xml">
|
|
<t t-call="web.frontend_layout">
|
|
<div class="container py-4"
|
|
t-att-data-poynt-business-id="poynt_business_id or ''"
|
|
t-att-data-poynt-application-id="poynt_application_id or ''">
|
|
<div class="row justify-content-center">
|
|
<div class="col-lg-8">
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-header bg-primary text-white"><h3 class="mb-0">Rental Agreement</h3></div>
|
|
<div class="card-body">
|
|
<h5>Order: <a t-att-href="'/my/orders/%d' % order.id" target="_blank" style="color: #0066a1; text-decoration: underline;"><t t-out="order.name">SO001</t></a></h5>
|
|
<p><strong>Customer:</strong> <t t-out="partner.name">Name</t></p>
|
|
<p><strong>Rental Period:</strong>
|
|
<t t-out="order.rental_start_date and order.rental_start_date.strftime('%B %d, %Y') or ''">Start</t>
|
|
to <t t-out="order.rental_return_date and order.rental_return_date.strftime('%B %d, %Y') or ''">End</t>
|
|
</p>
|
|
|
|
<h5 class="mt-4">Order Summary</h5>
|
|
<style>
|
|
.order-summary-table { table-layout: fixed; width: 100%; }
|
|
.order-summary-table .col-desc { width: 40%; }
|
|
.order-summary-table .col-qty { width: 8%; }
|
|
.order-summary-table .col-price { width: 17%; }
|
|
.order-summary-table .col-tax { width: 17%; }
|
|
.order-summary-table .col-total { width: 18%; }
|
|
.order-summary-table td, .order-summary-table th { word-wrap: break-word; overflow-wrap: break-word; }
|
|
@media (max-width: 576px) {
|
|
.order-summary-table .col-desc { width: 42%; }
|
|
.order-summary-table .col-qty { width: 7%; }
|
|
.order-summary-table .col-price { width: 16%; }
|
|
.order-summary-table .col-tax { width: 16%; }
|
|
.order-summary-table .col-total { width: 19%; }
|
|
.order-summary-table td, .order-summary-table th { font-size: 0.82rem; padding: 0.3rem 0.25rem !important; }
|
|
}
|
|
</style>
|
|
<table class="table table-sm table-bordered mb-0 order-summary-table">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th class="col-desc">Description</th>
|
|
<th class="text-center col-qty">Qty</th>
|
|
<th class="text-end col-price">Price</th>
|
|
<th class="text-center col-tax">Tax</th>
|
|
<th class="text-end col-total">Total</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<!-- Rental items -->
|
|
<t t-foreach="order.order_line.filtered(lambda l: l.is_rental and not l.is_security_deposit)" t-as="line">
|
|
<tr>
|
|
<td t-out="line.product_id.name">Product</td>
|
|
<td class="text-center" t-out="int(line.product_uom_qty)">1</td>
|
|
<td class="text-end"><t t-out="order.currency_id.symbol or '$'"/><t t-out="'%.2f' % line.price_unit"/></td>
|
|
<td class="text-center small"><t t-if="line.tax_ids"><t t-out="', '.join(line.tax_ids.mapped('name'))"/></t></td>
|
|
<td class="text-end"><t t-out="order.currency_id.symbol or '$'"/><t t-out="'%.2f' % line.price_total"/></td>
|
|
</tr>
|
|
</t>
|
|
<!-- Non-rental, non-deposit items (delivery, installation, etc.) -->
|
|
<t t-foreach="order.order_line.filtered(lambda l: not l.is_rental and not l.is_security_deposit and not l.display_type)" t-as="line">
|
|
<tr>
|
|
<td t-out="line.product_id.name">Service</td>
|
|
<td class="text-center" t-out="int(line.product_uom_qty)">1</td>
|
|
<td class="text-end"><t t-out="order.currency_id.symbol or '$'"/><t t-out="'%.2f' % line.price_unit"/></td>
|
|
<td class="text-center small"><t t-if="line.tax_ids"><t t-out="', '.join(line.tax_ids.mapped('name'))"/></t></td>
|
|
<td class="text-end"><t t-out="order.currency_id.symbol or '$'"/><t t-out="'%.2f' % line.price_total"/></td>
|
|
</tr>
|
|
</t>
|
|
<!-- Security deposit lines -->
|
|
<t t-foreach="order.order_line.filtered(lambda l: l.is_security_deposit)" t-as="line">
|
|
<tr class="table-warning">
|
|
<td><strong>SECURITY DEPOSIT - REFUNDABLE</strong>
|
|
<br/><small class="text-muted">REFUNDABLE UPON RETURN IN GOOD & CLEAN CONDITION</small></td>
|
|
<td class="text-center" t-out="int(line.product_uom_qty)">1</td>
|
|
<td class="text-end"><t t-out="order.currency_id.symbol or '$'"/><t t-out="'%.2f' % line.price_unit"/></td>
|
|
<td class="text-center small"><t t-if="line.tax_ids"><t t-out="', '.join(line.tax_ids.mapped('name'))"/></t></td>
|
|
<td class="text-end"><t t-out="order.currency_id.symbol or '$'"/><t t-out="'%.2f' % line.price_total"/></td>
|
|
</tr>
|
|
</t>
|
|
</tbody>
|
|
<tfoot>
|
|
<tr class="table-light">
|
|
<td colspan="4" class="text-end">Subtotal</td>
|
|
<td class="text-end"><t t-out="order.currency_id.symbol or '$'"/><t t-out="'%.2f' % order.amount_untaxed"/></td>
|
|
</tr>
|
|
<t t-if="order.amount_tax">
|
|
<tr class="table-light">
|
|
<td colspan="4" class="text-end">Taxes</td>
|
|
<td class="text-end"><t t-out="order.currency_id.symbol or '$'"/><t t-out="'%.2f' % order.amount_tax"/></td>
|
|
</tr>
|
|
</t>
|
|
<tr class="table-light">
|
|
<td colspan="4" class="text-end"><strong>Total</strong></td>
|
|
<td class="text-end"><strong><t t-out="order.currency_id.symbol or '$'"/><t t-out="'%.2f' % order.amount_total"/></strong></td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
<div class="alert alert-info mt-2 small mb-4">
|
|
<strong>Payment will be processed in two portions:</strong>
|
|
<ol class="mb-0 mt-1">
|
|
<li>Rental charges, delivery and services</li>
|
|
<li>Refundable security deposit (separate invoice)</li>
|
|
</ol>
|
|
</div>
|
|
|
|
<h5 class="mt-4">Rental Agreement</h5>
|
|
<p class="text-muted small">Please review the full rental agreement below before signing.</p>
|
|
<div style="border:1px solid #dee2e6; border-radius:4px; overflow:hidden; margin-bottom:20px; background:#f8f9fa;">
|
|
<iframe t-att-src="pdf_preview_url"
|
|
style="width:100%; height:600px; border:none;"
|
|
title="Rental Agreement Preview"/>
|
|
</div>
|
|
<div class="form-check mb-3">
|
|
<input type="checkbox" class="form-check-input" id="agree_terms" required="1"/>
|
|
<label class="form-check-label" for="agree_terms">
|
|
I have read and agree to the terms and conditions of the Rental Agreement.
|
|
</label>
|
|
</div>
|
|
|
|
<hr class="my-4"/>
|
|
<h5 class="mb-3">Credit Card Authorization</h5>
|
|
<p class="text-muted mb-3">Your card details are securely handled by our PCI-compliant payment partner. We never see your full card number.</p>
|
|
<div id="card-element" style="min-height:120px; border:1px solid #dee2e6; border-radius:6px; padding:16px 14px; background:#fff; margin-bottom:16px; box-shadow: inset 0 1px 3px rgba(0,0,0,0.06);">
|
|
<p class="text-muted mb-0" id="card-loading-text">Loading secure card form...</p>
|
|
</div>
|
|
<div id="card-element-error" class="text-danger small d-none mb-3"></div>
|
|
|
|
<hr class="my-4"/>
|
|
<h5 class="mb-3">Billing Address</h5>
|
|
<p class="text-muted mb-3">Enter the billing address associated with this card. Used for payment verification.</p>
|
|
<div class="row g-3">
|
|
<div class="col-12">
|
|
<label class="form-label">Street Address</label>
|
|
<input type="text" id="billing_address" class="form-control" placeholder="Start typing address..." required="1"
|
|
t-att-value="partner.street or ''"/>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">City</label>
|
|
<input type="text" id="billing_city" class="form-control" placeholder="City"
|
|
t-att-value="partner.city or ''"/>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">Province / State</label>
|
|
<input type="text" id="billing_state" class="form-control" placeholder="Province"
|
|
t-att-value="partner.state_id.name or ''"/>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label">Postal / Zip Code</label>
|
|
<input type="text" id="billing_postal_code" class="form-control" placeholder="Postal Code" required="1"
|
|
t-att-value="partner.zip or ''" style="border: 2px solid #0066a1;"/>
|
|
<small class="text-muted">Verify this matches the postal code on your card statement.</small>
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="my-4"/>
|
|
<h5 class="mb-3">Signature</h5>
|
|
<div class="mb-3"><label class="form-label">Full Name (Print)</label><input type="text" id="signer_name" class="form-control" t-att-value="partner.name" required="1"/></div>
|
|
<div class="mb-4">
|
|
<label class="form-label">Signature</label>
|
|
<div style="border:1px solid #ccc; border-radius:6px; background:#fff;">
|
|
<canvas id="signature_pad" width="600" height="200" style="width:100%; height:200px; cursor:crosshair;"/>
|
|
</div>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary mt-2" id="clear_signature">Clear</button>
|
|
</div>
|
|
|
|
<div id="agreement_error" class="alert alert-danger d-none"></div>
|
|
<div id="agreement_success" class="alert alert-success d-none"></div>
|
|
|
|
<button type="button" id="btn_sign_agreement" class="btn btn-primary btn-lg w-100 mt-4"
|
|
t-att-data-order-id="order.id" t-att-data-token="token">
|
|
Sign Agreement & Authorize Card
|
|
</button>
|
|
|
|
<hr class="mt-4"/>
|
|
<div class="text-center">
|
|
<p class="text-muted small mb-2">If you do not wish to proceed, you may cancel this rental order.</p>
|
|
<button type="button" id="btn_cancel_agreement" class="btn btn-outline-danger"
|
|
t-att-data-order-id="order.id" t-att-data-token="token">
|
|
Cancel Rental Order
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script t-att-src="'https://maps.googleapis.com/maps/api/js?key=%s&libraries=places' % (google_api_key or '')">/* Google Places */</script>
|
|
<script src="https://collect.commerce.godaddy.com/sdk.js" async="async" defer="defer">/* Poynt Collect */</script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
/* ---- Google Places address autocomplete ---- */
|
|
var addrInput = document.getElementById('billing_address');
|
|
if (addrInput && typeof google !== 'undefined' && google.maps && google.maps.places) {
|
|
var autocomplete = new google.maps.places.Autocomplete(addrInput, {
|
|
types: ['address'],
|
|
componentRestrictions: {country: ['ca', 'us']}
|
|
});
|
|
autocomplete.setFields(['address_components', 'formatted_address']);
|
|
autocomplete.addListener('place_changed', function() {
|
|
var place = autocomplete.getPlace();
|
|
if (!place.address_components) return;
|
|
var city = '', state = '', postal = '', streetNum = '', route = '';
|
|
for (var i = 0; i < place.address_components.length; i++) {
|
|
var comp = place.address_components[i];
|
|
var types = comp.types;
|
|
if (types.indexOf('street_number') !== -1) streetNum = comp.long_name;
|
|
if (types.indexOf('route') !== -1) route = comp.long_name;
|
|
if (types.indexOf('locality') !== -1) city = comp.long_name;
|
|
if (types.indexOf('administrative_area_level_1') !== -1) state = comp.short_name;
|
|
if (types.indexOf('postal_code') !== -1) postal = comp.long_name;
|
|
}
|
|
addrInput.value = (streetNum ? streetNum + ' ' : '') + route;
|
|
var cityEl = document.getElementById('billing_city');
|
|
var stateEl = document.getElementById('billing_state');
|
|
var postalEl = document.getElementById('billing_postal_code');
|
|
if (cityEl) cityEl.value = city;
|
|
if (stateEl) stateEl.value = state;
|
|
if (postalEl && postal) postalEl.value = postal;
|
|
});
|
|
}
|
|
|
|
/* ---- Poynt Collect iFrame initialization ---- */
|
|
var poyntCollect = null;
|
|
var poyntNonce = null;
|
|
var cardLoadingText = document.getElementById('card-loading-text');
|
|
var cardErrorDiv = document.getElementById('card-element-error');
|
|
|
|
function initPoyntCollect() {
|
|
if (typeof TokenizeJs === 'undefined') {
|
|
setTimeout(initPoyntCollect, 200);
|
|
return;
|
|
}
|
|
var containerEl = document.querySelector('.container[data-poynt-business-id]');
|
|
var bizId = containerEl ? containerEl.getAttribute('data-poynt-business-id') : '';
|
|
var appId = containerEl ? containerEl.getAttribute('data-poynt-application-id') : '';
|
|
if (!bizId || !appId) {
|
|
if (cardLoadingText) cardLoadingText.textContent = 'Payment provider not configured.';
|
|
return;
|
|
}
|
|
poyntCollect = new TokenizeJs(bizId, appId);
|
|
poyntCollect.mount('card-element', document, {
|
|
iFrame: {
|
|
width: '100%',
|
|
height: '90px',
|
|
border: '0',
|
|
borderRadius: '6px',
|
|
},
|
|
displayComponents: {
|
|
zipCode: false,
|
|
firstName: false,
|
|
lastName: false,
|
|
emailAddress: false,
|
|
submitButton: false,
|
|
},
|
|
style: {
|
|
'font-size': '16px',
|
|
'font-family': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
},
|
|
customCss: {
|
|
'input': 'border: 1px solid #dee2e6; border-radius: 4px; padding: 6px 10px;',
|
|
},
|
|
});
|
|
if (cardLoadingText) cardLoadingText.style.display = 'none';
|
|
|
|
poyntCollect.on('nonce', function(event) {
|
|
poyntNonce = event.data.nonce;
|
|
submitSignedAgreement(poyntNonce);
|
|
});
|
|
poyntCollect.on('error', function(event) {
|
|
var rawErr = (event && event.data && event.data.error) ? event.data.error : null;
|
|
var msg;
|
|
if (typeof rawErr === 'string') {
|
|
msg = rawErr;
|
|
} else if (rawErr && typeof rawErr === 'object') {
|
|
msg = rawErr.message || rawErr.developerMessage || JSON.stringify(rawErr);
|
|
} else {
|
|
msg = 'Card validation failed. Please check your card details.';
|
|
}
|
|
cardErrorDiv.textContent = msg;
|
|
cardErrorDiv.classList.remove('d-none');
|
|
var btn = document.getElementById('btn_sign_agreement');
|
|
if (btn) { btn.disabled = false; btn.textContent = 'Sign Agreement \x26 Authorize Card'; }
|
|
});
|
|
poyntCollect.on('ready', function() {
|
|
if (cardLoadingText) cardLoadingText.style.display = 'none';
|
|
});
|
|
}
|
|
initPoyntCollect();
|
|
|
|
/* ---- Signature pad ---- */
|
|
var canvas = document.getElementById('signature_pad');
|
|
var ctx = canvas.getContext('2d');
|
|
var drawing = false;
|
|
|
|
function getPos(e) {
|
|
var rect = canvas.getBoundingClientRect();
|
|
var x = (e.touches ? e.touches[0].clientX : e.clientX) - rect.left;
|
|
var y = (e.touches ? e.touches[0].clientY : e.clientY) - rect.top;
|
|
return {x: x * (canvas.width / rect.width), y: y * (canvas.height / rect.height)};
|
|
}
|
|
|
|
canvas.addEventListener('mousedown', function(e) { drawing = true; ctx.beginPath(); var p = getPos(e); ctx.moveTo(p.x, p.y); });
|
|
canvas.addEventListener('mousemove', function(e) { if (!drawing) return; var p = getPos(e); ctx.lineTo(p.x, p.y); ctx.stroke(); });
|
|
canvas.addEventListener('mouseup', function() { drawing = false; });
|
|
canvas.addEventListener('touchstart', function(e) { e.preventDefault(); drawing = true; ctx.beginPath(); var p = getPos(e); ctx.moveTo(p.x, p.y); });
|
|
canvas.addEventListener('touchmove', function(e) { e.preventDefault(); if (!drawing) return; var p = getPos(e); ctx.lineTo(p.x, p.y); ctx.stroke(); });
|
|
canvas.addEventListener('touchend', function() { drawing = false; });
|
|
ctx.strokeStyle = '#000'; ctx.lineWidth = 2; ctx.lineCap = 'round';
|
|
|
|
document.getElementById('clear_signature').addEventListener('click', function() { ctx.clearRect(0, 0, canvas.width, canvas.height); });
|
|
|
|
/* ---- Submit signed agreement to server ---- */
|
|
function submitSignedAgreement(nonce) {
|
|
var btn = document.getElementById('btn_sign_agreement');
|
|
var errDiv = document.getElementById('agreement_error');
|
|
var successDiv = document.getElementById('agreement_success');
|
|
|
|
var orderId = btn.dataset.orderId;
|
|
var token = btn.dataset.token;
|
|
var sigData = canvas.toDataURL('image/png');
|
|
var billingAddr = (document.getElementById('billing_address') || {}).value || '';
|
|
var billingCity = (document.getElementById('billing_city') || {}).value || '';
|
|
var billingState = (document.getElementById('billing_state') || {}).value || '';
|
|
var postalCode = (document.getElementById('billing_postal_code') || {}).value || '';
|
|
|
|
fetch('/rental/agreement/' + orderId + '/' + token + '/sign', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({
|
|
jsonrpc: '2.0', method: 'call', id: 1,
|
|
params: {
|
|
order_id: parseInt(orderId), token: token,
|
|
signer_name: document.getElementById('signer_name').value,
|
|
signature_data: sigData,
|
|
nonce: nonce,
|
|
billing_address: billingAddr,
|
|
billing_city: billingCity,
|
|
billing_state: billingState,
|
|
billing_postal_code: postalCode,
|
|
}
|
|
})
|
|
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
var res = data.result || data;
|
|
if (res.success) {
|
|
successDiv.textContent = (res.message || 'Signed successfully!') + ' Redirecting...';
|
|
successDiv.classList.remove('d-none');
|
|
btn.textContent = 'Signed!';
|
|
setTimeout(function() {
|
|
window.location.href = '/rental/agreement/' + orderId + '/' + token + '/thank-you';
|
|
}, 1500);
|
|
} else {
|
|
errDiv.textContent = res.error || 'An error occurred.';
|
|
errDiv.classList.remove('d-none');
|
|
btn.disabled = false;
|
|
btn.textContent = 'Sign Agreement \x26 Authorize Card';
|
|
}
|
|
}).catch(function(e) {
|
|
errDiv.textContent = 'Network error. Please try again.';
|
|
errDiv.classList.remove('d-none');
|
|
btn.disabled = false;
|
|
btn.textContent = 'Sign Agreement \x26 Authorize Card';
|
|
});
|
|
}
|
|
|
|
/* ---- Form submission: validate, then request nonce ---- */
|
|
document.getElementById('btn_sign_agreement').addEventListener('click', function() {
|
|
var btn = this;
|
|
var errDiv = document.getElementById('agreement_error');
|
|
var successDiv = document.getElementById('agreement_success');
|
|
errDiv.classList.add('d-none');
|
|
successDiv.classList.add('d-none');
|
|
cardErrorDiv.classList.add('d-none');
|
|
|
|
var agreeCheck = document.getElementById('agree_terms');
|
|
if (agreeCheck && !agreeCheck.checked) {
|
|
errDiv.textContent = 'You must agree to the rental agreement terms before signing.';
|
|
errDiv.classList.remove('d-none');
|
|
return;
|
|
}
|
|
|
|
var signerName = (document.getElementById('signer_name') || {}).value || '';
|
|
if (!signerName.trim()) {
|
|
errDiv.textContent = 'Full name is required.';
|
|
errDiv.classList.remove('d-none');
|
|
return;
|
|
}
|
|
|
|
var postalCode = (document.getElementById('billing_postal_code') || {}).value || '';
|
|
if (!postalCode.trim()) {
|
|
errDiv.textContent = 'Billing postal/zip code is required for payment verification.';
|
|
errDiv.classList.remove('d-none');
|
|
return;
|
|
}
|
|
|
|
if (!poyntCollect) {
|
|
errDiv.textContent = 'Payment form is not ready. Please wait a moment and try again.';
|
|
errDiv.classList.remove('d-none');
|
|
return;
|
|
}
|
|
|
|
btn.disabled = true;
|
|
btn.textContent = 'Processing...';
|
|
|
|
var nameParts = signerName.trim().split(' ');
|
|
var firstName = nameParts[0] || '';
|
|
var lastName = nameParts.slice(1).join(' ') || '';
|
|
|
|
var cEl = document.querySelector('.container[data-poynt-business-id]');
|
|
poyntCollect.getNonce({
|
|
businessId: cEl ? cEl.getAttribute('data-poynt-business-id') : '',
|
|
firstName: firstName,
|
|
lastName: lastName,
|
|
zipCode: postalCode,
|
|
});
|
|
});
|
|
|
|
/* ---- Cancel button ---- */
|
|
var cancelBtn = document.getElementById('btn_cancel_agreement');
|
|
if (cancelBtn) {
|
|
cancelBtn.addEventListener('click', function() {
|
|
if (!confirm('Are you sure you want to cancel this rental order? This action cannot be undone.')) return;
|
|
var btn = this;
|
|
var errDiv = document.getElementById('agreement_error');
|
|
btn.disabled = true;
|
|
btn.textContent = 'Cancelling...';
|
|
|
|
var orderId = btn.dataset.orderId;
|
|
var token = btn.dataset.token;
|
|
|
|
fetch('/rental/agreement/' + orderId + '/' + token + '/decline', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({
|
|
jsonrpc: '2.0', method: 'call', id: 1,
|
|
params: { order_id: parseInt(orderId), token: token }
|
|
})
|
|
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
var res = data.result || data;
|
|
if (res.success) {
|
|
var successDiv = document.getElementById('agreement_success');
|
|
successDiv.textContent = res.message || 'Rental order has been cancelled.';
|
|
successDiv.classList.remove('d-none');
|
|
btn.textContent = 'Cancelled';
|
|
var signBtn = document.getElementById('btn_sign_agreement');
|
|
if (signBtn) signBtn.style.display = 'none';
|
|
} else {
|
|
errDiv.textContent = res.error || 'An error occurred.';
|
|
errDiv.classList.remove('d-none');
|
|
btn.disabled = false;
|
|
btn.textContent = 'Cancel Rental Order';
|
|
}
|
|
}).catch(function(e) {
|
|
errDiv.textContent = 'Network error. Please try again.';
|
|
errDiv.classList.remove('d-none');
|
|
btn.disabled = false;
|
|
btn.textContent = 'Cancel Rental Order';
|
|
});
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
</t>
|
|
</field>
|
|
</record>
|
|
|
|
<!-- Agreement Signed Thank You Page -->
|
|
<record id="agreement_thank_you_page" model="ir.ui.view">
|
|
<field name="name">Rental Agreement Thank You</field>
|
|
<field name="type">qweb</field>
|
|
<field name="key">fusion_rental.agreement_thank_you_page</field>
|
|
<field name="arch" type="xml">
|
|
<t t-call="web.frontend_layout">
|
|
<div class="container py-5">
|
|
<div class="row justify-content-center">
|
|
<div class="col-md-8 col-lg-6">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-success text-white text-center py-4">
|
|
<i class="fa fa-check-circle" style="font-size:48px;" title="Success"/>
|
|
<h3 class="mb-0 mt-2">Agreement Signed Successfully</h3>
|
|
</div>
|
|
<div class="card-body text-center py-4">
|
|
<p class="lead mb-3">Thank you, <strong t-out="partner.name">Customer</strong>!</p>
|
|
<p class="mb-2">Your rental agreement for order <strong t-out="order.name">SO001</strong> has been signed and your payment has been processed.</p>
|
|
|
|
<div class="alert alert-light border mt-4 text-start">
|
|
<h6 class="fw-bold mb-3">What happens next?</h6>
|
|
<ol class="mb-0" style="padding-left:20px;">
|
|
<li class="mb-2">Our team will review your order and prepare the equipment for delivery.</li>
|
|
<li class="mb-2">We will contact you to schedule a delivery date and time that works for you.</li>
|
|
<li class="mb-2">On delivery day, our technician will set up the equipment and walk you through its use.</li>
|
|
<li>You will receive email confirmations at each step.</li>
|
|
</ol>
|
|
</div>
|
|
|
|
<p class="text-muted mt-4 mb-3">Have questions? Reach out to us anytime:</p>
|
|
|
|
<div class="d-flex justify-content-center gap-3 flex-wrap">
|
|
<a t-att-href="'tel:%s' % (company_phone or '')" class="btn btn-primary btn-lg"
|
|
t-if="company_phone">
|
|
<i class="fa fa-phone me-2" title="Call"/> Call Us
|
|
</a>
|
|
<a t-att-href="'mailto:%s?subject=Re: Order %s' % (company_email or '', order.name)" class="btn btn-outline-primary btn-lg"
|
|
t-if="company_email">
|
|
<i class="fa fa-envelope me-2" title="Email"/> Email Us
|
|
</a>
|
|
</div>
|
|
|
|
<hr class="my-4"/>
|
|
<a href="/my/orders" class="btn btn-outline-secondary">
|
|
<i class="fa fa-list me-1" title="Orders"/> View My Orders
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</t>
|
|
</field>
|
|
</record>
|
|
|
|
<!-- Card Reauthorization Portal Page -->
|
|
<record id="card_reauthorization_page" model="ir.ui.view">
|
|
<field name="name">Card Reauthorization</field>
|
|
<field name="type">qweb</field>
|
|
<field name="key">fusion_rental.card_reauthorization_page</field>
|
|
<field name="arch" type="xml">
|
|
<t t-call="web.frontend_layout">
|
|
<div class="container py-4"
|
|
t-att-data-poynt-business-id="poynt_business_id or ''"
|
|
t-att-data-poynt-application-id="poynt_application_id or ''">
|
|
<div class="row justify-content-center">
|
|
<div class="col-lg-8">
|
|
<div class="card shadow-sm mb-4">
|
|
<div class="card-header bg-primary text-white">
|
|
<h3 class="mb-0">Update Payment Card</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="text-muted mb-3">
|
|
Order: <strong t-out="order.name"/> |
|
|
Customer: <strong t-out="partner.name"/>
|
|
</p>
|
|
|
|
<div id="alertBox" class="alert d-none" role="alert"/>
|
|
|
|
<!-- Cardholder Information -->
|
|
<h5 class="mb-3">Cardholder Information</h5>
|
|
<div class="row mb-4">
|
|
<div class="col-md-12 mb-3">
|
|
<label class="form-label fw-bold">Full Name on Card <span class="text-danger">*</span></label>
|
|
<input type="text" class="form-control" id="cardholderName"
|
|
placeholder="Enter the name as it appears on the card" required="required"/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Billing Address -->
|
|
<h5 class="mb-3">Billing Address</h5>
|
|
<div class="row mb-4">
|
|
<div class="col-md-12 mb-3">
|
|
<label class="form-label fw-bold">Address <span class="text-danger">*</span></label>
|
|
<input type="text" class="form-control" id="billingAddress"
|
|
placeholder="Street address"/>
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<label class="form-label fw-bold">City</label>
|
|
<input type="text" class="form-control" id="billingCity"/>
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<label class="form-label fw-bold">Province/State</label>
|
|
<input type="text" class="form-control" id="billingState"/>
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<label class="form-label fw-bold">Postal Code <span class="text-danger">*</span></label>
|
|
<input type="text" class="form-control" id="billingPostalCode"
|
|
placeholder="Postal / ZIP code"/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Card Details via Poynt Collect -->
|
|
<hr class="my-4"/>
|
|
<h5 class="mb-3">Card Details</h5>
|
|
<p class="text-muted small mb-2" id="cardLoadingText">Loading secure payment form...</p>
|
|
<div id="card-element" style="min-height:120px;border:1px solid #dee2e6;border-radius:8px;padding:16px 14px;background:#fafafa;box-shadow:inset 0 1px 3px rgba(0,0,0,0.06);"/>
|
|
|
|
<hr class="my-4"/>
|
|
<div class="text-center">
|
|
<button id="btnSubmitCard" class="btn btn-primary btn-lg px-5">
|
|
<i class="fa fa-lock me-2"/> Authorize Card
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Poynt Collect JS SDK -->
|
|
<script src="https://cdn.poynt.net/collect.js"/>
|
|
<script>
|
|
(function() {
|
|
var cEl = document.querySelector('.container[data-poynt-business-id]');
|
|
var bizId = cEl ? cEl.getAttribute('data-poynt-business-id') : '';
|
|
var appId = cEl ? cEl.getAttribute('data-poynt-application-id') : '';
|
|
var cardLoadingText = document.getElementById('cardLoadingText');
|
|
var poyntCollect = null;
|
|
var poyntNonce = null;
|
|
|
|
function initPoyntCollect() {
|
|
if (typeof TokenizeJs === 'undefined') {
|
|
setTimeout(initPoyntCollect, 200);
|
|
return;
|
|
}
|
|
if (!bizId || !appId) {
|
|
if (cardLoadingText) cardLoadingText.textContent = 'Payment form configuration error. Please contact support.';
|
|
return;
|
|
}
|
|
poyntCollect = new TokenizeJs(bizId, appId);
|
|
poyntCollect.mount('card-element', document, {
|
|
iFrame: { width: '100%', height: '90px', border: 'none', borderRadius: '6px' },
|
|
displayComponents: { firstName: false, lastName: false, zipCode: false, submitButton: false, labels: false },
|
|
style: { theme: 'default' },
|
|
});
|
|
poyntCollect.on('nonce', function(event) { poyntNonce = event.data.nonce; });
|
|
poyntCollect.on('error', function(event) {
|
|
var rawErr = (event && event.data && event.data.error) ? event.data.error : null;
|
|
var msg;
|
|
if (typeof rawErr === 'string') {
|
|
msg = rawErr;
|
|
} else if (rawErr && typeof rawErr === 'object') {
|
|
msg = rawErr.message || rawErr.developerMessage || JSON.stringify(rawErr);
|
|
} else {
|
|
msg = 'Card validation failed. Please check your card details.';
|
|
}
|
|
showAlert(msg, 'danger');
|
|
});
|
|
poyntCollect.on('ready', function() {
|
|
if (cardLoadingText) cardLoadingText.style.display = 'none';
|
|
});
|
|
}
|
|
initPoyntCollect();
|
|
|
|
function showAlert(msg, type) {
|
|
var a = document.getElementById('alertBox');
|
|
a.className = 'alert alert-' + type;
|
|
a.textContent = msg;
|
|
a.classList.remove('d-none');
|
|
}
|
|
|
|
document.getElementById('btnSubmitCard').addEventListener('click', function() {
|
|
var errDiv = document.getElementById('alertBox');
|
|
errDiv.classList.add('d-none');
|
|
|
|
var name = document.getElementById('cardholderName').value.trim();
|
|
var postal = document.getElementById('billingPostalCode').value.trim();
|
|
|
|
if (!name) { showAlert('Cardholder name is required.', 'danger'); return; }
|
|
if (!postal) { showAlert('Billing postal/zip code is required.', 'danger'); return; }
|
|
if (!poyntCollect) { showAlert('Payment form is not ready.', 'danger'); return; }
|
|
|
|
poyntNonce = null;
|
|
poyntCollect.getNonce({ businessId: bizId });
|
|
|
|
var btn = this;
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<i class="fa fa-spinner fa-spin me-2"></i> Processing...';
|
|
|
|
var attempts = 0;
|
|
var waitForNonce = setInterval(function() {
|
|
attempts++;
|
|
if (poyntNonce) {
|
|
clearInterval(waitForNonce);
|
|
submitReauthorization(poyntNonce, btn);
|
|
} else if (attempts > 50) {
|
|
clearInterval(waitForNonce);
|
|
showAlert('Card authorization timed out. Please try again.', 'danger');
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i class="fa fa-lock me-2"></i> Authorize Card';
|
|
}
|
|
}, 200);
|
|
});
|
|
|
|
function submitReauthorization(nonce, btn) {
|
|
fetch(window.location.pathname + '/submit', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({
|
|
jsonrpc: '2.0',
|
|
method: 'call',
|
|
params: {
|
|
nonce: nonce,
|
|
cardholder_name: document.getElementById('cardholderName').value.trim(),
|
|
billing_address: document.getElementById('billingAddress').value.trim(),
|
|
billing_city: document.getElementById('billingCity').value.trim(),
|
|
billing_state: document.getElementById('billingState').value.trim(),
|
|
billing_postal_code: document.getElementById('billingPostalCode').value.trim(),
|
|
},
|
|
}),
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
var result = data.result || data;
|
|
if (result.success) {
|
|
showAlert('Card authorized successfully! You may close this page.', 'success');
|
|
btn.innerHTML = '<i class="fa fa-check me-2"></i> Card Authorized';
|
|
} else {
|
|
showAlert(result.error || 'Authorization failed.', 'danger');
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i class="fa fa-lock me-2"></i> Authorize Card';
|
|
}
|
|
})
|
|
.catch(function(err) {
|
|
showAlert('Network error. Please try again.', 'danger');
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i class="fa fa-lock me-2"></i> Authorize Card';
|
|
});
|
|
}
|
|
})();
|
|
</script>
|
|
</t>
|
|
</field>
|
|
</record>
|
|
|
|
<!-- Purchase Interest Success -->
|
|
<record id="purchase_interest_success_page" model="ir.ui.view">
|
|
<field name="name">Rental Purchase Interest</field>
|
|
<field name="type">qweb</field>
|
|
<field name="key">fusion_rental.purchase_interest_success_page</field>
|
|
<field name="arch" type="xml">
|
|
<t t-call="web.frontend_layout">
|
|
<div class="container py-5"><div class="row justify-content-center"><div class="col-md-8 col-lg-6"><div class="card shadow-sm">
|
|
<div class="card-header bg-success text-white"><h3 class="mb-0">Thank You for Your Interest!</h3></div>
|
|
<div class="card-body text-center">
|
|
<i class="fa fa-shopping-cart text-success" style="font-size:48px;"/>
|
|
<p class="lead mt-3">We've received your interest in purchasing your rental product from order <strong t-out="order.name">SO001</strong>.</p>
|
|
<p>A member of our team will contact you shortly to discuss the details and schedule delivery.</p>
|
|
</div>
|
|
</div></div></div></div>
|
|
</t>
|
|
</field>
|
|
</record>
|
|
|
|
</odoo>
|