from odoo import models, api, fields from odoo.exceptions import AccessError, UserError, ValidationError from datetime import timedelta, date, datetime from pytz import timezone, utc import logging import base64 import PyPDF2 import os import re from terbilang import Terbilang _logger = logging.getLogger(__name__) class AccountMove(models.Model): _inherit = 'account.move' invoice_day_to_due = fields.Integer(string="Day to Due", compute="_compute_invoice_day_to_due") bill_day_to_due = fields.Date(string="Day to Due", compute="_compute_bill_day_to_due") date_send_fp = fields.Datetime(string="Tanggal Kirim Faktur Pajak") last_log_fp = fields.Char(string="Log Terakhir Faktur Pajak") # use for industry business date_kirim_tukar_faktur = fields.Date(string='Kirim Faktur') resi_tukar_faktur = fields.Char(string='Resi Faktur') date_terima_tukar_faktur = fields.Date(string='Terima Faktur') payment_schedule = fields.Date(string='Jadwal Pembayaran') shipper_faktur_id = fields.Many2one('delivery.carrier', string='Shipper Faktur') due_extension = fields.Integer(string='Due Extension', default=0) new_due_date = fields.Date(string='New Due') counter = fields.Integer(string="Counter", default=0) cost_centre_id = fields.Many2one('cost.centre', string='Cost Centre') analytic_account_ids = fields.Many2many('account.analytic.account', string='Analytic Account') due_line = fields.One2many('due.extension.line', 'invoice_id', compute='_compute_due_line', string='Due Extension Lines') no_faktur_pajak = fields.Char(string='No Faktur Pajak') date_completed = fields.Datetime(string='Date Completed') mark_upload_efaktur = fields.Selection([ ('belum_upload', 'Belum Upload FP'), ('sudah_upload', 'Sudah Upload FP'), ], 'Mark Upload Faktur', compute='_compute_mark_upload_efaktur', default='belum_upload') sale_id = fields.Many2one('sale.order', string='Sale Order') reklas_id = fields.Many2one('account.move', string='Nomor CAB', domain="[('partner_id', '=', partner_id)]") new_invoice_day_to_due = fields.Integer(string="New Day Due", compute="_compute_invoice_day_to_due") date_efaktur_upload = fields.Datetime(string='eFaktur Upload Date', tracking=True) real_invoice_id = fields.Many2one( 'res.partner', string='Delivery Invoice Address', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)], 'sale': [('readonly', False)]}, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", help="Dipakai untuk alamat tempel") address_invoice = fields.Char(related='real_invoice_id.street', string='Invoice Address', readonly=True) bills_efaktur_exporter = fields.Many2one('res.users', string='Efaktur Exporter') bills_date_efaktur = fields.Datetime(string="eFaktur Exported Date", required=False) bills_efaktur_document = fields.Binary(string="eFaktur", required=False) bills_invoice_exporter =fields.Many2one('res.users', string='Invoice Exporter') bills_date_invoice = fields.Datetime(string="Invoice Exported Date", required=False) bills_invoice_document = fields.Binary(string="Invoice", required=False) is_invoice_uploaded = fields.Boolean(string="Is Invoice Uploaded", default=False) is_efaktur_uploaded = fields.Boolean(string="Is eFaktur Uploaded", default=False) already_paid = fields.Boolean(string="Sudah Dibayar?", default=False) delivery_amt_text = fields.Char(string="Delivery Amt Terbilang", compute='compute_delivery_amt_text') so_shipping_paid_by = fields.Char(string="SO Shipping Paid By", compute='compute_so_shipping_paid_by') so_shipping_covered_by = fields.Char(string="SO Shipping Covered By", compute='compute_so_shipping_paid_by') so_delivery_amt = fields.Char(string="SO Delivery Amount", compute='compute_so_shipping_paid_by') flag_delivery_amt = fields.Boolean(string="Flag Delivery Amount", compute='compute_flag_delivery_amt') nomor_kwitansi = fields.Char(string="Nomor Kwitansi") other_subtotal = fields.Float(string="Other Subtotal", compute='compute_other_subtotal') other_taxes = fields.Float(string="Other Taxes", compute='compute_other_taxes') is_hr = fields.Boolean(string="Is HR?", default=False) purchase_order_id = fields.Many2one('purchase.order', string='Purchase Order') def _update_line_name_from_ref(self): """Update all account.move.line name fields with ref from account.move""" for move in self: if move.move_type == 'entry' and move.ref and move.line_ids: for line in move.line_ids: line.name = move.ref def compute_other_taxes(self): for rec in self: rec.other_taxes = round(rec.other_subtotal * 0.12, 2) def compute_other_subtotal(self): for rec in self: rec.other_subtotal = round(rec.amount_untaxed * (11 / 12)) @api.model def generate_attachment(self, record): # Fetch the binary field file_content = record.efaktur_document file_name = "efaktur_document_{}.pdf".format(record.id) # Adjust the file extension if necessary attachment = self.env['ir.attachment'].create({ 'name': file_name, 'type': 'binary', 'datas': file_content, 'res_model': record._name, 'res_id': record.id, }) return attachment @api.constrains('efaktur_document') def send_scheduled_email(self): # Get the records for which emails need to be sent records = self.search([('id', 'in', self.ids)]) template = self.env.ref('indoteknik_custom.mail_template_efaktur_document') for record in records: if record.invoice_payment_term_id.id == 26: attachment = self.generate_attachment(record) email_values = { 'attachment_ids': [(4, attachment.id)] } template.send_mail(record.id, email_values=email_values, force_send=True) @api.model def create(self, vals): vals['nomor_kwitansi'] = self.env['ir.sequence'].next_by_code('nomor.kwitansi') or '0' result = super(AccountMove, self).create(vals) # result._update_line_name_from_ref() return result def compute_so_shipping_paid_by(self): for record in self: record.so_shipping_paid_by = record.sale_id.shipping_paid_by record.so_shipping_covered_by = record.sale_id.shipping_cost_covered record.so_delivery_amt = record.sale_id.delivery_amt def compute_flag_delivery_amt(self): for record in self: if record.sale_id.delivery_amt > 0: record.flag_delivery_amt = True else: record.flag_delivery_amt = False def compute_delivery_amt_text(self): tb = Terbilang() for record in self: res = '' try: if record.sale_id.delivery_amt > 0: tb.parse(int(record.sale_id.delivery_amt)) res = tb.getresult().title() record.delivery_amt_text = res + ' Rupiah' except: record.delivery_amt_text = res @api.constrains('bills_efaktur_document') def _constrains_efaktur_document(self): for move in self: current_time = datetime.utcnow() move.bills_date_efaktur = current_time move.bills_efaktur_exporter = self.env.user.id move.is_efaktur_uploaded = True @api.constrains('bills_invoice_document') def _constrains_invoice_efaktur(self): for move in self: current_time = datetime.utcnow() move.bills_date_invoice = current_time move.bills_invoice_exporter = self.env.user.id move.is_invoice_uploaded = True @api.constrains('partner_id') def _constrains_real_invoice(self): for move in self: move.real_invoice_id = move.sale_id.real_invoice_id @api.constrains('efaktur_document') def _constrains_date_efaktur(self): for move in self: current_time = datetime.utcnow() move.date_efaktur_upload = current_time def open_form_multi_create_reklas_penjualan(self): action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_view_invoice_reklas_penjualan') invoice = self.env['invoice.reklas.penjualan'].create([{ 'name': '-', }]) for move in self: sale_id = move.sale_id.id self.env['invoice.reklas.penjualan.line'].create([{ 'invoice_reklas_id': invoice.id, 'name': move.name, 'partner_id': move.partner_id.id, 'sale_id': move.sale_id.id, 'amount_untaxed_signed': move.amount_untaxed_signed, 'amount_total_signed': move.amount_total_signed, }]) action['res_id'] = invoice.id return action def _compute_mark_upload_efaktur(self): for move in self: if move.efaktur_document: move.mark_upload_efaktur = 'sudah_upload' else: move.mark_upload_efaktur = 'belum_upload' def _compute_due_line(self): for invoice in self: invoice.due_line = self.env['due.extension.line'].search([ ('invoice_id', '=', invoice.id), ('due_id.approval_status', '=', 'approved') ]) def unlink(self): res = super(AccountMove, self).unlink() for rec in self: if rec.state == 'posted': raise UserError('Data Hanya Bisa Di Cancel') return res def button_cancel(self): res = super(AccountMove, self).button_cancel() if self.move_type == 'entry': po = self.env['purchase.order'].search([ ('move_id', 'in', [self.id]) ]) for order in po: if order: order.is_create_uangmuka = False if self.id and not self.env.user.is_accounting: raise UserError('Hanya Accounting yang bisa Cancel') return res def button_draft(self): res = super(AccountMove, self).button_draft() if not self.env.user.is_accounting: raise UserError('Hanya Accounting yang bisa Reset to Draft') for rec in self.line_ids: if rec.write_date != rec.create_date: if rec.statement_line_id and not rec.statement_line_id.statement_id.is_edit and rec.statement_line_id.statement_id.state == 'confirm': raise UserError('Bank Statement di Lock, Minta admin reconcile untuk unlock') return res def action_post(self): if self._name != 'account.move': return super(AccountMove, self).action_post() # validation cant qty invoice greater than qty order if self.move_type == 'out_invoice': query = ["&",("name","=",self.invoice_origin),"|",("state","=","sale"),("state","=","done")] sale_order = self.env['sale.order'].search(query, limit=1) sum_qty_invoice = sum_qty_order = 0 for line in sale_order.order_line: sum_qty_invoice += line.qty_invoiced sum_qty_order += line.product_uom_qty if sum_qty_invoice > sum_qty_order: raise UserError('Error Qty Invoice akan lebih besar dari Qty Order jika lanjut Posting') elif self.move_type == 'in_invoice': query = ["&",("name","=",self.invoice_origin),"|",("state","=","purchase"),("state","=","done")] purchase_order = self.env['purchase.order'].search(query, limit=1) sum_qty_invoice = sum_qty_order = 0 for line in purchase_order.order_line: sum_qty_invoice += line.qty_invoiced sum_qty_order += line.product_qty if sum_qty_invoice > sum_qty_order: raise UserError('Error Qty Invoice akan lebih besar dari Qty Order jika lanjut Posting') res = super(AccountMove, self).action_post() # if not self.env.user.is_accounting: # raise UserError('Hanya Accounting yang bisa Posting') # if self._name == 'account.move': for entry in self: entry.date_completed = datetime.utcnow() for line in entry.line_ids: line.date_maturity = entry.date return res def button_draft(self): res = super(AccountMove, self).button_draft() if not self.env.user.is_accounting: raise UserError("Hanya Finence yang bisa ubah data") return res def _compute_invoice_day_to_due(self): for invoice in self: invoice_day_to_due = 0 new_invoice_day_to_due = 0 if invoice.payment_state not in ['paid', 'in_payment', 'reversed'] and invoice.invoice_date_due: invoice_day_to_due = invoice.invoice_date_due - date.today() new_invoice_day_to_due = invoice.invoice_date_due - date.today() if invoice.new_due_date: invoice_day_to_due = invoice.new_due_date - date.today() invoice_day_to_due = invoice_day_to_due.days new_invoice_day_to_due = new_invoice_day_to_due.days invoice.invoice_day_to_due = invoice_day_to_due invoice.new_invoice_day_to_due = new_invoice_day_to_due def _compute_bill_day_to_due(self): for rec in self: rec.bill_day_to_due = rec.payment_schedule or rec.invoice_date_due @api.onchange('date_kirim_tukar_faktur') def change_date_kirim_tukar_faktur(self): for invoice in self: if not invoice.date_kirim_tukar_faktur: return tukar_date = invoice.date_kirim_tukar_faktur term = invoice.invoice_payment_term_id add_days = 0 for line in term.line_ids: add_days += line.days due_date = tukar_date + timedelta(days=add_days) invoice.invoice_date_due = due_date @api.constrains('date_terima_tukar_faktur') def change_date_terima_tukar_faktur(self): for invoice in self: if not invoice.date_terima_tukar_faktur: return tukar_date = invoice.date_terima_tukar_faktur term = invoice.invoice_payment_term_id add_days = 0 for line in term.line_ids: add_days += line.days due_date = tukar_date + timedelta(days=add_days) invoice.invoice_date_due = due_date def open_form_multi_update(self): action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_account_move_multi_update') action['context'] = { 'move_ids': [x.id for x in self] } return action def open_form_multi_update_bills(self): action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_account_move_multi_update_bills') action['context'] = { 'move_ids': [x.id for x in self] } return action @api.constrains('efaktur_id', 'ref', 'date', 'journal_id', 'name') def constrains_edit(self): for rec in self.line_ids: if rec.statement_line_id and not rec.statement_line_id.statement_id.is_edit and rec.statement_line_id.statement_id.state == 'confirm': raise UserError('Bank Statement di Lock, Minta admin reconcile untuk unlock') # def write(self, vals): # res = super(AccountMove, self).write(vals) # for rec in self.line_ids: # if rec.write_date != rec.create_date: # if rec.statement_line_id and not rec.statement_line_id.statement_id.is_edit and rec.statement_line_id.statement_id.state == 'confirm': # raise UserError('Bank Statement di Lock, Minta admin reconcile untuk unlock') # return res def validate_faktur_for_export(self): invoices = self.filtered(lambda inv: not inv.is_efaktur_exported and inv.state == 'posted' and inv.move_type == 'out_invoice') invalid_invoices = self - invoices if invalid_invoices: invalid_ids = ", ".join(str(inv.id) for inv in invalid_invoices) raise UserError(( "Faktur dengan ID berikut tidak valid untuk diekspor: {}.\n" "Pastikan faktur dalam status 'posted', belum diekspor, dan merupakan 'out_invoice'.".format(invalid_ids) )) return invoices def export_faktur_to_xml(self): valid_invoices = self # Panggil model coretax.faktur untuk menghasilkan XML coretax_faktur = self.env['coretax.faktur'].create({}) response = coretax_faktur.export_to_download(invoices=valid_invoices) current_time = datetime.utcnow() # Tandai faktur sebagai sudah diekspor valid_invoices.write({ 'is_efaktur_exported': True, 'date_efaktur_exported': current_time, # Set tanggal ekspor }) return response