from odoo import models, fields, api, _ from odoo.exceptions import ValidationError from datetime import datetime import base64 import xlrd class UploadPayments(models.Model): _name = "upload.payments" _description = "Upload Payments" _order = "create_date desc" payments_lines = fields.One2many('upload.payments.line', 'upload_payments_id', string='Upload Payments Lines', auto_join=True, copy=False) number = fields.Char('Number', copy=False) date_upload = fields.Date('Upload Date', copy=False, default=fields.Date.context_today) user_id = fields.Many2one('res.users', string='Created By', copy=False, default=lambda self: self.env.user.id) excel_file = fields.Binary('Excel File', attachment=True) filename = fields.Char('File Name') state = fields.Selection([('draft', 'Draft'), ('running', 'Running'), ('done', 'Done')], default='draft') @api.model def create(self, vals): vals['number'] = self.env['ir.sequence'].next_by_code('upload.payments') or '/' return super(UploadPayments, self).create(vals) def action_import_excel(self): self.ensure_one() if not self.excel_file: raise ValidationError(_("Please upload an Excel file first.")) try: file_content = base64.b64decode(self.excel_file) workbook = xlrd.open_workbook(file_contents=file_content) sheet = workbook.sheet_by_index(0) except: raise ValidationError(_("Invalid Excel file format.")) # Process Excel rows (skip header row if exists) line_vals_list = [] for row in range(1, sheet.nrows): try: no_invoice = str(sheet.cell(row, 0).value).strip() date_invoice = xlrd.xldate.xldate_as_datetime(sheet.cell(row, 1).value, workbook.datemode).date() line_vals = { 'no_invoice': no_invoice, 'date_invoice': date_invoice, } line_vals_list.append((0, 0, line_vals)) except: continue # Update current record with lines self.write({ 'payments_lines': line_vals_list, }) return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': _('Success'), 'message': _('Imported %s lines from Excel.') % len(line_vals_list), 'sticky': False, 'next': {'type': 'ir.actions.act_window_close'}, } } def action_create_payments(self): self.state = 'running' def queue_job(self, limit=100): payments = self.env['upload.payments'].search([ ('state', '=', 'running') ], limit=1, order='id asc') for payment in payments: payment.date_upload = fields.Datetime.now() lines = payment.payments_lines.filtered( lambda l: not l.payment_id and not l.is_queued )[:limit] existing_jobs = self.env['queue.job'].search([ ('model_name', '=', 'upload.payments.line'), ('method_name', '=', 'action_create_payments'), ('res_id', 'in', lines.ids), ('state', '!=', 'error'), ]) existing_res_ids = set(existing_jobs.mapped('res_id')) for line in lines: if line.id in existing_res_ids: continue self.env['queue.job'].create({ 'name': f'Upload Payments {line.no_invoice}', 'model_name': 'upload.payments.line', 'method_name': 'action_create_payments', 'res_id': line.id, }) line.is_queued = True self.env.cr.commit() class UploadPaymentsLine(models.Model): _name = "upload.payments.line" _description = "Upload Payments Line" _inherit = ['mail.thread'] upload_payments_id = fields.Many2one('upload.payments', string='Upload Payments') no_invoice = fields.Char('Invoice Number', required=True) date_invoice = fields.Date('Invoice Date', required=True) move_id = fields.Many2one('account.move', string='Invoice') payment_id = fields.Many2one('account.payment', string='Created Payment') message_error = fields.Text('Error Message') is_queued = fields.Boolean(default=False) def action_create_payments(self): self.ensure_one() try: invoice = self.env['account.move'].search([ ('invoice_marketplace', '=', self.no_invoice), ('state', '=', 'posted'), ('payment_state', '!=', 'paid'), ], limit=1) if not invoice: raise ValidationError('Invoice tidak ditemukan / sudah lunas') payment = invoice._register_payment_automatically(self.date_invoice) self.payment_id = payment.id self.message_error = 'DONE' except Exception as e: self.message_error = str(e) finally: self._check_parent_done() def _check_parent_done(self): upload = self.upload_payments_id if not upload: return unfinished = upload.payments_lines.filtered( lambda l: not l.is_queued ) if not unfinished: upload.state = 'done'