from odoo import models, fields, api, _ from datetime import datetime import base64 import xlrd from odoo.exceptions import ValidationError, UserError import requests import json import hmac from hashlib import sha256 class UploadBills(models.Model): _name = "upload.bills" _description = "Upload Bills" _order = "create_date desc" _rec_name = "number" bills_lines = fields.One2many('upload.bills.line', 'upload_bills_id', string='Lines', copy=False, auto_join=True) number = fields.Char('Number', copy=False) date_upload = fields.Datetime('Upload Date', copy=False) user_id = fields.Many2one('res.users', 'Created By', default=lambda self: self.env.user.id) excel_file = fields.Binary('Excel File', attachment=True) filename = fields.Char('File Name') @api.model def create(self, vals): vals['number'] = self.env['ir.sequence'].next_by_code('upload.bills') or '/' return super().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 Exception: raise ValidationError(_("Invalid Excel file format.")) if sheet.ncols < 3: raise ValidationError(_("Excel format tidak valid. Minimal ada 3 kolom: No BU IN, Tanggal, dan Faktur Pajak.")) rows_data = [] for row_idx in range(sheet.nrows): try: no_bu = str(sheet.cell(row_idx, 0).value).strip() tanggal_raw = sheet.cell(row_idx, 1).value faktur_pajak = str(sheet.cell(row_idx, 2).value).strip() if isinstance(tanggal_raw, float): tanggal = datetime(*xlrd.xldate_as_tuple(tanggal_raw, workbook.datemode)).date() else: tanggal = datetime.strptime(tanggal_raw, '%Y-%m-%d').date() rows_data.append({ 'row_num': row_idx + 1, 'no_bu': no_bu, 'date': tanggal, 'faktur_pajak': faktur_pajak, }) except Exception: continue faktur_list = [row['faktur_pajak'] for row in rows_data] existing_faktur = set() no_bu_list = [row['no_bu'] for row in rows_data] existing_no_bu = set() chunk_size = 500 for i in range(0, len(faktur_list), chunk_size): chunk_faktur = faktur_list[i:i + chunk_size] chunk_no_bu = no_bu_list[i:i + chunk_size] existing_lines = self.env['upload.bills.line'].search([ '|', ('faktur_pajak', 'in', chunk_faktur), ('no_bu', 'in', chunk_no_bu) ]) existing_faktur.update(existing_lines.mapped('faktur_pajak')) existing_no_bu.update(existing_lines.mapped('no_bu')) duplicate_rows = [] for row in rows_data: if row['faktur_pajak'] in existing_faktur or row['no_bu'] in existing_no_bu: duplicate_rows.append(str(row['row_num'])) if duplicate_rows: raise ValidationError(_("Data duplikat ditemukan di baris: %s\nPeriksa 'No BU IN' atau 'Faktur Pajak' yang sudah pernah diupload.") % ", ".join(duplicate_rows)) line_vals_list = [] for row in rows_data: line_vals = { 'no_bu': row['no_bu'], 'date': row['date'], 'faktur_pajak': row['faktur_pajak'], 'upload_bills_id': self.id, } line_vals_list.append((0, 0, line_vals)) self.bills_lines.unlink() self.write({'bills_lines': line_vals_list}) return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': _('Success'), 'message': _('Berhasil mengimpor %s baris dari Excel.') % len(line_vals_list), 'sticky': False, 'next': {'type': 'ir.actions.act_window_close'}, } } def _show_notification(self, message): return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': _('Success'), 'message': message, 'sticky': False, } } def action_create_bills(self): all_invoice_ids = [] for line in self.bills_lines: bu_in = self.env['stock.picking'].search([ ('name', '=', line.no_bu), ('picking_type_code', '=', 'incoming') ], limit=1) if not bu_in: raise UserError(f"BU IN '{line.no_bu}' tidak ditemukan.") # Panggil pembuatan invoice created_invoices = bu_in.action_create_invoice_from_mr() # Sinkron faktur dan tanggal ke account.move if isinstance(created_invoices, dict) and 'res_id' in created_invoices: # Satu invoice invoice = self.env['account.move'].browse(created_invoices['res_id']) invoice.write({ 'faktur_pajak': line.faktur_pajak, 'invoice_date': line.date }) invoice.action_post() all_invoice_ids.append(invoice.id) elif isinstance(created_invoices, dict) and 'domain' in created_invoices: # Banyak invoice invoice_ids = created_invoices.get('domain', [])[0][2] or [] invoices = self.env['account.move'].browse(invoice_ids) invoices.write({ 'faktur_pajak': line.faktur_pajak, 'invoice_date': line.date }) invoices.action_post() all_invoice_ids.extend(invoices.ids) else: raise UserError("Gagal menemukan invoice yang baru dibuat.") # Tampilkan invoice(s) ke user if not all_invoice_ids: return {'type': 'ir.actions.act_window_close'} action = self.env.ref('account.action_move_in_invoice_type').read()[0] if len(all_invoice_ids) == 1: action.update({ 'view_mode': 'tree,form', 'res_id': all_invoice_ids, }) else: action.update({ 'domain': [('id', 'in', all_invoice_ids)], 'view_mode': 'tree,form', }) return action class UploadBillsLine(models.Model): _name = "upload.bills.line" _description = "Upload Bills Line" _inherit = ['mail.thread'] upload_bills_id = fields.Many2one('upload.bills', string='Upload') no_bu = fields.Char('No BU IN') date = fields.Date('Date') faktur_pajak = fields.Char('Faktur Pajak')