import base64 import logging import time # pyrefly: ignore [missing-import] from odoo import fields, _ # pyrefly: ignore [missing-import] from odoo.exceptions import UserError _logger = logging.getLogger(__name__) class ReconciliationService: @classmethod def reconcile_transaction(cls, env, transaction, razorpay_payment_id=None, raw_gateway_response=None): """ Execute automatic reconciliation inside a single DB transaction savepoint. """ if transaction.status == 'success': _logger.info("Transaction %s is already reconciled. Skipping duplicate execution.", transaction.name) return transaction with env.cr.savepoint(): student = transaction.student_id now = fields.Datetime.now() today_str = fields.Date.today().strftime('%Y%m%d') receipt_num = f"REC-ONL-{today_str}-{transaction.id}" # 1. Create education.student.payment payment = env['education.student.payment'].sudo().create({ 'student_id': student.id, 'amount': transaction.amount, 'payment_method': 'online', 'receipt_number': receipt_num, 'reference_number': razorpay_payment_id or transaction.razorpay_order_id or transaction.name, 'payment_date': fields.Date.today(), 'status': 'confirmed', 'remarks': f"Online Fee Payment via Gateway ({transaction.provider.title()}). TXN Ref: {transaction.name}", 'company_id': transaction.company_id.id, }) # 2. Mark fee assignments as paid if transaction.fee_assignment_ids: transaction.fee_assignment_ids.sudo().write({ 'payment_status': 'paid', }) # 3. Post Credit Entry in education.student.ledger ledger_entry = env['education.student.ledger'].sudo().create({ 'student_id': student.id, 'date': fields.Date.today(), 'description': f"Online Payment Received ({receipt_num})", 'debit': 0.0, 'credit': transaction.amount, 'status': 'paid', 'company_id': transaction.company_id.id, }) # 4. Generate PDF Receipt pdf_content, _ = env['ir.actions.report'].sudo()._render_qweb_pdf( 'education_finance.action_report_receipt_pdf', [payment.id] ) # Create Attachment attachment = env['ir.attachment'].sudo().create({ 'name': f"Receipt-{receipt_num}.pdf", 'type': 'binary', 'datas': base64.b64encode(pdf_content), 'res_model': 'education.student.payment', 'res_id': payment.id, 'mimetype': 'application/pdf', }) # 5. Send Email Confirmation if enabled ICP = env['ir.config_parameter'].sudo() auto_email = ICP.get_param('education_portal.auto_send_payment_receipt_email', 'True') if auto_email and student.email: cls._send_email_receipt(env, student, payment, transaction, attachment) # 6. Update Payment Transaction Record transaction.sudo().write({ 'status': 'success', 'verified_on': now, 'receipt_number': receipt_num, 'payment_id': payment.id, 'razorpay_payment_id': razorpay_payment_id or transaction.razorpay_payment_id, 'gateway_response': raw_gateway_response or transaction.gateway_response, }) transaction.log_audit( f"Reconciliation Complete. Payment Record: #{payment.id} ({receipt_num}), " f"Ledger Entry: #{ledger_entry.id}, Dues Reconciled: {len(transaction.fee_assignment_ids)} items." ) _logger.info("Successfully reconciled transaction %s for student %s", transaction.name, student.display_name) return transaction @classmethod def _send_email_receipt(cls, env, student, payment, transaction, attachment): """Send Email Receipt with attached PDF to student""" try: subject = f"Payment Receipt Confirmation - {payment.receipt_number}" body_html = f"""
Dear {student.display_name},
Thank you for your payment. Your fee payment has been successfully processed and verified.
| Receipt Number: | {payment.receipt_number} |
| Transaction Reference: | {transaction.name} |
| Amount Paid: | ₹{payment.amount:,.2f} |
| Payment Date: | {payment.payment_date} |
| Remaining Outstanding: | ₹{student.outstanding_amount:,.2f} |
Please find your official payment receipt PDF attached to this email.
This is an automated notification from your Education ERP Portal.