from cmath import e 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 _logger = logging.getLogger(__name__) class AccountMove(models.Model): _inherit = 'account.move' invoice_marketplace = fields.Char('Invoice Marketplace') address = fields.Char('Address') sale_id = fields.Many2one('sale.order', string='Sale Order') picking_id = fields.Many2one('stock.picking', string='Picking') transaction_type = fields.Selection( [('digunggung', 'Digunggung'), ('difaktur', 'Faktur Pajak')], string='Transaction Type' ) purchase_vendor_bill_ids = fields.Many2many( 'purchase.bill.union', string='Auto-complete', store=False, readonly=True, states={'draft': [('readonly', False)]}, help="Auto-complete from multiple past bills / purchase orders.", ) faktur_pajak = fields.Char('Faktur Pajak') count_payment = fields.Integer('Count Payment', compute='_compute_count_payment') reklas_misc_id = fields.Many2one('account.move', string='Journal Entries Reklas') purchase_order_id = fields.Many2one('purchase.order', string='Purchase Order') bill_id = fields.Many2one('account.move', string='Vendor Bill', domain=[('move_type', '=', 'in_invoice')], help='Bill asal dari proses reklas ini') count_reverse = fields.Integer('Count Reverse', compute='_compute_count_reverse') uangmuka = fields.Boolean('Uang Muka?') reklas = fields.Boolean('Reklas?') reklas_used = fields.Boolean('Reklas Used?', compute='_compute_reklas_used', store=True) reklas_used_by = fields.Many2one('account.move', string='Reklas Used By', compute='_compute_reklas_used', store=True) need_refund = fields.Boolean( string="Need Refund", compute="_compute_need_refund", help="Flag otomatis kalau invoice sudah paid dan picking terkait di-return." ) total_discount = fields.Monetary( string='Total Discount', related='sale_id.total_discount', currency_field='currency_id', ) # purchase_item_ids= fields.Many2many( # 'purchase.order.line', # string='Auto Complete (PO Item)', # ) partner_compute = fields.Char( string='Partner Compute', compute='_compute_partner_compute', ) soo_number = fields.Char('SOO Number') # def _check_lock_date(self): # _logger.warning("CHECK LOCK DATE TRIGGERED BY %s", self.env.user.name) # moves_to_check = self.filtered( # lambda m: self.env.user not in m.company_id.excluded_user_ids # ) # _logger.warning("MOVES TO CHECK: %s", moves_to_check) # if moves_to_check: # return super(AccountMove, moves_to_check)._check_lock_date() # _logger.warning("LOCK BYPASSED") # return True @api.depends('line_ids.partner_id') def _compute_partner_compute(self): for move in self: partners = move.mapped('line_ids.partner_id.name') move.partner_compute = ', '.join(partners) def action_open_po_item_wizard(self): self.ensure_one() return { 'type': 'ir.actions.act_window', 'name': 'Select PO Items', 'res_model': 'purchase.order.line.wizard', 'view_mode': 'form', 'target': 'new', 'context': { 'default_move_id': self.id, 'default_partner_id': self.partner_id.id, } } approval_refund = fields.Selection( [('approved', 'Approved'), ('rejected', 'Rejected'), ('pending', 'Pending')], string='Approval Refund', copy=False, tracking=True ) reklas_id = fields.Many2one('account.move', string='Nomor CAB', domain="[('move_type','=','entry')]", copy=False) def reject_refund(self): if self.move_type == 'entry' and self.sale_id and self.ref.startswith('UANG MUKA PENJUALAN') and self.env.user.id in [9, 10, 15]: self.approval_refund = 'rejected' else: raise UserError(_('Anda tidak memiliki akses untuk melakukan reject Uang Muka Penjualan!')) # def approve_refund(self): # if self.move_type == 'entry' and self.sale_id and self.ref.startswith('UANG MUKA PENJUALAN') and self.env.user.id in [9, 10, 15]: # self.approval_refund = 'approved' # else: # raise UserError(_('Anda tidak memiliki akses untuk melakukan approve Uang Muka Penjualan!')) def pending_refund(self): self.approval_refund = 'pending' def queue_job_cancel_bill(self): QueueJob = self.env['queue.job'] for move in self: exists = QueueJob.search([ ('res_id', '=', move.id), ('method_name', 'in', ['button_draft', 'button_cancel']) ], limit=1) if exists: continue QueueJob.create([ { 'name': f'Reset To Draft {move.name}', 'model_name': 'account.move', 'method_name': 'button_draft', 'res_id': move.id, }, { 'name': f'Cancel Bills {move.name}', 'model_name': 'account.move', 'method_name': 'button_cancel', 'res_id': move.id, } ]) def button_draft(self): if not self.env.context.get('active_model') == 'stock.picking': if self.env.user.id not in [24, 13, 10, 2, 9, 15, 8, 11]: raise UserError("Hanya Finance yang bisa ubah Draft") if self.state == 'posted' and self.payment_state == 'paid': raise UserError('Invoice Sudah Lunas, untuk edit invoice hapus pembayaran terlebih dahulu') res = super(AccountMove, self).button_draft() return res def button_cancel(self): if self.env.user.id not in [24, 13, 10, 2, 9, 15, 8, 11]: raise UserError('Hanya Accounting yang bisa Cancel') res = super(AccountMove, self).button_cancel() return res def _compute_need_refund(self): for move in self: flag = False if move.move_type == 'out_invoice' and move.payment_state == 'paid' and move.invoice_origin: refund_exists = bool(self.env['account.move'].search([('reversed_entry_id', '=', move.id), ('payment_state', '=', 'paid')])) if not refund_exists: sale_orders = self.env['sale.order'].search([('name', '=', move.invoice_origin)]) if sale_orders: pickings = sale_orders.picking_ids.filtered(lambda p: p.state == 'done' and p.is_return) if pickings: flag = True move.need_refund = flag def export_faktur_to_xml(self): valid_invoices = self coretax_faktur = self.env['coretax.faktur'].create({}) response = coretax_faktur.export_to_download( invoices=valid_invoices ) valid_invoices.write({ 'is_efaktur_exported': True, 'date_efaktur_exported': datetime.utcnow(), }) return response @api.depends('line_ids.reconciled', 'line_ids.matching_number') def _compute_reklas_used(self): for move in self: move.reklas_used = False move.reklas_used_by = None if move.move_type != 'entry': continue matching_numbers = move.line_ids.filtered(lambda l: l.reconciled and l.matching_number).mapped('matching_number') if not matching_numbers: continue invoice_lines = self.env['account.move.line'].search([ ('reconciled', '=', True), ('matching_number', 'in', matching_numbers), ('move_id.move_type', '=', 'out_invoice'), ], limit=1) if invoice_lines: move.reklas_used = True move.reklas_used_by = invoice_lines.move_id def _compute_count_reverse(self): for move in self: accountMove = self.env['account.move'] reverse = accountMove.search([]).filtered( lambda p: move.id in p.reversed_entry_id.ids ) move.count_reverse = len(reverse) def action_view_related_reverse(self): self.ensure_one() accountMove = self.env['account.move'] reverse = accountMove.search([]).filtered( lambda p: self.id in p.reversed_entry_id.ids ) reverses = reverse return { 'name': 'Refund', 'type': 'ir.actions.act_window', 'res_model': 'account.move', 'view_mode': 'tree,form', 'target': 'current', 'domain': [('id', 'in', list(reverses.ids))], } def action_reverse(self): action = self.env["ir.actions.actions"]._for_xml_id("account.action_view_account_move_reversal") if self.is_invoice(): action['name'] = _('Credit Note') if len(self) == 1: action['context'] = { 'default_journal_id': self.journal_id.id, } return action def open_form_multi_create_reklas_penjualan(self): action = self.env['ir.actions.act_window']._for_xml_id('fixco_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_count_payment(self): for move in self: accountPayment = self.env['account.payment'] payment = accountPayment.search([]).filtered( lambda p: move.id in p.reconciled_bill_ids.ids ) move.count_payment = len(payment) def action_view_related_payment(self): self.ensure_one() accountPayment = self.env['account.payment'] payment = accountPayment.search([]).filtered( lambda p: self.id in p.reconciled_bill_ids.ids ) payments = payment return { 'name': 'Payments', 'type': 'ir.actions.act_window', 'res_model': 'account.payment', 'view_mode': 'tree,form', 'target': 'current', 'domain': [('id', 'in', list(payments.ids))], } def action_post(self): _logger.warning("CTX: %s", self.env.context) if not self.env.context.get('active_model') == 'stock.picking': if self.env.user.id not in [24, 13, 10, 2, 9, 15, 8, 22, 11]: raise UserError('Hanya Accounting yang bisa Post') for entry in self: # ===== Vendor Bill ===== if entry.move_type == 'in_invoice': po = entry.invoice_line_ids.mapped('purchase_order_id') soo_list = list(set(filter(None, po.mapped('soo_number')))) entry.soo_number = ', '.join(soo_list) # ===== Customer Invoice / Credit Note ===== if entry.move_type in ['out_invoice', 'out_refund']: search_inv = entry.search([ ('move_type', '=', 'out_invoice'), ('id', '=', entry.id), ('invoice_marketplace', '=', entry.sale_id.invoice_mp) ], limit=1).invoice_marketplace entry.invoice_marketplace = search_inv if self.env.context.get('force_picking_date'): if entry.picking_id and entry.picking_id.date_done: picking_date = entry.picking_id.date_done.date() entry.invoice_date = picking_date entry.date = picking_date res = super(AccountMove, self).action_post() return res @api.onchange('purchase_vendor_bill_ids', 'purchase_id') def _onchange_purchase_auto_complete(self): """ Load from either multiple old purchase orders or vendor bills. """ vendor_bills = self.purchase_vendor_bill_ids.mapped('vendor_bill_id') purchase_orders = self.purchase_vendor_bill_ids.mapped('purchase_order_id') for bill in vendor_bills: self.invoice_vendor_bill_id = bill self._onchange_invoice_vendor_bill() for po in purchase_orders: self.purchase_id = po invoice_vals = po.with_company(po.company_id)._prepare_invoice() invoice_vals['currency_id'] = self.line_ids and self.currency_id or invoice_vals.get('currency_id') invoice_vals.pop('ref', None) self.update(invoice_vals) po_lines = po.order_line.filtered(lambda l: l.qty_received != l.qty_invoiced and l.qty_invoiced <= l.qty_received) - self.line_ids.mapped('purchase_line_id') new_lines = self.env['account.move.line'] sequence = max(self.line_ids.mapped('sequence')) + 1 if self.line_ids else 10 for line in po_lines.filtered(lambda l: not l.display_type): line_vals = line._prepare_account_move_line(self) line_vals.update({'sequence': sequence}) new_line = new_lines.new(line_vals) sequence += 1 new_line.account_id = new_line._get_computed_account() new_line._onchange_price_subtotal() new_lines += new_line new_lines._onchange_mark_recompute_taxes() # Compute invoice_origin origins = set(self.line_ids.mapped('purchase_line_id.order_id.name')) self.invoice_origin = ', '.join(origins) # Compute ref refs = self._get_invoice_reference() self.ref = ', '.join(refs) # Compute payment_reference if len(refs) == 1: self.payment_reference = refs[0] self.purchase_id = False self.purchase_vendor_bill_ids = [(5, 0, 0)] # clear after use self._onchange_currency() class PurchaseOrderLineWizard(models.TransientModel): _name = 'purchase.order.line.wizard' _description = 'PO Item Selector Wizard' move_id = fields.Many2one('account.move', required=True) partner_id = fields.Many2one('res.partner') line_ids = fields.Many2many( 'purchase.order.line', string='PO Items', ) def action_add_lines(self): for item in self.line_ids: if item.bill_remain <= 0: raise UserError(f"Item: {item.name} from PO: {item.order_id.name} has already been fully invoiced.") self.ensure_one() move = self.move_id if move.state != 'draft': raise UserError('Invoice harus draft') if not move.partner_id: raise UserError("Vendor belum dipilih") if not move.journal_id: raise UserError("Journal belum dipilih") new_lines = self.env['account.move.line'] for po_line in self.line_ids.filtered(lambda l: not l.display_type): vals = po_line._prepare_account_move_line(move) # override qty sisa vals['quantity'] = po_line.qty_received - po_line.qty_invoiced line = new_lines.new(vals) line.account_id = line._get_computed_account() line._onchange_price_subtotal() new_lines += line move.invoice_line_ids += new_lines move._recompute_dynamic_lines(recompute_all_taxes=True) return {'type': 'ir.actions.act_window_close'}