class KsGlobalDiscountInvoice(models.Model): # _inherit = "account.invoice" """ changing the model to account.move """ _inherit = "account.move" ks_global_discount_type = fields.Selection( [('percent', 'Percentage'), ('amount', 'Amount')], string='Universal Discount Type', readonly=True, states={ 'draft': [('readonly', False)], 'sent': [('readonly', False)] }, default='percent') ks_global_discount_rate = fields.Float('Universal Discount', readonly=True, states={ 'draft': [('readonly', False)], 'sent': [('readonly', False)] }) ks_amount_discount = fields.Monetary(string='Universal Discount', readonly=True, compute='_compute_amount', store=True, track_visibility='always') ks_enable_discount = fields.Boolean(compute='ks_verify_discount') ks_sales_discount_account = fields.Text(compute='ks_verify_discount') ks_purchase_discount_account = fields.Text(compute='ks_verify_discount') # @api.multi @api.depends('name') def ks_verify_discount(self): for rec in self: rec.ks_enable_discount = rec.env['ir.config_parameter'].sudo( ).get_param('ks_enable_discount') rec.ks_sales_discount_account = rec.env[ 'ir.config_parameter'].sudo().get_param( 'ks_sales_discount_account') rec.ks_purchase_discount_account = rec.env[ 'ir.config_parameter'].sudo().get_param( 'ks_purchase_discount_account') # @api.multi # 1. tax_line_ids is replaced with tax_line_id. 2. api.mulit is also removed. # @api.depends('invoice_line_ids.price_subtotal', 'tax_line_ids.amount', 'tax_line_ids.amount_rounding', # 'currency_id', 'company_id', 'date_invoice', 'type', 'ks_global_discount_type', # 'ks_global_discount_rate') @api.depends('line_ids.debit', 'line_ids.credit', 'line_ids.currency_id', 'line_ids.amount_currency', 'line_ids.amount_residual', 'line_ids.amount_residual_currency', 'line_ids.payment_id.state', 'ks_global_discount_type', 'ks_global_discount_rate') def _compute_amount(self): for rec in self: res = super(KsGlobalDiscountInvoice, rec)._compute_amount() if not ('ks_global_tax_rate' in rec): rec.ks_calculate_discount() sign = rec.type in ['in_refund', 'out_refund'] and -1 or 1 rec.amount_total_company_signed = rec.amount_total * sign rec.amount_total_signed = rec.amount_total * sign return res # @api.multi def ks_calculate_discount(self): for rec in self: if rec.ks_global_discount_type == "amount": rec.ks_amount_discount = rec.ks_global_discount_rate if rec.amount_untaxed > 0 else 0 elif rec.ks_global_discount_type == "percent": if rec.ks_global_discount_rate != 0.0: rec.ks_amount_discount = ( rec.amount_untaxed + rec.amount_tax) * rec.ks_global_discount_rate / 100 else: rec.ks_amount_discount = 0 elif not rec.ks_global_discount_type: rec.ks_global_discount_rate = 0 rec.ks_amount_discount = 0 rec.amount_total = rec.amount_tax + rec.amount_untaxed - rec.ks_amount_discount rec.ks_update_universal_discount() @api.constrains('ks_global_discount_rate') def ks_check_discount_value(self): if self.ks_global_discount_type == "percent": if self.ks_global_discount_rate > 100 or self.ks_global_discount_rate < 0: raise ValidationError( 'You cannot enter percentage value greater than 100.') else: if self.ks_global_discount_rate < 0 or self.amount_untaxed < 0: raise ValidationError( 'You cannot enter discount amount greater than actual cost or value lower than 0.' ) # @api.onchange('purchase_id') # def ks_get_purchase_order_discount(self): # self.ks_global_discount_rate = self.purchase_id.ks_global_discount_rate # self.ks_global_discount_type = self.purchase_id.ks_global_discount_type # @api.model # def invoice_line_move_line_get(self): # ks_res = super(KsGlobalDiscountInvoice, self).invoice_line_move_line_get() # if self.ks_amount_discount > 0: # ks_name = "Universal Discount" # if self.ks_global_discount_type == "percent": # ks_name = ks_name + " (" + str(self.ks_global_discount_rate) + "%)" # ks_name = ks_name + " for " + (self.origin if self.origin else ("Invoice No " + str(self.id))) # if self.ks_sales_discount_account and (self.type == "out_invoice" or self.type == "out_refund"): # # dict = { # 'invl_id': self.number, # 'type': 'src', # 'name': ks_name, # 'price_unit': self.move_id.ks_amount_discount, # 'quantity': 1, # 'amount': -self.move_id.ks_amount_discount, # 'account_id': int(self.move_id.ks_sales_discount_account), # 'move_id': self.id, # 'date': self.date, # 'user_id': self.move_id.invoice_user_id.id or self._uid, # 'company_id': self.move_id.account_id.company_id.id or self.env.company.id, # } # ks_res.append(dict) # # elif self.ks_purchase_discount_account and (self.type == "in_invoice" or self.type == "in_refund"): # dict = { # 'invl_id': self.number, # 'type': 'src', # 'name': ks_name, # 'price_unit': self.ks_amount_discount, # 'quantity': 1, # 'price': -self.ks_amount_discount, # 'account_id': int(self.ks_purchase_discount_account), # # 'invoice_id': self.id, # } # ks_res.append(dict) # # return ks_res @api.model def _prepare_refund(self, invoice, date_invoice=None, date=None, description=None, journal_id=None): ks_res = super(KsGlobalDiscountInvoice, self)._prepare_refund(invoice, date_invoice=None, date=None, description=None, journal_id=None) ks_res['ks_global_discount_rate'] = self.ks_global_discount_rate ks_res['ks_global_discount_type'] = self.ks_global_discount_type return ks_res def ks_update_universal_discount(self): """This Function Updates the Universal Discount through Sale Order""" for rec in self: already_exists = self.line_ids.filtered( lambda line: line.name and line.name.find('Universal Discount' ) == 0) terms_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type in ('receivable', 'payable')) other_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type not in ('receivable', 'payable')) if already_exists: amount = rec.ks_amount_discount if rec.ks_sales_discount_account \ and (rec.type == "out_invoice" or rec.type == "out_refund")\ and amount > 0: if rec.type == "out_invoice": already_exists.update({ 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, }) else: already_exists.update({ 'debit': amount < 0.0 and -amount or 0.0, 'credit': amount > 0.0 and amount or 0.0, }) if rec.ks_purchase_discount_account \ and (rec.type == "in_invoice" or rec.type == "in_refund")\ and amount > 0: if rec.type == "in_invoice": already_exists.update({ 'debit': amount < 0.0 and -amount or 0.0, 'credit': amount > 0.0 and amount or 0.0, }) else: already_exists.update({ 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, }) total_balance = sum(other_lines.mapped('balance')) total_amount_currency = sum( other_lines.mapped('amount_currency')) terms_lines.update({ 'amount_currency': -total_amount_currency, 'debit': total_balance < 0.0 and -total_balance or 0.0, 'credit': total_balance > 0.0 and total_balance or 0.0, }) if not already_exists and rec.ks_global_discount_rate > 0: in_draft_mode = self != self._origin if not in_draft_mode and rec.type == 'out_invoice': rec._recompute_universal_discount_lines() print() @api.onchange('ks_global_discount_rate', 'ks_global_discount_type', 'line_ids') def _recompute_universal_discount_lines(self): """This Function Create The General Entries for Universal Discount""" for rec in self: type_list = [ 'out_invoice', 'out_refund', 'in_invoice', 'in_refund' ] if rec.ks_global_discount_rate > 0 and rec.type in type_list: if rec.is_invoice(include_receipts=True): in_draft_mode = self != self._origin ks_name = "Universal Discount " if rec.ks_global_discount_type == "amount": ks_value = "of amount #" + str( self.ks_global_discount_rate) elif rec.ks_global_discount_type == "percent": ks_value = " @" + str( self.ks_global_discount_rate) + "%" else: ks_value = '' ks_name = ks_name + ks_value # ("Invoice No: " + str(self.ids) # if self._origin.id # else (self.display_name)) terms_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type in ('receivable', 'payable')) already_exists = self.line_ids.filtered( lambda line: line.name and line.name.find( 'Universal Discount') == 0) if already_exists: amount = self.ks_amount_discount if self.ks_sales_discount_account \ and (self.type == "out_invoice" or self.type == "out_refund"): if self.type == "out_invoice": already_exists.update({ 'name': ks_name, 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, }) else: already_exists.update({ 'name': ks_name, 'debit': amount < 0.0 and -amount or 0.0, 'credit': amount > 0.0 and amount or 0.0, }) if self.ks_purchase_discount_account\ and (self.type == "in_invoice" or self.type == "in_refund"): if self.type == "in_invoice": already_exists.update({ 'name': ks_name, 'debit': amount < 0.0 and -amount or 0.0, 'credit': amount > 0.0 and amount or 0.0, }) else: already_exists.update({ 'name': ks_name, 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, }) else: new_tax_line = self.env['account.move.line'] create_method = in_draft_mode and \ self.env['account.move.line'].new or\ self.env['account.move.line'].create if self.ks_sales_discount_account \ and (self.type == "out_invoice" or self.type == "out_refund"): amount = self.ks_amount_discount dict = { 'move_name': self.name, 'name': ks_name, 'price_unit': self.ks_amount_discount, 'quantity': 1, 'debit': amount < 0.0 and -amount or 0.0, 'credit': amount > 0.0 and amount or 0.0, 'account_id': int(self.ks_sales_discount_account), 'move_id': self._origin, 'date': self.date, 'exclude_from_invoice_tab': True, 'partner_id': terms_lines.partner_id.id, 'company_id': terms_lines.company_id.id, 'company_currency_id': terms_lines.company_currency_id.id, } if self.type == "out_invoice": dict.update({ 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, }) else: dict.update({ 'debit': amount < 0.0 and -amount or 0.0, 'credit': amount > 0.0 and amount or 0.0, }) if in_draft_mode: self.line_ids += create_method(dict) # Updation of Invoice Line Id duplicate_id = self.invoice_line_ids.filtered( lambda line: line.name and line.name.find( 'Universal Discount') == 0) self.invoice_line_ids = self.invoice_line_ids - duplicate_id else: dict.update({ 'price_unit': 0.0, 'debit': 0.0, 'credit': 0.0, }) self.line_ids = [(0, 0, dict)] if self.ks_purchase_discount_account\ and (self.type == "in_invoice" or self.type == "in_refund"): amount = self.ks_amount_discount dict = { 'move_name': self.name, 'name': ks_name, 'price_unit': self.ks_amount_discount, 'quantity': 1, 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, 'account_id': int(self.ks_purchase_discount_account), 'move_id': self.id, 'date': self.date, 'exclude_from_invoice_tab': True, 'partner_id': terms_lines.partner_id.id, 'company_id': terms_lines.company_id.id, 'company_currency_id': terms_lines.company_currency_id.id, } if self.type == "in_invoice": dict.update({ 'debit': amount < 0.0 and -amount or 0.0, 'credit': amount > 0.0 and amount or 0.0, }) else: dict.update({ 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, }) self.line_ids += create_method(dict) # updation of invoice line id duplicate_id = self.invoice_line_ids.filtered( lambda line: line.name and line.name.find( 'Universal Discount') == 0) self.invoice_line_ids = self.invoice_line_ids - duplicate_id if in_draft_mode: # Update the payement account amount terms_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type in ('receivable', 'payable')) other_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type not in ('receivable', 'payable')) total_balance = sum(other_lines.mapped('balance')) total_amount_currency = sum( other_lines.mapped('amount_currency')) terms_lines.update({ 'amount_currency': -total_amount_currency, 'debit': total_balance < 0.0 and -total_balance or 0.0, 'credit': total_balance > 0.0 and total_balance or 0.0, }) else: terms_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type in ('receivable', 'payable')) other_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type not in ('receivable', 'payable')) already_exists = self.line_ids.filtered( lambda line: line.name and line.name.find( 'Universal Discount') == 0) total_balance = sum( other_lines.mapped('balance')) + amount total_amount_currency = sum( other_lines.mapped('amount_currency')) dict1 = { 'debit': amount > 0.0 and amount or 0.0, 'credit': amount < 0.0 and -amount or 0.0, } dict2 = { 'debit': total_balance < 0.0 and -total_balance or 0.0, 'credit': total_balance > 0.0 and total_balance or 0.0, } self.line_ids = [(1, already_exists.id, dict1), (1, terms_lines.id, dict2)] print() elif self.ks_global_discount_rate <= 0: already_exists = self.line_ids.filtered( lambda line: line.name and line.name.find( 'Universal Discount') == 0) if already_exists: self.line_ids -= already_exists terms_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type in ('receivable', 'payable')) other_lines = self.line_ids.filtered( lambda line: line.account_id.user_type_id.type not in ('receivable', 'payable')) total_balance = sum(other_lines.mapped('balance')) total_amount_currency = sum( other_lines.mapped('amount_currency')) terms_lines.update({ 'amount_currency': -total_amount_currency, 'debit': total_balance < 0.0 and -total_balance or 0.0, 'credit': total_balance > 0.0 and total_balance or 0.0, })
class PaymentAccountMoveLine(models.TransientModel): _name = "payment.account.move.line" _description = "Assistente Para Lançamento de Pagamentos" company_id = fields.Many2one( "res.company", related="journal_id.company_id", string="Exmpresa", readonly=True, ) move_line_id = fields.Many2one( "account.move.line", readonly=True, string="Conta à Pagar/Receber" ) move_id = fields.Many2one("account.move", readonly=True, string="Fatura") partner_type = fields.Selection( [("customer", "Cliente"), ("supplier", "Fornecedor")], readonly=True ) partner_id = fields.Many2one( "res.partner", string="Cliente/Fornecedor", readonly=True ) journal_id = fields.Many2one( "account.journal", string="Diário", required=True, domain=[("type", "in", ("bank", "cash"))], ) communication = fields.Char(string="Anotações") payment_date = fields.Date( string="Data do Pagamento", default=fields.Date.context_today, required=True, ) currency_id = fields.Many2one( "res.currency", string="Moeda", required=True, default=lambda self: self.env.user.company_id.currency_id, ) amount_residual = fields.Monetary( string="Saldo", readonly=True, related="move_line_id.amount_residual" ) amount = fields.Monetary(string="Valor do Pagamento", required=True,) @api.model def default_get(self, fields): rec = super(PaymentAccountMoveLine, self).default_get(fields) move_line_id = rec.get("move_line_id", False) amount = 0 if not move_line_id: raise UserError( _("Não foi selecionada nenhuma linha de cobrança.") ) move_line = self.env["account.move.line"].browse(move_line_id) if move_line[0].amount_residual: amount = ( move_line[0].amount_residual if rec["partner_type"] == "customer" else move_line[0].amount_residual * -1 ) if move_line[0].move_id: rec.update({"move_id": move_line[0].move_id.id}) rec.update( {"amount": amount,} ) return rec def _get_payment_vals(self): """ Method responsible for generating payment record amounts """ payment_type = "inbound" if self.move_line_id.debit else "outbound" payment_methods = ( payment_type == "inbound" and self.journal_id.inbound_payment_method_ids or self.journal_id.outbound_payment_method_ids ) payment_method_id = payment_methods and payment_methods[0].id or False if not payment_method_id: domain = [("payment_type", "=", payment_type)] payment_method_id = ( self.env["account.payment.method"].search(domain, limit=1).id ) vals = { "partner_id": self.partner_id.id, "move_line_ids": [(4, 0, self.move_line_id.id)], "journal_id": self.journal_id.id, "communication": self.communication, "amount": self.amount, "payment_date": self.payment_date, "payment_type": payment_type, "payment_method_id": payment_method_id, "currency_id": self.currency_id.id, } return vals def action_confirm_payment(self): """ Method responsible for creating the payment """ payment = self.env["account.payment"] vals = self._get_payment_vals() pay = payment.with_context( force_counterpart_account=self.move_line_id.account_id.id ).create(vals) pay.post() move_line = self.env["account.move.line"].browse( vals["move_line_ids"][0][2] ) lines_to_reconcile = (pay.move_line_ids + move_line).filtered( lambda l: l.account_id == move_line.account_id ) lines_to_reconcile.reconcile()
class ProcessMailsDocument(models.Model): _name = 'mail.message.dte.document' _description = "Pre Documento Recibido" _inherit = ['mail.thread'] dte_id = fields.Many2one( 'mail.message.dte', string="DTE", readonly=True, ondelete='cascade', ) new_partner = fields.Char( string="Proveedor Nuevo", readonly=True, ) partner_id = fields.Many2one( 'res.partner', string='Proveedor', domain=[('supplier', '=', True)], ) date = fields.Date( string="Fecha Emsisión", readonly=True, ) number = fields.Char( string='Folio', readonly=True, ) document_class_id = fields.Many2one( 'sii.document_class', string="Tipo de Documento", readonly=True, oldname="sii_document_class_id", ) amount = fields.Monetary( string="Monto", readonly=True, ) currency_id = fields.Many2one( 'res.currency', string="Moneda", readonly=True, default=lambda self: self.env.user.company_id.currency_id, ) invoice_line_ids = fields.One2many( 'mail.message.dte.document.line', 'document_id', string="Líneas del Documento", ) company_id = fields.Many2one( 'res.company', string="Compañía", readonly=True, ) state = fields.Selection( [ ('draft', 'Recibido'), ('accepted', 'Aceptado'), ('rejected', 'Rechazado'), ], default='draft', ) invoice_id = fields.Many2one( 'account.invoice', string="Factura", readonly=True, ) xml = fields.Text( string="XML Documento", readonly=True, ) purchase_to_done = fields.Many2many( 'purchase.order', string="Ordenes de Compra a validar", domain=[('state', 'not in', ['accepted', 'rejected'])], ) claim = fields.Selection( [ ('N/D', "No definido"), ('ACD', 'Acepta Contenido del Documento'), ('RCD', 'Reclamo al Contenido del Documento '), ('ERM', ' Otorga Recibo de Mercaderías o Servicios'), ('RFP', 'Reclamo por Falta Parcial de Mercaderías'), ('RFT', 'Reclamo por Falta Total de Mercaderías'), ], string="Reclamo", copy=False, default="N/D", ) claim_description = fields.Char( string="Detalle Reclamo", readonly=True, ) _order = 'create_date DESC' @api.model def auto_accept_documents(self): self.env.cr.execute(""" select id from mail_message_dte_document where create_date + interval '8 days' < now() and state = 'draft' """) for d in self.browse([line.get('id') for line in \ self.env.cr.dictfetchall()]): d.accept_document() @api.multi def accept_document(self): created = [] for r in self: vals = { 'xml_file': r.xml.encode('ISO-8859-1'), 'filename': r.dte_id.name, 'pre_process': False, 'document_id': r.id, 'option': 'accept' } val = self.env['sii.dte.upload_xml.wizard'].create(vals) resp = val.confirm(ret=True) created.extend(resp) r.get_dte_claim() for i in self.env['account.invoice'].browse(resp): if i.claim in ['ACD', 'ERM']: r.state = 'accepted' xml_id = 'account.action_invoice_tree2' result = self.env.ref('%s' % (xml_id)).read()[0] if created: domain = safe_eval(result.get('domain', '[]')) domain.append(('id', 'in', created)) result['domain'] = domain return result @api.multi def reject_document(self): for r in self: r.set_dte_claim(claim='RCD') if r.claim in ['RCD']: r.state = 'rejected' def set_dte_claim(self, claim): if self.document_class_id.sii_code not in [33, 34, 43]: self.claim = claim return if not self.partner_id: rut_emisor = self.new_partner.split(' ')[0] else: rut_emisor = self.env['account.invoice'].format_vat( self.partner_id.vat) token = self.env['sii.xml.envio'].get_token(self.env.user, self.company_id) url = claim_url[self.company_id.dte_service_provider] + '?wsdl' _server = Client( url, headers={ 'Cookie': 'TOKEN=' + token, }, ) try: respuesta = _server.service.ingresarAceptacionReclamoDoc( rut_emisor[:-2], rut_emisor[-1], str(self.document_class_id.sii_code), str(self.number), claim, ) except Exception as e: msg = "Error al ingresar Reclamo DTE" _logger.warning("%s: %s" % (msg, str(e))) if e.args[0][0] == 503: raise UserError( '%s: Conexión al SII caída/rechazada o el SII está temporalmente fuera de línea, reintente la acción' % (msg)) raise UserError(("%s: %s" % (msg, str(e)))) self.claim_description = respuesta if respuesta.codResp in [0, 7]: self.claim = claim @api.multi def get_dte_claim(self): if not self.partner_id: rut_emisor = self.new_partner.split(' ')[0] else: rut_emisor = self.env['account.invoice'].format_vat( self.partner_id.vat) token = self.env['sii.xml.envio'].get_token(self.env.user, self.company_id) url = claim_url[self.company_id.dte_service_provider] + '?wsdl' _server = Client( url, headers={ 'Cookie': 'TOKEN=' + token, }, ) try: respuesta = _server.service.listarEventosHistDoc( rut_emisor[:-2], rut_emisor[-1], str(self.document_class_id.sii_code), str(self.number), ) self.claim_description = respuesta except Exception as e: _logger.warning("Error al obtener aceptación %s" % (str(e))) if self.company_id.dte_service_provider == 'SII': raise UserError("Error al obtener aceptación: %s" % str(e))
class Project(models.Model): _name = 'project.project' _inherit = ['project.project', 'mail.thread', 'mail.activity.mixin'] _order = 'name' # We Override this method from 'project_task_default_stage def _get_default_type_common(self): ids = self.env['project.task.type'].search([ ('case_default', '=', True), ('project_type_default', '=', self.project_type)]) _logger.info("Default Stages: {} for project type {}".format(ids.mapped('name'),self.project_type)) return ids type_ids = fields.Many2many( default=lambda self: self._get_default_type_common()) user_id = fields.Many2one('res.users', string='Project Manager', default=lambda self: self._default_user_id() , track_visibility="onchange") project_type = fields.Selection([ ('dev', 'Developement'), ('client', 'Client'), ('internal','Internal')], string='Project Type', default='client', ) # Maximum parent level here is meant to be 1 at max for now parent_id = fields.Many2one( 'project.project', 'Parent project', index=True, ondelete='cascade', compute='_get_parent_id', store=True ) child_id = fields.One2many('project.project', 'parent_id', 'Child projects') parent_task_count = fields.Integer( compute='_compute_parent_task_count' ) child_task_count = fields.Integer( compute='_compute_child_task_count' ) #consultant_ids = fields.Many2many('hr.employee', string='Consultants') #ta_ids = fields.Many2many('hr.employee', string='Ta') completion_ratio = fields.Float('Task Complete', compute='compute_project_completion_ratio', store=True, help='All parent tasks completion %, weighted by contractual budget') consummed_completed_ratio = fields.Float('BC / TC', compute='compute_project_consummed_completed_ratio', store=True, help='BC/TC in Percentage, lower is "better", 100 is on target') summary_ids = fields.One2many( 'project.summary', 'project_id', 'Project summaries' ) is_project_manager = fields.Boolean(compute="_get_is_project_manager") scope_of_work = fields.Html(string='Scope of work') orders_count = fields.Integer(compute='compute_orders_count') tasks_count = fields.Integer() timesheets_count = fields.Integer() lead_consultant = fields.Many2one('hr.employee', related='core_team_id.lead_consultant', string='Lead Consultant') assistant_id = fields.Many2one('hr.employee', related='core_team_id.assistant_id', string='Project Assistant') lead_backup = fields.Many2one('hr.employee', related='core_team_id.lead_backup', string='Lead Consultant Backup') consultant_ids = fields.Many2many('hr.employee', 'rel_project_consultants', related='core_team_id.consultant_ids', string='Consultants') ta_ids = fields.Many2many('hr.employee', relation='rel_project_tas', related='core_team_id.ta_ids', string='Ta') controller_id = fields.Many2one('res.users', related='partner_id.controller_id', string='Project Controller') invoice_admin_id = fields.Many2one('res.users', related='partner_id.invoice_admin_id', string='Invoice Administrator') account_manager_id = fields.Many2one('res.users', related='sale_order_id.user_id', string='Account Manager', store=True) invoices_count = fields.Integer( compute='_get_out_invoice_ids', compute_sudo = True ) out_invoice_ids = fields.Many2many( string='Out invoices', compute='_get_out_invoice_ids', comodel_name="account.invoice", relation="project_out_invoice_rel", column1="project_id", column2="invoice_id", store=True, copy=False, readonly=True, compute_sudo=True ) risk_ids = fields.Many2many( 'risk', string='Risk', compute='_compute_risk_ids', # store=True, ) risk_score = fields.Integer( string='Risk Score', compute='_compute_risk_score', store=True, ) risk_last_update = fields.Datetime( string='Risk Last Update', compute='_compute_risk_last_update', # store=True, ) invoiceable_amount = fields.Monetary( related="sale_order_id.invoiceable_amount", readonly=True, store=True, ) invoicing_mode = fields.Selection(related="sale_order_id.invoicing_mode", readonly=True) last_summary_date = fields.Datetime(string="Last Summary Date", compute="_compute_last_create_date", readonly=True) subscription_count = fields.Integer(compute='_compute_subscription_count') date_start = fields.Date( string='Start Date', compute='_compute_dates',) date = fields.Date( string='Expiration Date', compute='_compute_dates', index=True, track_visibility='onchange', ) # accounting fields for legacy integration external_account = fields.Char( default="/", ) sharepoint_folder = fields.Char( string='Sharepoint Folder', compute='_compute_sharepoint_folder', readonly=True, store=True, ) show_folder_path = fields.Boolean() @api.depends('sale_order_id', 'project_type', 'parent_id','partner_id','sale_order_id.internal_ref') def _compute_sharepoint_folder(self): pre = self.env.ref('vcls-contact.SP_client_root_prefix').value post = self.env.ref('vcls-contact.SP_client_root_postfix').value for project in self.filtered(lambda p: p.project_type=='client' and p.sale_order_id and p.partner_id.altname): reference = project.parent_id.sale_order_id.internal_ref if project.parent_id \ else project.sale_order_id.internal_ref or '' if reference: reference = "{}-{}".format(reference.split('-')[0],reference.split('-')[1]) project.sharepoint_folder = "{}/{}/{}/{}{}".format(pre,project.partner_id.altname[0],project.partner_id.altname,reference,post) project.show_folder_path = True def _compute_dates(self): for project in self: tasks = project.task_ids.filtered(lambda t: t.date_start and t.date_end) if tasks: project.date_start = min(tasks.mapped('date_start')).date() project.date = max(tasks.mapped('date_end')).date() elif project.sale_order_id: project.date_start = project.sale_order_id.expected_start_date project.date = project.sale_order_id.expected_end_date else: project.date_start = False project.date = False def _compute_subscription_count(self): """Compute the number of distinct subscriptions linked to the orders of the project(s).""" for project in self: all_projects = project if project.child_id: all_projects |= project.child_id if project.parent_id: all_projects |= project.parent_id sub_count = len(self.env['sale.order.line'].read_group([('order_id', 'in', all_projects.mapped('sale_order_id.id')), ('subscription_id', '!=', False)], ['subscription_id'], ['subscription_id'])) project.subscription_count = sub_count def action_open_subscriptions(self): """Display the linked subscription and adapt the view to the number of records to display.""" self.ensure_one() all_projects = self if self.child_id: all_projects |= self.child_id if self.parent_id: all_projects |= self.parent_id subscriptions = all_projects.mapped('sale_order_id.order_line.subscription_id') action = self.env.ref('sale_subscription.sale_subscription_action').read()[0] if len(subscriptions) > 1: action['domain'] = [('id', 'in', subscriptions.ids)] elif len(subscriptions) == 1: form_view = [(self.env.ref('sale_subscription.sale_subscription_view_form').id, 'form')] if 'views' in action: action['views'] = form_view + [(state,view) for state,view in action['views'] if view != 'form'] else: action['views'] = form_view action['res_id'] = subscriptions.ids[0] else: action = {'type': 'ir.actions.act_window_close'} return action @api.depends('summary_ids') def _compute_last_create_date(self): for project in self: if project.summary_ids: project.last_summary_date = project.summary_ids.sorted(lambda s: s.create_date, reverse=True)[0].create_date @api.depends('sale_order_id.risk_ids') def _compute_risk_ids(self): for project in self: project.risk_ids = self.env['risk'].search([ ('resource', '=', 'project.project,{}'.format(project.id)),('risk_level', '>', 0) ]) @api.depends('risk_ids', 'risk_ids.score') def _compute_risk_score(self): for project in self: project.risk_score = sum(project.risk_ids.mapped('score')) @api.depends('risk_ids.write_date') def _compute_risk_last_update(self): for project in self: last_one = datetime(1970, 1, 1) for individual_risks in project.risk_ids: if not last_one or individual_risks.write_date > last_one: last_one = individual_risks.write_date elif not last_one or individual_risks.create_date > last_one: last_one = individual_risks.create_date if last_one != datetime(1970, 1, 1): project.risk_last_update = last_one else: project.risk_last_update = False @api.one @api.depends( 'sale_line_id.order_id.order_line.invoice_lines', 'tasks.sale_order_id', ) def _get_out_invoice_ids(self): # use of sudo here because of the non possibility of lc to read some invoices project_sudo = self.sudo() out_invoice_ids = project_sudo.mapped('sale_line_id.order_id.invoice_ids') \ | project_sudo.mapped('tasks.sale_order_id.invoice_ids') self.out_invoice_ids = out_invoice_ids self.invoices_count = len(out_invoice_ids) @api.multi @api.depends( 'sale_order_id', 'sale_order_id.parent_id', 'sale_order_id.parent_id.project_id' ) def _get_parent_id(self): for project in self: if project.sale_order_id: project.parent_id = project.sale_order_id.parent_id.project_id ################## # CUSTOM METHODS # ################## @api.multi def get_tasks_not_cancelled_and_completable(self): """This function will return all the tasks and subtasks that can be completed and is not cancelled""" self.ensure_one() all_tasks = self.task_ids return all_tasks.filtered(lambda task: task.sale_line_id.product_id.product_tmpl_id.completion_elligible and task.stage_id.status not in ['cancelled']) @api.multi def action_raise_new_invoice(self): """This function will trigger the Creation of invoice regrouping all the sale orders.""" orders = self.env['sale.order'] for project in self: if not project.sale_order_id and project.sale_order_id.invoice_status != 'to invoice': raise UserError(_("The selected Sales Order should contain something to invoice.")) else: orders |= project.sale_order_id action = self.env.ref('sale.action_view_sale_advance_payment_inv').read()[0] action['context'] = { 'active_ids': orders.ids } return action @api.multi def action_raise_new_risk(self): self.ensure_one() action = self.env.ref('vcls-risk.action_view_risk_wizard').read()[0] action['context'] = { 'default_resource': 'project.project,{}'.format(self.id), 'new_risk':True, } return action @api.multi def show_risks(self): self.ensure_one() action = self.env.ref('vcls-risk.action_view_risk').read()[0] action['domain'] = [('resource', '=', 'project.project,{}'.format(self.id))] action['target'] = 'current' return action @api.multi def sale_orders_tree_view(self): action = self.env.ref('sale.action_quotations').read()[0] action['domain'] = [('project_id', 'child_of', self.id)] action['context'] = {} return action @api.multi def invoices_tree_view(self): self.ensure_one() action = self.env.ref('account.action_invoice_tree1').read()[0] action['domain'] = [('id', 'in', self.out_invoice_ids.ids)] return action @api.multi def tasks_tree_view(self): action = self.env.ref('project.act_project_project_2_project_task_all').read()[0] return action @api.multi def forecasts_tree_view(self): action = self.env.ref('project_forecast.project_forecast_action_from_project').read()[0] action['domain'] = [('project_id', 'child_of', self.id)] return action @api.multi def timesheets_tree_view(self): action = self.env.ref('hr_timesheet.act_hr_timesheet_line_by_project').read()[0] action['domain'] = [('project_id', 'child_of', self.id)] return action @api.multi def report_analysis_tree_view(self): action = self.env.ref('vcls-timesheet.project_timesheet_forecast_report_action').read()[0] action['domain'] = [('project_id', 'child_of', self.id)] action['context'] = {'active_id': self.id, 'active_model': 'project.project'} return action ############### # ORM METHODS # ############### @api.model def create(self, vals): #default visibility vals['privacy_visibility'] = 'portal' #if no ID defined, then increment using the sequence if vals.get('external_account','/')=='/': vals['external_account'] = self.env['ir.sequence'].next_by_code('seq_customer_project_id') #we automatically assign the project manager to be the one defined in the core team if vals.get('sale_order_id',False): so = self.env['sale.order'].browse(vals.get('sale_order_id')) vals['scope_of_work'] = so.scope_of_work lc = so.core_team_id.lead_consultant _logger.info("SO info {}".format(lc.name)) if lc: vals['user_id']=lc.user_id.id project = super(Project, self).create(vals) ids = project._get_default_type_common() project.type_ids = ids if ids else project.type_ids if project.project_type != 'client': project.privacy_visibility = 'employees' return project @api.multi def unlink(self): authorized = self.env.user.has_group('vcls_security.group_project_controller') or self.env.user.has_group('vcls_security.group_bd_admin') if not authorized: raise UserError(_("PROJECT UNLINK | You need to be a member of 'BD Admin' or 'Project Controller' to delete project(s).")) #we catch all child projects projects = self.env['project.project'] for project in self: if project.active: raise UserError(_("PROJECT UNLINK | To avoid un-wanted deletion, we don't delete active project(s). Please archive {} 1st").format(project.name)) else: projects |= project projects |= project.child_id #we look for invoices invoices = projects.mapped('out_invoice_ids') if invoices: raise UserError(_("PROJECT UNLINK | We can't delete projects having invoices, please archive instead\n{}").format(invoices.mapped('name'))) for project in projects: _logger.info("PROJECT UNLINK | Cleaning {}".format(project.name)) #we look for timesheets ts = self.env['account.analytic.line'].with_context(active_test=False).search([('project_id','=',project.id)]) if ts: _logger.info("PROJECT UNLINK | Timesheets found {}".format(len(ts))) if not self.env.user.has_group('vcls_security.group_project_controller'): raise UserError(_("PROJECT UNLINK | You need to be a member of 'Project Controller' to delete timesheet(s).")) ts.unlink() #we look for forecasts fc = self.env['project.forecast'].with_context(active_test=False).search([('project_id','=',project.id)]) if fc: _logger.info("PROJECT UNLINK | Forecasts found {}".format(len(fc))) fc.unlink() #we clean the tasks tasks = self.env['project.task'].with_context(active_test=False).search([('project_id','=',project.id)]) if tasks: _logger.info("PROJECT UNLINK | Tasks found {}".format(len(tasks))) tasks.write({'sale_line_id':False}) tasks.unlink() #we clean the mapping if project.sale_line_employee_ids: _logger.info("PROJECT UNLINK | SO line mappings {}".format(len(project.sale_line_employee_ids))) project.sale_line_employee_ids.unlink() return super(Project, self).unlink() ################### # COMPUTE METHODS # ################### @api.model def _default_user_id(self): if self.project_type == 'client': if self.sale_order_id: return self.sale_order_id.opportunity_id.user_id return self.env.user def _compute_parent_task_count(self): task_data = self.env['project.task'].read_group([('parent_id','=',False),('project_id', 'in', self.ids), '|', ('stage_id.fold', '=', False), ('stage_id', '=', False)], ['project_id'], ['project_id']) result = dict((data['project_id'][0], data['project_id_count']) for data in task_data) for project in self: project.parent_task_count = result.get(project.id, 0) def _compute_child_task_count(self): task_data = self.env['project.task'].read_group([('parent_id','!=',False),('project_id', 'in', self.ids), '|', ('stage_id.fold', '=', False), ('stage_id', '=', False)], ['project_id'], ['project_id']) result = dict((data['project_id'][0], data['project_id_count']) for data in task_data) for project in self: project.child_task_count = result.get(project.id, 0) def _compute_task_count(self): read_group_res = self.env['project.task'].read_group([('project_id', 'child_of', self.ids), '|', ('stage_id.fold', '=', False), ('stage_id', '=', False)], ['project_id'], ['project_id']) group_data = dict((data['project_id'][0], data['project_id_count']) for data in read_group_res) for project in self: task_count = 0 for sub_project_id in project.search([('id', 'child_of', project.id)]).ids: task_count += group_data.get(sub_project_id, 0) project.task_count = task_count @api.multi @api.depends('task_ids.completion_ratio') def compute_project_completion_ratio(self): for project in self: tasks = project.get_tasks_not_cancelled_and_completable() weight_sum = 0 num_times_weight_factor = 0 #this weights the tasks complete with the contractual budget for task in tasks: weight_sum += task.contractual_budget num_times_weight_factor += ( task.completion_ratio / 100) * task.contractual_budget project.completion_ratio = num_times_weight_factor * 100 / weight_sum if weight_sum > 0 else 0 @api.multi @api.depends('task_ids.consummed_completed_ratio') def compute_project_consummed_completed_ratio(self): for project in self: project.consummed_completed_ratio = (project.budget_consumed / project.completion_ratio) * 100 if project.completion_ratio > 0 else 0 @api.multi def _get_is_project_manager(self): for p in self: p.is_project_manager = p.user_id == self.env.user @api.multi def compute_orders_count(self): for project in self: project.orders_count = self.env['sale.order'].search_count([('project_id', 'child_of', project.id)]) @api.multi def compute_invoices_count(self): self.ensure_one() projects = self.env['project.project'].search([('id', 'child_of', self.id)]) sale_orders = projects.mapped('sale_line_id.order_id') | projects.mapped('tasks.sale_order_id') invoices = sale_orders.mapped('invoice_ids').filtered(lambda inv: inv.type == 'out_invoice') self.invoices_count = len(invoices) @api.multi def toggle_active(self): if any(project.activity_ids for project in self): raise ValidationError(_("Its not possible to archive a project while there is still undone activities ")) super(Project, self).toggle_active() @api.model def end_project_activities_scheduling(self): for project_id in self.search([('active', '=', True), ('parent_id', '=', False)]): task_ids = [task for child_id in project_id.child_id + project_id for task in child_id.task_ids] if task_ids and all(task.stage_id.status in ("completed", "cancelled") for task in task_ids): users_summary = { project_id.partner_id.user_id.id: _('Client Feedback'), project_id.user_id.id: _('End of project form filling'), project_id.partner_id.invoice_admin_id.id: _('Invoicing Closing') } for user_id, summary in users_summary.items(): if user_id: activity_vals = { 'user_id': user_id, 'summary': summary, 'act_type_xmlid': 'mail.mail_activity_data_todo', 'automated': True, } project_id.sudo().activity_schedule(**activity_vals) return True def invoicing_session_done(self): """ Timesheets stage_id from pc_review to invoiceable """ if not self.env.user.has_group('vcls_security.group_project_controller'): raise ValidationError(_("You need to have the project controller access right.")) project_ids = self.browse(self._context.get('active_ids')) all_projects = project_ids.filtered(lambda p: p.project_type=='client') for project in project_ids: all_projects |= project.child_id #we update the timesheet limit date to the end of the previous month today = fields.Date.today() ts_limit_date = today.replace(day=1) - relativedelta(days=1) timesheet_ids = self.env['account.analytic.line'].search([('main_project_id', 'in', all_projects.ids), ('stage_id', '=', 'pc_review'), ('date','<=',ts_limit_date)]) if timesheet_ids: #fixed price usecase fp_ts = timesheet_ids.filtered(lambda t: t.so_line.order_id.invoicing_mode == 'fixed_price') if fp_ts: fp_ts.write({'stage_id': 'fixed_price'}) #t&m usecase tm_ts = timesheet_ids.filtered(lambda t: t.so_line.order_id.invoicing_mode == 'tm') if tm_ts: tm_ts.write({'stage_id': 'invoiceable'}) if all_projects: all_projects.mapped('sale_order_id').write({'timesheet_limit_date':ts_limit_date}) #we trigger the computation of KPIs self.env['project.task']._cron_compute_kpi() @api.multi def write(self, values): if values.get('active', None) is False: tasks = self.mapped('tasks') tasks |= tasks.mapped('child_ids') tasks.write({'active': False}) return super(Project, self).write(values) @api.multi def _get_family_project_ids(self): self.ensure_one() parent_project_id = self.parent_id or self family_project_ids = parent_project_id | parent_project_id.child_id return family_project_ids @api.model def detect_bad_tasks(self): projects = self.search([('project_type','=','client')]) tag = self.env.ref('vcls-project.proj_tag_bad_task') for project in projects: #we get authorized parent taks from the sale_order authorized_tasks = project.sale_order_id.order_line.mapped('task_id') to_update = self.env['project.task'] bad = project.task_ids.filtered(lambda t: (t.id not in authorized_tasks.ids) and not t.parent_id) for task in bad: to_update |= task | task.child_ids if bad: _logger.info("Bad Tasks in {} | {}".format(project.name,to_update.mapped('name'))) to_update.write({ 'tag_ids':[(4,tag.id,0)], }) """@api.onchange('sale_line_employee_ids')
class Contract(models.Model): _inherit = 'hr.contract' hourly_wage = fields.Monetary(digits=(16, 2), track_visibility="onchange")
class PerformanceSales(models.Model): _name = 'performance.sales' _description = 'Performance Sales' _inherit = 'performance.mixin' currency_id = fields.Many2one( comodel_name = 'res.currency', readonly = True, ) #Objectives sales_objective_period = fields.Monetary() sales_objective_cumulative = fields.Monetary( compute = '_compute_sales_objective_cumulative', store = True, ) @api.depends('sales_objective_period') def _compute_sales_objective_cumulative(self): for perf in self: perf.sales_objective_cumulative = perf.sales_objective_period*(perf.period_index+1) #Realized Sales sales_new_period = fields.Monetary(readonly=True) #OK sales_new_cumulative = fields.Monetary(readonly=True) #OK sales_retained_period = fields.Monetary(readonly=True) #OK sales_retained_cumulative = fields.Monetary(readonly=True) #OK sales_total_period = fields.Monetary(readonly=True) #OK sales_total_cumulative = fields.Monetary(readonly=True) #OK sales_count_new_period = fields.Integer(readonly=True) #OK sales_count_new_cumulative = fields.Integer(readonly=True) #OK sales_count_retained_period = fields.Integer(readonly=True) #OK sales_count_retained_cumulative = fields.Integer(readonly=True) #OK sales_count_total_period = fields.Integer(readonly=True) #OK sales_count_total_cumulative = fields.Integer(readonly=True) #OK #Realized Losses losses_new_period = fields.Monetary(readonly=True) #OK losses_new_cumulative = fields.Monetary(readonly=True) #OK losses_retained_period = fields.Monetary(readonly=True) #OK losses_retained_cumulative = fields.Monetary(readonly=True) #OK losses_total_period = fields.Monetary(readonly=True) #OK losses_total_cumulative = fields.Monetary(readonly=True) #OK losses_count_new_period = fields.Integer(readonly=True) #OK losses_count_new_cumulative = fields.Integer(readonly=True) #OK losses_count_retained_period = fields.Integer(readonly=True) #OK losses_count_retained_cumulative = fields.Integer(readonly=True) #OK losses_count_total_period = fields.Integer(readonly=True) #OK losses_count_total_cumulative = fields.Integer(readonly=True) #OK #ratios win_loss_count_new_period = fields.Float(readonly=True) #OK win_loss_count_new_cumulative = fields.Float(readonly=True) #OK win_loss_count_retained_period = fields.Float(readonly=True) #OK win_loss_count_retained_cumulative = fields.Float(readonly=True) #OK win_loss_count_total_period = fields.Float(readonly=True) #OK win_loss_count_total_cumulative = fields.Float(readonly=True) #OK win_loss_new_period = fields.Float(readonly=True) #OK win_loss_new_cumulative = fields.Float(readonly=True) #OK win_loss_retained_period = fields.Float(readonly=True) #OK win_loss_retained_cumulative = fields.Float(readonly=True) #OK win_loss_total_period = fields.Float(readonly=True) #OK win_loss_total_cumulative = fields.Float(readonly=True) #OK #pipeline pipe_new_period = fields.Monetary(readonly=True) #OK pipe_new_period_weighted = fields.Monetary(readonly=True) #OK pipe_retained_period = fields.Monetary(readonly=True) #OK pipe_retained_period_weighted = fields.Monetary(readonly=True) #OK pipe_total_period = fields.Monetary(readonly=True) #OK pipe_total_period_weighted = fields.Monetary(readonly=True) #OK pipe_count_new_period = fields.Integer(readonly=True) #OK pipe_count_retained_period = fields.Integer(readonly=True) #OK pipe_count_total_period = fields.Integer(readonly=True) #OK pipe_new_cumulative = fields.Monetary(readonly=True) #OK pipe_new_cumulative_weighted = fields.Monetary(readonly=True) #OK pipe_retained_cumulative = fields.Monetary(readonly=True) #OK pipe_retained_cumulative_weighted = fields.Monetary(readonly=True) #OK pipe_total_cumulative = fields.Monetary(readonly=True) #OK pipe_total_cumulative_weighted = fields.Monetary(readonly=True) #OK pipe_count_new_cumulative = fields.Integer(readonly=True) #OK pipe_count_retained_cumulative = fields.Integer(readonly=True) #OK pipe_count_total_cumulative = fields.Integer(readonly=True) #OK #active snapshot active_count_new = fields.Integer(readonly=True) #OK active_count_retained = fields.Integer(readonly=True) #OK active_count_total = fields.Integer(readonly=True) #OK active_new = fields.Monetary(readonly=True) #OK active_retained = fields.Monetary(readonly=True) #OK active_total = fields.Monetary(readonly=True) #OK active_new_weighted = fields.Monetary(readonly=True) #OK active_retained_weighted = fields.Monetary(readonly=True) #OK active_total_weighted = fields.Monetary(readonly=True) #OK def _compute_active_snapshot(self): today = fields.Date.today() for perf in self.filtered(lambda p: p.date_start<=today and p.date_end>=today): open_sos = self.env['sale.order'].search([ ('company_id','=',perf.company_id.id), ('sale_status','in',['draft','sent'])]) perf.active_new = sum(open_sos.filtered(lambda s: s.sale_profile == 'new').mapped('converted_untaxed_amount')) perf.active_retained = sum(open_sos.filtered(lambda s: s.sale_profile == 'retained').mapped('converted_untaxed_amount')) perf.active_total = perf.active_new + perf.active_retained perf.active_count_new = len(open_sos.filtered(lambda s: s.sale_profile == 'new')) perf.active_count_retained = len(open_sos.filtered(lambda s: s.sale_profile == 'retained')) perf.active_count_total = perf.active_count_new + perf.active_count_retained perf.active_new_weighted = sum(open_sos.filtered(lambda s: s.sale_profile == 'new').mapped(lambda p: (p.converted_untaxed_amount*p.probability)/100)) perf.active_retained_weighted = sum(open_sos.filtered(lambda s: s.sale_profile == 'retained').mapped(lambda p: (p.converted_untaxed_amount*p.probability)/100)) perf.active_total_weighted = perf.active_new_weighted + perf.active_retained_weighted def _compute_period_sales(self): ordered_perf = self.sorted(lambda p: (p.period_id.date_start,p.period_index)) for perf in ordered_perf: _logger.info("PERF | Period Sales Calculation | {}-{} index {}-{}".format(perf.period_id.name,perf.period_id.date_start,perf.period_index,perf.date_start)) #we get the relevant sale.order sos = self.env['sale.order'].search([ ('company_id','=',perf.company_id.id), ('sales_reporting_date','>=',perf.date_start), ('sales_reporting_date','<=',perf.date_end), ('sale_status','not in',['cancel'])]) new_sos = sos.filtered(lambda p: p.sale_profile=='new') retained_sos = sos.filtered(lambda p: p.sale_profile=='retained') _logger.info("PERF | Found {} SO in period {}\n{} NEW and {} RETAINED".format(len(sos),perf.date_start,len(new_sos),len(retained_sos))) #sales & losses perf.sales_new_period = sum(new_sos.filtered(lambda s: s.sale_status == 'won').mapped('converted_untaxed_amount')) perf.sales_retained_period = sum(retained_sos.filtered(lambda s: s.sale_status == 'won').mapped('converted_untaxed_amount')) perf.sales_total_period = perf.sales_new_period + perf.sales_retained_period perf.sales_count_new_period = len(new_sos.filtered(lambda s: s.sale_status == 'won')) perf.sales_count_retained_period = len(retained_sos.filtered(lambda s: s.sale_status == 'won')) perf.sales_count_total_period = perf.sales_count_new_period + perf.sales_count_retained_period perf.losses_new_period = sum(new_sos.filtered(lambda s: s.sale_status == 'lost').mapped('converted_untaxed_amount')) perf.losses_retained_period = sum(retained_sos.filtered(lambda s: s.sale_status == 'lost').mapped('converted_untaxed_amount')) perf.losses_total_period = perf.losses_new_period + perf.losses_retained_period perf.losses_count_new_period = len(new_sos.filtered(lambda s: s.sale_status == 'lost')) perf.losses_count_retained_period = len(retained_sos.filtered(lambda s: s.sale_status == 'lost')) perf.losses_count_total_period = perf.losses_count_new_period + perf.losses_count_retained_period #ratios if (perf.sales_count_new_period + perf.losses_new_period) > 0: perf.win_loss_count_new_period = perf.sales_count_new_period / (perf.sales_count_new_period + perf.losses_count_new_period) perf.win_loss_new_period = perf.sales_new_period / (perf.sales_new_period + perf.losses_new_period) else: perf.win_loss_count_new_period = False perf.win_loss_new_period = False if (perf.sales_count_retained_period + perf.losses_retained_period) > 0: perf.win_loss_count_retained_period = perf.sales_count_retained_period / (perf.sales_count_retained_period + perf.losses_count_retained_period) perf.win_loss_retained_period = perf.sales_retained_period / (perf.sales_retained_period + perf.losses_retained_period) else: perf.win_loss_count_retained_period = False perf.win_loss_retained_period = False if (perf.sales_total_period + perf.losses_total_period) > 0: perf.win_loss_total_period = perf.sales_total_period / (perf.sales_total_period + perf.losses_total_period) else: perf.win_loss_total_period = False #pipeline perf.pipe_new_period = sum(new_sos.filtered(lambda s: s.sale_status in ['draft','sent']).mapped('converted_untaxed_amount')) perf.pipe_new_period_weighted = sum(new_sos.filtered(lambda s: s.sale_status in ['draft','sent']).mapped(lambda p: (p.converted_untaxed_amount*p.probability)/100)) perf.pipe_count_new_period = len(new_sos.filtered(lambda s: s.sale_status in ['draft','sent'])) perf.pipe_retained_period = sum(retained_sos.filtered(lambda s: s.sale_status in ['draft','sent']).mapped('converted_untaxed_amount')) perf.pipe_retained_period_weighted = sum(retained_sos.filtered(lambda s: s.sale_status in ['draft','sent']).mapped(lambda p: (p.converted_untaxed_amount*p.probability)/100)) perf.pipe_count_retained_period = len(retained_sos.filtered(lambda s: s.sale_status in ['draft','sent'])) perf.pipe_total_period = perf.pipe_new_period + perf.pipe_retained_period perf.pipe_total_period_weighted = perf.pipe_new_period_weighted + perf.pipe_retained_period_weighted perf.pipe_count_total_period = perf.pipe_count_new_period + perf.pipe_count_retained_period def _compute_cumulative_sales(self): #we order per period and index c_fields = [ 'sales_new_cumulative', 'sales_retained_cumulative', 'sales_total_cumulative', 'sales_count_new_cumulative', 'sales_count_retained_cumulative', 'sales_count_total_cumulative', 'losses_new_cumulative', 'losses_retained_cumulative', 'losses_total_cumulative', 'losses_count_new_cumulative', 'losses_count_retained_cumulative', 'losses_count_total_cumulative', 'pipe_new_cumulative', 'pipe_new_cumulative_weighted', 'pipe_retained_cumulative', 'pipe_retained_cumulative_weighted', 'pipe_total_cumulative', 'pipe_total_cumulative_weighted', 'pipe_count_new_cumulative', 'pipe_count_retained_cumulative', 'pipe_count_total_cumulative', ] p_fields = [f.replace('cumulative','period') for f in c_fields] ordered_perf = self.sorted(lambda p: (p.period_id.date_start,p.period_index)) for perf in ordered_perf: _logger.info("PERF | Cumulative Sales Calculation | {}-{} index {}-{}".format(perf.period_id.name,perf.period_id.date_start,perf.period_index,perf.date_start)) #prev_perfs = self.search([('period_id','=',perf.period_id.id),('period_index','<=',perf.period_index)]) last_perf = self.search([('period_id','=',perf.period_id.id),('period_index','=',perf.period_index-1)],limit=1) if last_perf: last_data = last_perf.read(c_fields)[0] else: last_data = {field_name:0 for field_name in c_fields} current_data = perf.read(p_fields)[0] _logger.info("PERF | \nLast Data {}\nCurrent Data {}".format(last_data,current_data)) vals = {} for field in c_fields: vals.update({field: last_data[field] + current_data[field.replace('cumulative','period')]}) _logger.info("PERF | New Values {}".format(vals)) perf.write(vals) #ratios if (perf.sales_count_new_cumulative + perf.losses_new_cumulative) > 0: perf.win_loss_count_new_cumulative = perf.sales_count_new_cumulative / (perf.sales_count_new_cumulative + perf.losses_count_new_cumulative) perf.win_loss_new_cumulative = perf.sales_new_cumulative / (perf.sales_new_cumulative + perf.losses_new_cumulative) else: perf.win_loss_count_new_cumulative = False perf.win_loss_new_cumulative = False if (perf.sales_count_retained_cumulative + perf.losses_retained_cumulative) > 0: perf.win_loss_count_retained_cumulative = perf.sales_count_retained_cumulative / (perf.sales_count_retained_cumulative + perf.losses_count_retained_cumulative) perf.win_loss_retained_cumulative = perf.sales_retained_cumulative / (perf.sales_retained_cumulative + perf.losses_retained_cumulative) else: perf.win_loss_count_retained_cumulative = False perf.win_loss_retained_cumulative = False if (perf.sales_total_cumulative + perf.losses_total_cumulative) > 0: perf.win_loss_total_cumulative = perf.sales_total_cumulative / (perf.sales_total_cumulative + perf.losses_total_cumulative) else: perf.win_loss_total_cumulative = False
class ResPartner(models.Model): _name = 'res.partner' _inherit = 'res.partner' @api.depends_context('company') def _credit_debit_get(self): tables, where_clause, where_params = self.env[ 'account.move.line'].with_context( state='posted', company_id=self.env.company.id)._query_get() where_params = [tuple(self.ids)] + where_params if where_clause: where_clause = 'AND ' + where_clause self._cr.execute( """SELECT account_move_line.partner_id, act.type, SUM(account_move_line.amount_residual) FROM """ + tables + """ LEFT JOIN account_account a ON (account_move_line.account_id=a.id) LEFT JOIN account_account_type act ON (a.user_type_id=act.id) WHERE act.type IN ('receivable','payable') AND account_move_line.partner_id IN %s AND account_move_line.reconciled IS NOT TRUE """ + where_clause + """ GROUP BY account_move_line.partner_id, act.type """, where_params) treated = self.browse() for pid, type, val in self._cr.fetchall(): partner = self.browse(pid) if type == 'receivable': partner.credit = val if partner not in treated: partner.debit = False treated |= partner elif type == 'payable': partner.debit = -val if partner not in treated: partner.credit = False treated |= partner remaining = (self - treated) remaining.debit = False remaining.credit = False def _asset_difference_search(self, account_type, operator, operand): if operator not in ('<', '=', '>', '>=', '<='): return [] if type(operand) not in (float, int): return [] sign = 1 if account_type == 'payable': sign = -1 res = self._cr.execute( ''' SELECT partner.id FROM res_partner partner LEFT JOIN account_move_line aml ON aml.partner_id = partner.id JOIN account_move move ON move.id = aml.move_id RIGHT JOIN account_account acc ON aml.account_id = acc.id WHERE acc.internal_type = %s AND NOT acc.deprecated AND acc.company_id = %s AND move.state = 'posted' GROUP BY partner.id HAVING %s * COALESCE(SUM(aml.amount_residual), 0) ''' + operator + ''' %s''', (account_type, self.env.company.id, sign, operand)) res = self._cr.fetchall() if not res: return [('id', '=', '0')] return [('id', 'in', [r[0] for r in res])] @api.model def _credit_search(self, operator, operand): return self._asset_difference_search('receivable', operator, operand) @api.model def _debit_search(self, operator, operand): return self._asset_difference_search('payable', operator, operand) def _invoice_total(self): self.total_invoiced = 0 if not self.ids: return True all_partners_and_children = {} all_partner_ids = [] for partner in self.filtered('id'): # price_total is in the company currency all_partners_and_children[partner] = self.with_context( active_test=False).search([('id', 'child_of', partner.id)]).ids all_partner_ids += all_partners_and_children[partner] domain = [ ('partner_id', 'in', all_partner_ids), ('state', 'not in', ['draft', 'cancel']), ('move_type', 'in', ('out_invoice', 'out_refund')), ] price_totals = self.env['account.invoice.report'].read_group( domain, ['price_subtotal'], ['partner_id']) for partner, child_ids in all_partners_and_children.items(): partner.total_invoiced = sum( price['price_subtotal'] for price in price_totals if price['partner_id'][0] in child_ids) def _compute_journal_item_count(self): AccountMoveLine = self.env['account.move.line'] for partner in self: partner.journal_item_count = AccountMoveLine.search_count([ ('partner_id', '=', partner.id) ]) def _compute_has_unreconciled_entries(self): for partner in self: # Avoid useless work if has_unreconciled_entries is not relevant for this partner if not partner.active or not partner.is_company and partner.parent_id: partner.has_unreconciled_entries = False continue self.env.cr.execute( """ SELECT 1 FROM( SELECT p.last_time_entries_checked AS last_time_entries_checked, MAX(l.write_date) AS max_date FROM account_move_line l RIGHT JOIN account_account a ON (a.id = l.account_id) RIGHT JOIN res_partner p ON (l.partner_id = p.id) WHERE p.id = %s AND EXISTS ( SELECT 1 FROM account_move_line l WHERE l.account_id = a.id AND l.partner_id = p.id AND l.amount_residual > 0 ) AND EXISTS ( SELECT 1 FROM account_move_line l WHERE l.account_id = a.id AND l.partner_id = p.id AND l.amount_residual < 0 ) GROUP BY p.last_time_entries_checked ) as s WHERE (last_time_entries_checked IS NULL OR max_date > last_time_entries_checked) """, (partner.id, )) partner.has_unreconciled_entries = self.env.cr.rowcount == 1 def mark_as_reconciled(self): self.env['account.partial.reconcile'].check_access_rights('write') return self.sudo().write({ 'last_time_entries_checked': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) }) def _get_company_currency(self): for partner in self: if partner.company_id: partner.currency_id = partner.sudo().company_id.currency_id else: partner.currency_id = self.env.company.currency_id credit = fields.Monetary(compute='_credit_debit_get', search=_credit_search, string='Total Receivable', help="Total amount this customer owes you.") debit = fields.Monetary( compute='_credit_debit_get', search=_debit_search, string='Total Payable', help="Total amount you have to pay to this vendor.") debit_limit = fields.Monetary('Payable Limit') total_invoiced = fields.Monetary( compute='_invoice_total', string="Total Invoiced", groups='account.group_account_invoice,account.group_account_readonly') currency_id = fields.Many2one( 'res.currency', compute='_get_company_currency', readonly=True, string="Currency", help='Utility field to express amount currency') journal_item_count = fields.Integer(compute='_compute_journal_item_count', string="Journal Items") property_account_payable_id = fields.Many2one( 'account.account', company_dependent=True, string="Account Payable", domain= "[('internal_type', '=', 'payable'), ('deprecated', '=', False), ('company_id', '=', current_company_id)]", help= "This account will be used instead of the default one as the payable account for the current partner", required=True) property_account_receivable_id = fields.Many2one( 'account.account', company_dependent=True, string="Account Receivable", domain= "[('internal_type', '=', 'receivable'), ('deprecated', '=', False), ('company_id', '=', current_company_id)]", help= "This account will be used instead of the default one as the receivable account for the current partner", required=True) property_account_position_id = fields.Many2one( 'account.fiscal.position', company_dependent=True, string="Fiscal Position", domain="[('company_id', '=', current_company_id)]", help= "The fiscal position determines the taxes/accounts used for this contact." ) property_payment_term_id = fields.Many2one( 'account.payment.term', company_dependent=True, string='Customer Payment Terms', domain="[('company_id', 'in', [current_company_id, False])]", help= "This payment term will be used instead of the default one for sales orders and customer invoices" ) property_supplier_payment_term_id = fields.Many2one( 'account.payment.term', company_dependent=True, string='Vendor Payment Terms', domain="[('company_id', 'in', [current_company_id, False])]", help= "This payment term will be used instead of the default one for purchase orders and vendor bills" ) ref_company_ids = fields.One2many( 'res.company', 'partner_id', string='Companies that refers to partner') has_unreconciled_entries = fields.Boolean( compute='_compute_has_unreconciled_entries', help= "The partner has at least one unreconciled debit and credit since last time the invoices & payments matching was performed." ) last_time_entries_checked = fields.Datetime( string='Latest Invoices & Payments Matching Date', readonly=True, copy=False, help= 'Last time the invoices & payments matching was performed for this partner. ' 'It is set either if there\'s not at least an unreconciled debit and an unreconciled credit ' 'or if you click the "Done" button.') invoice_ids = fields.One2many('account.move', 'partner_id', string='Invoices', readonly=True, copy=False) contract_ids = fields.One2many('account.analytic.account', 'partner_id', string='Partner Contracts', readonly=True) bank_account_count = fields.Integer(compute='_compute_bank_count', string="Bank") trust = fields.Selection([('good', 'Good Debtor'), ('normal', 'Normal Debtor'), ('bad', 'Bad Debtor')], string='Degree of trust you have in this debtor', default='normal', company_dependent=True) invoice_warn = fields.Selection(WARNING_MESSAGE, 'Invoice', help=WARNING_HELP, default="no-message") invoice_warn_msg = fields.Text('Message for Invoice') # Computed fields to order the partners as suppliers/customers according to the # amount of their generated incoming/outgoing account moves supplier_rank = fields.Integer(default=0, copy=False) customer_rank = fields.Integer(default=0, copy=False) duplicated_bank_account_partners_count = fields.Integer( compute='_compute_duplicated_bank_account_partners_count', help= 'Technical field holding the amount partners that share the same account number as any set on this partner.', ) def _get_name_search_order_by_fields(self): res = super()._get_name_search_order_by_fields() partner_search_mode = self.env.context.get('res_partner_search_mode') if not partner_search_mode in ('customer', 'supplier'): return res order_by_field = 'COALESCE(res_partner.%s, 0) DESC,' if partner_search_mode == 'customer': field = 'customer_rank' else: field = 'supplier_rank' order_by_field = order_by_field % field return '%s, %s' % (res, order_by_field % field) if res else order_by_field def _compute_bank_count(self): bank_data = self.env['res.partner.bank'].read_group( [('partner_id', 'in', self.ids)], ['partner_id'], ['partner_id']) mapped_data = dict([(bank['partner_id'][0], bank['partner_id_count']) for bank in bank_data]) for partner in self: partner.bank_account_count = mapped_data.get(partner.id, 0) def _get_duplicated_bank_accounts(self): self.ensure_one() if not self.bank_ids: return self.env['res.partner.bank'] domains = [] for bank in self.bank_ids: domains.append([('acc_number', '=', bank.acc_number), ('bank_id', '=', bank.bank_id.id)]) domain = expression.OR(domains) if self.company_id: domain = expression.AND( [domain, [('company_id', 'in', (False, self.company_id.id))]]) domain = expression.AND( [domain, [('partner_id', '!=', self._origin.id)]]) return self.env['res.partner.bank'].search(domain) @api.depends('bank_ids') def _compute_duplicated_bank_account_partners_count(self): for partner in self: partner.duplicated_bank_account_partners_count = len( partner._get_duplicated_bank_accounts()) def _find_accounting_partner(self, partner): ''' Find the partner for which the accounting entries will be created ''' return partner.commercial_partner_id @api.model def _commercial_fields(self): return super(ResPartner, self)._commercial_fields() + \ ['debit_limit', 'property_account_payable_id', 'property_account_receivable_id', 'property_account_position_id', 'property_payment_term_id', 'property_supplier_payment_term_id', 'last_time_entries_checked'] def action_view_partner_invoices(self): self.ensure_one() action = self.env["ir.actions.actions"]._for_xml_id( "account.action_move_out_invoice_type") action['domain'] = [ ('move_type', 'in', ('out_invoice', 'out_refund')), ('partner_id', 'child_of', self.id), ] action['context'] = { 'default_move_type': 'out_invoice', 'move_type': 'out_invoice', 'journal_type': 'sale', 'search_default_open': 1 } return action def action_view_partner_with_same_bank(self): self.ensure_one() partners = self._get_duplicated_bank_accounts() # Open a list view or form view of the partner(s) with the same bank accounts if self.duplicated_bank_account_partners_count == 1: action_vals = { 'type': 'ir.actions.act_window', 'res_model': 'res.partner', 'view_mode': 'form', 'res_id': partners.id, 'views': [(False, 'form')], } else: action_vals = { 'name': _("Partners"), 'type': 'ir.actions.act_window', 'res_model': 'res.partner', 'view_mode': 'tree,form', 'views': [(False, 'list'), (False, 'form')], 'domain': [('id', 'in', partners.ids)], } return action_vals def can_edit_vat(self): ''' Can't edit `vat` if there is (non draft) issued invoices. ''' can_edit_vat = super(ResPartner, self).can_edit_vat() if not can_edit_vat: return can_edit_vat has_invoice = self.env['account.move'].search( [('move_type', 'in', ['out_invoice', 'out_refund']), ('partner_id', 'child_of', self.commercial_partner_id.id), ('state', '=', 'posted')], limit=1) return can_edit_vat and not (bool(has_invoice)) @api.model_create_multi def create(self, vals_list): search_partner_mode = self.env.context.get('res_partner_search_mode') is_customer = search_partner_mode == 'customer' is_supplier = search_partner_mode == 'supplier' if search_partner_mode: for vals in vals_list: if is_customer and 'customer_rank' not in vals: vals['customer_rank'] = 1 elif is_supplier and 'supplier_rank' not in vals: vals['supplier_rank'] = 1 return super().create(vals_list) def _increase_rank(self, field, n=1): if self.ids and field in ['customer_rank', 'supplier_rank']: try: with self.env.cr.savepoint(flush=False): query = sql.SQL(""" SELECT {field} FROM res_partner WHERE ID IN %(partner_ids)s FOR UPDATE NOWAIT; UPDATE res_partner SET {field} = {field} + %(n)s WHERE id IN %(partner_ids)s """).format(field=sql.Identifier(field)) self.env.cr.execute(query, { 'partner_ids': tuple(self.ids), 'n': n }) for partner in self: self.env.cache.remove(partner, partner._fields[field]) except DatabaseError as e: if e.pgcode == '55P03': _logger.debug( 'Another transaction already locked partner rows. Cannot update partner ranks.' ) else: raise e
class ResCompany(models.Model): _inherit = "res.company" @api.multi def _compute_l10n_br_data(self): """ Read the l10n_br specific functional fields. """ super(ResCompany, self)._compute_l10n_br_data() for c in self: c.tax_framework = c.partner_id.tax_framework c.cnae_main_id = c.partner_id.cnae_main_id def _inverse_cnae_main_id(self): """ Write the l10n_br specific functional fields. """ for c in self: c.partner_id.cnae_main_id = c.cnae_main_id def _inverse_tax_framework(self): """ Write the l10n_br specific functional fields. """ for c in self: c.partner_id.tax_framework = c.tax_framework @api.depends("simplifed_tax_id", "annual_revenue") def _compute_simplifed_tax_range(self): for record in self: tax_range = record.env["l10n_br_fiscal.simplified.tax.range"].search( [ ("inital_revenue", "<=", record.annual_revenue), ("final_revenue", ">=", record.annual_revenue), ], limit=1, ) if tax_range: record.simplifed_tax_range_id = tax_range.id cnae_main_id = fields.Many2one( comodel_name="l10n_br_fiscal.cnae", compute="_compute_l10n_br_data", inverse="_inverse_cnae_main_id", domain="[('internal_type', '=', 'normal'), " "('id', 'not in', cnae_secondary_ids)]", string="Main CNAE") cnae_secondary_ids = fields.Many2many( comodel_name="l10n_br_fiscal.cnae", relation="res_company_fiscal_cnae_rel", colunm1="company_id", colunm2="cnae_id", domain="[('internal_type', '=', 'normal'), " "('id', '!=', cnae_main_id)]", string="Secondary CNAE") tax_framework = fields.Selection( selection=TAX_FRAMEWORK, default=TAX_FRAMEWORK_NORMAL, compute="_compute_l10n_br_data", inverse="_inverse_tax_framework", string="Tax Framework") profit_calculation = fields.Selection( selection=PROFIT_CALCULATION, default=PROFIT_CALCULATION_PRESUMED, string="Profit Calculation") is_industry = fields.Boolean( string="Is Industry", help="If your company is industry or ......", default=False) industry_type = fields.Selection( selection=INDUSTRY_TYPE, default=INDUSTRY_TYPE_TRANSFORMATION, string="Industry Type") annual_revenue = fields.Monetary( string="Annual Revenue", currency_field="currency_id", default=0.00, digits=dp.get_precision("Fiscal Documents")) simplifed_tax_id = fields.Many2one( comodel_name="l10n_br_fiscal.simplified.tax", domain="[('cnae_ids', '=', cnae_main_id)]", string="Simplified Tax") simplifed_tax_range_id = fields.Many2one( comodel_name="l10n_br_fiscal.simplified.tax.range", domain="[('simplified_tax_id', '=', simplifed_tax_id)]", compute="_compute_simplifed_tax_range", store=True, readyonly=True, string="Simplified Tax Range") simplifed_tax_percent = fields.Float( string="Simplifed Tax Percent", default=0.00, related="simplifed_tax_range_id.total_tax_percent", digits=dp.get_precision("Fiscal Tax Percent")) ibpt_api = fields.Boolean( string="Use IBPT API", default=False) ibpt_token = fields.Char( string="IBPT Token") ibpt_update_days = fields.Integer( string="IBPT Token Updates", default=15) certificate_ecnpj_id = fields.Many2one( comodel_name="l10n_br_fiscal.certificate", string="E-CNPJ", domain="[('type', '=', 'e-cnpj'), ('is_valid', '=', True)]") certificate_nfe_id = fields.Many2one( comodel_name="l10n_br_fiscal.certificate", string="NFe", domain="[('type', '=', 'nf-e'), ('is_valid', '=', True)]") accountant_id = fields.Many2one( comodel_name="res.partner", string="Accountant") technical_support_id = fields.Many2one( comodel_name="res.partner", string="Technical Support") piscofins_id = fields.Many2one( comodel_name="l10n_br_fiscal.tax.pis.cofins", string="PIS/COFINS", domain="[('piscofins_type', '=', 'company')]") tax_cofins_wh_id = fields.Many2one( comodel_name="l10n_br_fiscal.tax", string="Default COFINS RET", domain=[('tax_domain', '=', TAX_DOMAIN_COFINS_WH)]) tax_pis_wh_id = fields.Many2one( comodel_name="l10n_br_fiscal.tax", string="Default PIS RET", domain=[('tax_domain', '=', TAX_DOMAIN_PIS_WH)]) tax_csll_id = fields.Many2one( comodel_name="l10n_br_fiscal.tax", string="Default CSLL", domain=[('tax_domain', '=', TAX_DOMAIN_CSLL)]) tax_csll_wh_id = fields.Many2one( comodel_name="l10n_br_fiscal.tax", string="Default CSLL RET", domain=[('tax_domain', '=', TAX_DOMAIN_CSLL_WH)]) ripi = fields.Boolean( string="RIPI") tax_ipi_id = fields.Many2one( comodel_name="l10n_br_fiscal.tax", string="Default IPI", domain=[("tax_domain", "=", TAX_DOMAIN_IPI)]) tax_icms_id = fields.Many2one( comodel_name="l10n_br_fiscal.tax", string="Default ICMS", domain=[('tax_domain', 'in', (TAX_DOMAIN_ICMS, TAX_DOMAIN_ICMS_SN))]) icms_regulation_id = fields.Many2one( comodel_name="l10n_br_fiscal.icms.regulation", string="ICMS Regulation") tax_issqn_id = fields.Many2one( comodel_name="l10n_br_fiscal.tax", string="Default ISSQN", domain=[('tax_domain', '=', TAX_DOMAIN_ISSQN)]) tax_issqn_wh_id = fields.Many2one( comodel_name="l10n_br_fiscal.tax", string="Default ISSQN RET", domain=[('tax_domain', '=', TAX_DOMAIN_ISSQN_WH)]) tax_irpj_id = fields.Many2one( comodel_name="l10n_br_fiscal.tax", string="Default IRPJ", domain=[('tax_domain', '=', TAX_DOMAIN_IRPJ)]) tax_irpj_wh_id = fields.Many2one( comodel_name="l10n_br_fiscal.tax", string="Default IRPJ RET", domain=[('tax_domain', '=', TAX_DOMAIN_IRPJ_WH)]) tax_inss_id = fields.Many2one( comodel_name="l10n_br_fiscal.tax", string="Default INSS", domain=[('tax_domain', '=', TAX_DOMAIN_INSS)]) tax_inss_wh_id = fields.Many2one( comodel_name="l10n_br_fiscal.tax", string="Default INSS RET", domain=[('tax_domain', '=', TAX_DOMAIN_INSS_WH)]) tax_definition_ids = fields.One2many( comodel_name="l10n_br_fiscal.tax.definition", inverse_name="company_id", string="Tax Definition") processador_edoc = fields.Selection( selection=PROCESSADOR, string='Processador documentos eletrônicos', default=PROCESSADOR_NENHUM) document_type_id = fields.Many2one( comodel_name='l10n_br_fiscal.document.type', string='Default Document Type') def _del_tax_definition(self, tax_domain): tax_def = self.tax_definition_ids.filtered( lambda d: d.tax_group_id.tax_domain != tax_domain ) self.tax_definition_ids = tax_def def _set_tax_definition(self, tax): tax_def = self.tax_definition_ids.filtered( lambda d: d.tax_group_id == tax.tax_group_id ) tax_def_values = { "type_in_out": "out", "tax_group_id": tax.tax_group_id.id, "is_taxed": True, "is_debit_credit": True, "custom_tax": True, "tax_id": tax.id, "cst_id": tax.cst_out_id.id, "company_id": self._origin.id, } if tax_def: tax_def.update(tax_def_values) else: self.tax_definition_ids |= self.tax_definition_ids.create(tax_def_values) @api.onchange("cnae_main_id") def _onchange_cnae_main_id(self): if self.cnae_main_id: simplified_tax = self.env["l10n_br_fiscal.simplified.tax"].search( [("cnae_ids", "=", self.cnae_main_id.id)], limit=1) if simplified_tax: self.simplifed_tax_id = simplified_tax.id @api.onchange("profit_calculation", "tax_framework") def _onchange_profit_calculation(self): # Get all Simples Nacional default taxes sn_piscofins_id = self.env.ref("l10n_br_fiscal.tax_pis_cofins_simples_nacional") sn_tax_icms_id = self.env.ref("l10n_br_fiscal.tax_icms_sn_com_credito") # If Tax Framework is Simples Nacional if self.tax_framework in TAX_FRAMEWORK_SIMPLES_ALL: # Set taxes self.piscofins_id = sn_piscofins_id self.tax_icms_id = sn_tax_icms_id # If Tax Framework is Regine Normal if self.tax_framework == TAX_FRAMEWORK_NORMAL: pis_cofins_refs = { "real": self.env.ref("l10n_br_fiscal.tax_pis_cofins_nao_columativo"), "presumed": self.env.ref("l10n_br_fiscal.tax_pis_cofins_columativo"), "arbitrary": self.env.ref("l10n_br_fiscal.tax_pis_cofins_columativo"), } self.piscofins_id = pis_cofins_refs.get(self.profit_calculation) self.tax_icms_id = False self._onchange_cnae_main_id() self._onchange_piscofins_id() self._onchange_ripi() self._onchange_tax_ipi_id() self._onchange_tax_icms_id() self._onchange_tax_issqn_id() self._onchange_tax_csll_id() self._onchange_tax_irpj_id() self._onchange_tax_inss_id() self._onchange_tax_issqn_wh_id() self._onchange_tax_pis_wh_id() self._onchange_tax_cofins_wh_id() self._onchange_tax_csll_wh_id() self._onchange_tax_irpj_wh_id() self._onchange_tax_inss_wh_id() @api.onchange("is_industry") def _onchange_is_industry(self): if self.is_industry and self.tax_framework == TAX_FRAMEWORK_SIMPLES: self.ripi = True else: self.ripi = False @api.onchange("ripi") def _onchange_ripi(self): if not self.ripi and self.tax_framework == TAX_FRAMEWORK_NORMAL: self.tax_ipi_id = self.env.ref("l10n_br_fiscal.tax_ipi_nt") elif self.tax_framework in TAX_FRAMEWORK_SIMPLES_ALL: self.tax_ipi_id = self.env.ref("l10n_br_fiscal.tax_ipi_simples_nacional") self.ripi = False else: self.tax_ipi_id = False @api.onchange("piscofins_id") def _onchange_piscofins_id(self): if self.piscofins_id: self._set_tax_definition(self.piscofins_id.tax_cofins_id) self._set_tax_definition(self.piscofins_id.tax_pis_id) else: self._del_tax_definition(TAX_DOMAIN_PIS) self._del_tax_definition(TAX_DOMAIN_COFINS) @api.onchange("tax_pis_wh_id") def _onchange_tax_pis_wh_id(self): if self.tax_pis_wh_id: self._set_tax_definition(self.tax_pis_wh_id) else: self._del_tax_definition(TAX_DOMAIN_PIS_WH) @api.onchange("tax_cofins_wh_id") def _onchange_tax_cofins_wh_id(self): if self.tax_cofins_wh_id: self._set_tax_definition(self.tax_cofins_wh_id) else: self._del_tax_definition(TAX_DOMAIN_COFINS_WH) @api.onchange("tax_csll_id") def _onchange_tax_csll_id(self): if self.tax_csll_id: self._set_tax_definition(self.tax_csll_id) else: self._del_tax_definition(TAX_DOMAIN_CSLL) @api.onchange("tax_csll_wh_id") def _onchange_tax_csll_wh_id(self): if self.tax_csll_wh_id: self._set_tax_definition(self.tax_csll_wh_id) else: self._del_tax_definition(TAX_DOMAIN_CSLL_WH) @api.onchange("tax_ipi_id") def _onchange_tax_ipi_id(self): if self.tax_ipi_id: self._set_tax_definition(self.tax_ipi_id) else: self._del_tax_definition(TAX_DOMAIN_IPI) @api.onchange("tax_icms_id") def _onchange_tax_icms_id(self): if self.tax_icms_id: self._set_tax_definition(self.tax_icms_id) else: self._del_tax_definition(TAX_DOMAIN_ICMS) self._del_tax_definition(TAX_DOMAIN_ICMS_SN) @api.onchange("tax_issqn_id") def _onchange_tax_issqn_id(self): if self.tax_issqn_id: self._set_tax_definition(self.tax_issqn_id) else: self._del_tax_definition(TAX_DOMAIN_ISSQN) @api.onchange("tax_issqn_wh_id") def _onchange_tax_issqn_wh_id(self): if self.tax_issqn_wh_id: self._set_tax_definition(self.tax_issqn_wh_id) else: self._del_tax_definition(TAX_DOMAIN_ISSQN_WH) @api.onchange("tax_irpj_id") def _onchange_tax_irpj_id(self): if self.tax_irpj_id: self._set_tax_definition(self.tax_irpj_id) else: self._del_tax_definition(TAX_DOMAIN_IRPJ) @api.onchange("tax_irpj_wh_id") def _onchange_tax_irpj_wh_id(self): if self.tax_irpj_wh_id: self._set_tax_definition(self.tax_irpj_wh_id) else: self._del_tax_definition(TAX_DOMAIN_IRPJ_WH) @api.onchange("tax_inss_id") def _onchange_tax_inss_id(self): if self.tax_inss_id: self._set_tax_definition(self.tax_inss_id) else: self._del_tax_definition(TAX_DOMAIN_INSS) @api.onchange("tax_inss_wh_id") def _onchange_tax_inss_wh_id(self): if self.tax_inss_wh_id: self._set_tax_definition(self.tax_inss_wh_id) else: self._del_tax_definition(TAX_DOMAIN_INSS_WH)
class AccountMoveReversal(models.TransientModel): """ Account move reversal wizard, it cancel an account move by reversing it. """ _name = 'account.move.reversal' _description = 'Account Move Reversal' @api.model def _get_default_move(self): if self._context.get('active_id'): move = self.env['account.move'].browse(self._context['active_id']) if move.state != 'posted' or move.type in ('out_refund', 'in_refund'): raise UserError(_('Only posted journal entries being not already a refund can be reversed.')) return move return self.env['account.move'] @api.model def _get_default_reason(self): move = self._get_default_move() return move and move.invoice_payment_ref or False move_id = fields.Many2one('account.move', string='Journal Entry', default=_get_default_move, domain=[('state', '=', 'posted'), ('type', 'not in', ('out_refund', 'in_refund'))]) date = fields.Date(string='Reversal date', default=fields.Date.context_today, required=True) reason = fields.Char(string='Reason', default=_get_default_reason) refund_method = fields.Selection(selection=[ ('refund', 'Create a draft credit note (partial refunding)'), ('cancel', 'Cancel: create credit note and reconcile (full refunding)'), ('modify', 'Create credit note, reconcile and create a new draft invoice (cancel)') ], default='refund', string='Credit Method', required=True, help='Choose how you want to credit this invoice. You cannot "modify" nor "cancel" if the invoice is already reconciled.') journal_id = fields.Many2one('account.journal', string='Use Specific Journal', help='If empty, uses the journal of the journal entry to be reversed.') # related fields residual = fields.Monetary(related='move_id.amount_residual') currency_id = fields.Many2one(related='move_id.currency_id') move_type = fields.Selection(related='move_id.type') def reverse_moves(self): moves = self.move_id or self.env['account.move'].browse(self._context['active_ids']) # Create default values. default_values_list = [] for move in moves: default_values_list.append({ 'ref': _('Reversal of: %s') % move.name, 'invoice_payment_ref': self.reason, 'date': self.date or move.date, 'invoice_date': move.is_invoice(include_receipts=True) and (self.date or move.date) or False, 'journal_id': self.journal_id and self.journal_id.id or move.journal_id.id, }) # Handle reverse method. if self.refund_method == 'cancel' or (moves and moves[0].type == 'entry'): new_moves = moves._reverse_moves(default_values_list, cancel=True) elif self.refund_method == 'modify': new_moves = moves._reverse_moves(default_values_list, cancel=True) moves_vals_list = [] for move in moves.with_context(include_business_fields=True): moves_vals_list.append(move.copy_data({ 'invoice_payment_ref': move.name, 'date': self.date or move.date, })[0]) new_moves = moves.create(moves_vals_list) elif self.refund_method == 'refund': new_moves = moves._reverse_moves(default_values_list) else: return # Create action. action = { 'name': _('Reverse Moves'), 'type': 'ir.actions.act_window', 'res_model': 'account.move', } if len(new_moves) == 1: action.update({ 'view_mode': 'form', 'res_id': new_moves.id, }) else: action.update({ 'view_mode': 'tree,form', 'domain': [('id', 'in', new_moves.ids)], }) return action
class ProjectProductEmployeeMap(models.Model): _name = 'project.sale.line.employee.map' _description = 'Project Sales line, employee mapping' project_id = fields.Many2one('project.project', "Project", required=True) employee_id = fields.Many2one('hr.employee', "Employee", required=True) sale_line_id = fields.Many2one('sale.order.line', "Sale Order Item", compute="_compute_sale_line_id", store=True, readonly=False, domain="""[ ('is_service', '=', True), ('is_expense', '=', False), ('state', 'in', ['sale', 'done']), ('order_partner_id', '=?', partner_id), '|', ('company_id', '=', False), ('company_id', '=', company_id)]""" ) company_id = fields.Many2one('res.company', string='Company', related='project_id.company_id') partner_id = fields.Many2one(related='project_id.partner_id') price_unit = fields.Float("Unit Price", compute='_compute_price_unit', store=True, readonly=True) currency_id = fields.Many2one('res.currency', string="Currency", compute='_compute_currency_id', store=True, readonly=False) cost = fields.Monetary( currency_field='cost_currency_id', compute='_compute_cost', store=True, readonly=False, help= "This cost overrides the employee's default timesheet cost in employee's HR Settings" ) cost_currency_id = fields.Many2one('res.currency', string="Cost Currency", related='employee_id.currency_id', readonly=True) is_cost_changed = fields.Boolean('Is Cost Manually Changed', compute='_compute_is_cost_changed', store=True) _sql_constraints = [ ('uniqueness_employee', 'UNIQUE(project_id,employee_id)', 'An employee cannot be selected more than once in the mapping. Please remove duplicate(s) and try again.' ), ] @api.depends('partner_id') def _compute_sale_line_id(self): self.filtered( lambda map_entry: map_entry.sale_line_id and map_entry.partner_id and map_entry.sale_line_id.order_partner_id.commercial_partner_id != map_entry.partner_id.commercial_partner_id).update( {'sale_line_id': False}) @api.depends('sale_line_id.price_unit') def _compute_price_unit(self): for line in self: if line.sale_line_id: line.price_unit = line.sale_line_id.price_unit else: line.price_unit = 0 @api.depends('sale_line_id.price_unit') def _compute_currency_id(self): for line in self: line.currency_id = line.sale_line_id.currency_id if line.sale_line_id else False @api.depends('employee_id.timesheet_cost') def _compute_cost(self): for map_entry in self: if not map_entry.is_cost_changed: map_entry.cost = map_entry.employee_id.timesheet_cost or 0.0 @api.depends('cost') def _compute_is_cost_changed(self): for map_entry in self: map_entry.is_cost_changed = map_entry.employee_id and map_entry.cost != map_entry.employee_id.timesheet_cost @api.model def create(self, values): res = super(ProjectProductEmployeeMap, self).create(values) res._update_project_timesheet() return res def write(self, values): res = super(ProjectProductEmployeeMap, self).write(values) self._update_project_timesheet() return res def _update_project_timesheet(self): self.filtered(lambda l: l.sale_line_id ).project_id._update_timesheets_sale_line_id()
class account_payment(models.Model): _name = "account.payment" _inherit = ['mail.thread', 'mail.activity.mixin'] _description = "Payments" _order = "payment_date desc, name desc" name = fields.Char(readonly=True, copy=False) # The name is attributed upon post() payment_reference = fields.Char( copy=False, readonly=True, help= "Reference of the document used to issue this payment. Eg. check number, file name, etc." ) move_name = fields.Char( string='Journal Entry Name', readonly=True, default=False, copy=False, help= "Technical field holding the number given to the journal entry, automatically set when the statement line is reconciled then stored to set the same number again if the line is cancelled, set to draft and re-processed again." ) # Money flows from the journal_id's default_debit_account_id or default_credit_account_id to the destination_account_id destination_account_id = fields.Many2one( 'account.account', compute='_compute_destination_account_id', readonly=True) # For money transfer, money goes from journal_id to a transfer account, then from the transfer account to destination_journal_id destination_journal_id = fields.Many2one( 'account.journal', string='Transfer To', domain=[('type', 'in', ('bank', 'cash'))], readonly=True, states={'draft': [('readonly', False)]}) invoice_ids = fields.Many2many( 'account.invoice', 'account_invoice_payment_rel', 'payment_id', 'invoice_id', string="Invoices", copy=False, readonly=True, help= """Technical field containing the invoice for which the payment has been generated. This does not especially correspond to the invoices reconciled with the payment, as it can have been generated first, and reconciled later""" ) reconciled_invoice_ids = fields.Many2many( 'account.invoice', string='Reconciled Invoices', compute='_compute_reconciled_invoice_ids', help= "Invoices whose journal items have been reconciled with this payment's." ) has_invoices = fields.Boolean( compute="_compute_reconciled_invoice_ids", help="Technical field used for usability purposes") move_line_ids = fields.One2many('account.move.line', 'payment_id', readonly=True, copy=False, ondelete='restrict') move_reconciled = fields.Boolean(compute="_get_move_reconciled", readonly=True) state = fields.Selection([('draft', 'Draft'), ('posted', 'Posted'), ('sent', 'Sent'), ('reconciled', 'Reconciled'), ('cancelled', 'Cancelled')], readonly=True, default='draft', copy=False, string="Status") payment_type = fields.Selection([('outbound', 'Send Money'), ('inbound', 'Receive Money'), ('transfer', 'Internal Transfer')], string='Payment Type', required=True, readonly=True, states={'draft': [('readonly', False)]}) payment_method_id = fields.Many2one('account.payment.method', string='Payment Method Type', required=True, readonly=True, states={'draft': [('readonly', False)]}, oldname="payment_method", help="Manual: Get paid by cash, check or any other method outside of Odoo.\n"\ "Electronic: Get paid automatically through a payment acquirer by requesting a transaction on a card saved by the customer when buying or subscribing online (payment token).\n"\ "Check: Pay bill by check and print it from Odoo.\n"\ "Batch Deposit: Encase several customer checks at once by generating a batch deposit to submit to your bank. When encoding the bank statement in Odoo, you are suggested to reconcile the transaction with the batch deposit.To enable batch deposit, module account_batch_payment must be installed.\n"\ "SEPA Credit Transfer: Pay bill from a SEPA Credit Transfer file you submit to your bank. To enable sepa credit transfer, module account_sepa must be installed ") payment_method_code = fields.Char( related='payment_method_id.code', help= "Technical field used to adapt the interface to the payment type selected.", readonly=True) partner_type = fields.Selection([('customer', 'Customer'), ('supplier', 'Vendor')], tracking=True, readonly=True, states={'draft': [('readonly', False)]}) partner_id = fields.Many2one('res.partner', string='Partner', tracking=True, readonly=True, states={'draft': [('readonly', False)]}) amount = fields.Monetary(string='Payment Amount', required=True, readonly=True, states={'draft': [('readonly', False)]}, tracking=True) currency_id = fields.Many2one( 'res.currency', string='Currency', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env.company_id.currency_id) payment_date = fields.Date(string='Payment Date', default=fields.Date.context_today, required=True, readonly=True, states={'draft': [('readonly', False)]}, copy=False, tracking=True) communication = fields.Char(string='Memo', readonly=True, states={'draft': [('readonly', False)]}) journal_id = fields.Many2one('account.journal', string='Payment Journal', required=True, readonly=True, states={'draft': [('readonly', False)]}, tracking=True, domain=[('type', 'in', ('bank', 'cash'))]) company_id = fields.Many2one('res.company', related='journal_id.company_id', string='Company', readonly=True) hide_payment_method = fields.Boolean( compute='_compute_hide_payment_method', help="Technical field used to hide the payment method if the" "selected journal has only one available which is 'manual'") payment_difference = fields.Monetary(compute='_compute_payment_difference', readonly=True) payment_difference_handling = fields.Selection( [('open', 'Keep open'), ('reconcile', 'Mark invoice as fully paid')], default='open', string="Payment Difference Handling", copy=False) writeoff_account_id = fields.Many2one('account.account', string="Difference Account", domain=[('deprecated', '=', False)], copy=False) writeoff_label = fields.Char( string='Journal Item Label', help= 'Change label of the counterpart that will hold the payment difference', default='Write-Off') partner_bank_account_id = fields.Many2one( 'res.partner.bank', string="Recipient Bank Account", readonly=True, states={'draft': [('readonly', False)]}) show_partner_bank_account = fields.Boolean( compute='_compute_show_partner_bank', help= 'Technical field used to know whether the field `partner_bank_account_id` needs to be displayed or not in the payments form views' ) require_partner_bank_account = fields.Boolean( compute='_compute_show_partner_bank', help= 'Technical field used to know whether the field `partner_bank_account_id` needs to be required or not in the payments form views' ) @api.model def default_get(self, fields): rec = super(account_payment, self).default_get(fields) active_ids = self._context.get('active_ids') or self._context.get( 'active_id') active_model = self._context.get('active_model') # Check for selected invoices ids if not active_ids or active_model != 'account.invoice': return rec invoices = self.env['account.invoice'].browse(active_ids) # Check all invoices are open if any(invoice.state != 'open' for invoice in invoices): raise UserError( _("You can only register payments for open invoices")) # Check if, in batch payments, there are not negative invoices and positive invoices dtype = invoices[0].type for inv in invoices[1:]: if inv.type != dtype: if ((dtype == 'in_refund' and inv.type == 'in_invoice') or (dtype == 'in_invoice' and inv.type == 'in_refund')): raise UserError( _("You cannot register payments for vendor bills and supplier refunds at the same time." )) if ((dtype == 'out_refund' and inv.type == 'out_invoice') or (dtype == 'out_invoice' and inv.type == 'out_refund')): raise UserError( _("You cannot register payments for customer invoices and credit notes at the same time." )) amount = self._compute_payment_amount(invoices, invoices[0].currency_id) rec.update({ 'currency_id': invoices[0].currency_id.id, 'amount': abs(amount), 'payment_type': 'inbound' if amount > 0 else 'outbound', 'partner_id': invoices[0].commercial_partner_id.id, 'partner_type': MAP_INVOICE_TYPE_PARTNER_TYPE[invoices[0].type], 'communication': invoices[0].reference or invoices[0].number, 'invoice_ids': [(6, 0, invoices.ids)], }) return rec @api.one @api.constrains('amount') def _check_amount(self): if self.amount < 0: raise ValidationError(_('The payment amount cannot be negative.')) @api.model def _get_method_codes_using_bank_account(self): return [] @api.model def _get_method_codes_needing_bank_account(self): return [] @api.depends('payment_method_code') def _compute_show_partner_bank(self): """ Computes if the destination bank account must be displayed in the payment form view. By default, it won't be displayed but some modules might change that, depending on the payment type.""" for payment in self: payment.show_partner_bank_account = payment.payment_method_code in self._get_method_codes_using_bank_account( ) payment.require_partner_bank_account = payment.payment_method_code in self._get_method_codes_needing_bank_account( ) @api.multi @api.depends('payment_type', 'journal_id') def _compute_hide_payment_method(self): for payment in self: if not payment.journal_id or payment.journal_id.type not in [ 'bank', 'cash' ]: payment.hide_payment_method = True continue journal_payment_methods = payment.payment_type == 'inbound'\ and payment.journal_id.inbound_payment_method_ids\ or payment.journal_id.outbound_payment_method_ids payment.hide_payment_method = len( journal_payment_methods ) == 1 and journal_payment_methods[0].code == 'manual' @api.depends('invoice_ids', 'amount', 'payment_date', 'currency_id', 'payment_type') def _compute_payment_difference(self): for pay in self.filtered( lambda p: p.invoice_ids and p.state == 'draft'): payment_amount = -pay.amount if pay.payment_type == 'outbound' else pay.amount pay.payment_difference = pay._compute_payment_amount( ) - payment_amount @api.onchange('journal_id') def _onchange_journal(self): if self.journal_id: # Set default payment method (we consider the first to be the default one) payment_methods = self.payment_type == 'inbound' and self.journal_id.inbound_payment_method_ids or self.journal_id.outbound_payment_method_ids payment_methods_list = payment_methods.ids default_payment_method_id = self.env.context.get( 'default_payment_method_id') if default_payment_method_id: # Ensure the domain will accept the provided default value payment_methods_list.append(default_payment_method_id) else: self.payment_method_id = payment_methods and payment_methods[ 0] or False # Set payment method domain (restrict to methods enabled for the journal and to selected payment type) payment_type = self.payment_type in ( 'outbound', 'transfer') and 'outbound' or 'inbound' domain = { 'payment_method_id': [('payment_type', '=', payment_type), ('id', 'in', payment_methods_list)] } if self.env.context.get('active_model') == 'account.invoice': active_ids = self._context.get('active_ids') invoices = self.env['account.invoice'].browse(active_ids) self.amount = abs(self._compute_payment_amount(invoices)) return {'domain': domain} return {} @api.onchange('partner_id') def _onchange_partner_id(self): if self.invoice_ids and self.invoice_ids[0].partner_bank_id: self.partner_bank_account_id = self.invoice_ids[0].partner_bank_id elif self.partner_id != self.partner_bank_account_id.partner_id: # This condition ensures we use the default value provided into # context for partner_bank_account_id properly when provided with a # default partner_id. Without it, the onchange recomputes the bank account # uselessly and might assign a different value to it. if self.partner_id and len(self.partner_id.bank_ids) > 0: self.partner_bank_account_id = self.partner_id.bank_ids[0] elif self.partner_id and len( self.partner_id.commercial_partner_id.bank_ids) > 0: self.partner_bank_account_id = self.partner_id.commercial_partner_id.bank_ids[ 0] else: self.partner_bank_account_id = False return { 'domain': { 'partner_bank_account_id': [('partner_id', 'in', [ self.partner_id.id, self.partner_id.commercial_partner_id.id ])] } } @api.onchange('partner_type') def _onchange_partner_type(self): self.ensure_one() # Set partner_id domain if self.partner_type: return {'domain': {'partner_id': [(self.partner_type, '=', True)]}} @api.onchange('payment_type') def _onchange_payment_type(self): if not self.invoice_ids and not self.partner_type: # Set default partner type for the payment type if self.payment_type == 'inbound': self.partner_type = 'customer' elif self.payment_type == 'outbound': self.partner_type = 'supplier' elif self.payment_type not in ('inbound', 'outbound'): self.partner_type = False # Set payment method domain res = self._onchange_journal() if not res.get('domain', {}): res['domain'] = {} jrnl_filters = self._compute_journal_domain_and_types() journal_types = jrnl_filters['journal_types'] journal_types.update(['bank', 'cash']) res['domain']['journal_id'] = jrnl_filters['domain'] + [ ('type', 'in', list(journal_types)) ] return res def _compute_journal_domain_and_types(self): journal_type = ['bank', 'cash'] domain = [] if self.invoice_ids: domain.append( ('company_id', '=', self.invoice_ids[0].company_id.id)) if self.currency_id.is_zero(self.amount) and self.has_invoices: # In case of payment with 0 amount, allow to select a journal of type 'general' like # 'Miscellaneous Operations' and set this journal by default. journal_type = ['general'] self.payment_difference_handling = 'reconcile' else: if self.payment_type == 'inbound': domain.append(('at_least_one_inbound', '=', True)) else: domain.append(('at_least_one_outbound', '=', True)) return {'domain': domain, 'journal_types': set(journal_type)} @api.onchange('amount', 'currency_id') def _onchange_amount(self): jrnl_filters = self._compute_journal_domain_and_types() journal_types = jrnl_filters['journal_types'] domain_on_types = [('type', 'in', list(journal_types))] if self.invoice_ids: domain_on_types.append( ('company_id', '=', self.invoice_ids[0].company_id.id)) if self.journal_id.type not in journal_types or ( self.invoice_ids and self.journal_id.company_id != self.invoice_ids[0].company_id): self.journal_id = self.env['account.journal'].search( domain_on_types, limit=1) return { 'domain': { 'journal_id': jrnl_filters['domain'] + domain_on_types } } @api.onchange('currency_id') def _onchange_currency(self): self.amount = abs(self._compute_payment_amount()) if self.journal_id: # TODO: only return if currency differ? return # Set by default the first liquidity journal having this currency if exists. domain = [('type', 'in', ('bank', 'cash')), ('currency_id', '=', self.currency_id.id)] if self.invoice_ids: domain.append( ('company_id', '=', self.invoice_ids[0].company_id.id)) journal = self.env['account.journal'].search(domain, limit=1) if journal: return {'value': {'journal_id': journal.id}} @api.multi def _compute_payment_amount(self, invoices=None, currency=None): '''Compute the total amount for the payment wizard. :param invoices: If not specified, pick all the invoices. :param currency: If not specified, search a default currency on wizard/journal. :return: The total amount to pay the invoices. ''' # Get the payment invoices if not invoices: invoices = self.invoice_ids # Get the payment currency if not currency: currency = self.currency_id or self.journal_id.currency_id or self.journal_id.company_id.currency_id # Avoid currency rounding issues by summing the amounts according to the company_currency_id before invoice_datas = invoices.read_group( [('id', 'in', invoices.ids)], ['currency_id', 'type', 'residual_signed'], ['currency_id', 'type'], lazy=False) total = 0.0 for invoice_data in invoice_datas: amount_total = MAP_INVOICE_TYPE_PAYMENT_SIGN[ invoice_data['type']] * invoice_data['residual_signed'] payment_currency = self.env['res.currency'].browse( invoice_data['currency_id'][0]) if payment_currency == currency: total += amount_total else: total += payment_currency._convert( amount_total, currency, self.env.company_id, self.payment_date or fields.Date.today()) return total @api.multi def name_get(self): return [(payment.id, payment.name or _('Draft Payment')) for payment in self] @api.multi @api.depends('move_line_ids.reconciled') def _get_move_reconciled(self): for payment in self: rec = True for aml in payment.move_line_ids.filtered( lambda x: x.account_id.reconcile): if not aml.reconciled: rec = False break payment.move_reconciled = rec def open_payment_matching_screen(self): # Open reconciliation view for customers/suppliers move_line_id = False for move_line in self.move_line_ids: if move_line.account_id.reconcile: move_line_id = move_line.id break if not self.partner_id: raise UserError(_("Payments without a customer can't be matched")) action_context = { 'company_ids': [self.company_id.id], 'partner_ids': [self.partner_id.commercial_partner_id.id] } if self.partner_type == 'customer': action_context.update({'mode': 'customers'}) elif self.partner_type == 'supplier': action_context.update({'mode': 'suppliers'}) if move_line_id: action_context.update({'move_line_id': move_line_id}) return { 'type': 'ir.actions.client', 'tag': 'manual_reconciliation_view', 'context': action_context, } @api.one @api.depends('invoice_ids', 'payment_type', 'partner_type', 'partner_id') def _compute_destination_account_id(self): if self.invoice_ids: self.destination_account_id = self.invoice_ids[0].account_id.id elif self.payment_type == 'transfer': if not self.company_id.transfer_account_id.id: raise UserError( _('There is no Transfer Account defined in the accounting settings. Please define one to be able to confirm this transfer.' )) self.destination_account_id = self.company_id.transfer_account_id.id elif self.partner_id: if self.partner_type == 'customer': self.destination_account_id = self.partner_id.property_account_receivable_id.id else: self.destination_account_id = self.partner_id.property_account_payable_id.id elif self.partner_type == 'customer': default_account = self.env['ir.property'].get( 'property_account_receivable_id', 'res.partner') self.destination_account_id = default_account.id elif self.partner_type == 'supplier': default_account = self.env['ir.property'].get( 'property_account_payable_id', 'res.partner') self.destination_account_id = default_account.id @api.depends('move_line_ids.matched_debit_ids', 'move_line_ids.matched_credit_ids') def _compute_reconciled_invoice_ids(self): for record in self: record.reconciled_invoice_ids = ( record.move_line_ids.mapped( 'matched_debit_ids.debit_move_id.invoice_id') | record.move_line_ids.mapped( 'matched_credit_ids.credit_move_id.invoice_id')) record.has_invoices = bool(record.reconciled_invoice_ids) @api.multi def action_register_payment(self): active_ids = self.env.context.get('active_ids') if not active_ids: return '' return { 'name': _('Register Payment'), 'res_model': len(active_ids) == 1 and 'account.payment' or 'account.payment.register', 'view_type': 'form', 'view_mode': 'form', 'view_id': len(active_ids) != 1 and self.env.ref('account.view_account_payment_form_multi').id or self.env.ref('account.view_account_payment_invoice_form').id, 'context': self.env.context, 'target': 'new', 'type': 'ir.actions.act_window', } @api.multi def button_journal_entries(self): return { 'name': _('Journal Items'), 'view_type': 'form', 'view_mode': 'tree,form', 'res_model': 'account.move.line', 'view_id': False, 'type': 'ir.actions.act_window', 'domain': [('payment_id', 'in', self.ids)], } @api.multi def button_invoices(self): if self.partner_type == 'supplier': views = [ (self.env.ref('account.invoice_supplier_tree').id, 'tree'), (self.env.ref('account.invoice_supplier_form').id, 'form') ] else: views = [(self.env.ref('account.invoice_tree').id, 'tree'), (self.env.ref('account.invoice_form').id, 'form')] return { 'name': _('Paid Invoices'), 'view_type': 'form', 'view_mode': 'tree,form', 'res_model': 'account.invoice', 'view_id': False, 'views': views, 'type': 'ir.actions.act_window', 'domain': [('id', 'in', [x.id for x in self.reconciled_invoice_ids])], } @api.multi def unreconcile(self): """ Set back the payments in 'posted' or 'sent' state, without deleting the journal entries. Called when cancelling a bank statement line linked to a pre-registered payment. """ for payment in self: if payment.payment_reference: payment.write({'state': 'sent'}) else: payment.write({'state': 'posted'}) @api.multi def cancel(self): for rec in self: for move in rec.move_line_ids.mapped('move_id'): if rec.reconciled_invoice_ids: move.line_ids.remove_move_reconcile() move.button_cancel() move.unlink() rec.state = 'cancelled' @api.multi def unlink(self): if any(bool(rec.move_line_ids) for rec in self): raise UserError( _("You cannot delete a payment that is already posted.")) if any(rec.move_name for rec in self): raise UserError( _('It is not allowed to delete a payment that already created a journal entry since it would create a gap in the numbering. You should create the journal entry again and cancel it thanks to a regular revert.' )) return super(account_payment, self).unlink() @api.multi def post(self): """ Create the journal items for the payment and update the payment's state to 'posted'. A journal entry is created containing an item in the source liquidity account (selected journal's default_debit or default_credit) and another in the destination reconcilable account (see _compute_destination_account_id). If invoice_ids is not empty, there will be one reconcilable move line per invoice to reconcile with. If the payment is a transfer, a second journal entry is created in the destination journal to receive money from the transfer account. """ for rec in self: if rec.state != 'draft': raise UserError(_("Only a draft payment can be posted.")) if any(inv.state != 'open' for inv in rec.invoice_ids): raise ValidationError( _("The payment cannot be processed because the invoice is not open!" )) # keep the name in case of a payment reset to draft if not rec.name: # Use the right sequence to set the name if rec.payment_type == 'transfer': sequence_code = 'account.payment.transfer' else: if rec.partner_type == 'customer': if rec.payment_type == 'inbound': sequence_code = 'account.payment.customer.invoice' if rec.payment_type == 'outbound': sequence_code = 'account.payment.customer.refund' if rec.partner_type == 'supplier': if rec.payment_type == 'inbound': sequence_code = 'account.payment.supplier.refund' if rec.payment_type == 'outbound': sequence_code = 'account.payment.supplier.invoice' rec.name = self.env['ir.sequence'].with_context( ir_sequence_date=rec.payment_date).next_by_code( sequence_code) if not rec.name and rec.payment_type != 'transfer': raise UserError( _("You have to define a sequence for %s in your company." ) % (sequence_code, )) # Create the journal entry amount = rec.amount * (rec.payment_type in ('outbound', 'transfer') and 1 or -1) move = rec._create_payment_entry(amount) # In case of a transfer, the first journal entry created debited the source liquidity account and credited # the transfer account. Now we debit the transfer account and credit the destination liquidity account. if rec.payment_type == 'transfer': transfer_credit_aml = move.line_ids.filtered( lambda r: r.account_id == rec.company_id. transfer_account_id) transfer_debit_aml = rec._create_transfer_entry(amount) (transfer_credit_aml + transfer_debit_aml).reconcile() rec.write({'state': 'posted', 'move_name': move.name}) return True @api.multi def action_draft(self): return self.write({'state': 'draft'}) def _create_payment_entry(self, amount): """ Create a journal entry corresponding to a payment, if the payment references invoice(s) they are reconciled. Return the journal entry. """ aml_obj = self.env['account.move.line'].with_context( check_move_validity=False) debit, credit, amount_currency, currency_id = aml_obj.with_context( date=self.payment_date)._compute_amount_fields( amount, self.currency_id, self.company_id.currency_id) move = self.env['account.move'].create(self._get_move_vals()) #Write line corresponding to invoice payment counterpart_aml_dict = self._get_shared_move_line_vals( debit, credit, amount_currency, move.id, False) counterpart_aml_dict.update( self._get_counterpart_move_line_vals(self.invoice_ids)) counterpart_aml_dict.update({'currency_id': currency_id}) counterpart_aml = aml_obj.create(counterpart_aml_dict) #Reconcile with the invoices if self.payment_difference_handling == 'reconcile' and self.payment_difference: writeoff_line = self._get_shared_move_line_vals( 0, 0, 0, move.id, False) debit_wo, credit_wo, amount_currency_wo, currency_id = aml_obj.with_context( date=self.payment_date)._compute_amount_fields( self.payment_difference, self.currency_id, self.company_id.currency_id) writeoff_line['name'] = self.writeoff_label writeoff_line['account_id'] = self.writeoff_account_id.id writeoff_line['debit'] = debit_wo writeoff_line['credit'] = credit_wo writeoff_line['amount_currency'] = amount_currency_wo writeoff_line['currency_id'] = currency_id writeoff_line = aml_obj.create(writeoff_line) if counterpart_aml['debit'] or (writeoff_line['credit'] and not counterpart_aml['credit']): counterpart_aml['debit'] += credit_wo - debit_wo if counterpart_aml['credit'] or (writeoff_line['debit'] and not counterpart_aml['debit']): counterpart_aml['credit'] += debit_wo - credit_wo counterpart_aml['amount_currency'] -= amount_currency_wo #Write counterpart lines if not self.currency_id.is_zero(self.amount): if not self.currency_id != self.company_id.currency_id: amount_currency = 0 liquidity_aml_dict = self._get_shared_move_line_vals( credit, debit, -amount_currency, move.id, False) liquidity_aml_dict.update( self._get_liquidity_move_line_vals(-amount)) aml_obj.create(liquidity_aml_dict) #validate the payment if not self.journal_id.post_at_bank_rec: move.post() #reconcile the invoice receivable/payable line(s) with the payment if self.invoice_ids: self.invoice_ids.register_payment(counterpart_aml) return move def _create_transfer_entry(self, amount): """ Create the journal entry corresponding to the 'incoming money' part of an internal transfer, return the reconcilable move line """ aml_obj = self.env['account.move.line'].with_context( check_move_validity=False) debit, credit, amount_currency, dummy = aml_obj.with_context( date=self.payment_date)._compute_amount_fields( amount, self.currency_id, self.company_id.currency_id) amount_currency = self.destination_journal_id.currency_id and self.currency_id._convert( amount, self.destination_journal_id.currency_id, self.company_id, self.payment_date or fields.Date.today()) or 0 dst_move = self.env['account.move'].create( self._get_move_vals(self.destination_journal_id)) dst_liquidity_aml_dict = self._get_shared_move_line_vals( debit, credit, amount_currency, dst_move.id) dst_liquidity_aml_dict.update({ 'name': _('Transfer from %s') % self.journal_id.name, 'account_id': self.destination_journal_id.default_credit_account_id.id, 'currency_id': self.destination_journal_id.currency_id.id, 'journal_id': self.destination_journal_id.id }) aml_obj.create(dst_liquidity_aml_dict) transfer_debit_aml_dict = self._get_shared_move_line_vals( credit, debit, 0, dst_move.id) transfer_debit_aml_dict.update({ 'name': self.name, 'account_id': self.company_id.transfer_account_id.id, 'journal_id': self.destination_journal_id.id }) if self.currency_id != self.company_id.currency_id: transfer_debit_aml_dict.update({ 'currency_id': self.currency_id.id, 'amount_currency': -self.amount, }) transfer_debit_aml = aml_obj.create(transfer_debit_aml_dict) if not self.destination_journal_id.post_at_bank_rec: dst_move.post() return transfer_debit_aml def _get_move_vals(self, journal=None): """ Return dict to create the payment move """ journal = journal or self.journal_id move_vals = { 'date': self.payment_date, 'ref': self.communication or '', 'company_id': self.company_id.id, 'journal_id': journal.id, } if self.move_name: move_vals['name'] = self.move_name return move_vals def _get_shared_move_line_vals(self, debit, credit, amount_currency, move_id, invoice_id=False): """ Returns values common to both move lines (except for debit, credit and amount_currency which are reversed) """ return { 'partner_id': self.payment_type in ('inbound', 'outbound') and self.env['res.partner']._find_accounting_partner( self.partner_id).id or False, 'invoice_id': invoice_id and invoice_id.id or False, 'move_id': move_id, 'debit': debit, 'credit': credit, 'amount_currency': amount_currency or False, 'payment_id': self.id, 'journal_id': self.journal_id.id, } def _get_counterpart_move_line_vals(self, invoice=False): if self.payment_type == 'transfer': name = self.name else: name = '' if self.partner_type == 'customer': if self.payment_type == 'inbound': name += _("Customer Payment") elif self.payment_type == 'outbound': name += _("Customer Credit Note") elif self.partner_type == 'supplier': if self.payment_type == 'inbound': name += _("Vendor Credit Note") elif self.payment_type == 'outbound': name += _("Vendor Payment") if invoice: name += ': ' for inv in invoice: if inv.move_id: name += inv.number + ', ' name = name[:len(name) - 2] return { 'name': name, 'account_id': self.destination_account_id.id, 'currency_id': self.currency_id != self.company_id.currency_id and self.currency_id.id or False, } def _get_liquidity_move_line_vals(self, amount): name = self.name if self.payment_type == 'transfer': name = _('Transfer to %s') % self.destination_journal_id.name vals = { 'name': name, 'account_id': self.payment_type in ('outbound', 'transfer') and self.journal_id.default_debit_account_id.id or self.journal_id.default_credit_account_id.id, 'journal_id': self.journal_id.id, 'currency_id': self.currency_id != self.company_id.currency_id and self.currency_id.id or False, } # If the journal has a currency specified, the journal item need to be expressed in this currency if self.journal_id.currency_id and self.currency_id != self.journal_id.currency_id: amount = self.currency_id._convert( amount, self.journal_id.currency_id, self.company_id, self.payment_date or fields.Date.today()) debit, credit, amount_currency, dummy = self.env[ 'account.move.line'].with_context( date=self.payment_date)._compute_amount_fields( amount, self.journal_id.currency_id, self.company_id.currency_id) vals.update({ 'amount_currency': amount_currency, 'currency_id': self.journal_id.currency_id.id, }) return vals def _get_invoice_payment_amount(self, inv): """ Computes the amount covered by the current payment in the given invoice. :param inv: an invoice object :returns: the amount covered by the payment in the invoice """ self.ensure_one() return sum([ data['amount'] for data in inv._get_payments_vals() if data['account_payment_id'] == self.id ])
class AccountPayment(models.Model): _name = "account.payment" _inherits = {'account.move': 'move_id'} _inherit = ['mail.thread', 'mail.activity.mixin'] _description = "Payments" _order = "date desc, name desc" _check_company_auto = True def _get_default_journal(self): ''' Retrieve the default journal for the account.payment. /!\ This method will not override the method in 'account.move' because the ORM doesn't allow overriding methods using _inherits. Then, this method will be called manually in 'create' and 'new'. :return: An account.journal record. ''' return self.env['account.move']._search_default_journal( ('bank', 'cash')) # == Business fields == move_id = fields.Many2one(comodel_name='account.move', string='Journal Entry', required=True, readonly=True, ondelete='cascade', check_company=True) is_reconciled = fields.Boolean( string="Is Reconciled", store=True, compute='_compute_reconciliation_status', help="Technical field indicating if the payment is already reconciled." ) is_matched = fields.Boolean( string="Is Matched With a Bank Statement", store=True, compute='_compute_reconciliation_status', help= "Technical field indicating if the payment has been matched with a statement line." ) partner_bank_id = fields.Many2one( 'res.partner.bank', string="Recipient Bank Account", readonly=False, store=True, compute='_compute_partner_bank_id', domain="[('partner_id', '=', partner_id)]", check_company=True) is_internal_transfer = fields.Boolean( string="Is Internal Transfer", readonly=False, store=True, compute="_compute_is_internal_transfer") qr_code = fields.Char( string="QR Code", compute="_compute_qr_code", help= "QR-code report URL to use to generate the QR-code to scan with a banking app to perform this payment." ) # == Payment methods fields == payment_method_id = fields.Many2one('account.payment.method', string='Payment Method', readonly=False, store=True, compute='_compute_payment_method_id', domain="[('id', 'in', available_payment_method_ids)]", help="Manual: Get paid by cash, check or any other method outside of Odoo.\n"\ "Electronic: Get paid automatically through a payment acquirer by requesting a transaction on a card saved by the customer when buying or subscribing online (payment token).\n"\ "Check: Pay bill by check and print it from Odoo.\n"\ "Batch Deposit: Encase several customer checks at once by generating a batch deposit to submit to your bank. When encoding the bank statement in Odoo, you are suggested to reconcile the transaction with the batch deposit.To enable batch deposit, module account_batch_payment must be installed.\n"\ "SEPA Credit Transfer: Pay bill from a SEPA Credit Transfer file you submit to your bank. To enable sepa credit transfer, module account_sepa must be installed ") available_payment_method_ids = fields.Many2many( 'account.payment.method', compute='_compute_payment_method_fields') hide_payment_method = fields.Boolean( compute='_compute_payment_method_fields', help= "Technical field used to hide the payment method if the selected journal has only one available which is 'manual'" ) # == Synchronized fields with the account.move.lines == amount = fields.Monetary(currency_field='currency_id') payment_type = fields.Selection([ ('outbound', 'Send Money'), ('inbound', 'Receive Money'), ], string='Payment Type', default='inbound', required=True) partner_type = fields.Selection([ ('customer', 'Customer'), ('supplier', 'Vendor'), ], default='customer', tracking=True, required=True) payment_reference = fields.Char( string="Payment Reference", copy=False, help= "Reference of the document used to issue this payment. Eg. check number, file name, etc." ) currency_id = fields.Many2one('res.currency', string='Currency', store=True, readonly=False, compute='_compute_currency_id', help="The payment's currency.") partner_id = fields.Many2one( comodel_name='res.partner', string="Customer/Vendor", store=True, readonly=False, ondelete='restrict', compute='_compute_partner_id', domain="['|', ('parent_id','=', False), ('is_company','=', True)]", check_company=True) destination_account_id = fields.Many2one( comodel_name='account.account', string='Destination Account', store=True, readonly=False, compute='_compute_destination_account_id', domain= "[('user_type_id.type', 'in', ('receivable', 'payable')), ('company_id', '=', company_id)]", check_company=True, help="The payment's currency.") # == Stat buttons == reconciled_invoice_ids = fields.Many2many( 'account.move', string="Reconciled Invoices", compute='_compute_stat_buttons_from_reconciliation', help= "Invoices whose journal items have been reconciled with these payments." ) reconciled_invoices_count = fields.Integer( string="# Reconciled Invoices", compute="_compute_stat_buttons_from_reconciliation") reconciled_bill_ids = fields.Many2many( 'account.move', string="Reconciled Bills", compute='_compute_stat_buttons_from_reconciliation', help= "Invoices whose journal items have been reconciled with these payments." ) reconciled_bills_count = fields.Integer( string="# Reconciled Bills", compute="_compute_stat_buttons_from_reconciliation") reconciled_statement_ids = fields.Many2many( 'account.move', string="Reconciled Statements", compute='_compute_stat_buttons_from_reconciliation', help="Statements matched to this payment") reconciled_statements_count = fields.Integer( string="# Reconciled Statements", compute="_compute_stat_buttons_from_reconciliation") # == Display purpose fields == payment_method_code = fields.Char( related='payment_method_id.code', help= "Technical field used to adapt the interface to the payment type selected." ) show_partner_bank_account = fields.Boolean( compute='_compute_show_require_partner_bank', help= "Technical field used to know whether the field `partner_bank_id` needs to be displayed or not in the payments form views" ) require_partner_bank_account = fields.Boolean( compute='_compute_show_require_partner_bank', help= "Technical field used to know whether the field `partner_bank_id` needs to be required or not in the payments form views" ) country_code = fields.Char(related='company_id.country_id.code') _sql_constraints = [ ( 'check_amount_not_negative', 'CHECK(amount >= 0.0)', "The payment amount cannot be negative.", ), ] # ------------------------------------------------------------------------- # HELPERS # ------------------------------------------------------------------------- def _seek_for_lines(self): ''' Helper used to dispatch the journal items between: - The lines using the temporary liquidity account. - The lines using the counterpart account. - The lines being the write-off lines. :return: (liquidity_lines, counterpart_lines, writeoff_lines) ''' self.ensure_one() liquidity_lines = self.env['account.move.line'] counterpart_lines = self.env['account.move.line'] writeoff_lines = self.env['account.move.line'] for line in self.move_id.line_ids: if line.account_id in (self.journal_id.payment_debit_account_id, self.journal_id.payment_credit_account_id): liquidity_lines += line elif line.account_id.internal_type in ( 'receivable', 'payable' ) or line.partner_id == line.company_id.partner_id: counterpart_lines += line else: writeoff_lines += line return liquidity_lines, counterpart_lines, writeoff_lines def _prepare_move_line_default_vals(self, write_off_line_vals=None): ''' Prepare the dictionary to create the default account.move.lines for the current payment. :param write_off_line_vals: Optional dictionary to create a write-off account.move.line easily containing: * amount: The amount to be added to the counterpart amount. * name: The label to set on the line. * account_id: The account on which create the write-off. :return: A list of python dictionary to be passed to the account.move.line's 'create' method. ''' self.ensure_one() write_off_line_vals = write_off_line_vals or {} if not self.journal_id.payment_debit_account_id or not self.journal_id.payment_credit_account_id: raise UserError( _( "You can't create a new payment without an outstanding payments/receipts account set on the %s journal.", self.journal_id.display_name)) # Compute amounts. write_off_amount = write_off_line_vals.get('amount', 0.0) if self.payment_type == 'inbound': # Receive money. counterpart_amount = -self.amount write_off_amount *= -1 elif self.payment_type == 'outbound': # Send money. counterpart_amount = self.amount else: counterpart_amount = 0.0 write_off_amount = 0.0 balance = self.currency_id._convert(counterpart_amount, self.company_id.currency_id, self.company_id, self.date) counterpart_amount_currency = counterpart_amount write_off_balance = self.currency_id._convert( write_off_amount, self.company_id.currency_id, self.company_id, self.date) write_off_amount_currency = write_off_amount currency_id = self.currency_id.id if self.is_internal_transfer: if self.payment_type == 'inbound': liquidity_line_name = _('Transfer to %s', self.journal_id.name) else: # payment.payment_type == 'outbound': liquidity_line_name = _('Transfer from %s', self.journal_id.name) else: liquidity_line_name = self.payment_reference # Compute a default label to set on the journal items. payment_display_name = { 'outbound-customer': _("Customer Reimbursement"), 'inbound-customer': _("Customer Payment"), 'outbound-supplier': _("Vendor Payment"), 'inbound-supplier': _("Vendor Reimbursement"), } default_line_name = self.env[ 'account.move.line']._get_default_line_name( payment_display_name['%s-%s' % (self.payment_type, self.partner_type)], self.amount, self.currency_id, self.date, partner=self.partner_id, ) line_vals_list = [ # Liquidity line. { 'name': liquidity_line_name or default_line_name, 'date_maturity': self.date, 'amount_currency': -counterpart_amount_currency, 'currency_id': currency_id, 'debit': balance < 0.0 and -balance or 0.0, 'credit': balance > 0.0 and balance or 0.0, 'partner_id': self.partner_id.id, 'account_id': self.journal_id.payment_debit_account_id.id if balance < 0.0 else self.journal_id.payment_credit_account_id.id, }, # Receivable / Payable. { 'name': self.payment_reference or default_line_name, 'date_maturity': self.date, 'amount_currency': counterpart_amount_currency + write_off_amount_currency if currency_id else 0.0, 'currency_id': currency_id, 'debit': balance + write_off_balance > 0.0 and balance + write_off_balance or 0.0, 'credit': balance + write_off_balance < 0.0 and -balance - write_off_balance or 0.0, 'partner_id': self.partner_id.id, 'account_id': self.destination_account_id.id, }, ] if write_off_balance: # Write-off line. line_vals_list.append({ 'name': write_off_line_vals.get('name') or default_line_name, 'amount_currency': -write_off_amount_currency, 'currency_id': currency_id, 'debit': write_off_balance < 0.0 and -write_off_balance or 0.0, 'credit': write_off_balance > 0.0 and write_off_balance or 0.0, 'partner_id': self.partner_id.id, 'account_id': write_off_line_vals.get('account_id'), }) return line_vals_list # ------------------------------------------------------------------------- # COMPUTE METHODS # ------------------------------------------------------------------------- @api.depends('move_id.line_ids.amount_residual', 'move_id.line_ids.amount_residual_currency') def _compute_reconciliation_status(self): ''' Compute the field indicating if the payments are already reconciled with something. This field is used for display purpose (e.g. display the 'reconcile' button redirecting to the reconciliation widget). ''' for pay in self: liquidity_lines, counterpart_lines, writeoff_lines = pay._seek_for_lines( ) if not pay.currency_id or not pay.id: pay.is_reconciled = False pay.is_matched = False elif pay.currency_id.is_zero(pay.amount): pay.is_reconciled = True pay.is_matched = True else: residual_field = 'amount_residual' if pay.currency_id == pay.company_id.currency_id else 'amount_residual_currency' if pay.journal_id.default_account_id and pay.journal_id.default_account_id in liquidity_lines.account_id: # Allow user managing payments without any statement lines by using the bank account directly. # In that case, the user manages transactions only using the register payment wizard. pay.is_matched = True else: pay.is_matched = pay.currency_id.is_zero( sum(liquidity_lines.mapped(residual_field))) reconcile_lines = (counterpart_lines + writeoff_lines).filtered( lambda line: line.account_id.reconcile) pay.is_reconciled = pay.currency_id.is_zero( sum(reconcile_lines.mapped(residual_field))) @api.model def _get_method_codes_using_bank_account(self): return ['manual'] @api.model def _get_method_codes_needing_bank_account(self): return [] @api.depends('payment_method_code') def _compute_show_require_partner_bank(self): """ Computes if the destination bank account must be displayed in the payment form view. By default, it won't be displayed but some modules might change that, depending on the payment type.""" for payment in self: payment.show_partner_bank_account = payment.payment_method_code in self._get_method_codes_using_bank_account( ) payment.require_partner_bank_account = payment.state == 'draft' and payment.payment_method_code in self._get_method_codes_needing_bank_account( ) @api.depends('partner_id') def _compute_partner_bank_id(self): ''' The default partner_bank_id will be the first available on the partner. ''' for pay in self: available_partner_bank_accounts = pay.partner_id.bank_ids if available_partner_bank_accounts: pay.partner_bank_id = available_partner_bank_accounts[ 0]._origin else: pay.partner_bank_id = False @api.depends('partner_id', 'destination_account_id', 'journal_id') def _compute_is_internal_transfer(self): for payment in self: is_partner_ok = payment.partner_id == payment.journal_id.company_id.partner_id is_account_ok = payment.destination_account_id and payment.destination_account_id == payment.journal_id.company_id.transfer_account_id payment.is_internal_transfer = is_partner_ok and is_account_ok @api.depends('payment_type', 'journal_id') def _compute_payment_method_id(self): ''' Compute the 'payment_method_id' field. This field is not computed in '_compute_payment_method_fields' because it's a stored editable one. ''' for pay in self: if pay.payment_type == 'inbound': available_payment_methods = pay.journal_id.inbound_payment_method_ids else: available_payment_methods = pay.journal_id.outbound_payment_method_ids # Select the first available one by default. if available_payment_methods: pay.payment_method_id = available_payment_methods[0]._origin else: pay.payment_method_id = False @api.depends('payment_type', 'journal_id.inbound_payment_method_ids', 'journal_id.outbound_payment_method_ids') def _compute_payment_method_fields(self): for pay in self: if pay.payment_type == 'inbound': pay.available_payment_method_ids = pay.journal_id.inbound_payment_method_ids else: pay.available_payment_method_ids = pay.journal_id.outbound_payment_method_ids pay.hide_payment_method = len( pay.available_payment_method_ids ) == 1 and pay.available_payment_method_ids.code == 'manual' @api.depends('journal_id') def _compute_currency_id(self): for pay in self: pay.currency_id = pay.journal_id.currency_id or pay.journal_id.company_id.currency_id @api.depends('is_internal_transfer') def _compute_partner_id(self): for pay in self: if pay.is_internal_transfer: pay.partner_id = pay.journal_id.company_id.partner_id elif pay.partner_id == pay.journal_id.company_id.partner_id: pay.partner_id = False else: pay.partner_id = pay.partner_id @api.depends('journal_id', 'partner_id', 'partner_type', 'is_internal_transfer') def _compute_destination_account_id(self): self.destination_account_id = False for pay in self: if pay.is_internal_transfer: pay.destination_account_id = pay.journal_id.company_id.transfer_account_id elif pay.partner_type == 'customer': # Receive money from invoice or send money to refund it. if pay.partner_id: pay.destination_account_id = pay.partner_id.with_company( pay.company_id).property_account_receivable_id else: pay.destination_account_id = self.env[ 'account.account'].search([ ('company_id', '=', pay.company_id.id), ('internal_type', '=', 'receivable'), ], limit=1) elif pay.partner_type == 'supplier': # Send money to pay a bill or receive money to refund it. if pay.partner_id: pay.destination_account_id = pay.partner_id.with_company( pay.company_id).property_account_payable_id else: pay.destination_account_id = self.env[ 'account.account'].search([ ('company_id', '=', pay.company_id.id), ('internal_type', '=', 'payable'), ], limit=1) @api.depends('partner_bank_id', 'amount', 'ref', 'currency_id', 'journal_id', 'move_id.state', 'payment_method_id', 'payment_type') def _compute_qr_code(self): for pay in self: if pay.state in ('draft', 'posted') \ and pay.partner_bank_id \ and pay.payment_method_id.code == 'manual' \ and pay.payment_type == 'outbound' \ and pay.currency_id: if pay.partner_bank_id: qr_code = pay.partner_bank_id.build_qr_code_url( pay.amount, pay.ref, None, pay.currency_id, pay.partner_id) else: qr_code = None if qr_code: pay.qr_code = ''' <br/> <img class="border border-dark rounded" src="{qr_code}"/> <br/> <strong class="text-center">{txt}</strong> '''.format(txt=_('Scan me with your banking app.'), qr_code=qr_code) continue pay.qr_code = None @api.depends('move_id.line_ids.matched_debit_ids', 'move_id.line_ids.matched_credit_ids') def _compute_stat_buttons_from_reconciliation(self): ''' Retrieve the invoices reconciled to the payments through the reconciliation (account.partial.reconcile). ''' stored_payments = self.filtered('id') if not stored_payments: self.reconciled_invoice_ids = False self.reconciled_invoices_count = 0 self.reconciled_bill_ids = False self.reconciled_bills_count = 0 self.reconciled_statement_ids = False self.reconciled_statements_count = 0 return self.env['account.move'].flush() self.env['account.move.line'].flush() self.env['account.partial.reconcile'].flush() self._cr.execute( ''' SELECT payment.id, ARRAY_AGG(DISTINCT invoice.id) AS invoice_ids, invoice.move_type FROM account_payment payment JOIN account_move move ON move.id = payment.move_id JOIN account_move_line line ON line.move_id = move.id JOIN account_partial_reconcile part ON part.debit_move_id = line.id OR part.credit_move_id = line.id JOIN account_move_line counterpart_line ON part.debit_move_id = counterpart_line.id OR part.credit_move_id = counterpart_line.id JOIN account_move invoice ON invoice.id = counterpart_line.move_id JOIN account_account account ON account.id = line.account_id WHERE account.internal_type IN ('receivable', 'payable') AND payment.id IN %(payment_ids)s AND line.id != counterpart_line.id AND invoice.move_type in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt') GROUP BY payment.id, invoice.move_type ''', {'payment_ids': tuple(stored_payments.ids)}) query_res = self._cr.dictfetchall() self.reconciled_invoice_ids = self.reconciled_invoices_count = False self.reconciled_bill_ids = self.reconciled_bills_count = False for res in query_res: pay = self.browse(res['id']) if res['move_type'] in self.env['account.move'].get_sale_types( True): pay.reconciled_invoice_ids += self.env['account.move'].browse( res.get('invoice_ids', [])) pay.reconciled_invoices_count = len(res.get('invoice_ids', [])) else: pay.reconciled_bill_ids += self.env['account.move'].browse( res.get('invoice_ids', [])) pay.reconciled_bills_count = len(res.get('invoice_ids', [])) self._cr.execute( ''' SELECT payment.id, ARRAY_AGG(DISTINCT counterpart_line.statement_id) AS statement_ids FROM account_payment payment JOIN account_move move ON move.id = payment.move_id JOIN account_journal journal ON journal.id = move.journal_id JOIN account_move_line line ON line.move_id = move.id JOIN account_account account ON account.id = line.account_id JOIN account_partial_reconcile part ON part.debit_move_id = line.id OR part.credit_move_id = line.id JOIN account_move_line counterpart_line ON part.debit_move_id = counterpart_line.id OR part.credit_move_id = counterpart_line.id WHERE (account.id = journal.payment_debit_account_id OR account.id = journal.payment_credit_account_id) AND payment.id IN %(payment_ids)s AND line.id != counterpart_line.id AND counterpart_line.statement_id IS NOT NULL GROUP BY payment.id ''', {'payment_ids': tuple(stored_payments.ids)}) query_res = dict((payment_id, statement_ids) for payment_id, statement_ids in self._cr.fetchall()) for pay in self: statement_ids = query_res.get(pay.id, []) pay.reconciled_statement_ids = [(6, 0, statement_ids)] pay.reconciled_statements_count = len(statement_ids) # ------------------------------------------------------------------------- # CONSTRAINT METHODS # ------------------------------------------------------------------------- @api.constrains('payment_method_id') def _check_payment_method_id(self): ''' Ensure the 'payment_method_id' field is not null. Can't be done using the regular 'required=True' because the field is a computed editable stored one. ''' for pay in self: if not pay.payment_method_id: raise ValidationError( _("Please define a payment method on your payment.")) # ------------------------------------------------------------------------- # LOW-LEVEL METHODS # ------------------------------------------------------------------------- @api.model_create_multi def create(self, vals_list): # OVERRIDE write_off_line_vals_list = [] for vals in vals_list: # Hack to add a custom write-off line. write_off_line_vals_list.append( vals.pop('write_off_line_vals', None)) # Force the move_type to avoid inconsistency with residual 'default_move_type' inside the context. vals['move_type'] = 'entry' # Force the computation of 'journal_id' since this field is set on account.move but must have the # bank/cash type. if 'journal_id' not in vals: vals['journal_id'] = self._get_default_journal().id # Since 'currency_id' is a computed editable field, it will be computed later. # Prevent the account.move to call the _get_default_currency method that could raise # the 'Please define an accounting miscellaneous journal in your company' error. if 'currency_id' not in vals: journal = self.env['account.journal'].browse( vals['journal_id']) vals[ 'currency_id'] = journal.currency_id.id or journal.company_id.currency_id.id payments = super().create(vals_list) for i, pay in enumerate(payments): write_off_line_vals = write_off_line_vals_list[i] # Write payment_id on the journal entry plus the fields being stored in both models but having the same # name, e.g. partner_bank_id. The ORM is currently not able to perform such synchronization and make things # more difficult by creating related fields on the fly to handle the _inherits. # Then, when partner_bank_id is in vals, the key is consumed by account.payment but is never written on # account.move. to_write = {'payment_id': pay.id} for k, v in vals_list[i].items(): if k in self._fields and self._fields[ k].store and k in pay.move_id._fields and pay.move_id._fields[ k].store: to_write[k] = v if 'line_ids' not in vals_list[i]: to_write['line_ids'] = [ (0, 0, line_vals) for line_vals in pay._prepare_move_line_default_vals( write_off_line_vals=write_off_line_vals) ] pay.move_id.write(to_write) return payments def write(self, vals): # OVERRIDE res = super().write(vals) self._synchronize_to_moves(set(vals.keys())) return res def unlink(self): # OVERRIDE to unlink the inherited account.move (move_id field) as well. moves = self.with_context(force_delete=True).move_id res = super().unlink() moves.unlink() return res @api.depends('move_id.name') def name_get(self): return [(payment.id, payment.move_id.name or _('Draft Payment')) for payment in self] # ------------------------------------------------------------------------- # SYNCHRONIZATION account.payment <-> account.move # ------------------------------------------------------------------------- def _synchronize_from_moves(self, changed_fields): ''' Update the account.payment regarding its related account.move. Also, check both models are still consistent. :param changed_fields: A set containing all modified fields on account.move. ''' if self._context.get('skip_account_move_synchronization'): return for pay in self.with_context(skip_account_move_synchronization=True): move = pay.move_id move_vals_to_write = {} payment_vals_to_write = {} if 'journal_id' in changed_fields: if pay.journal_id.type not in ('bank', 'cash'): raise UserError( _("A payment must always belongs to a bank or cash journal." )) if 'line_ids' in changed_fields: all_lines = move.line_ids liquidity_lines, counterpart_lines, writeoff_lines = pay._seek_for_lines( ) if len(liquidity_lines) != 1 or len(counterpart_lines) != 1: raise UserError( _("The journal entry %s reached an invalid state relative to its payment.\n" "To be consistent, the journal entry must always contains:\n" "- one journal item involving the outstanding payment/receipts account.\n" "- one journal item involving a receivable/payable account.\n" "- optional journal items, all sharing the same account.\n\n" ) % move.display_name) if writeoff_lines and len(writeoff_lines.account_id) != 1: raise UserError( _("The journal entry %s reached an invalid state relative to its payment.\n" "To be consistent, all the write-off journal items must share the same account." ) % move.display_name) if any(line.currency_id != all_lines[0].currency_id for line in all_lines): raise UserError( _("The journal entry %s reached an invalid state relative to its payment.\n" "To be consistent, the journal items must share the same currency." ) % move.display_name) if any(line.partner_id != all_lines[0].partner_id for line in all_lines): raise UserError( _("The journal entry %s reached an invalid state relative to its payment.\n" "To be consistent, the journal items must share the same partner." ) % move.display_name) if counterpart_lines.account_id.user_type_id.type == 'receivable': partner_type = 'customer' else: partner_type = 'supplier' liquidity_amount = liquidity_lines.amount_currency move_vals_to_write.update({ 'currency_id': liquidity_lines.currency_id.id, 'partner_id': liquidity_lines.partner_id.id, }) payment_vals_to_write.update({ 'amount': abs(liquidity_amount), 'payment_type': 'inbound' if liquidity_amount > 0.0 else 'outbound', 'partner_type': partner_type, 'currency_id': liquidity_lines.currency_id.id, 'destination_account_id': counterpart_lines.account_id.id, 'partner_id': liquidity_lines.partner_id.id, }) move.write(move._cleanup_write_orm_values(move, move_vals_to_write)) pay.write( move._cleanup_write_orm_values(pay, payment_vals_to_write)) def _synchronize_to_moves(self, changed_fields): ''' Update the account.move regarding the modified account.payment. :param changed_fields: A list containing all modified fields on account.payment. ''' if self._context.get('skip_account_move_synchronization'): return if not any(field_name in changed_fields for field_name in ( 'date', 'amount', 'payment_type', 'partner_type', 'payment_reference', 'is_internal_transfer', 'currency_id', 'partner_id', 'destination_account_id', 'partner_bank_id', )): return for pay in self.with_context(skip_account_move_synchronization=True): liquidity_lines, counterpart_lines, writeoff_lines = pay._seek_for_lines( ) # Make sure to preserve the write-off amount. # This allows to create a new payment with custom 'line_ids'. if writeoff_lines: writeoff_amount = sum(writeoff_lines.mapped('amount_currency')) counterpart_amount = counterpart_lines['amount_currency'] if writeoff_amount > 0.0 and counterpart_amount > 0.0: sign = 1 else: sign = -1 write_off_line_vals = { 'name': writeoff_lines[0].name, 'amount': writeoff_amount * sign, 'account_id': writeoff_lines[0].account_id.id, } else: write_off_line_vals = {} line_vals_list = pay._prepare_move_line_default_vals( write_off_line_vals=write_off_line_vals) line_ids_commands = [ (1, liquidity_lines.id, line_vals_list[0]), (1, counterpart_lines.id, line_vals_list[1]), ] for line in writeoff_lines: line_ids_commands.append((2, line.id)) if writeoff_lines: line_ids_commands.append((0, 0, line_vals_list[2])) # Update the existing journal items. # If dealing with multiple write-off lines, they are dropped and a new one is generated. pay.move_id.write({ 'partner_id': pay.partner_id.id, 'currency_id': pay.currency_id.id, 'partner_bank_id': pay.partner_bank_id.id, 'line_ids': line_ids_commands, }) # ------------------------------------------------------------------------- # BUSINESS METHODS # ------------------------------------------------------------------------- def mark_as_sent(self): self.write({'is_move_sent': True}) def unmark_as_sent(self): self.write({'is_move_sent': False}) def action_post(self): ''' draft -> posted ''' self.move_id._post(soft=False) def action_cancel(self): ''' draft -> cancelled ''' self.move_id.button_cancel() def action_draft(self): ''' posted -> draft ''' self.move_id.button_draft() def button_open_invoices(self): ''' Redirect the user to the invoice(s) paid by this payment. :return: An action on account.move. ''' self.ensure_one() action = { 'name': _("Paid Invoices"), 'type': 'ir.actions.act_window', 'res_model': 'account.move', 'context': { 'create': False }, } if len(self.reconciled_invoice_ids) == 1: action.update({ 'view_mode': 'form', 'res_id': self.reconciled_invoice_ids.id, }) else: action.update({ 'view_mode': 'list,form', 'domain': [('id', 'in', self.reconciled_invoice_ids.ids)], }) return action def button_open_bills(self): ''' Redirect the user to the bill(s) paid by this payment. :return: An action on account.move. ''' self.ensure_one() action = { 'name': _("Paid Bills"), 'type': 'ir.actions.act_window', 'res_model': 'account.move', 'context': { 'create': False }, } if len(self.reconciled_bill_ids) == 1: action.update({ 'view_mode': 'form', 'res_id': self.reconciled_bill_ids.id, }) else: action.update({ 'view_mode': 'list,form', 'domain': [('id', 'in', self.reconciled_bill_ids.ids)], }) return action def button_open_statements(self): ''' Redirect the user to the statement line(s) reconciled to this payment. :return: An action on account.move. ''' self.ensure_one() action = { 'name': _("Matched Statements"), 'type': 'ir.actions.act_window', 'res_model': 'account.bank.statement', 'context': { 'create': False }, } if len(self.reconciled_statement_ids) == 1: action.update({ 'view_mode': 'form', 'res_id': self.reconciled_statement_ids.id, }) else: action.update({ 'view_mode': 'list,form', 'domain': [('id', 'in', self.reconciled_statement_ids.ids)], }) return action
class resusers(models.Model): _inherit = 'res.users' x_nominal_otorisasi = fields.Monetary(string='Nominal Otorisasi', store=True, default=0.0)
class PurchaseOrder(models.Model): _inherit = "purchase.order" third_party_order = fields.Boolean(default=False, states=Purchase.READONLY_STATES) third_party_partner_id = fields.Many2one("res.partner", states=Purchase.READONLY_STATES) tp_amount_untaxed = fields.Monetary( string="Untaxed Amount", store=True, readonly=True, compute="_compute_amount_all_tp", track_visibility="always", ) tp_amount_tax = fields.Monetary( string="Taxes", store=True, readonly=True, compute="_compute_amount_all_tp", ) tp_amount_total = fields.Monetary( string="Total", store=True, readonly=True, compute="_compute_amount_all_tp", ) @api.depends("order_line.third_party_price_total") def _compute_amount_all_tp(self): for order in self: tp_amount_untaxed = tp_amount_tax = 0.0 for line in order.order_line: tp_amount_untaxed += line.third_party_price_subtotal tp_amount_tax += line.third_party_price_tax order.update({ "tp_amount_untaxed": order.currency_id.round(tp_amount_untaxed), "tp_amount_tax": order.currency_id.round(tp_amount_tax), "tp_amount_total": tp_amount_untaxed + tp_amount_tax, }) @api.multi def action_rfq_send(self): res = super().action_rfq_send() if self.env.context.get("third_party_send"): ctx = res.get("context") ir_model_data = self.env["ir.model.data"] try: if self.env.context.get("send_rfq", False): template_id = ir_model_data.get_object_reference( "purchase_third_party", "email_template_edi_purchase")[1] else: template_id = ir_model_data.get_object_reference( "purchase_third_party", "email_template_edi_purchase_done", )[1] except ValueError: template_id = False ctx.update({ "default_use_template": bool(template_id), "default_template_id": template_id, "tpl_partners_only": False, "custom_layout": "purchase_third_party." "mail_template_data_notification_email_purchase_order", "not_display_company": True, }) res.update({"context": ctx}) return res
class ResPartner(models.Model): _name = 'res.partner' _inherit = 'res.partner' _description = 'Partner' @api.multi def _credit_debit_get(self): tables, where_clause, where_params = self.env['account.move.line']._query_get() where_params = [tuple(self.ids)] + where_params self._cr.execute("""SELECT l.partner_id, act.type, SUM(l.amount_residual) FROM account_move_line l LEFT JOIN account_account a ON (l.account_id=a.id) LEFT JOIN account_account_type act ON (a.user_type_id=act.id) WHERE act.type IN ('receivable','payable') AND l.partner_id IN %s AND l.reconciled IS FALSE """ + where_clause + """ GROUP BY l.partner_id, act.type """, where_params) for pid, type, val in self._cr.fetchall(): partner = self.browse(pid) if type == 'receivable': partner.credit = val elif type == 'payable': partner.debit = -val @api.multi def _asset_difference_search(self, account_type, operator, operand): if operator not in ('<', '=', '>', '>=', '<='): return [] if type(operand) not in (float, int): return [] sign = 1 if account_type == 'payable': sign = -1 res = self._cr.execute(''' SELECT partner.id FROM res_partner partner LEFT JOIN account_move_line aml ON aml.partner_id = partner.id RIGHT JOIN account_account acc ON aml.account_id = acc.id WHERE acc.internal_type = %s AND NOT acc.deprecated GROUP BY partner.id HAVING %s * COALESCE(SUM(aml.amount_residual), 0) ''' + operator + ''' %s''', (account_type, sign, operand)) res = self._cr.fetchall() if not res: return [('id', '=', '0')] return [('id', 'in', map(itemgetter(0), res))] @api.model def _credit_search(self, operator, operand): return self._asset_difference_search('receivable', operator, operand) @api.model def _debit_search(self, operator, operand): return self._asset_difference_search('payable', operator, operand) @api.multi def _invoice_total(self): account_invoice_report = self.env['account.invoice.report'] if not self.ids: self.total_invoiced = 0.0 return True user_currency_id = self.env.user.company_id.currency_id.id all_partners_and_children = {} all_partner_ids = [] for partner in self: # price_total is in the company currency all_partners_and_children[partner] = self.search([('id', 'child_of', partner.id)]).ids all_partner_ids += all_partners_and_children[partner] # searching account.invoice.report via the orm is comparatively expensive # (generates queries "id in []" forcing to build the full table). # In simple cases where all invoices are in the same currency than the user's company # access directly these elements # generate where clause to include multicompany rules where_query = account_invoice_report._where_calc([ ('partner_id', 'in', all_partner_ids), ('state', 'not in', ['draft', 'cancel']), ('company_id', '=', self.env.user.company_id.id), ('type', 'in', ('out_invoice', 'out_refund')) ]) account_invoice_report._apply_ir_rules(where_query, 'read') from_clause, where_clause, where_clause_params = where_query.get_sql() # price_total is in the company currency query = """ SELECT SUM(price_total) as total, partner_id FROM account_invoice_report account_invoice_report WHERE %s GROUP BY partner_id """ % where_clause self.env.cr.execute(query, where_clause_params) price_totals = self.env.cr.dictfetchall() for partner, child_ids in all_partners_and_children.items(): partner.total_invoiced = sum(price['total'] for price in price_totals if price['partner_id'] in child_ids) @api.multi def _journal_item_count(self): for partner in self: partner.journal_item_count = self.env['account.move.line'].search_count([('partner_id', '=', partner.id)]) partner.contracts_count = self.env['account.analytic.account'].search_count([('partner_id', '=', partner.id)]) def get_followup_lines_domain(self, date, overdue_only=False, only_unblocked=False): domain = [('reconciled', '=', False), ('account_id.deprecated', '=', False), ('account_id.internal_type', '=', 'receivable'), '|', ('debit', '!=', 0), ('credit', '!=', 0), ('company_id', '=', self.env.user.company_id.id)] if only_unblocked: domain += [('blocked', '=', False)] if self.ids: if 'exclude_given_ids' in self._context: domain += [('partner_id', 'not in', self.ids)] else: domain += [('partner_id', 'in', self.ids)] #adding the overdue lines overdue_domain = ['|', '&', ('date_maturity', '!=', False), ('date_maturity', '<', date), '&', ('date_maturity', '=', False), ('date', '<', date)] if overdue_only: domain += overdue_domain return domain @api.multi def _compute_issued_total(self): """ Returns the issued total as will be displayed on partner view """ today = fields.Date.context_today(self) for partner in self: domain = partner.get_followup_lines_domain(today, overdue_only=True) issued_total = 0 for aml in self.env['account.move.line'].search(domain): issued_total += aml.amount_residual partner.issued_total = issued_total @api.one def _compute_has_unreconciled_entries(self): # Avoid useless work if has_unreconciled_entries is not relevant for this partner if not self.active or not self.is_company and self.parent_id: return self.env.cr.execute( """ SELECT 1 FROM( SELECT p.last_time_entries_checked AS last_time_entries_checked, MAX(l.write_date) AS max_date FROM account_move_line l RIGHT JOIN account_account a ON (a.id = l.account_id) RIGHT JOIN res_partner p ON (l.partner_id = p.id) WHERE p.id = %s AND EXISTS ( SELECT 1 FROM account_move_line l WHERE l.account_id = a.id AND l.partner_id = p.id AND l.amount_residual > 0 ) AND EXISTS ( SELECT 1 FROM account_move_line l WHERE l.account_id = a.id AND l.partner_id = p.id AND l.amount_residual < 0 ) GROUP BY p.last_time_entries_checked ) as s WHERE (last_time_entries_checked IS NULL OR max_date > last_time_entries_checked) """, (self.id,)) self.has_unreconciled_entries = self.env.cr.rowcount == 1 @api.multi def mark_as_reconciled(self): self.env['account.partial.reconcile'].check_access_rights('write') return self.sudo().write({'last_time_entries_checked': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}) @api.one def _get_company_currency(self): if self.company_id: self.currency_id = self.sudo().company_id.currency_id else: self.currency_id = self.env.user.company_id.currency_id credit = fields.Monetary(compute='_credit_debit_get', search=_credit_search, string='Total Receivable', help="Total amount this customer owes you.") debit = fields.Monetary(compute='_credit_debit_get', search=_debit_search, string='Total Payable', help="Total amount you have to pay to this vendor.") debit_limit = fields.Monetary('Payable Limit') total_invoiced = fields.Monetary(compute='_invoice_total', string="Total Invoiced", groups='account.group_account_invoice') currency_id = fields.Many2one('res.currency', compute='_get_company_currency', readonly=True, string="Currency", help='Utility field to express amount currency') contracts_count = fields.Integer(compute='_journal_item_count', string="Contracts", type='integer') journal_item_count = fields.Integer(compute='_journal_item_count', string="Journal Items", type="integer") issued_total = fields.Monetary(compute='_compute_issued_total', string="Journal Items") property_account_payable_id = fields.Many2one('account.account', company_dependent=True, string="Account Payable", oldname="property_account_payable", domain="[('internal_type', '=', 'payable'), ('deprecated', '=', False)]", help="This account will be used instead of the default one as the payable account for the current partner", required=True) property_account_receivable_id = fields.Many2one('account.account', company_dependent=True, string="Account Receivable", oldname="property_account_receivable", domain="[('internal_type', '=', 'receivable'), ('deprecated', '=', False)]", help="This account will be used instead of the default one as the receivable account for the current partner", required=True) property_account_position_id = fields.Many2one('account.fiscal.position', company_dependent=True, string="Fiscal Position", help="The fiscal position will determine taxes and accounts used for the partner.", oldname="property_account_position") property_payment_term_id = fields.Many2one('account.payment.term', company_dependent=True, string='Customer Payment Terms', help="This payment term will be used instead of the default one for sales orders and customer invoices", oldname="property_payment_term") property_supplier_payment_term_id = fields.Many2one('account.payment.term', company_dependent=True, string='Vendor Payment Terms', help="This payment term will be used instead of the default one for purchase orders and vendor bills", oldname="property_supplier_payment_term") ref_company_ids = fields.One2many('res.company', 'partner_id', string='Companies that refers to partner', oldname="ref_companies") has_unreconciled_entries = fields.Boolean(compute='_compute_has_unreconciled_entries', help="The partner has at least one unreconciled debit and credit since last time the invoices & payments matching was performed.") last_time_entries_checked = fields.Datetime(oldname='last_reconciliation_date', string='Latest Invoices & Payments Matching Date', readonly=True, copy=False, help='Last time the invoices & payments matching was performed for this partner. ' 'It is set either if there\'s not at least an unreconciled debit and an unreconciled credit ' 'or if you click the "Done" button.') invoice_ids = fields.One2many('account.invoice', 'partner_id', string='Invoices', readonly=True, copy=False) contract_ids = fields.One2many('account.analytic.account', 'partner_id', string='Contracts', readonly=True) bank_account_count = fields.Integer(compute='_compute_bank_count', string="Bank") trust = fields.Selection([('good', 'Good Debtor'), ('normal', 'Normal Debtor'), ('bad', 'Bad Debtor')], string='Degree of trust you have in this debtor', default='normal', company_dependent=True) invoice_warn = fields.Selection(WARNING_MESSAGE, 'Invoice', help=WARNING_HELP, required=True, default="no-message") invoice_warn_msg = fields.Text('Message for Invoice') @api.multi def _compute_bank_count(self): bank_data = self.env['res.partner.bank'].read_group([('partner_id', 'in', self.ids)], ['partner_id'], ['partner_id']) mapped_data = dict([(bank['partner_id'][0], bank['partner_id_count']) for bank in bank_data]) for partner in self: partner.bank_account_count = mapped_data.get(partner.id, 0) def _find_accounting_partner(self, partner): ''' Find the partner for which the accounting entries will be created ''' return partner.commercial_partner_id @api.model def _commercial_fields(self): return super(ResPartner, self)._commercial_fields() + \ ['debit_limit', 'property_account_payable_id', 'property_account_receivable_id', 'property_account_position_id', 'property_payment_term_id', 'property_supplier_payment_term_id', 'last_time_entries_checked'] def open_partner_history(self): ''' This function returns an action that display invoices/refunds made for the given partners. ''' action = self.env.ref('account.action_invoice_refund_out_tree') result = action.read()[0] result['domain'] = [('partner_id', 'in', self.ids)] return result
class InvoiceEletronic(models.Model): _inherit = 'invoice.eletronic' state = fields.Selection(selection_add=[('waiting', 'Esperando processamento')]) def _get_state_to_send(self): res = super(InvoiceEletronic, self)._get_state_to_send() return res + ('waiting', ) @api.depends('valor_retencao_pis', 'valor_retencao_cofins', 'valor_retencao_irrf', 'valor_retencao_inss', 'valor_retencao_csll') def _compute_total_retencoes(self): for item in self: total = item.valor_retencao_pis + item.valor_retencao_cofins + \ item.valor_retencao_irrf + item.valor_retencao_inss + \ item.valor_retencao_csll item.retencoes_federais = total retencoes_federais = fields.Monetary(string="Retenções Federais", compute=_compute_total_retencoes) def _hook_validation(self): errors = super(InvoiceEletronic, self)._hook_validation() if self.model == '002': issqn_codigo = '' if not self.company_id.inscr_mun: errors.append(u'Inscrição municipal obrigatória') if not self.company_id.cnae_main_id.code: errors.append(u'CNAE Principal da empresa obrigatório') for eletr in self.eletronic_item_ids: prod = u"Produto: %s - %s" % (eletr.product_id.default_code, eletr.product_id.name) if eletr.tipo_produto == 'product': errors.append( u'Esse documento permite apenas serviços - %s' % prod) if eletr.tipo_produto == 'service': if not eletr.issqn_codigo: errors.append(u'%s - Código de Serviço' % prod) if not issqn_codigo: issqn_codigo = eletr.issqn_codigo if issqn_codigo != eletr.issqn_codigo: errors.append(u'%s - Apenas itens com o mesmo código \ de serviço podem ser enviados' % prod) if not eletr.codigo_tributacao_municipio: errors.append( u'%s - %s - Código de tributação do município \ obrigatório' % (eletr.product_id.name, eletr.product_id.service_type_id.name)) return errors def _prepare_eletronic_invoice_values(self): res = super(InvoiceEletronic, self)._prepare_eletronic_invoice_values() if self.model != '002': return res tz = pytz.timezone(self.env.user.partner_id.tz) or pytz.utc dt_emissao = pytz.utc.localize(self.data_emissao).astimezone(tz) dt_emissao = dt_emissao.strftime('%Y-%m-%dT%H:%M:%S') partner = self.commercial_partner_id city_tomador = partner.city_id tomador = { 'tipo_cpfcnpj': 2 if partner.is_company else 1, 'cnpj_cpf': re.sub('[^0-9]', '', partner.cnpj_cpf or ''), 'razao_social': partner.legal_name or partner.name, 'logradouro': partner.street or '', 'numero': partner.number or '', 'complemento': partner.street2 or '', 'bairro': partner.district or 'Sem Bairro', 'cidade': '%s%s' % (city_tomador.state_id.ibge_code, city_tomador.ibge_code), 'uf': partner.state_id.code, 'cep': re.sub('[^0-9]', '', partner.zip), 'telefone': re.sub('[^0-9]', '', partner.phone or ''), 'inscricao_municipal': re.sub('[^0-9]', '', partner.inscr_mun or ''), 'email': self.partner_id.email or partner.email or '', } city_prestador = self.company_id.partner_id.city_id prestador = { 'cnpj': re.sub('[^0-9]', '', self.company_id.partner_id.cnpj_cpf or ''), 'inscricao_municipal': re.sub('[^0-9]', '', self.company_id.partner_id.inscr_mun or ''), 'cidade': '%s%s' % (city_prestador.state_id.ibge_code, city_prestador.ibge_code), 'cnae': re.sub('[^0-9]', '', self.company_id.cnae_main_id.code) } itens_servico = [] descricao = '' codigo_servico = '' for item in self.eletronic_item_ids: descricao += item.name + '\n' itens_servico.append({ 'descricao': item.name, 'quantidade': str("%.2f" % item.quantidade), 'valor_unitario': str("%.2f" % item.preco_unitario) }) codigo_servico = re.sub('[^0-9]', '', item.issqn_codigo) rps = { 'numero': self.numero, 'serie': self.serie.code or '', 'tipo_rps': '1', 'data_emissao': dt_emissao, 'natureza_operacao': '1', # Tributada no municipio 'regime_tributacao': '2', # Estimativa 'optante_simples': # 1 - Sim, 2 - Não '2' if self.company_id.fiscal_type == '3' else '1', 'incentivador_cultural': '2', # 2 - Não 'status': '1', # 1 - Normal 'valor_servico': str("%.2f" % self.valor_servicos), 'valor_deducao': '0', 'valor_pis': str("%.2f" % self.valor_retencao_pis), 'valor_cofins': str("%.2f" % self.valor_retencao_cofins), 'valor_inss': str("%.2f" % self.valor_retencao_inss), 'valor_ir': str("%.2f" % self.valor_retencao_irrf), 'valor_csll': str("%.2f" % self.valor_retencao_csll), 'iss_retido': '1' if self.valor_retencao_issqn > 0 else '2', 'valor_iss': str("%.2f" % self.valor_issqn), 'valor_iss_retido': str("%.2f" % self.valor_retencao_issqn), 'base_calculo': str("%.2f" % self.valor_bc_issqn), 'aliquota_issqn': str("%.4f" % ( self.eletronic_item_ids[0].issqn_aliquota / 100)), 'valor_liquido_nfse': str("%.2f" % self.valor_final), 'codigo_servico': int(codigo_servico), 'codigo_tributacao_municipio': self.eletronic_item_ids[0].codigo_tributacao_municipio, # '01.07.00 / 00010700', 'descricao': descricao, 'codigo_municipio': prestador['cidade'], 'itens_servico': itens_servico, 'tomador': tomador, 'prestador': prestador, } nfse_vals = { 'numero_lote': self.id, 'inscricao_municipal': prestador['inscricao_municipal'], 'cnpj_prestador': prestador['cnpj'], 'lista_rps': [rps], } res.update(nfse_vals) return res def action_post_validate(self): super(InvoiceEletronic, self).action_post_validate() if self.model not in ('002'): return cert = self.company_id.with_context({'bin_size': False}).nfe_a1_file cert_pfx = base64.decodestring(cert) certificado = Certificado(cert_pfx, self.company_id.nfe_a1_password) nfse_values = self._prepare_eletronic_invoice_values() xml_enviar = xml_recepcionar_lote_rps(certificado, nfse=nfse_values) self.xml_to_send = base64.encodestring(xml_enviar.encode('utf-8')) self.xml_to_send_name = 'nfse-enviar-%s.xml' % self.numero def _find_attachment_ids_email(self): atts = super(InvoiceEletronic, self)._find_attachment_ids_email() if self.model not in ('002'): return atts attachment_obj = self.env['ir.attachment'] danfe_report = self.env['ir.actions.report'].search([ ('report_name', '=', 'br_nfse_ginfes.main_template_br_nfse_danfe_ginfes') ]) report_service = danfe_report.xml_id report_name = safe_eval(danfe_report.print_report_name, { 'object': self, 'time': time }) danfse, dummy = self.env.ref(report_service).render_qweb_pdf([self.id]) filename = "%s.%s" % (report_name, "pdf") if danfse: danfe_id = attachment_obj.create( dict( name=filename, datas_fname=filename, datas=base64.b64encode(danfse), mimetype='application/pdf', res_model='account.move', res_id=self.invoice_id.id, )) atts.append(danfe_id.id) return atts def action_send_eletronic_invoice(self): super(InvoiceEletronic, self).action_send_eletronic_invoice() if self.model != '002' or self.state in ('done', 'cancel'): return self.state = 'error' cert = self.company_id.with_context({'bin_size': False}).nfe_a1_file cert_pfx = base64.decodestring(cert) certificado = Certificado(cert_pfx, self.company_id.nfe_a1_password) consulta_lote = None recebe_lote = None # Envia o lote apenas se não existir protocolo if not self.recibo_nfe: xml_to_send = base64.decodestring(self.xml_to_send) recebe_lote = recepcionar_lote_rps(certificado, xml=xml_to_send, ambiente=self.ambiente) retorno = recebe_lote['object'] if "NumeroLote" in dir(retorno): self.recibo_nfe = retorno.Protocolo # Espera alguns segundos antes de consultar time.sleep(5) else: mensagem_retorno = retorno.ListaMensagemRetorno\ .MensagemRetorno self.codigo_retorno = mensagem_retorno.Codigo self.mensagem_retorno = mensagem_retorno.Mensagem self._create_attachment('nfse-ret', self, recebe_lote['received_xml']) return # Monta a consulta de situação do lote # 1 - Não Recebido # 2 - Não processado # 3 - Processado com erro # 4 - Processado com sucesso obj = { 'cnpj_prestador': re.sub('[^0-9]', '', self.company_id.cnpj_cpf), 'inscricao_municipal': re.sub('[^0-9]', '', self.company_id.inscr_mun), 'protocolo': self.recibo_nfe, } consulta_situacao = consultar_situacao_lote(certificado, consulta=obj, ambiente=self.ambiente) ret_rec = consulta_situacao['object'] if "Situacao" in dir(ret_rec): if ret_rec.Situacao in (3, 4): consulta_lote = consultar_lote_rps(certificado, consulta=obj, ambiente=self.ambiente) retLote = consulta_lote['object'] if "ListaNfse" in dir(retLote): self.state = 'done' self.codigo_retorno = '100' self.mensagem_retorno = 'NFSe emitida com sucesso' self.verify_code = retLote.ListaNfse.CompNfse \ .Nfse.InfNfse.CodigoVerificacao self.numero_nfse = \ retLote.ListaNfse.CompNfse.Nfse.InfNfse.Numero else: mensagem_retorno = retLote.ListaMensagemRetorno \ .MensagemRetorno self.codigo_retorno = mensagem_retorno.Codigo self.mensagem_retorno = mensagem_retorno.Mensagem elif ret_rec.Situacao == 1: # Reenviar caso não recebido self.codigo_retorno = '' self.mensagem_retorno = 'Aguardando envio' self.state = 'draft' else: self.state = 'waiting' self.codigo_retorno = '2' self.mensagem_retorno = 'Lote aguardando processamento' else: self.codigo_retorno = \ ret_rec.ListaMensagemRetorno.MensagemRetorno.Codigo self.mensagem_retorno = \ ret_rec.ListaMensagemRetorno.MensagemRetorno.Mensagem self.env['invoice.eletronic.event'].create({ 'code': self.codigo_retorno, 'name': self.mensagem_retorno, 'invoice_eletronic_id': self.id, }) if recebe_lote: self._create_attachment('nfse-ret', self, recebe_lote['received_xml']) if consulta_lote: self._create_attachment('rec', self, consulta_lote['sent_xml']) self._create_attachment('rec-ret', self, consulta_lote['received_xml']) def action_cancel_document(self, context=None, justificativa=None): if self.model not in ('002'): return super( InvoiceEletronic, self).action_cancel_document(justificativa=justificativa) cert = self.company_id.with_context({'bin_size': False}).nfe_a1_file cert_pfx = base64.decodestring(cert) certificado = Certificado(cert_pfx, self.company_id.nfe_a1_password) company = self.company_id city_prestador = self.company_id.partner_id.city_id canc = { 'cnpj_prestador': re.sub('[^0-9]', '', company.cnpj_cpf), 'inscricao_municipal': re.sub('[^0-9]', '', company.inscr_mun), 'cidade': '%s%s' % (city_prestador.state_id.ibge_code, city_prestador.ibge_code), 'numero_nfse': self.numero_nfse, 'codigo_cancelamento': '1', 'senha': self.company_id.senha_ambiente_nfse } cancel = cancelar_nfse(certificado, cancelamento=canc, ambiente=self.ambiente) retorno = cancel['object'].Body.CancelarNfseResponse.CancelarNfseResult if "Cancelamento" in dir(retorno): self.state = 'cancel' self.codigo_retorno = '100' self.mensagem_retorno = u'Nota Fiscal de Serviço Cancelada' else: # E79 - Nota já está cancelada if retorno.ListaMensagemRetorno.MensagemRetorno.Codigo != 'E79': mensagem = "%s - %s" % ( retorno.ListaMensagemRetorno.MensagemRetorno.Codigo, retorno.ListaMensagemRetorno.MensagemRetorno.Mensagem) raise UserError(mensagem) self.state = 'cancel' self.codigo_retorno = '100' self.mensagem_retorno = u'Nota Fiscal de Serviço Cancelada' self.env['invoice.eletronic.event'].create({ 'code': self.codigo_retorno, 'name': self.mensagem_retorno, 'invoice_eletronic_id': self.id, }) self._create_attachment('canc', self, cancel['sent_xml']) self._create_attachment('canc-ret', self, cancel['received_xml'])
class SpedOperacaoFiscal(models.Model): _name = b'sped.operacao' _description = 'Operações Fiscais' _order = 'emissao, modelo, nome' _rec_name = 'nome' empresa_id = fields.Many2one(comodel_name='sped.empresa', string='Empresa', ondelete='restrict') modelo = fields.Selection(selection=MODELO_FISCAL, string='Modelo', required=True, index=True, default=MODELO_FISCAL_NFE) emissao = fields.Selection(selection=TIPO_EMISSAO, string='Tipo de emissão', index=True, default=TIPO_EMISSAO_PROPRIA) entrada_saida = fields.Selection(selection=ENTRADA_SAIDA, string='Entrada/saída', index=True, default=ENTRADA_SAIDA_SAIDA) nome = fields.Char(string='Nome', size=120, index=True) codigo = fields.Char(string='Código', size=60, index=True) serie = fields.Char(string='Série', size=3) regime_tributario = fields.Selection(selection=REGIME_TRIBUTARIO, string='Regime tributário', default=REGIME_TRIBUTARIO_SIMPLES) ind_forma_pagamento = fields.Selection(selection=IND_FORMA_PAGAMENTO, string='Tipo de pagamento', default=IND_FORMA_PAGAMENTO_A_VISTA) finalidade_nfe = fields.Selection(selection=FINALIDADE_NFE, string='Finalidade da NF-e', default=FINALIDADE_NFE_NORMAL) modalidade_frete = fields.Selection( selection=MODALIDADE_FRETE, string='Modalidade do frete', default=MODALIDADE_FRETE_DESTINATARIO_PROPRIO) natureza_operacao_id = fields.Many2one( comodel_name='sped.natureza.operacao', string='Natureza da operação', ondelete='restrict') infadfisco = fields.Text( string='Informações adicionais de interesse do fisco') infcomplementar = fields.Text(string='Informações complementares') # # Retenção de impostos # deduz_retencao = fields.Boolean(string='Deduz retenção do total da NF?', default=True) pis_cofins_retido = fields.Boolean(string='PIS-COFINS retidos?') al_pis_retido = fields.Float(string='Alíquota do PIS', default=0.65, digits=(5, 2)) al_cofins_retido = fields.Float(string='Alíquota da COFINS', default=3, digits=(5, 2)) csll_retido = fields.Boolean(string='CSLL retido?') al_csll = fields.Float(string='Alíquota da CSLL', default=1, digits=(5, 2)) # # Para todos os valores de referência numa operação fiscal, # a moeda é SEMPRE o # Real BRL # currency_id = fields.Many2one( comodel_name='res.currency', string='Moeda', default=lambda self: self.env.ref('base.BRL').id) limite_retencao_pis_cofins_csll = fields.Monetary( string='Obedecer limite de faturamento para retenção de', default=LIMITE_RETENCAO_PIS_COFINS_CSLL) irrf_retido = fields.Boolean(string='IR retido?') irrf_retido_ignora_limite = fields.Boolean( string='IR retido ignora limite de R$ 10,00?', ) al_irrf = fields.Float( string='Alíquota do IR', default=1, digits=(5, 2), ) # # Notas de serviço # inss_retido = fields.Boolean(string='INSS retido?', ) cnae_id = fields.Many2one(comodel_name='sped.cnae', string='CNAE') natureza_tributacao_nfse = fields.Selection( selection=NATUREZA_TRIBUTACAO_NFSE, string='Natureza da tributação', ) servico_id = fields.Many2one(comodel_name='sped.servico', string='Serviço') cst_iss = fields.Selection(selection=ST_ISS, string='CST ISS') # 'forca_recalculo_st_compra': fields.boolean( # 'Força recálculo do ST na compra?'), # 'operacao_entrada_id': fields.many2one( # 'sped.operacao', 'Operação de entrada equivalente'), consumidor_final = fields.Selection(selection=TIPO_CONSUMIDOR_FINAL, string='Tipo do consumidor', default=TIPO_CONSUMIDOR_FINAL_NORMAL) presenca_comprador = fields.Selection( selection=INDICADOR_PRESENCA_COMPRADOR, string='Presença do comprador', default=INDICADOR_PRESENCA_COMPRADOR_NAO_SE_APLICA) preco_automatico = fields.Selection( selection=[ ('V', 'Venda'), ('C', 'Custo'), ], string='Traz preço automático?', )
class AccountAccount(models.Model): _name = "account.account" _inherit = ['mail.thread'] _description = "Account" _order = "is_off_balance, code, company_id" _check_company_auto = True @api.constrains('internal_type', 'reconcile') def _check_reconcile(self): for account in self: if account.internal_type in ('receivable', 'payable') and account.reconcile == False: raise ValidationError(_('You cannot have a receivable/payable account that is not reconcilable. (account code: %s)', account.code)) @api.constrains('user_type_id') def _check_user_type_id_unique_current_year_earning(self): data_unaffected_earnings = self.env.ref('account.data_unaffected_earnings') result = self._read_group( domain=[('user_type_id', '=', data_unaffected_earnings.id)], fields=['company_id', 'ids:array_agg(id)'], groupby=['company_id'], ) for res in result: if res.get('company_id_count', 0) >= 2: account_unaffected_earnings = self.browse(res['ids']) raise ValidationError(_('You cannot have more than one account with "Current Year Earnings" as type. (accounts: %s)', [a.code for a in account_unaffected_earnings])) name = fields.Char(string="Account Name", required=True, index='trigram', tracking=True) currency_id = fields.Many2one('res.currency', string='Account Currency', help="Forces all moves for this account to have this account currency.", tracking=True) code = fields.Char(size=64, required=True, index=True, tracking=True) deprecated = fields.Boolean(default=False, tracking=True) used = fields.Boolean(compute='_compute_used', search='_search_used') user_type_id = fields.Many2one('account.account.type', string='Type', required=True, tracking=True, help="Account Type is used for information purpose, to generate country-specific legal reports, and set the rules to close a fiscal year and generate opening entries.") internal_type = fields.Selection(related='user_type_id.type', string="Internal Type", store=True, readonly=True) internal_group = fields.Selection(related='user_type_id.internal_group', string="Internal Group", store=True, readonly=True) #has_unreconciled_entries = fields.Boolean(compute='_compute_has_unreconciled_entries', # help="The account has at least one unreconciled debit and credit since last time the invoices & payments matching was performed.") reconcile = fields.Boolean(string='Allow Reconciliation', default=False, tracking=True, help="Check this box if this account allows invoices & payments matching of journal items.") tax_ids = fields.Many2many('account.tax', 'account_account_tax_default_rel', 'account_id', 'tax_id', string='Default Taxes', check_company=True, context={'append_type_to_tax_name': True}) note = fields.Text('Internal Notes', tracking=True) company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, default=lambda self: self.env.company) tag_ids = fields.Many2many('account.account.tag', 'account_account_account_tag', string='Tags', help="Optional tags you may want to assign for custom reporting") group_id = fields.Many2one('account.group', compute='_compute_account_group', store=True, readonly=True, help="Account prefixes can determine account groups.") root_id = fields.Many2one('account.root', compute='_compute_account_root', store=True) allowed_journal_ids = fields.Many2many('account.journal', string="Allowed Journals", help="Define in which journals this account can be used. If empty, can be used in all journals.") opening_debit = fields.Monetary(string="Opening Debit", compute='_compute_opening_debit_credit', inverse='_set_opening_debit', help="Opening debit value for this account.") opening_credit = fields.Monetary(string="Opening Credit", compute='_compute_opening_debit_credit', inverse='_set_opening_credit', help="Opening credit value for this account.") opening_balance = fields.Monetary(string="Opening Balance", compute='_compute_opening_debit_credit', help="Opening balance value for this account.") is_off_balance = fields.Boolean(compute='_compute_is_off_balance', default=False, store=True, readonly=True) current_balance = fields.Float(compute='_compute_current_balance') related_taxes_amount = fields.Integer(compute='_compute_related_taxes_amount') _sql_constraints = [ ('code_company_uniq', 'unique (code,company_id)', 'The code of the account must be unique per company !') ] non_trade = fields.Boolean(default=False, help="If set, this account will belong to Non Trade Receivable/Payable in reports and filters.\n" "If not, this account will belong to Trade Receivable/Payable in reports and filters.") @api.constrains('reconcile', 'internal_group', 'tax_ids') def _constrains_reconcile(self): for record in self: if record.internal_group == 'off_balance': if record.reconcile: raise UserError(_('An Off-Balance account can not be reconcilable')) if record.tax_ids: raise UserError(_('An Off-Balance account can not have taxes')) @api.constrains('allowed_journal_ids') def _constrains_allowed_journal_ids(self): self.env['account.move.line'].flush(['account_id', 'journal_id']) self.flush(['allowed_journal_ids']) self._cr.execute(""" SELECT aml.id FROM account_move_line aml WHERE aml.account_id in %s AND EXISTS (SELECT 1 FROM account_account_account_journal_rel WHERE account_account_id = aml.account_id) AND NOT EXISTS (SELECT 1 FROM account_account_account_journal_rel WHERE account_account_id = aml.account_id AND account_journal_id = aml.journal_id) """, [tuple(self.ids)]) ids = self._cr.fetchall() if ids: raise ValidationError(_('Some journal items already exist with this account but in other journals than the allowed ones.')) @api.constrains('currency_id') def _check_journal_consistency(self): ''' Ensure the currency set on the journal is the same as the currency set on the linked accounts. ''' if not self: return self.env['account.account'].flush(['currency_id']) self.env['account.journal'].flush([ 'currency_id', 'default_account_id', 'suspense_account_id', ]) self.env['account.payment.method'].flush(['payment_type']) self.env['account.payment.method.line'].flush(['payment_method_id', 'payment_account_id']) self._cr.execute(''' SELECT account.id, journal.id FROM account_journal journal JOIN res_company company ON company.id = journal.company_id JOIN account_account account ON account.id = journal.default_account_id WHERE journal.currency_id IS NOT NULL AND journal.currency_id != company.currency_id AND account.currency_id != journal.currency_id AND account.id IN %(accounts)s UNION ALL SELECT account.id, journal.id FROM account_journal journal JOIN res_company company ON company.id = journal.company_id JOIN account_payment_method_line apml ON apml.journal_id = journal.id JOIN account_payment_method apm on apm.id = apml.payment_method_id JOIN account_account account ON account.id = COALESCE(apml.payment_account_id, company.account_journal_payment_debit_account_id) WHERE journal.currency_id IS NOT NULL AND journal.currency_id != company.currency_id AND account.currency_id != journal.currency_id AND apm.payment_type = 'inbound' AND account.id IN %(accounts)s UNION ALL SELECT account.id, journal.id FROM account_journal journal JOIN res_company company ON company.id = journal.company_id JOIN account_payment_method_line apml ON apml.journal_id = journal.id JOIN account_payment_method apm on apm.id = apml.payment_method_id JOIN account_account account ON account.id = COALESCE(apml.payment_account_id, company.account_journal_payment_credit_account_id) WHERE journal.currency_id IS NOT NULL AND journal.currency_id != company.currency_id AND account.currency_id != journal.currency_id AND apm.payment_type = 'outbound' AND account.id IN %(accounts)s ''', { 'accounts': tuple(self.ids) }) res = self._cr.fetchone() if res: account = self.env['account.account'].browse(res[0]) journal = self.env['account.journal'].browse(res[1]) raise ValidationError(_( "The foreign currency set on the journal '%(journal)s' and the account '%(account)s' must be the same.", journal=journal.display_name, account=account.display_name )) @api.constrains('company_id') def _check_company_consistency(self): if not self: return self.flush(['company_id']) self._cr.execute(''' SELECT line.id FROM account_move_line line JOIN account_account account ON account.id = line.account_id WHERE line.account_id IN %s AND line.company_id != account.company_id ''', [tuple(self.ids)]) if self._cr.fetchone(): raise UserError(_("You can't change the company of your account since there are some journal items linked to it.")) @api.constrains('user_type_id') def _check_user_type_id_sales_purchase_journal(self): if not self: return self.flush(['user_type_id']) self._cr.execute(''' SELECT account.id FROM account_account account JOIN account_account_type acc_type ON account.user_type_id = acc_type.id JOIN account_journal journal ON journal.default_account_id = account.id WHERE account.id IN %s AND acc_type.type IN ('receivable', 'payable') AND journal.type IN ('sale', 'purchase') LIMIT 1; ''', [tuple(self.ids)]) if self._cr.fetchone(): raise ValidationError(_("The account is already in use in a 'sale' or 'purchase' journal. This means that the account's type couldn't be 'receivable' or 'payable'.")) @api.constrains('reconcile') def _check_used_as_journal_default_debit_credit_account(self): accounts = self.filtered(lambda a: not a.reconcile) if not accounts: return self.flush(['reconcile']) self.env['account.payment.method.line'].flush(['journal_id', 'payment_account_id']) self._cr.execute(''' SELECT journal.id FROM account_journal journal JOIN res_company company on journal.company_id = company.id LEFT JOIN account_payment_method_line apml ON journal.id = apml.journal_id WHERE ( company.account_journal_payment_credit_account_id IN %(accounts)s AND company.account_journal_payment_credit_account_id != journal.default_account_id ) OR ( company.account_journal_payment_debit_account_id in %(accounts)s AND company.account_journal_payment_debit_account_id != journal.default_account_id ) OR ( apml.payment_account_id IN %(accounts)s AND apml.payment_account_id != journal.default_account_id ) ''', { 'accounts': tuple(accounts.ids), }) rows = self._cr.fetchall() if rows: journals = self.env['account.journal'].browse([r[0] for r in rows]) raise ValidationError(_( "This account is configured in %(journal_names)s journal(s) (ids %(journal_ids)s) as payment debit or credit account. This means that this account's type should be reconcilable.", journal_names=journals.mapped('display_name'), journal_ids=journals.ids )) @api.depends('code') def _compute_account_root(self): # this computes the first 2 digits of the account. # This field should have been a char, but the aim is to use it in a side panel view with hierarchy, and it's only supported by many2one fields so far. # So instead, we make it a many2one to a psql view with what we need as records. for record in self: record.root_id = (ord(record.code[0]) * 1000 + ord(record.code[1:2] or '\x00')) if record.code else False @api.depends('code') def _compute_account_group(self): if self.ids: self.env['account.group']._adapt_accounts_for_account_groups(self) else: self.group_id = False def _search_used(self, operator, value): if operator not in ['=', '!='] or not isinstance(value, bool): raise UserError(_('Operation not supported')) if operator != '=': value = not value self._cr.execute(""" SELECT id FROM account_account account WHERE EXISTS (SELECT 1 FROM account_move_line aml WHERE aml.account_id = account.id LIMIT 1) """) return [('id', 'in' if value else 'not in', [r[0] for r in self._cr.fetchall()])] def _compute_used(self): ids = set(self._search_used('=', True)[0][2]) for record in self: record.used = record.id in ids @api.model def _search_new_account_code(self, company, digits, prefix): for num in range(1, 10000): new_code = str(prefix.ljust(digits - 1, '0')) + str(num) rec = self.search([('code', '=', new_code), ('company_id', '=', company.id)], limit=1) if not rec: return new_code raise UserError(_('Cannot generate an unused account code.')) def _compute_current_balance(self): balances = { read['account_id'][0]: read['balance'] for read in self.env['account.move.line']._read_group( domain=[('account_id', 'in', self.ids)], fields=['balance', 'account_id'], groupby=['account_id'], ) } for record in self: record.current_balance = balances.get(record.id, 0) def _compute_related_taxes_amount(self): for record in self: record.related_taxes_amount = self.env['account.tax'].search_count([ '|', ('invoice_repartition_line_ids.account_id', '=', record.id), ('refund_repartition_line_ids.account_id', '=', record.id), ]) def _compute_opening_debit_credit(self): self.opening_debit = 0 self.opening_credit = 0 self.opening_balance = 0 if not self.ids: return self.env.cr.execute(""" SELECT line.account_id, SUM(line.balance) AS balance, SUM(line.debit) AS debit, SUM(line.credit) AS credit FROM account_move_line line JOIN res_company comp ON comp.id = line.company_id WHERE line.move_id = comp.account_opening_move_id AND line.account_id IN %s GROUP BY line.account_id """, [tuple(self.ids)]) result = {r['account_id']: r for r in self.env.cr.dictfetchall()} for record in self: res = result.get(record.id) or {'debit': 0, 'credit': 0, 'balance': 0} record.opening_debit = res['debit'] record.opening_credit = res['credit'] record.opening_balance = res['balance'] @api.depends('internal_group') def _compute_is_off_balance(self): for account in self: account.is_off_balance = account.internal_group == "off_balance" def _set_opening_debit(self): for record in self: record._set_opening_debit_credit(record.opening_debit, 'debit') def _set_opening_credit(self): for record in self: record._set_opening_debit_credit(record.opening_credit, 'credit') def _set_opening_debit_credit(self, amount, field): """ Generic function called by both opening_debit and opening_credit's inverse function. 'Amount' parameter is the value to be set, and field either 'debit' or 'credit', depending on which one of these two fields got assigned. """ self.company_id.create_op_move_if_non_existant() opening_move = self.company_id.account_opening_move_id if opening_move.state == 'draft': # check whether we should create a new move line or modify an existing one account_op_lines = self.env['account.move.line'].search([('account_id', '=', self.id), ('move_id','=', opening_move.id), (field,'!=', False), (field,'!=', 0.0)]) # 0.0 condition important for import if account_op_lines: op_aml_debit = sum(account_op_lines.mapped('debit')) op_aml_credit = sum(account_op_lines.mapped('credit')) # There might be more than one line on this account if the opening entry was manually edited # If so, we need to merge all those lines into one before modifying its balance opening_move_line = account_op_lines[0] if len(account_op_lines) > 1: merge_write_cmd = [(1, opening_move_line.id, {'debit': op_aml_debit, 'credit': op_aml_credit, 'partner_id': None ,'name': _("Opening balance")})] unlink_write_cmd = [(2, line.id) for line in account_op_lines[1:]] opening_move.write({'line_ids': merge_write_cmd + unlink_write_cmd}) if amount: # modify the line opening_move_line.with_context(check_move_validity=False)[field] = amount else: # delete the line (no need to keep a line with value = 0) opening_move_line.with_context(check_move_validity=False).unlink() elif amount: # create a new line, as none existed before self.env['account.move.line'].with_context(check_move_validity=False).create({ 'name': _('Opening balance'), field: amount, 'move_id': opening_move.id, 'account_id': self.id, }) # Then, we automatically balance the opening move, to make sure it stays valid if not 'import_file' in self.env.context: # When importing a file, avoid recomputing the opening move for each account and do it at the end, for better performances self.company_id._auto_balance_opening_move() @api.model def default_get(self, default_fields): """If we're creating a new account through a many2one, there are chances that we typed the account code instead of its name. In that case, switch both fields values. """ if 'name' not in default_fields and 'code' not in default_fields: return super().default_get(default_fields) default_name = self._context.get('default_name') default_code = self._context.get('default_code') if default_name and not default_code: try: default_code = int(default_name) except ValueError: pass if default_code: default_name = False contextual_self = self.with_context(default_name=default_name, default_code=default_code) return super(AccountAccount, contextual_self).default_get(default_fields) @api.model def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): args = args or [] domain = [] if name: domain = ['|', ('code', '=ilike', name.split(' ')[0] + '%'), ('name', operator, name)] if operator in expression.NEGATIVE_TERM_OPERATORS: domain = ['&', '!'] + domain[1:] return self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid) @api.onchange('user_type_id') def _onchange_user_type_id(self): self.reconcile = self.internal_type in ('receivable', 'payable') if self.internal_type == 'liquidity': self.reconcile = False elif self.internal_group == 'off_balance': self.reconcile = False self.tax_ids = False elif self.internal_group == 'income' and not self.tax_ids: self.tax_ids = self.company_id.account_sale_tax_id elif self.internal_group == 'expense' and not self.tax_ids: self.tax_ids = self.company_id.account_purchase_tax_id def name_get(self): result = [] for account in self: name = account.code + ' ' + account.name result.append((account.id, name)) return result @api.returns('self', lambda value: value.id) def copy(self, default=None): default = dict(default or {}) if default.get('code', False): return super(AccountAccount, self).copy(default) try: default['code'] = (str(int(self.code) + 10) or '').zfill(len(self.code)) default.setdefault('name', _("%s (copy)") % (self.name or '')) while self.env['account.account'].search([('code', '=', default['code']), ('company_id', '=', default.get('company_id', False) or self.company_id.id)], limit=1): default['code'] = (str(int(default['code']) + 10) or '') default['name'] = _("%s (copy)") % (self.name or '') except ValueError: default['code'] = _("%s (copy)") % (self.code or '') default['name'] = self.name return super(AccountAccount, self).copy(default) @api.model def load(self, fields, data): """ Overridden for better performances when importing a list of account with opening debit/credit. In that case, the auto-balance is postpone until the whole file has been imported. """ rslt = super(AccountAccount, self).load(fields, data) if 'import_file' in self.env.context: companies = self.search([('id', 'in', rslt['ids'])]).mapped('company_id') for company in companies: company._auto_balance_opening_move() return rslt def _toggle_reconcile_to_true(self): '''Toggle the `reconcile´ boolean from False -> True Note that: lines with debit = credit = amount_currency = 0 are set to `reconciled´ = True ''' if not self.ids: return None query = """ UPDATE account_move_line SET reconciled = CASE WHEN debit = 0 AND credit = 0 AND amount_currency = 0 THEN true ELSE false END, amount_residual = (debit-credit), amount_residual_currency = amount_currency WHERE full_reconcile_id IS NULL and account_id IN %s """ self.env.cr.execute(query, [tuple(self.ids)]) def _toggle_reconcile_to_false(self): '''Toggle the `reconcile´ boolean from True -> False Note that it is disallowed if some lines are partially reconciled. ''' if not self.ids: return None partial_lines_count = self.env['account.move.line'].search_count([ ('account_id', 'in', self.ids), ('full_reconcile_id', '=', False), ('|'), ('matched_debit_ids', '!=', False), ('matched_credit_ids', '!=', False), ]) if partial_lines_count > 0: raise UserError(_('You cannot switch an account to prevent the reconciliation ' 'if some partial reconciliations are still pending.')) query = """ UPDATE account_move_line SET amount_residual = 0, amount_residual_currency = 0 WHERE full_reconcile_id IS NULL AND account_id IN %s """ self.env.cr.execute(query, [tuple(self.ids)]) def write(self, vals): # Do not allow changing the company_id when account_move_line already exist if vals.get('company_id', False): move_lines = self.env['account.move.line'].search([('account_id', 'in', self.ids)], limit=1) for account in self: if (account.company_id.id != vals['company_id']) and move_lines: raise UserError(_('You cannot change the owner company of an account that already contains journal items.')) if 'reconcile' in vals: if vals['reconcile']: self.filtered(lambda r: not r.reconcile)._toggle_reconcile_to_true() else: self.filtered(lambda r: r.reconcile)._toggle_reconcile_to_false() if vals.get('currency_id'): for account in self: if self.env['account.move.line'].search_count([('account_id', '=', account.id), ('currency_id', 'not in', (False, vals['currency_id']))]): raise UserError(_('You cannot set a currency on this account as it already has some journal entries having a different foreign currency.')) return super(AccountAccount, self).write(vals) @api.ondelete(at_uninstall=False) def _unlink_except_contains_journal_items(self): if self.env['account.move.line'].search([('account_id', 'in', self.ids)], limit=1): raise UserError(_('You cannot perform this action on an account that contains journal items.')) @api.ondelete(at_uninstall=False) def _unlink_except_account_set_on_customer(self): #Checking whether the account is set as a property to any Partner or not values = ['account.account,%s' % (account_id,) for account_id in self.ids] partner_prop_acc = self.env['ir.property'].sudo().search([('value_reference', 'in', values)], limit=1) if partner_prop_acc: account_name = partner_prop_acc.get_by_record().display_name raise UserError( _('You cannot remove/deactivate the account %s which is set on a customer or vendor.', account_name) ) @api.ondelete(at_uninstall=False) def _unlink_except_linked_to_fiscal_position(self): if self.env['account.fiscal.position.account'].search(['|', ('account_src_id', 'in', self.ids), ('account_dest_id', 'in', self.ids)], limit=1): raise UserError(_('You cannot remove/deactivate the accounts "%s" which are set on the account mapping of a fiscal position.', ', '.join(f"{a.code} - {a.name}" for a in self))) @api.ondelete(at_uninstall=False) def _unlink_except_linked_to_tax_repartition_line(self): if self.env['account.tax.repartition.line'].search([('account_id', 'in', self.ids)], limit=1): raise UserError(_('You cannot remove/deactivate the accounts "%s" which are set on a tax repartition line.', ', '.join(f"{a.code} - {a.name}" for a in self))) def action_read_account(self): self.ensure_one() return { 'name': self.display_name, 'type': 'ir.actions.act_window', 'view_type': 'form', 'view_mode': 'form', 'res_model': 'account.account', 'res_id': self.id, } def action_duplicate_accounts(self): for account in self.browse(self.env.context['active_ids']): account.copy() def action_open_related_taxes(self): related_taxes_ids = self.env['account.tax'].search([ '|', ('invoice_repartition_line_ids.account_id', '=', self.id), ('refund_repartition_line_ids.account_id', '=', self.id), ]).ids return { 'type': 'ir.actions.act_window', 'name': _('Taxes'), 'res_model': 'account.tax', 'view_type': 'list', 'view_mode': 'list', 'views': [[False, 'list'], [False, 'form']], 'domain': [('id', 'in', related_taxes_ids)], }
class AccountRegisterPayments(models.TransientModel): _inherit = 'account.register.payments' @api.onchange('journal_id') def onchange_journal(self): res = super(AccountRegisterPayments, self)._onchange_journal() res['value'] = {'check_ids': []} return res @api.onchange('payment_type') def _onchange_payment_type(self): # Set payment method domain res = self._onchange_journal() if not res.get('domain', {}): res['domain'] = {} res['domain']['journal_id'] = self.payment_type == 'inbound' and [ ('at_least_one_inbound', '=', True) ] or [('at_least_one_outbound', '=', True)] res['domain']['journal_id'].append(('type', 'in', ('bank', 'cash'))) return res check_ids = fields.Many2many( 'account.check', string='Cheques', copy=False, ) # only for v8 comatibility where more than one check could be received # or ownd check_ids_copy = fields.Many2many( related='check_ids', readonly=True, ) readonly_currency_id = fields.Many2one( related='currency_id', readonly=True, ) readonly_amount = fields.Monetary( related='amount', readonly=True, ) # we add this field for better usability on own checks and received # checks. We keep m2m field for backward compatibility where we allow to # use more than one check per payment check_id = fields.Many2one( 'account.check', compute='_compute_check', string='Cheque', ) @api.multi @api.depends('check_ids') def _compute_check(self): for rec in self: # we only show checks for own checks or received thid checks # if len of checks is 1 if rec.payment_method_code in ( 'received_third_check', 'own_check', ) and len(rec.check_ids) == 1: rec.check_id = rec.check_ids[0].id # check fields, just to make it easy to load checks without need to create # them by a m2o record check_name = fields.Char( 'Nombre del cheque', copy=False, ) check_number = fields.Integer('Número del cheque', copy=False) check_own_date = fields.Date( 'Fecha del cheque', copy=False, default=fields.Date.context_today, ) check_payment_date = fields.Date( 'Fecha del pago de cheque', help="Only if this check is post dated", ) checkbook_id = fields.Many2one( 'account.checkbook', 'Chequera', ) check_subtype = fields.Selection( related='checkbook_id.own_check_subtype', readonly=True, ) check_bank_id = fields.Many2one( 'res.bank', 'Banco del cheque', copy=False, ) check_owner_vat = fields.Char( 'Vat del emisor', copy=False, ) check_owner_name = fields.Char( 'Emisor', copy=False, ) # this fields is to help with code and view check_type = fields.Char(compute='_compute_check_type', ) checkbook_block_manual_number = fields.Boolean( related='checkbook_id.block_manual_number', ) check_number_readonly = fields.Integer( related='check_number', readonly=True, ) operation_no = fields.Char(string='Operation No.') @api.multi @api.depends('payment_method_code') def _compute_check_type(self): for rec in self: if rec.payment_method_code == 'own_check': rec.check_type = 'own_check' elif rec.payment_method_code in [ 'received_third_check', 'received_third_check' ]: rec.check_type = 'third_check' # on change methods # @api.constrains('check_ids') @api.onchange('check_ids', 'payment_method_code') def onchange_checks(self): # we only overwrite if payment method is delivered if self.payment_method_code == 'received_third_check': self.amount = sum(self.check_ids.mapped('amount')) @api.multi @api.onchange('check_number') def change_check_number(self): # TODO make default padding a parameter def _get_name_from_number(number): padding = 8 if len(str(number)) > padding: padding = len(str(number)) return ('%%0%sd' % padding % number) for rec in self: if rec.payment_method_code in ['received_third_check']: if not rec.check_number: check_name = False else: check_name = _get_name_from_number(rec.check_number) rec.check_name = check_name elif rec.payment_method_code in ['own_check']: sequence = rec.checkbook_id.sequence_id if not rec.check_number: check_name = False elif sequence: if rec.check_number != sequence.number_next_actual: sequence.write( {'number_next_actual': rec.check_number}) check_name = rec.checkbook_id.sequence_id.next_by_id() else: # in sipreco, for eg, no sequence on checkbooks check_name = _get_name_from_number(rec.check_number) rec.check_name = check_name @api.onchange('check_own_date', 'check_payment_date') def onchange_date(self): if (self.check_own_date and self.check_payment_date and self.check_own_date > self.check_payment_date): self.check_payment_date = False raise UserError( _('Fecha de pago del cheque debe ser mayor a fecha del cheque') ) @api.onchange('partner_id', 'payment_method_code') def onchange_partner_check(self): commercial_partner = self.partner_id.commercial_partner_id #Modificacion para v10. Preguntamos existencia de campo payment_method_code if self.payment_method_code: if self.payment_method_code == 'received_third_check': self.check_bank_id = (commercial_partner.bank_ids and commercial_partner.bank_ids[0].bank_id or False) self.check_owner_name = commercial_partner.name vat_field = 'vat' # to avoid needed of another module, we add this check to see # if l10n_ar cuit field is available if 'cuit' in commercial_partner._fields: vat_field = 'cuit' self.check_owner_vat = commercial_partner[vat_field] elif self.payment_method_code == 'own_check': self.check_bank_id = self.journal_id.bank_id self.check_owner_name = False self.check_owner_vat = False @api.onchange('payment_method_code') def _onchange_payment_method_code(self): if self.payment_method_code == 'own_check': checkbook = self.env['account.checkbook'].search( [('state', '=', 'active'), ('journal_id', '=', self.journal_id.id)], limit=1) self.checkbook_id = checkbook elif self.checkbook_id: # TODO ver si interesa implementar volver atras numeracion self.checkbook_id = False @api.onchange('checkbook_id') def onchange_checkbook(self): if self.checkbook_id: self.check_number = self.checkbook_id.next_number @api.multi def create_payment(self): ctx = dict(self._context) payment_register_wzd = self ctx['register_payment'] = True ctx['payment_register_wzd'] = payment_register_wzd super(AccountRegisterPayments, self.with_context(ctx)).create_payment() def get_payment_vals(self): vals = super(AccountRegisterPayments, self).get_payment_vals() vals.update({ 'check_ids': [(4, check.id, None) for check in self.check_ids], 'check_id': self.check_id.id, 'check_name': self.check_name, 'check_number': self.check_number, 'check_own_date': self.check_own_date, 'check_payment_date': self.check_payment_date, 'checkbook_id': self.checkbook_id.id, 'check_bank_id': self.check_bank_id.id, 'check_owner_vat': self.check_owner_vat, 'check_owner_name': self.check_owner_name, }) return vals
class manifest_pembayaran(models.Model): _name = "manifest.pembayaran" name = fields.Char('Reference', readonly=True, default='/') user_id = fields.Many2one('res.users', 'Operator', readonly=True, required=True, default=lambda self: self.env.user, copy=False) date = fields.Date('Tanggal', readonly=True, required=True, states={'draft': [('readonly', False)]}, default=fields.Date.context_today) siswa_id = fields.Many2one('res.partner', 'Siswa', domain=[('student', '=', True)], readonly=True, states={'draft': [('readonly', False)]}) orangtua_id = fields.Many2one('res.partner', 'Orang Tua', domain=[('parent', '=', True)], readonly=True, states={'draft': [('readonly', False)]}) tagihan_ids = fields.Many2many('account.invoice', 'tagihan_rel', 'manifest_id', 'tagihan_id', 'Invoice', domain=[('type', '=', 'out_invoice')], readonly=True, states={'draft': [('readonly', False)]}) state = fields.Selection(compute='compute_state', selection=[('draft', 'Draft'), ('paid', 'Paid')], string='Status', default='draft', store=True) currency_id = fields.Many2one("res.currency", string="Currency", compute='_compute_currency_id') amount_total = fields.Monetary(string='Total', store=True, readonly=True, compute='_amount_all') @api.model def create(self, vals): vals['name'] = self.env['ir.sequence'].next_by_code( 'manifest.pembayaran') or '/' result = super(manifest_pembayaran, self).create(vals) return result @api.multi def unlink(self): for o in self: if o.state == 'paid': raise UserError(( 'Manifest pembayaran tidak bisa dihapus pada status PAID !' )) return super(manifest_pembayaran, self).unlink() @api.onchange('orangtua_id', 'siswa_id') def onchange_orangtua_siswa(self): value = {} domain_tagihan = [('state', '=', 'open'), ('type', '=', 'out_invoice')] if self.siswa_id: value = {'orangtua_id': self.siswa_id.orangtua_id.id} domain_tagihan.append(('partner_id', '=', self.siswa_id.id)) if self.orangtua_id: # value = {'orangtua_id': self.siswa_id.orangtua_id.id} domain_tagihan.append(('orangtua_id', '=', self.orangtua_id.id)) return {'domain': {'tagihan_ids': domain_tagihan}, 'value': value} @api.depends('tagihan_ids.amount_total') def _amount_all(self): for o in self: total = 0 for i in o.tagihan_ids: total += i.amount_total o.update({ 'amount_total': total, }) @api.multi def _compute_currency_id(self): try: main_company = self.sudo().env.ref('base.main_company') except ValueError: main_company = self.env['res.company'].sudo().search([], limit=1, order="id") for template in self: template.currency_id = main_company.currency_id.id @api.depends('tagihan_ids.state') def compute_state(self): for payment in self: if len( set([ i.state for i in payment.tagihan_ids if i.state == 'paid' ])) == 1: payment.state = 'paid' else: payment.state = 'draft' @api.multi def proses_pembayaran(self): for i in self.tagihan_ids: if i.state == 'draft': raise UserError( ('Invoice status draft harus di validate terlebih dahulu')) # i.action_invoice_open() elif i.state == 'paid': raise UserError( ('Invoice yang sudah paid tidak bisa diproses')) return { 'name': _('Manifest Pembayaaran'), 'type': 'ir.actions.act_window', 'res_model': 'account.register.payments', 'view_type': 'form', 'view_mode': 'form', 'target': 'new', 'context': { 'active_ids': [x.id for x in self.tagihan_ids], 'active_model': 'account.invoice', } } @api.multi def print_manifest(self): return self.env.ref( 'alhamra_akademik.action_report_manifest').report_action(self)
class PosDebtReport(models.Model): _name = "report.pos.debt" _inherit = ['base_groupby_extra'] _description = "POS Debt Statistics" _auto = False _order = 'date desc' order_id = fields.Many2one('pos.order', string='POS Order', readonly=True) invoice_id = fields.Many2one('account.invoice', string='Invoice', readonly=True) update_id = fields.Many2one('pos.credit.update', string='Manual Update', readonly=True) date = fields.Datetime(string='Date', readonly=True) partner_id = fields.Many2one('res.partner', string='Partner', readonly=True) user_id = fields.Many2one('res.users', string='Salesperson', readonly=True) session_id = fields.Many2one('pos.session', string='Session', readonly=True) config_id = fields.Many2one('pos.config', string='POS', readonly=True) company_id = fields.Many2one('res.company', string='Company', readonly=True) currency_id = fields.Many2one('res.currency', string='Currency', readonly=True) state = fields.Selection([('open', 'Open'), ('confirm', 'Validated')], readonly=True) credit_product = fields.Boolean( 'Credit Product', help="Record is registered as Purchasing credit product", readonly=True) balance = fields.Monetary( 'Balance', help= "Negative value for purchases without money (debt). Positive for credit payments (prepament or payments for debts).", readonly=True) product_list = fields.Text('Product List', readonly=True) @api.model_cr def init(self): tools.drop_view_if_exists(self._cr, 'report_pos_debt') self._cr.execute(""" CREATE OR REPLACE VIEW report_pos_debt AS ( ( SELECT st_line.id as id, o.id as order_id, NULL::integer as invoice_id, NULL::integer as update_id, -st_line.amount as balance, st.state as state, false as credit_product, o.date_order as date, o.partner_id as partner_id, o.user_id as user_id, o.session_id as session_id, session.config_id as config_id, o.company_id as company_id, pricelist.currency_id as currency_id, o.product_list as product_list FROM account_bank_statement_line as st_line LEFT JOIN account_bank_statement st ON (st.id=st_line.statement_id) LEFT JOIN account_journal journal ON (journal.id=st.journal_id) LEFT JOIN pos_order o ON (o.id=st_line.pos_statement_id) LEFT JOIN pos_session session ON (session.id=o.session_id) LEFT JOIN product_pricelist pricelist ON (pricelist.id=o.pricelist_id) WHERE journal.debt=true ) UNION ALL ( SELECT -pos_line.id as id, o.id as order_id, NULL::integer as invoice_id, NULL::integer as update_id, pos_line.price_unit * qty as balance, CASE o.state WHEN 'done' THEN 'confirm' WHEN 'paid' THEN 'open' ELSE o.state END as state, true as credit_product, o.date_order as date, o.partner_id as partner_id, o.user_id as user_id, o.session_id as session_id, session.config_id as config_id, o.company_id as company_id, pricelist.currency_id as currency_id, o.product_list as product_list FROM pos_order_line as pos_line LEFT JOIN product_product pp ON (pp.id=pos_line.product_id) LEFT JOIN product_template pt ON (pt.id=pp.product_tmpl_id) LEFT JOIN pos_order o ON (o.id=pos_line.order_id) LEFT JOIN pos_session session ON (session.id=o.session_id) LEFT JOIN product_pricelist pricelist ON (pricelist.id=o.pricelist_id) WHERE pt.credit_product=true AND o.state IN ('paid','done') ) UNION ALL ( SELECT (2147483647 - inv_line.id) as id, NULL::integer as order_id, inv.id as invoice_id, NULL::integer as update_id, inv_line.price_subtotal as balance, 'confirm' as state, true as credit_product, inv.date_invoice as date, inv.partner_id as partner_id, inv.user_id as user_id, NULL::integer as session_id, NULL::integer as config_id, inv.company_id as company_id, inv.currency_id as currency_id, '' as product_list FROM account_invoice_line as inv_line LEFT JOIN product_product pp ON (pp.id=inv_line.product_id) LEFT JOIN product_template pt ON (pt.id=pp.product_tmpl_id) LEFT JOIN account_invoice inv ON (inv.id=inv_line.invoice_id) WHERE pt.credit_product=true AND inv.state in ('paid') ) UNION ALL ( SELECT (-2147483647 + record.id) as id, NULL::integer as order_id, NULL::integer as invoice_id, record.id as update_id, record.balance as balance, record.state as state, false as credit_product, record.date as date, record.partner_id as partner_id, record.user_id as user_id, NULL::integer as session_id, NULL::integer as config_id, record.company_id as company_id, record.currency_id as currency_id, record.note as product_list FROM pos_credit_update as record WHERE record.state in ('confirm') ) ) """)
class Document(models.Model): """ Implementação base dos documentos fiscais Devemos sempre ter em mente que o modelo que vai usar este módulo abstrato tem diversos metodos importantes e a intenção que os módulos da OCA que extendem este modelo, funcionem se possível sem a necessidade de codificação extra. É preciso também estar atento que o documento fiscal tem dois estados: - Estado do documento eletrônico / não eletônico: state_edoc - Estado FISCAL: state_fiscal O estado fiscal é um campo que é alterado apenas algumas vezes pelo código e é de responsabilidade do responsável fiscal pela empresa de manter a integridade do mesmo, pois ele não tem um fluxo realmente definido e interfere no lançamento do registro no arquivo do SPED FISCAL. """ _name = 'l10n_br_fiscal.document' _inherit = [ 'mail.thread', 'mail.activity.mixin', 'l10n_br_fiscal.document.mixin', 'l10n_br_fiscal.document.electronic' ] _description = 'Fiscal Document' @api.depends('line_ids') def _compute_amount(self): for record in self: record.amount_untaxed = sum(line.amount_untaxed for line in record.line_ids) record.amount_icms_base = sum(line.icms_base for line in record.line_ids) record.amount_icms_value = sum(line.icms_value for line in record.line_ids) record.amount_icmssn_value = sum(line.icmssn_credit_value for line in record.line_ids) record.amount_ipi_base = sum(line.ipi_base for line in record.line_ids) record.amount_ipi_value = sum(line.ipi_value for line in record.line_ids) record.amount_pis_base = sum(line.pis_base for line in record.line_ids) record.amount_pis_value = sum(line.pis_value for line in record.line_ids) record.amount_pis_ret_base = sum(line.pis_wh_base for line in record.line_ids) record.amount_pis_ret_value = sum(line.pis_wh_value for line in record.line_ids) record.amount_cofins_base = sum(line.cofins_base for line in record.line_ids) record.amount_cofins_value = sum(line.cofins_value for line in record.line_ids) record.amount_cofins_ret_base = sum(line.cofins_wh_base for line in record.line_ids) record.amount_cofins_ret_value = sum(line.cofins_wh_value for line in record.line_ids) record.amount_csll_base = sum(line.csll_base for line in record.line_ids) record.amount_csll_value = sum(line.csll_value for line in record.line_ids) record.amount_csll_ret_base = sum(line.csll_wh_base for line in record.line_ids) record.amount_csll_ret_value = sum(line.csll_wh_value for line in record.line_ids) record.amount_issqn_base = sum(line.issqn_base for line in record.line_ids) record.amount_issqn_value = sum(line.issqn_value for line in record.line_ids) record.amount_issqn_ret_base = sum(line.issqn_wh_base for line in record.line_ids) record.amount_issqn_ret_value = sum(line.issqn_wh_value for line in record.line_ids) record.amount_irpj_base = sum(line.irpj_base for line in record.line_ids) record.amount_irpj_value = sum(line.irpj_value for line in record.line_ids) record.amount_irpj_ret_base = sum(line.irpj_wh_base for line in record.line_ids) record.amount_irpj_ret_value = sum(line.irpj_wh_value for line in record.line_ids) record.amount_inss_base = sum(line.inss_base for line in record.line_ids) record.amount_inss_value = sum(line.inss_value for line in record.line_ids) record.amount_inss_wh_base = sum(line.inss_wh_base for line in record.line_ids) record.amount_inss_wh_value = sum(line.inss_wh_value for line in record.line_ids) record.amount_tax = sum(line.amount_tax for line in record.line_ids) record.amount_discount = sum(line.discount_value for line in record.line_ids) record.amount_insurance_value = sum(line.insurance_value for line in record.line_ids) record.amount_other_costs_value = sum(line.other_costs_value for line in record.line_ids) record.amount_freight_value = sum(line.freight_value for line in record.line_ids) record.amount_total = sum(line.amount_total for line in record.line_ids) record.amount_financial = sum(line.amount_financial for line in record.line_ids) record.amount_tax_withholding = sum(line.amount_tax_withholding for line in record.line_ids) record.amount_estimate_tax = sum(line.amount_estimate_tax for line in record.line_ids) # used mostly to enable _inherits of account.invoice on # fiscal_document when existing invoices have no fiscal document. active = fields.Boolean( string='Active', default=True, ) fiscal_operation_id = fields.Many2one( domain="[('state', '=', 'approved'), " "'|', ('fiscal_operation_type', '=', fiscal_operation_type)," " ('fiscal_operation_type', '=', 'all')]", ) fiscal_operation_type = fields.Selection(related=False, ) is_edoc_printed = fields.Boolean( string='Printed', readonly=True, ) number = fields.Char( string='Number', copy=False, index=True, ) key = fields.Char( string='key', copy=False, index=True, ) issuer = fields.Selection( selection=DOCUMENT_ISSUER, default=DOCUMENT_ISSUER_COMPANY, required=True, string='Issuer', ) date = fields.Datetime( string='Date', copy=False, ) user_id = fields.Many2one( comodel_name='res.users', string='User', index=True, default=lambda self: self.env.user, ) document_type_id = fields.Many2one( comodel_name='l10n_br_fiscal.document.type', ) operation_name = fields.Char( string='Operation Name', copy=False, ) document_electronic = fields.Boolean( related='document_type_id.electronic', string='Electronic?', store=True, ) date_in_out = fields.Datetime( string='Date Move', copy=False, ) document_serie_id = fields.Many2one( comodel_name='l10n_br_fiscal.document.serie', domain="[('active', '=', True)," "('document_type_id', '=', document_type_id)]", ) document_serie = fields.Char(string='Serie Number', ) fiscal_document_related_ids = fields.One2many( comodel_name='l10n_br_fiscal.document.related', inverse_name='fiscal_document_id', string='Fiscal Document Related', ) partner_id = fields.Many2one( comodel_name='res.partner', string='Partner', ) partner_legal_name = fields.Char( string='Legal Name', related='partner_id.legal_name', ) partner_name = fields.Char( string='Name', related='partner_id.name', ) partner_cnpj_cpf = fields.Char( string='CNPJ', related='partner_id.cnpj_cpf', ) partner_inscr_est = fields.Char( string='State Tax Number', related='partner_id.inscr_est', ) partner_ind_ie_dest = fields.Selection( selection=NFE_IND_IE_DEST, string='Contribuinte do ICMS', related='partner_id.ind_ie_dest', ) partner_inscr_mun = fields.Char( string='Municipal Tax Number', related='partner_id.inscr_mun', ) partner_suframa = fields.Char( string='Suframa', related='partner_id.suframa', ) partner_cnae_main_id = fields.Many2one( comodel_name='l10n_br_fiscal.cnae', string='Main CNAE', related='partner_id.cnae_main_id', ) partner_tax_framework = fields.Selection( selection=TAX_FRAMEWORK, string='Tax Framework', related='partner_id.tax_framework', ) partner_street = fields.Char( string='Partner Street', related='partner_id.street', ) partner_number = fields.Char( string='Partner Number', related='partner_id.street_number', ) partner_street2 = fields.Char( string='Partner Street2', related='partner_id.street2', ) partner_district = fields.Char( string='Partner District', related='partner_id.district', ) partner_country_id = fields.Many2one( comodel_name='res.country', string='Partner Country', related='partner_id.country_id', ) partner_state_id = fields.Many2one( comodel_name='res.country.state', string='Partner State', related='partner_id.state_id', ) partner_city_id = fields.Many2one( comodel_name='res.city', string='Partner City', related='partner_id.city_id', ) partner_zip = fields.Char( string='Partner Zip', related='partner_id.zip', ) partner_phone = fields.Char( string='Partner Phone', related='partner_id.phone', ) partner_is_company = fields.Boolean( string='Partner Is Company?', related='partner_id.is_company', ) partner_shipping_id = fields.Many2one( comodel_name='res.partner', string='Shipping Address', ) company_id = fields.Many2one( comodel_name='res.company', string='Company', default=lambda self: self.env['res.company']._company_default_get( 'l10n_br_fiscal.document'), ) processador_edoc = fields.Selection( related='company_id.processador_edoc', store=True, ) company_legal_name = fields.Char( string='Company Legal Name', related='company_id.legal_name', ) company_name = fields.Char( string='Company Name', size=128, related='company_id.name', ) company_cnpj_cpf = fields.Char( string='Company CNPJ', related='company_id.cnpj_cpf', ) company_inscr_est = fields.Char( string='Company State Tax Number', related='company_id.inscr_est', ) company_inscr_mun = fields.Char( string='Company Municipal Tax Number', related='company_id.inscr_mun', ) company_suframa = fields.Char( string='Company Suframa', related='company_id.suframa', ) company_cnae_main_id = fields.Many2one( comodel_name='l10n_br_fiscal.cnae', string='Company Main CNAE', related='company_id.cnae_main_id', ) company_tax_framework = fields.Selection( selection=TAX_FRAMEWORK, string='Company Tax Framework', related='company_id.tax_framework', ) company_street = fields.Char( string='Company Street', related='company_id.street', ) company_number = fields.Char( string='Company Number', related='company_id.street_number', ) company_street2 = fields.Char( string='Company Street2', related='company_id.street2', ) company_district = fields.Char( string='Company District', related='company_id.district', ) company_country_id = fields.Many2one( comodel_name='res.country', string='Company Country', related='company_id.country_id', ) company_state_id = fields.Many2one( comodel_name='res.country.state', string='Company State', related='company_id.state_id', ) company_city_id = fields.Many2one( comodel_name='res.city', string='Company City', related='company_id.city_id', ) company_zip = fields.Char( string='Company ZIP', related='company_id.zip', ) company_phone = fields.Char( string='Company Phone', related='company_id.phone', ) currency_id = fields.Many2one( comodel_name='res.currency', default=lambda self: self.env.user.company_id.currency_id, store=True, readonly=True, ) amount_untaxed = fields.Monetary( string='Amount Untaxed', compute='_compute_amount', ) amount_icms_base = fields.Monetary( string='ICMS Base', compute='_compute_amount', ) amount_icms_value = fields.Monetary( string='ICMS Value', compute='_compute_amount', ) amount_icmssn_value = fields.Monetary( string='ICMSSN Value', compute='_compute_amount', ) amount_ipi_base = fields.Monetary( string='IPI Base', compute='_compute_amount', ) amount_ipi_value = fields.Monetary( string='IPI Value', compute='_compute_amount', ) amount_pis_base = fields.Monetary( string='PIS Base', compute='_compute_amount', ) amount_pis_value = fields.Monetary( string='PIS Value', compute='_compute_amount', ) amount_pis_ret_base = fields.Monetary( string='PIS Ret Base', compute='_compute_amount', ) amount_pis_ret_value = fields.Monetary( string='PIS Ret Value', compute='_compute_amount', ) amount_cofins_base = fields.Monetary( string='COFINS Base', compute='_compute_amount', ) amount_cofins_value = fields.Monetary( string='COFINS Value', compute='_compute_amount', ) amount_cofins_ret_base = fields.Monetary( string='COFINS Ret Base', compute='_compute_amount', ) amount_cofins_ret_value = fields.Monetary( string='COFINS Ret Value', compute='_compute_amount', ) amount_issqn_base = fields.Monetary( string='ISSQN Base', compute='_compute_amount', ) amount_issqn_value = fields.Monetary( string='ISSQN Value', compute='_compute_amount', ) amount_issqn_ret_base = fields.Monetary( string='ISSQN Ret Base', compute='_compute_amount', ) amount_issqn_ret_value = fields.Monetary( string='ISSQN Ret Value', compute='_compute_amount', ) amount_csll_base = fields.Monetary( string='CSLL Base', compute='_compute_amount', ) amount_csll_value = fields.Monetary( string='CSLL Value', compute='_compute_amount', ) amount_csll_ret_base = fields.Monetary( string='CSLL Ret Base', compute='_compute_amount', ) amount_csll_ret_value = fields.Monetary( string='CSLL Ret Value', compute='_compute_amount', ) amount_irpj_base = fields.Monetary( string='IRPJ Base', compute='_compute_amount', ) amount_irpj_value = fields.Monetary( string='IRPJ Value', compute='_compute_amount', ) amount_irpj_ret_base = fields.Monetary( string='IRPJ Ret Base', compute='_compute_amount', ) amount_irpj_ret_value = fields.Monetary( string='IRPJ Ret Value', compute='_compute_amount', ) amount_inss_base = fields.Monetary( string='INSS Base', compute='_compute_amount', ) amount_inss_value = fields.Monetary( string='INSS Value', compute='_compute_amount', ) amount_inss_wh_base = fields.Monetary( string='INSS Ret Base', compute='_compute_amount', ) amount_inss_wh_value = fields.Monetary( string='INSS Ret Value', compute='_compute_amount', ) amount_estimate_tax = fields.Monetary( string='Amount Estimate Tax', compute='_compute_amount', ) amount_tax = fields.Monetary( string='Amount Tax', compute='_compute_amount', ) amount_total = fields.Monetary( string='Amount Total', compute='_compute_amount', ) amount_tax_withholding = fields.Monetary(string="Amount Tax Withholding", compute='_compute_amount') amount_financial = fields.Monetary( string='Amount Financial', compute='_compute_amount', ) amount_discount = fields.Monetary( string='Amount Discount', compute='_compute_amount', ) amount_insurance_value = fields.Monetary( string='Insurance Value', default=0.00, compute='_compute_amount', ) amount_other_costs_value = fields.Monetary( string='Other Costs', default=0.00, compute='_compute_amount', ) amount_freight_value = fields.Monetary( string='Freight Value', default=0.00, compute='_compute_amount', ) line_ids = fields.One2many( comodel_name='l10n_br_fiscal.document.line', inverse_name='document_id', string='Document Lines', copy=True, ) # TODO esse campo deveria ser calculado de # acordo com o fiscal_document_id document_section = fields.Selection( selection=[ ('nfe', 'NF-e'), ('nfse_recibos', 'NFS-e e Recibos'), ('nfce_cfe', 'NFC-e e CF-e'), ('cte', 'CT-e'), ('todos', 'Todos'), ], string='Document Section', readonly=True, copy=True, ) edoc_purpose = fields.Selection( selection=[ ('1', 'Normal'), ('2', 'Complementar'), ('3', 'Ajuste'), ('4', 'Devolução de mercadoria'), ], string='Finalidade', default='1', ) document_event_ids = fields.One2many( comodel_name='l10n_br_fiscal.document.event', inverse_name='fiscal_document_id', string='Events', copy=False, readonly=True, ) close_id = fields.Many2one(comodel_name='l10n_br_fiscal.closing', string='Close ID') document_type = fields.Char( related='document_type_id.code', stored=True, ) dfe_id = fields.Many2one( comodel_name='l10n_br_fiscal.dfe', string='DF-e Consult', ) # Você não vai poder fazer isso em modelos que já tem state # TODO Porque não usar o campo state do fiscal.document??? state = fields.Selection(related="state_edoc", string='State') document_subsequent_ids = fields.One2many( comodel_name='l10n_br_fiscal.subsequent.document', inverse_name='source_document_id', copy=True, ) document_subsequent_generated = fields.Boolean( string='Subsequent documents generated?', compute='_compute_document_subsequent_generated', default=False, ) @api.multi @api.constrains('number') def _check_number(self): for record in self: if not record.number: return domain = [('id', '!=', record.id), ('active', '=', True), ('company_id', '=', record.company_id.id), ('issuer', '=', record.issuer), ('document_type_id', '=', record.document_type_id.id), ('document_serie', '=', record.document_serie), ('number', '=', record.number)] if record.issuer == DOCUMENT_ISSUER_PARTNER: domain.append(('partner_id', '=', record.partner_id.id)) if record.env["l10n_br_fiscal.document"].search(domain): raise ValidationError( _("There is already a fiscal document with this " "Serie: {0}, Number: {1} !".format( record.document_serie, record.number))) @api.multi def name_get(self): return [(r.id, '{0} - Série: {1} - Número: {2}'.format( r.document_type_id.name, r.document_serie, r.number)) for r in self] @api.model def create(self, values): if not values.get('date'): values['date'] = self._date_server_format() return super().create(values) @api.onchange('company_id') def _onchange_company_id(self): if self.company_id: self.currency_id = self.company_id.currency_id @api.multi @api.onchange('document_section') def _onchange_document_section(self): if self.document_section: domain = dict() if self.document_section == 'nfe': domain['document_type_id'] = [('code', '=', '55')] self.document_type_id = \ self.env['l10n_br_fiscal.document.type'].search([ ('code', '=', '55') ])[0] elif self.document_section == 'nfse_recibos': domain['document_type_id'] = [('code', '=', 'SE')] self.document_type_id = \ self.env['l10n_br_fiscal.document.type'].search([ ('code', '=', 'SE') ])[0] elif self.document_section == 'nfce_cfe': domain['document_type_id'] = [('code', 'in', ('59', '65'))] self.document_type_id = \ self.env['l10n_br_fiscal.document.type'].search([ ('code', '=', '59') ])[0] elif self.document_section == 'cte': domain['document_type_id'] = [('code', '=', '57')] self.document_type_id = \ self.env['l10n_br_fiscal.document.type'].search([ ('code', '=', '57') ])[0] return {'domain': domain} def _create_return(self): return_docs = self.env[self._name] for record in self: fsc_op = record.fiscal_operation_id.return_fiscal_operation_id if not fsc_op: raise ValidationError( _("The fiscal operation {} has no return Fiscal " "Operation defined".format(record.fiscal_operation_id))) new_doc = record.copy() new_doc.fiscal_operation_id = fsc_op new_doc._onchange_fiscal_operation_id() for l in new_doc.line_ids: fsc_op_line = l.fiscal_operation_id.return_fiscal_operation_id if not fsc_op_line: raise ValidationError( _("The fiscal operation {} has no return Fiscal " "Operation defined".format(l.fiscal_operation_id))) l.fiscal_operation_id = fsc_op_line l._onchange_fiscal_operation_id() l._onchange_fiscal_operation_line_id() return_docs |= new_doc return return_docs @api.multi def action_create_return(self): action = self.env.ref('l10n_br_fiscal.document_all_action').read()[0] return_docs = self._create_return() if return_docs: action['domain'] = literal_eval(action['domain'] or '[]') action['domain'].append(('id', 'in', return_docs.ids)) return action def _get_email_template(self, state): self.ensure_one() return self.document_type_id.document_email_ids.search( [ '|', ('state_edoc', '=', False), ('state_edoc', '=', state), ('issuer', '=', self.issuer), '|', ('document_type_id', '=', False), ('document_type_id', '=', self.document_type_id.id) ], limit=1, order='state_edoc, document_type_id').mapped('email_template_id') def send_email(self, state): email_template = self._get_email_template(state) if email_template: email_template.send_mail(self.id) def _after_change_state(self, old_state, new_state): super()._after_change_state(old_state, new_state) self.send_email(new_state) def _exec_after_SITUACAO_EDOC_A_ENVIAR(self, old_state, new_state): super()._exec_after_SITUACAO_EDOC_A_ENVIAR(old_state, new_state) self.document_comment() @api.onchange('fiscal_operation_id') def _onchange_fiscal_operation_id(self): super()._onchange_fiscal_operation_id() if self.fiscal_operation_id: self.fiscal_operation_type = ( self.fiscal_operation_id.fiscal_operation_type) self.ind_final = self.fiscal_operation_id.ind_final if self.issuer == DOCUMENT_ISSUER_COMPANY: self.document_type_id = self.company_id.document_type_id subsequent_documents = [(6, 0, {})] for subsequent_id in self.fiscal_operation_id.mapped( 'operation_subsequent_ids'): subsequent_documents.append((0, 0, { 'source_document_id': self.id, 'subsequent_operation_id': subsequent_id.id, 'fiscal_operation_id': subsequent_id.subsequent_operation_id.id, })) self.document_subsequent_ids = subsequent_documents @api.onchange('document_type_id') def _onchange_document_type_id(self): if self.document_type_id and self.issuer == DOCUMENT_ISSUER_COMPANY: self.document_serie_id = self.document_type_id.get_document_serie( self.company_id, self.fiscal_operation_id) @api.onchange('document_serie_id') def _onchange_document_serie_id(self): if self.document_serie_id and self.issuer == DOCUMENT_ISSUER_COMPANY: self.document_serie = self.document_serie_id.code def _exec_after_SITUACAO_EDOC_AUTORIZADA(self, old_state, new_state): super(Document, self)._exec_after_SITUACAO_EDOC_AUTORIZADA(old_state, new_state) self._generates_subsequent_operations() def _prepare_referenced_subsequent(self): vals = { 'fiscal_document_id': self.id, 'partner_id': self.partner_id.id, 'document_type_id': self.document_type, 'serie': self.document_serie, 'number': self.number, 'date': self.date, 'document_key': self.key, } reference_id = self.env['l10n_br_fiscal.document.related'].create(vals) return reference_id def _document_reference(self, reference_ids): for referenced_item in reference_ids: referenced_item.fiscal_document_related_ids = self.id self.fiscal_document_related_ids |= referenced_item @api.depends('document_subsequent_ids.subsequent_document_id') def _compute_document_subsequent_generated(self): for document in self: if not document.document_subsequent_ids: continue document.document_subsequent_generated = all( subsequent_id.operation_performed for subsequent_id in document.document_subsequent_ids) def _generates_subsequent_operations(self): for record in self.filtered( lambda doc: not doc.document_subsequent_generated): for subsequent_id in record.document_subsequent_ids.filtered( lambda doc_sub: doc_sub._confirms_document_generation()): subsequent_id.generate_subsequent_document() def cancel_edoc(self): self.ensure_one() if any(doc.state_edoc == SITUACAO_EDOC_AUTORIZADA for doc in self.document_subsequent_ids.mapped('document_subsequent_ids')): message = _("Canceling the document is not allowed: one or more " "associated documents have already been authorized.") raise UserWarning(message) @api.multi def action_send_email(self): """ Open a window to compose an email, with the fiscal document_type template message loaded by default """ self.ensure_one() template = self._get_email_template(self.state) compose_form = self.env.ref('mail.email_compose_message_wizard_form', False) lang = self.env.context.get('lang') if template and template.lang: lang = template._render_template(template.lang, self._name, self.id) self = self.with_context(lang=lang) ctx = dict(default_model='l10n_br_fiscal.document', default_res_id=self.id, default_use_template=bool(template), default_template_id=template and template.id or False, default_composition_mode='comment', model_description=self.document_type_id.name or self._name, force_email=True) return { 'name': _('Send Fiscal Document Email Notification'), 'type': 'ir.actions.act_window', 'view_type': 'form', 'view_mode': 'form', 'res_model': 'mail.compose.message', 'views': [(compose_form.id, 'form')], 'view_id': compose_form.id, 'target': 'new', 'context': ctx, }
class excise_move(models.Model): _name = 'excise.move' _description = 'Excise Line' name = fields.Text('Description', index=True, required=True) stock_move_id = fields.Many2one('stock.move', 'Stock Move', check_company=True, index=True) stock_move_line_id = fields.Many2one('stock.move.line', 'Stock Move Line', check_company=True, index=True) date = fields.Datetime('Date', default=fields.Datetime.now, index=True, required=True, readonly=True, related='stock_move_id.date', store=True) company_id = fields.Many2one('res.company', string='Company', readonly=True, index=True) currency_id = fields.Many2one('res.currency', string="Currency", readonly=True) product_id = fields.Many2one( 'product.product', 'Product', check_company=True, domain= "[('type', '!=', 'service'), '|', ('company_id', '=', False), ('company_id', '=', company_id)]" ) move_qty = fields.Float('Movement Quantity', default=0.0, digits='Product Unit of Measure', copy=False) move_state = fields.Selection( [('draft', 'New'), ('cancel', 'Cancelled'), ('waiting', 'Waiting Another Move'), ('confirmed', 'Waiting Availability'), ('partially_available', 'Partially Available'), ('assigned', 'Available'), ('done', 'Done')], string='Status', copy=False, default='draft', index=True, readonly=True, related='stock_move_id.state', store=True) move_reference = fields.Char(related='stock_move_id.reference', string="Reference", store=True) move_location_id = fields.Many2one('stock.location', 'Source Location', related='stock_move_id.location_id', readonly=True) move_location_dest_id = fields.Many2one( 'stock.location', 'Destination Location', related='stock_move_id.location_dest_id', readonly=True) move_partner_id = fields.Many2one('res.partner', 'Destination Address ', related='stock_move_id.partner_id', readonly=True) excise_abv = fields.Float('ABV', help='Average By Volume (% Alcohol)', readonly=True) excise_move_volume = fields.Float( 'Excisable Volume (L)', help='Volume being moved for the basis of the Excise calculation') excise_alcohol = fields.Float('Volume of alcohol (L)', readonly=True) excise_category = fields.Many2one('excise.category', 'Excise Category', readonly=True) excise_rate = fields.Monetary('Rate', readonly=True) excise_amount_tax = fields.Monetary(string='Excise Amount', readonly=True) excise_payable = fields.Monetary( string='Total Excise Amount.', help= 'Total excise payable after releifs (e.g. samll brewers allowance)', readonly=True)
class StockMoveLine(models.Model): _inherit = 'stock.move.line' sale_line = fields.Many2one( related='move_id.sale_line_id', readonly=True, string='Related order line', related_sudo=True, # See explanation for sudo in compute method ) currency_id = fields.Many2one( related='sale_line.currency_id', readonly=True, string='Sale Currency', related_sudo=True, ) sale_tax_id = fields.Many2many( related='sale_line.tax_id', readonly=True, string='Sale Tax', related_sudo=True, ) sale_price_unit = fields.Float( related='sale_line.price_unit', readonly=True, string='Sale price unit', related_sudo=True, ) sale_discount = fields.Float( related='sale_line.discount', readonly=True, string='Sale discount (%)', related_sudo=True, ) sale_tax_description = fields.Char( compute='_compute_sale_order_line_fields', string='Tax Description', compute_sudo=True, # See explanation for sudo in compute method ) sale_price_subtotal = fields.Monetary( compute='_compute_sale_order_line_fields', string='Price subtotal', compute_sudo=True, ) sale_price_tax = fields.Float( compute='_compute_sale_order_line_fields', string='Taxes', compute_sudo=True, ) sale_price_total = fields.Monetary( compute='_compute_sale_order_line_fields', string='Total', compute_sudo=True, ) @api.multi def _compute_sale_order_line_fields(self): """This is computed with sudo for avoiding problems if you don't have access to sales orders (stricter warehouse users, inter-company records...). """ for line in self: taxes = line.sale_tax_id.compute_all( price_unit=line.sale_line.price_reduce, currency=line.currency_id, quantity=line.qty_done or line.product_qty, product=line.product_id, partner=line.sale_line.order_id.partner_shipping_id) if line.sale_line.company_id.tax_calculation_rounding_method == ( 'round_globally'): price_tax = sum( t.get('amount', 0.0) for t in taxes.get('taxes', [])) else: price_tax = taxes['total_included'] - taxes['total_excluded'] line.update({ 'sale_tax_description': ', '.join( t.name or t.description for t in line.sale_tax_id), 'sale_price_subtotal': taxes['total_excluded'], 'sale_price_tax': price_tax, 'sale_price_total': taxes['total_included'], })
class HrExpenseSheet(models.Model): _inherit = "hr.expense.sheet" advance = fields.Boolean(string="Employee Advance", compute="_compute_advance", store=True) advance_sheet_id = fields.Many2one( comodel_name="hr.expense.sheet", string="Clear Advance", domain="[('advance', '=', True), ('employee_id', '=', employee_id)," " ('clearing_residual', '>', 0.0)]", readonly=True, states={ "draft": [("readonly", False)], "submit": [("readonly", False)], "approve": [("readonly", False)], }, help="Show remaining advance of this employee", ) clearing_residual = fields.Monetary( string="Amount to clear", compute="_compute_clearing_residual", store=True, help="Amount to clear of this expense sheet in company currency", ) advance_sheet_residual = fields.Monetary( string="Advance Remaining", related="advance_sheet_id.clearing_residual", store=True, help="Remaining amount to clear the selected advance sheet", ) amount_payable = fields.Monetary( string="Payable Amount", compute="_compute_amount_payable", help="Final regiter payment amount even after advance clearing", ) @api.depends("expense_line_ids") def _compute_advance(self): for sheet in self: sheet.advance = bool( sheet.expense_line_ids.filtered("advance") and len(sheet.expense_line_ids) == 1) return @api.constrains("advance_sheet_id", "expense_line_ids") def _check_advance_expense(self): advance_lines = self.expense_line_ids.filtered("advance") if self.advance_sheet_id and advance_lines: raise ValidationError( _("Advance clearing must not contain any advance expense line") ) if advance_lines and len(self.expense_line_ids) != 1: raise ValidationError( _("Advance must contain only 1 advance expense line")) @api.depends("account_move_id.line_ids.amount_residual") def _compute_clearing_residual(self): emp_advance = self.env.ref( "hr_expense_advance_clearing.product_emp_advance", False) for sheet in self: residual_company = 0.0 if emp_advance: for line in sheet.sudo().account_move_id.line_ids: if line.account_id == emp_advance.property_account_expense_id: residual_company += line.amount_residual sheet.clearing_residual = residual_company def _compute_amount_payable(self): for sheet in self: rec_lines = sheet.account_move_id.line_ids.filtered( lambda x: x.credit and x.account_id.reconcile and not x. reconciled) sheet.amount_payable = -sum(rec_lines.mapped("amount_residual")) def action_sheet_move_create(self): res = super(HrExpenseSheet, self).action_sheet_move_create() # Reconcile advance of this sheet with the advance_sheet emp_advance = self.env.ref( "hr_expense_advance_clearing.product_emp_advance") for sheet in self: move_lines = (sheet.account_move_id.line_ids | sheet.advance_sheet_id.account_move_id.line_ids) account_id = emp_advance.property_account_expense_id.id adv_move_lines = (self.env["account.move.line"].sudo().search([ ("id", "in", move_lines.ids), ("account_id", "=", account_id) ])) adv_move_lines.reconcile() return res def open_clear_advance(self): self.ensure_one() action = self.env.ref("hr_expense_advance_clearing." "action_hr_expense_sheet_advance_clearing") vals = action.read()[0] context1 = vals.get("context", {}) if context1: context1 = safe_eval(context1) context1["default_advance_sheet_id"] = self.id vals["context"] = context1 return vals
class ProductWishlist(models.Model): _name = 'product.wishlist' _description = 'Product Wishlist' _sql_constraints = [ ("product_unique_partner_id", "UNIQUE(product_id, partner_id)", "Duplicated wishlisted product for this partner."), ] partner_id = fields.Many2one('res.partner', string='Owner') product_id = fields.Many2one('product.product', string='Product', required=True) currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', readonly=True) pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', help='Pricelist when added') price = fields.Monetary(currency_field='currency_id', string='Price', help='Price of the product when it has been added in the wishlist') website_id = fields.Many2one('website', ondelete='cascade', required=True) active = fields.Boolean(default=True, required=True) @api.model def current(self): """Get all wishlist items that belong to current user or session, filter products that are unpublished.""" if not request: return self if request.website.is_public_user(): wish = self.sudo().search([('id', 'in', request.session.get('wishlist_ids', []))]) else: wish = self.search([("partner_id", "=", self.env.user.partner_id.id)]) return wish.sudo().filtered('product_id.product_tmpl_id.website_published') @api.model def _add_to_wishlist(self, pricelist_id, currency_id, website_id, price, product_id, partner_id=False): wish = self.env['product.wishlist'].create({ 'partner_id': partner_id, 'product_id': product_id, 'currency_id': currency_id, 'pricelist_id': pricelist_id, 'price': price, 'website_id': website_id, }) return wish @api.model def _check_wishlist_from_session(self): """Assign all wishlist withtout partner from this the current session""" session_wishes = self.sudo().search([('id', 'in', request.session.get('wishlist_ids', []))]) partner_wishes = self.sudo().search([("partner_id", "=", self.env.user.partner_id.id)]) partner_products = partner_wishes.mapped("product_id") # Remove session products already present for the user duplicated_wishes = session_wishes.filtered(lambda wish: wish.product_id <= partner_products) session_wishes -= duplicated_wishes duplicated_wishes.unlink() # Assign the rest to the user session_wishes.write({"partner_id": self.env.user.partner_id.id}) request.session.pop('wishlist_ids') @api.model def _garbage_collector(self, *args, **kwargs): """Remove wishlists for unexisting sessions.""" self.with_context(active_test=False).search([ ("create_date", "<", fields.Datetime.to_string(datetime.now() - timedelta(weeks=kwargs.get('wishlist_week', 5)))), ("partner_id", "=", False), ]).unlink()
class SaleAdvancePaymentInv(models.TransientModel): _name = "sale.advance.payment.inv" _description = "Sales Advance Payment Invoice" @api.model def _count(self): return len(self._context.get('active_ids', [])) @api.model def _default_product_id(self): product_id = self.env['ir.config_parameter'].sudo().get_param('sale.default_deposit_product_id') return self.env['product.product'].browse(int(product_id)).exists() @api.model def _default_deposit_account_id(self): return self._default_product_id()._get_product_accounts()['income'] @api.model def _default_deposit_taxes_id(self): return self._default_product_id().taxes_id @api.model def _default_has_down_payment(self): if self._context.get('active_model') == 'sale.order' and self._context.get('active_id', False): sale_order = self.env['sale.order'].browse(self._context.get('active_id')) return sale_order.order_line.filtered( lambda sale_order_line: sale_order_line.is_downpayment ) return False @api.model def _default_currency_id(self): if self._context.get('active_model') == 'sale.order' and self._context.get('active_id', False): sale_order = self.env['sale.order'].browse(self._context.get('active_id')) return sale_order.currency_id advance_payment_method = fields.Selection([ ('delivered', 'Regular invoice'), ('percentage', 'Down payment (percentage)'), ('fixed', 'Down payment (fixed amount)') ], string='Create Invoice', default='delivered', required=True, help="A standard invoice is issued with all the order lines ready for invoicing, \ according to their invoicing policy (based on ordered or delivered quantity).") deduct_down_payments = fields.Boolean('Deduct down payments', default=True) has_down_payments = fields.Boolean('Has down payments', default=_default_has_down_payment, readonly=True) product_id = fields.Many2one('product.product', string='Down Payment Product', domain=[('type', '=', 'service')], default=_default_product_id) count = fields.Integer(default=_count, string='Order Count') amount = fields.Float('Down Payment Amount', digits='Account', help="The percentage of amount to be invoiced in advance, taxes excluded.") currency_id = fields.Many2one('res.currency', string='Currency', default=_default_currency_id) fixed_amount = fields.Monetary('Down Payment Amount (Fixed)', help="The fixed amount to be invoiced in advance, taxes excluded.") deposit_account_id = fields.Many2one("account.account", string="Income Account", domain=[('deprecated', '=', False)], help="Account used for deposits", default=_default_deposit_account_id) deposit_taxes_id = fields.Many2many("account.tax", string="Customer Taxes", help="Taxes used for deposits", default=_default_deposit_taxes_id) @api.onchange('advance_payment_method') def onchange_advance_payment_method(self): if self.advance_payment_method == 'percentage': amount = self.default_get(['amount']).get('amount') return {'value': {'amount': amount}} return {} def _prepare_invoice_values(self, order, name, amount, so_line): invoice_vals = { 'ref': order.client_order_ref, 'move_type': 'out_invoice', 'invoice_origin': order.name, 'invoice_user_id': order.user_id.id, 'narration': order.note, 'partner_id': order.partner_invoice_id.id, 'fiscal_position_id': (order.fiscal_position_id or order.fiscal_position_id.get_fiscal_position(order.partner_id.id)).id, 'partner_shipping_id': order.partner_shipping_id.id, 'currency_id': order.pricelist_id.currency_id.id, 'payment_reference': order.reference, 'invoice_payment_term_id': order.payment_term_id.id, 'partner_bank_id': order.company_id.partner_id.bank_ids[:1].id, 'team_id': order.team_id.id, 'campaign_id': order.campaign_id.id, 'medium_id': order.medium_id.id, 'source_id': order.source_id.id, 'invoice_line_ids': [(0, 0, { 'name': name, 'price_unit': amount, 'quantity': 1.0, 'product_id': self.product_id.id, 'product_uom_id': so_line.product_uom.id, 'tax_ids': [(6, 0, so_line.tax_id.ids)], 'sale_line_ids': [(6, 0, [so_line.id])], 'analytic_tag_ids': [(6, 0, so_line.analytic_tag_ids.ids)], 'analytic_account_id': order.analytic_account_id.id or False, })], } return invoice_vals def _get_advance_details(self, order): if self.advance_payment_method == 'percentage': amount = order.amount_untaxed * self.amount / 100 name = _("Down payment of %s%%") % (self.amount) else: amount = self.fixed_amount name = _('Down Payment') return amount, name def _create_invoice(self, order, so_line, amount): if (self.advance_payment_method == 'percentage' and self.amount <= 0.00) or (self.advance_payment_method == 'fixed' and self.fixed_amount <= 0.00): raise UserError(_('The value of the down payment amount must be positive.')) amount, name = self._get_advance_details(order) invoice_vals = self._prepare_invoice_values(order, name, amount, so_line) if order.fiscal_position_id: invoice_vals['fiscal_position_id'] = order.fiscal_position_id.id invoice = self.env['account.move'].sudo().create(invoice_vals).with_user(self.env.uid) invoice.message_post_with_view('mail.message_origin_link', values={'self': invoice, 'origin': order}, subtype_id=self.env.ref('mail.mt_note').id) return invoice def _prepare_so_line(self, order, analytic_tag_ids, tax_ids, amount): so_values = { 'name': _('Down Payment: %s') % (time.strftime('%m %Y'),), 'price_unit': amount, 'product_uom_qty': 0.0, 'order_id': order.id, 'discount': 0.0, 'product_uom': self.product_id.uom_id.id, 'product_id': self.product_id.id, 'analytic_tag_ids': analytic_tag_ids, 'tax_id': [(6, 0, tax_ids)], 'is_downpayment': True, 'sequence': order.order_line and order.order_line[-1].sequence + 1 or 10, } return so_values def create_invoices(self): sale_orders = self.env['sale.order'].browse(self._context.get('active_ids', [])) if self.advance_payment_method == 'delivered': sale_orders._create_invoices(final=self.deduct_down_payments) else: # Create deposit product if necessary if not self.product_id: vals = self._prepare_deposit_product() self.product_id = self.env['product.product'].create(vals) self.env['ir.config_parameter'].sudo().set_param('sale.default_deposit_product_id', self.product_id.id) sale_line_obj = self.env['sale.order.line'] for order in sale_orders: amount, name = self._get_advance_details(order) if self.product_id.invoice_policy != 'order': raise UserError(_('The product used to invoice a down payment should have an invoice policy set to "Ordered quantities". Please update your deposit product to be able to create a deposit invoice.')) if self.product_id.type != 'service': raise UserError(_("The product used to invoice a down payment should be of type 'Service'. Please use another product or update this product.")) taxes = self.product_id.taxes_id.filtered(lambda r: not order.company_id or r.company_id == order.company_id) tax_ids = order.fiscal_position_id.map_tax(taxes, self.product_id, order.partner_shipping_id).ids context = {'lang': order.partner_id.lang} analytic_tag_ids = [] for line in order.order_line: analytic_tag_ids = [(4, analytic_tag.id, None) for analytic_tag in line.analytic_tag_ids] so_line_values = self._prepare_so_line(order, analytic_tag_ids, tax_ids, amount) so_line = sale_line_obj.create(so_line_values) del context self._create_invoice(order, so_line, amount) if self._context.get('open_invoices', False): return sale_orders.action_view_invoice() return {'type': 'ir.actions.act_window_close'} def _prepare_deposit_product(self): return { 'name': 'Down payment', 'type': 'service', 'invoice_policy': 'order', 'property_account_income_id': self.deposit_account_id.id, 'taxes_id': [(6, 0, self.deposit_taxes_id.ids)], 'company_id': False, }
class InvoiceEletronic(models.Model): _inherit = 'invoice.eletronic' @api.multi @api.depends('chave_nfe') def _format_danfe_key(self): for item in self: item.chave_nfe_danfe = re.sub("(.{4})", "\\1.", item.chave_nfe, 10, re.DOTALL) @api.multi def generate_correction_letter(self): return { "type": "ir.actions.act_window", "res_model": "wizard.carta.correcao.eletronica", "views": [[False, "form"]], "name": "Carta de Correção", "target": "new", "context": { 'default_eletronic_doc_id': self.id }, } payment_mode_id = fields.Many2one('payment.mode', string='Modo de Pagamento', readonly=True, states=STATE) state = fields.Selection(selection_add=[('denied', 'Denegado')]) ambiente_nfe = fields.Selection(string=u"Ambiente NFe", related="company_id.tipo_ambiente", readonly=True) ind_final = fields.Selection([('0', u'Não'), ('1', u'Sim')], u'Consumidor Final', readonly=True, states=STATE, required=False, help=u'Indica operação com Consumidor final.', default='0') ind_pres = fields.Selection([ ('0', u'Não se aplica'), ('1', u'Operação presencial'), ('2', u'Operação não presencial, pela Internet'), ('3', u'Operação não presencial, Teleatendimento'), ('4', u'NFC-e em operação com entrega em domicílio'), ('5', u'Operação presencial, fora do estabelecimento'), ('9', u'Operação não presencial, outros'), ], u'Indicador de Presença', readonly=True, states=STATE, required=False, help=u'Indicador de presença do comprador no\n' u'estabelecimento comercial no momento\n' u'da operação.', default='0') ind_dest = fields.Selection([('1', u'1 - Operação Interna'), ('2', u'2 - Operação Interestadual'), ('3', u'3 - Operação com exterior')], string=u"Indicador Destinatário", readonly=True, states=STATE) ind_ie_dest = fields.Selection( [('1', u'1 - Contribuinte ICMS'), ('2', u'2 - Contribuinte Isento de Cadastro'), ('9', u'9 - Não Contribuinte')], string=u"Indicador IE Dest.", help=u"Indicador da IE do desinatário", readonly=True, states=STATE) tipo_emissao = fields.Selection( [('1', u'1 - Emissão normal'), ('2', u'2 - Contingência FS-IA, com impressão do DANFE em formulário \ de segurança'), ('3', u'3 - Contingência SCAN'), ('4', u'4 - Contingência DPEC'), ('5', u'5 - Contingência FS-DA, com impressão do DANFE em \ formulário de segurança'), ('6', u'6 - Contingência SVC-AN'), ('7', u'7 - Contingência SVC-RS'), ('9', u'9 - Contingência off-line da NFC-e')], string=u"Tipo de Emissão", readonly=True, states=STATE, default='1') # Transporte modalidade_frete = fields.Selection( [('0', '0 - Contratação do Frete por conta do Remetente (CIF)'), ('1', '1 - Contratação do Frete por conta do Destinatário (FOB)'), ('2', '2 - Contratação do Frete por conta de Terceiros'), ('3', '3 - Transporte Próprio por conta do Remetente'), ('4', '4 - Transporte Próprio por conta do Destinatário'), ('9', '9 - Sem Ocorrência de Transporte')], string=u'Modalidade do frete', default="9", readonly=True, states=STATE) transportadora_id = fields.Many2one('res.partner', string=u"Transportadora", readonly=True, states=STATE) placa_veiculo = fields.Char(string=u'Placa do Veículo', size=7, readonly=True, states=STATE) uf_veiculo = fields.Char(string=u'UF da Placa', size=2, readonly=True, states=STATE) rntc = fields.Char(string="RNTC", size=20, readonly=True, states=STATE, help=u"Registro Nacional de Transportador de Carga") reboque_ids = fields.One2many('nfe.reboque', 'invoice_eletronic_id', string=u"Reboques", readonly=True, states=STATE) volume_ids = fields.One2many('nfe.volume', 'invoice_eletronic_id', string=u"Volumes", readonly=True, states=STATE) # Exportação uf_saida_pais_id = fields.Many2one('res.country.state', domain=[('country_id.code', '=', 'BR')], string=u"UF Saída do País", readonly=True, states=STATE) local_embarque = fields.Char(string=u'Local de Embarque', size=60, readonly=True, states=STATE) local_despacho = fields.Char(string=u'Local de Despacho', size=60, readonly=True, states=STATE) # Cobrança numero_fatura = fields.Char(string=u"Fatura", readonly=True, states=STATE) fatura_bruto = fields.Monetary(string=u"Valor Original", readonly=True, states=STATE) fatura_desconto = fields.Monetary(string=u"Desconto", readonly=True, states=STATE) fatura_liquido = fields.Monetary(string=u"Valor Líquido", readonly=True, states=STATE) duplicata_ids = fields.One2many('nfe.duplicata', 'invoice_eletronic_id', string=u"Duplicatas", readonly=True, states=STATE) # Compras nota_empenho = fields.Char(string="Nota de Empenho", size=22, readonly=True, states=STATE) pedido_compra = fields.Char(string="Pedido Compra", size=60, readonly=True, states=STATE) contrato_compra = fields.Char(string="Contrato Compra", size=60, readonly=True, states=STATE) sequencial_evento = fields.Integer(string=u"Sequêncial Evento", default=1, readonly=True, states=STATE) recibo_nfe = fields.Char(string=u"Recibo NFe", size=50, readonly=True, states=STATE) chave_nfe = fields.Char(string=u"Chave NFe", size=50, readonly=True, states=STATE) chave_nfe_danfe = fields.Char(string=u"Chave Formatado", compute="_format_danfe_key") protocolo_nfe = fields.Char(string=u"Protocolo", size=50, readonly=True, states=STATE, help=u"Protocolo de autorização da NFe") nfe_processada = fields.Binary(string=u"Xml da NFe", readonly=True) nfe_processada_name = fields.Char(string=u"Xml da NFe", size=100, readonly=True) valor_icms_uf_remet = fields.Monetary( string=u"ICMS Remetente", readonly=True, states=STATE, help=u'Valor total do ICMS Interestadual para a UF do Remetente') valor_icms_uf_dest = fields.Monetary( string=u"ICMS Destino", readonly=True, states=STATE, help=u'Valor total do ICMS Interestadual para a UF de destino') valor_icms_fcp_uf_dest = fields.Monetary( string=u"Total ICMS FCP", readonly=True, states=STATE, help=u'Total total do ICMS relativo Fundo de Combate à Pobreza (FCP) \ da UF de destino') # Documentos Relacionados fiscal_document_related_ids = fields.One2many( 'br_account.document.related', 'invoice_eletronic_id', u'Documentos Fiscais Relacionados', readonly=True, states=STATE) # CARTA DE CORRECAO cartas_correcao_ids = fields.One2many('carta.correcao.eletronica.evento', 'eletronic_doc_id', string=u"Cartas de Correção", readonly=True, states=STATE) def can_unlink(self): res = super(InvoiceEletronic, self).can_unlink() if self.state == 'denied': return False return res @api.multi def unlink(self): for item in self: if item.state in ('denied'): raise UserError( u'Documento Eletrônico Denegado - Proibido excluir') super(InvoiceEletronic, self).unlink() @api.multi def _hook_validation(self): errors = super(InvoiceEletronic, self)._hook_validation() if self.model == '55': if not self.company_id.partner_id.inscr_est: errors.append(u'Emitente / Inscrição Estadual') if not self.fiscal_position_id: errors.append(u'Configure a posição fiscal') if self.company_id.accountant_id and not \ self.company_id.accountant_id.cnpj_cpf: errors.append(u'Emitente / CNPJ do escritório contabilidade') for eletr in self.eletronic_item_ids: prod = u"Produto: %s - %s" % (eletr.product_id.default_code, eletr.product_id.name) if not eletr.cfop: errors.append(u'%s - CFOP' % prod) if eletr.tipo_produto == 'product': if not eletr.icms_cst: errors.append(u'%s - CST do ICMS' % prod) if not eletr.ipi_cst: errors.append(u'%s - CST do IPI' % prod) if eletr.tipo_produto == 'service': if not eletr.issqn_codigo: errors.append(u'%s - Código de Serviço' % prod) if not eletr.pis_cst: errors.append(u'%s - CST do PIS' % prod) if not eletr.cofins_cst: errors.append(u'%s - CST do Cofins' % prod) return errors @api.multi def _prepare_eletronic_invoice_item(self, item, invoice): res = super(InvoiceEletronic, self)._prepare_eletronic_invoice_item(item, invoice) if self.model not in ('55', '65'): return res if self.ambiente != 'homologacao': xProd = item.product_id.with_context( display_default_code=False).name_get()[0][1] else: xProd = 'NOTA FISCAL EMITIDA EM AMBIENTE DE HOMOLOGACAO -\ SEM VALOR FISCAL' prod = { 'cProd': item.product_id.default_code, 'cEAN': item.product_id.barcode or 'SEM GTIN', 'xProd': xProd, 'NCM': re.sub('[^0-9]', '', item.ncm or '')[:8], 'EXTIPI': re.sub('[^0-9]', '', item.ncm or '')[8:], 'CFOP': item.cfop, 'uCom': '{:.6}'.format(item.uom_id.name or ''), 'qCom': item.quantidade, 'vUnCom': "%.02f" % item.preco_unitario, 'vProd': "%.02f" % (item.preco_unitario * item.quantidade), 'cEANTrib': item.product_id.barcode or 'SEM GTIN', 'uTrib': '{:.6}'.format(item.uom_id.name or ''), 'qTrib': item.quantidade, 'vUnTrib': "%.02f" % item.preco_unitario, 'vFrete': "%.02f" % item.frete if item.frete else '', 'vSeg': "%.02f" % item.seguro if item.seguro else '', 'vDesc': "%.02f" % item.desconto if item.desconto else '', 'vOutro': "%.02f" % item.outras_despesas if item.outras_despesas else '', 'indTot': item.indicador_total, 'cfop': item.cfop, 'CEST': re.sub('[^0-9]', '', item.cest or ''), 'nItemPed': item.item_pedido_compra if item.item_pedido_compra else '', } di_vals = [] for di in item.import_declaration_ids: adicoes = [] for adi in di.line_ids: adicoes.append({ 'nAdicao': adi.name, 'nSeqAdic': adi.sequence, 'cFabricante': adi.manufacturer_code, 'vDescDI': "%.02f" % adi.amount_discount if adi.amount_discount else '', 'nDraw': adi.drawback_number or '', }) dt_registration = datetime.strptime(di.date_registration, DATE_FORMAT) dt_release = datetime.strptime(di.date_release, DATE_FORMAT) di_vals.append({ 'nDI': di.name, 'dDI': dt_registration.strftime('%Y-%m-%d'), 'xLocDesemb': di.location, 'UFDesemb': di.state_id.code, 'dDesemb': dt_release.strftime('%Y-%m-%d'), 'tpViaTransp': di.type_transportation, 'vAFRMM': "%.02f" % di.afrmm_value if di.afrmm_value else '', 'tpIntermedio': di.type_import, 'CNPJ': di.thirdparty_cnpj or '', 'UFTerceiro': di.thirdparty_state_id.code or '', 'cExportador': di.exporting_code, 'adi': adicoes, }) prod["DI"] = di_vals imposto = { 'vTotTrib': "%.02f" % item.tributos_estimados, 'ICMS': { 'orig': item.origem, 'CST': item.icms_cst, 'modBC': item.icms_tipo_base, 'vBC': "%.02f" % item.icms_base_calculo, 'pRedBC': "%.02f" % item.icms_aliquota_reducao_base, 'pICMS': "%.02f" % item.icms_aliquota, 'vICMS': "%.02f" % item.icms_valor, 'modBCST': item.icms_st_tipo_base, 'pMVAST': "%.02f" % item.icms_st_aliquota_mva, 'pRedBCST': "%.02f" % item.icms_st_aliquota_reducao_base, 'vBCST': "%.02f" % item.icms_st_base_calculo, 'pICMSST': "%.02f" % item.icms_st_aliquota, 'vICMSST': "%.02f" % item.icms_st_valor, 'pCredSN': "%.02f" % item.icms_aliquota_credito, 'vCredICMSSN': "%.02f" % item.icms_valor_credito }, 'IPI': { 'clEnq': item.classe_enquadramento_ipi or '', 'cEnq': item.codigo_enquadramento_ipi, 'CST': item.ipi_cst, 'vBC': "%.02f" % item.ipi_base_calculo, 'pIPI': "%.02f" % item.ipi_aliquota, 'vIPI': "%.02f" % item.ipi_valor }, 'PIS': { 'CST': item.pis_cst, 'vBC': "%.02f" % item.pis_base_calculo, 'pPIS': "%.02f" % item.pis_aliquota, 'vPIS': "%.02f" % item.pis_valor }, 'COFINS': { 'CST': item.cofins_cst, 'vBC': "%.02f" % item.cofins_base_calculo, 'pCOFINS': "%.02f" % item.cofins_aliquota, 'vCOFINS': "%.02f" % item.cofins_valor }, 'II': { 'vBC': "%.02f" % item.ii_base_calculo, 'vDespAdu': "%.02f" % item.ii_valor_despesas, 'vII': "%.02f" % item.ii_valor, 'vIOF': "%.02f" % item.ii_valor_iof }, } if item.tem_difal: imposto['ICMSUFDest'] = { 'vBCUFDest': "%.02f" % item.icms_bc_uf_dest, 'pFCPUFDest': "%.02f" % item.icms_aliquota_fcp_uf_dest, 'pICMSUFDest': "%.02f" % item.icms_aliquota_uf_dest, 'pICMSInter': "%.02f" % item.icms_aliquota_interestadual, 'pICMSInterPart': "%.02f" % item.icms_aliquota_inter_part, 'vFCPUFDest': "%.02f" % item.icms_fcp_uf_dest, 'vICMSUFDest': "%.02f" % item.icms_uf_dest, 'vICMSUFRemet': "%.02f" % item.icms_uf_remet, } return { 'prod': prod, 'imposto': imposto, 'infAdProd': item.informacao_adicional } @api.multi def _prepare_eletronic_invoice_values(self): res = super(InvoiceEletronic, self)._prepare_eletronic_invoice_values() if self.model not in ('55', '65'): return res dt_emissao = datetime.strptime(self.data_emissao, DTFT) ide = { 'cUF': self.company_id.state_id.ibge_code, 'cNF': "%08d" % self.numero_controle, 'natOp': self.fiscal_position_id.name, 'mod': self.model, 'serie': self.serie.code, 'nNF': self.numero, 'dhEmi': dt_emissao.strftime('%Y-%m-%dT%H:%M:%S-00:00'), 'dhSaiEnt': dt_emissao.strftime('%Y-%m-%dT%H:%M:%S-00:00'), 'tpNF': '0' if self.tipo_operacao == 'entrada' else '1', 'idDest': self.ind_dest or 1, 'cMunFG': "%s%s" % (self.company_id.state_id.ibge_code, self.company_id.city_id.ibge_code), # Formato de Impressão do DANFE - 1 - Danfe Retrato, 4 - Danfe NFCe 'tpImp': '1' if self.model == '55' else '4', 'tpEmis': int(self.tipo_emissao), 'tpAmb': 2 if self.ambiente == 'homologacao' else 1, 'finNFe': self.finalidade_emissao, 'indFinal': self.ind_final or '1', 'indPres': self.ind_pres or '1', 'procEmi': 0 } # Documentos Relacionados documentos = [] for doc in self.fiscal_document_related_ids: data = fields.Datetime.from_string(doc.date) if doc.document_type == 'nfe': documentos.append({'refNFe': doc.access_key}) elif doc.document_type == 'nf': documentos.append({ 'refNF': { 'cUF': doc.state_id.ibge_code, 'AAMM': data.strftime("%y%m"), 'CNPJ': re.sub('[^0-9]', '', doc.cnpj_cpf), 'mod': doc.fiscal_document_id.code, 'serie': doc.serie, 'nNF': doc.internal_number, } }) elif doc.document_type == 'cte': documentos.append({'refCTe': doc.access_key}) elif doc.document_type == 'nfrural': cnpj_cpf = re.sub('[^0-9]', '', doc.cnpj_cpf) documentos.append({ 'refNFP': { 'cUF': doc.state_id.ibge_code, 'AAMM': data.strftime("%y%m"), 'CNPJ': cnpj_cpf if len(cnpj_cpf) == 14 else '', 'CPF': cnpj_cpf if len(cnpj_cpf) == 11 else '', 'IE': doc.inscr_est, 'mod': doc.fiscal_document_id.code, 'serie': doc.serie, 'nNF': doc.internal_number, } }) elif doc.document_type == 'cf': documentos.append({ 'refECF': { 'mod': doc.fiscal_document_id.code, 'nECF': doc.serie, 'nCOO': doc.internal_number, } }) ide['NFref'] = documentos emit = { 'tipo': self.company_id.partner_id.company_type, 'cnpj_cpf': re.sub('[^0-9]', '', self.company_id.cnpj_cpf), 'xNome': self.company_id.legal_name, 'xFant': self.company_id.name, 'enderEmit': { 'xLgr': self.company_id.street, 'nro': self.company_id.number, 'xBairro': self.company_id.district, 'cMun': '%s%s' % (self.company_id.partner_id.state_id.ibge_code, self.company_id.partner_id.city_id.ibge_code), 'xMun': self.company_id.city_id.name, 'UF': self.company_id.state_id.code, 'CEP': re.sub('[^0-9]', '', self.company_id.zip), 'cPais': self.company_id.country_id.ibge_code, 'xPais': self.company_id.country_id.name, 'fone': re.sub('[^0-9]', '', self.company_id.phone or '') }, 'IE': re.sub('[^0-9]', '', self.company_id.inscr_est), 'CRT': self.company_id.fiscal_type, } if self.company_id.cnae_main_id and self.company_id.inscr_mun: emit['IM'] = re.sub('[^0-9]', '', self.company_id.inscr_mun or '') emit['CNAE'] = re.sub('[^0-9]', '', self.company_id.cnae_main_id.code or '') dest = None exporta = None if self.commercial_partner_id: partner = self.commercial_partner_id dest = { 'tipo': partner.company_type, 'cnpj_cpf': re.sub('[^0-9]', '', partner.cnpj_cpf or ''), 'xNome': partner.legal_name or partner.name, 'enderDest': { 'xLgr': partner.street, 'nro': partner.number, 'xBairro': partner.district, 'cMun': '%s%s' % (partner.state_id.ibge_code, partner.city_id.ibge_code), 'xMun': partner.city_id.name, 'UF': partner.state_id.code, 'CEP': re.sub('[^0-9]', '', partner.zip or ''), 'cPais': (partner.country_id.bc_code or '')[-4:], 'xPais': partner.country_id.name, 'fone': re.sub('[^0-9]', '', partner.phone or '') }, 'indIEDest': self.ind_ie_dest, 'IE': re.sub('[^0-9]', '', partner.inscr_est or ''), } if self.model == '65': dest.update( {'CPF': re.sub('[^0-9]', '', partner.cnpj_cpf or '')}) if self.ambiente == 'homologacao': dest['xNome'] = \ u'NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO -\ SEM VALOR FISCAL' _logger.info( "==============before===idEstrangeiro=====================") if partner.country_id.id != self.company_id.country_id.id: dest['idEstrangeiro'] = re.sub('[^0-9]', '', partner.cnpj_cpf or '') _logger.info( "=================idEstrangeiro=====================%s", dest['idEstrangeiro']) dest['enderDest']['UF'] = 'EX' dest['enderDest']['xMun'] = 'Exterior' dest['enderDest']['cMun'] = '9999999' exporta = { 'UFSaidaPais': self.uf_saida_pais_id.code or '', 'xLocExporta': self.local_embarque or '', 'xLocDespacho': self.local_despacho or '', } autorizados = [] if self.company_id.accountant_id: autorizados.append({ 'CNPJ': re.sub('[^0-9]', '', self.company_id.accountant_id.cnpj_cpf) }) eletronic_items = [] for item in self.eletronic_item_ids: eletronic_items.append( self._prepare_eletronic_invoice_item(item, self)) total = { # ICMS 'vBC': "%.02f" % self.valor_bc_icms, 'vICMS': "%.02f" % self.valor_icms, 'vICMSDeson': '0.00', 'vFCP': '0.00', # TODO Implementar aqui 'vBCST': "%.02f" % self.valor_bc_icmsst, 'vST': "%.02f" % self.valor_icmsst, 'vFCPST': '0.00', 'vFCPSTRet': '0.00', 'vProd': "%.02f" % self.valor_bruto, 'vFrete': "%.02f" % self.valor_frete, 'vSeg': "%.02f" % self.valor_seguro, 'vDesc': "%.02f" % self.valor_desconto, 'vII': "%.02f" % self.valor_ii, 'vIPI': "%.02f" % self.valor_ipi, 'vIPIDevol': '0.00', 'vPIS': "%.02f" % self.valor_pis, 'vCOFINS': "%.02f" % self.valor_cofins, 'vOutro': "%.02f" % self.valor_despesas, 'vNF': "%.02f" % self.valor_final, 'vFCPUFDest': "%.02f" % self.valor_icms_fcp_uf_dest, 'vICMSUFDest': "%.02f" % self.valor_icms_uf_dest, 'vICMSUFRemet': "%.02f" % self.valor_icms_uf_remet, 'vTotTrib': "%.02f" % self.valor_estimado_tributos, # ISSQn 'vServ': '0.00', # Retenções } if self.transportadora_id.street: end_transp = "%s - %s, %s" % ( self.transportadora_id.street, self.transportadora_id.number or '', self.transportadora_id.district or '') else: end_transp = '' transp = { 'modFrete': self.modalidade_frete, 'transporta': { 'xNome': self.transportadora_id.legal_name or self.transportadora_id.name or '', 'IE': re.sub('[^0-9]', '', self.transportadora_id.inscr_est or ''), 'xEnder': end_transp if self.transportadora_id else '', 'xMun': self.transportadora_id.city_id.name or '', 'UF': self.transportadora_id.state_id.code or '' }, 'veicTransp': { 'placa': self.placa_veiculo or '', 'UF': self.uf_veiculo or '', 'RNTC': self.rntc or '', } } cnpj_cpf = re.sub('[^0-9]', '', self.transportadora_id.cnpj_cpf or '') if self.transportadora_id.is_company: transp['transporta']['CNPJ'] = cnpj_cpf else: transp['transporta']['CPF'] = cnpj_cpf reboques = [] for item in self.reboque_ids: reboques.append({ 'placa': item.placa_veiculo or '', 'UF': item.uf_veiculo or '', 'RNTC': item.rntc or '', 'vagao': item.vagao or '', 'balsa': item.balsa or '', }) transp['reboque'] = reboques volumes = [] for item in self.volume_ids: volumes.append({ 'qVol': item.quantidade_volumes or '', 'esp': item.especie or '', 'marca': item.marca or '', 'nVol': item.numeracao or '', 'pesoL': "%.03f" % item.peso_liquido if item.peso_liquido else '', 'pesoB': "%.03f" % item.peso_bruto if item.peso_bruto else '', }) transp['vol'] = volumes duplicatas = [] for dup in self.duplicata_ids: vencimento = fields.Datetime.from_string(dup.data_vencimento) duplicatas.append({ 'nDup': dup.numero_duplicata, 'dVenc': vencimento.strftime('%Y-%m-%d'), 'vDup': "%.02f" % dup.valor }) cobr = { 'fat': { 'nFat': self.numero_fatura or '', 'vOrig': "%.02f" % (self.fatura_liquido + self.fatura_desconto), 'vDesc': "%.02f" % self.fatura_desconto, 'vLiq': "%.02f" % self.fatura_liquido, }, 'dup': duplicatas } pag = [{ 'indPag': self.payment_term_id.indPag or '0', 'tPag': self.payment_mode_id.tipo_pagamento or '99', 'vPag': "%.02f" % self.valor_final }] self.informacoes_complementares = self.informacoes_complementares.\ replace('\n', '<br />') self.informacoes_legais = self.informacoes_legais.replace( '\n', '<br />') infAdic = { 'infCpl': self.informacoes_complementares or '', 'infAdFisco': self.informacoes_legais or '', } compras = { 'xNEmp': self.nota_empenho or '', 'xPed': self.pedido_compra or '', 'xCont': self.contrato_compra or '', } vals = { 'Id': '', 'ide': ide, 'emit': emit, 'dest': dest, 'autXML': autorizados, 'detalhes': eletronic_items, 'total': total, 'pag': pag, 'transp': transp, 'infAdic': infAdic, 'exporta': exporta, 'compra': compras, } if len(duplicatas) > 0: vals['cobr'] = cobr return vals @api.multi def _prepare_lote(self, lote, nfe_values): return { 'idLote': lote, 'indSinc': 0, 'estado': self.company_id.partner_id.state_id.ibge_code, 'ambiente': 1 if self.ambiente == 'producao' else 2, 'NFes': [{ 'infNFe': nfe_values }], 'modelo': self.model, } def _find_attachment_ids_email(self): atts = super(InvoiceEletronic, self)._find_attachment_ids_email() if self.model not in ('55'): return atts attachment_obj = self.env['ir.attachment'] nfe_xml = base64.decodestring(self.nfe_processada) logo = base64.decodestring(self.invoice_id.company_id.logo) tmpLogo = io.BytesIO() tmpLogo.write(logo) tmpLogo.seek(0) xml_element = etree.fromstring(nfe_xml) oDanfe = danfe(list_xml=[xml_element], logo=tmpLogo) tmpDanfe = io.BytesIO() oDanfe.writeto_pdf(tmpDanfe) if danfe: danfe_id = attachment_obj.create( dict( name="Danfe-%08d.pdf" % self.numero, datas_fname="Danfe-%08d.pdf" % self.numero, datas=base64.b64encode(tmpDanfe.getvalue()), mimetype='application/pdf', res_model='account.invoice', res_id=self.invoice_id.id, )) atts.append(danfe_id.id) if nfe_xml: xml_id = attachment_obj.create( dict( name=self.nfe_processada_name, datas_fname=self.nfe_processada_name, datas=base64.encodestring(nfe_xml), mimetype='application/xml', res_model='account.invoice', res_id=self.invoice_id.id, )) atts.append(xml_id.id) return atts @api.multi def action_post_validate(self): super(InvoiceEletronic, self).action_post_validate() if self.model not in ('55', '65'): return chave_dict = { 'cnpj': re.sub('[^0-9]', '', self.company_id.cnpj_cpf), 'estado': self.company_id.state_id.ibge_code, 'emissao': self.data_emissao[2:4] + self.data_emissao[5:7], 'modelo': self.model, 'numero': self.numero, 'serie': self.serie.code.zfill(3), 'tipo': int(self.tipo_emissao), 'codigo': "%08d" % self.numero_controle } self.chave_nfe = gerar_chave(ChaveNFe(**chave_dict)) cert = self.company_id.with_context({'bin_size': False}).nfe_a1_file cert_pfx = base64.decodestring(cert) certificado = Certificado(cert_pfx, self.company_id.nfe_a1_password) nfe_values = self._prepare_eletronic_invoice_values() lote = self._prepare_lote(self.id, nfe_values) xml_enviar = xml_autorizar_nfe(certificado, **lote) # _logger.info("============================xml_enviar before===========================%s", xml_enviar) # xml_enviar1 = xml_enviar.split('<dest>')[0] # xml_enviar2 = xml_enviar.split('<dest>')[1] # if self.ind_dest == '3': # xml_enviar = xml_enviar1+'<dest><idEstrangeiro>'+re.sub('[^0-9]', '', self.partner_id.cnpj_cpf or '')+'</idEstrangeiro>'+xml_enviar2 if self.partner_id.cnpj_cpf else xml_enviar1+'<dest><idEstrangeiro/>'+xml_enviar2 # _logger.info("========================xml_enviar after===========================%s", xml_enviar) mensagens_erro = valida_nfe(xml_enviar) _logger.info( "========================mensagens_erro===========================%s", mensagens_erro) if mensagens_erro: raise UserError(mensagens_erro) self.xml_to_send = base64.encodestring(xml_enviar.encode('utf-8')) self.xml_to_send_name = 'nfse-enviar-%s.xml' % self.numero _logger.info( "========================self.xml_to_send_name===========================%s", self.xml_to_send_name) @api.multi def action_send_eletronic_invoice(self): super(InvoiceEletronic, self).action_send_eletronic_invoice() if self.model not in ('55', '65') or self.state in ('done', 'denied', 'cancel'): return self.state = 'error' self.data_emissao = datetime.now() cert = self.company_id.with_context({'bin_size': False}).nfe_a1_file cert_pfx = base64.decodestring(cert) certificado = Certificado(cert_pfx, self.company_id.nfe_a1_password) xml_to_send = base64.decodestring(self.xml_to_send).decode( 'utf-8', 'ignore') import string _logger.info("----------before-------------%s", xml_to_send) xml_to_send = ''.join(x for x in xml_to_send if x in string.printable) xml_to_send = re.sub('\s+(?=<)', '', xml_to_send) xml_to_send = re.sub('\n', '', xml_to_send) _logger.info("----------xml_to_send-------------%s", xml_to_send) resposta_recibo = None resposta = autorizar_nfe( certificado, xml=xml_to_send, estado=self.company_id.state_id.ibge_code, ambiente=1 if self.ambiente == 'producao' else 2, modelo=self.model) retorno = resposta['object'].getchildren()[0] if retorno.cStat == 103: obj = { 'estado': self.company_id.partner_id.state_id.ibge_code, 'ambiente': 1 if self.ambiente == 'producao' else 2, 'obj': { 'ambiente': 1 if self.ambiente == 'producao' else 2, 'numero_recibo': retorno.infRec.nRec }, 'modelo': self.model, } self.recibo_nfe = obj['obj']['numero_recibo'] import time while True: time.sleep(2) resposta_recibo = retorno_autorizar_nfe(certificado, **obj) retorno = resposta_recibo['object'].getchildren()[0] if retorno.cStat != 105: break if retorno.cStat != 104: self.codigo_retorno = retorno.cStat self.mensagem_retorno = retorno.xMotivo else: self.codigo_retorno = retorno.protNFe.infProt.cStat self.mensagem_retorno = retorno.protNFe.infProt.xMotivo if self.codigo_retorno == '100': self.write({ 'state': 'done', 'protocolo_nfe': retorno.protNFe.infProt.nProt, 'data_autorizacao': retorno.protNFe.infProt.dhRecbto }) # Duplicidade de NF-e significa que a nota já está emitida # TODO Buscar o protocolo de autorização, por hora só finalizar if self.codigo_retorno == '204': self.write({ 'state': 'done', 'codigo_retorno': '100', 'mensagem_retorno': 'Autorizado o uso da NF-e' }) # Denegada e nota já está denegada if self.codigo_retorno in ('302', '205'): self.write({'state': 'denied'}) self.env['invoice.eletronic.event'].create({ 'code': self.codigo_retorno, 'name': self.mensagem_retorno, 'invoice_eletronic_id': self.id, }) self._create_attachment('nfe-envio', self, resposta['sent_xml']) self._create_attachment('nfe-ret', self, resposta['received_xml']) recibo_xml = resposta['received_xml'] if resposta_recibo: self._create_attachment('rec', self, resposta_recibo['sent_xml']) self._create_attachment('rec-ret', self, resposta_recibo['received_xml']) recibo_xml = resposta_recibo['received_xml'] if self.codigo_retorno == '100': nfe_proc = gerar_nfeproc(resposta['sent_xml'], recibo_xml) self.nfe_processada = base64.encodestring(nfe_proc) self.nfe_processada_name = "NFe%08d.xml" % self.numero @api.multi def generate_nfe_proc(self): if self.state in ['cancel', 'done', 'denied']: recibo = self.env['ir.attachment'].search( [('res_model', '=', 'invoice.eletronic'), ('res_id', '=', self.id), ('datas_fname', 'like', 'rec-ret')], limit=1) if not recibo: recibo = self.env['ir.attachment'].search( [('res_model', '=', 'invoice.eletronic'), ('res_id', '=', self.id), ('datas_fname', 'like', 'nfe-ret')], limit=1) nfe_envio = self.env['ir.attachment'].search( [('res_model', '=', 'invoice.eletronic'), ('res_id', '=', self.id), ('datas_fname', 'like', 'nfe-envio')], limit=1) if nfe_envio.datas and recibo.datas: nfe_proc = gerar_nfeproc( base64.decodestring(nfe_envio.datas).decode('utf-8'), base64.decodestring(recibo.datas).decode('utf-8'), ) self.nfe_processada = base64.encodestring(nfe_proc) self.nfe_processada_name = "NFe%08d.xml" % self.numero else: raise UserError('A NFe não está validada') @api.multi def action_cancel_document(self, context=None, justificativa=None): if self.model not in ('55', '65'): return super( InvoiceEletronic, self).action_cancel_document(justificativa=justificativa) if not justificativa: return { 'name': 'Cancelamento NFe', 'type': 'ir.actions.act_window', 'res_model': 'wizard.cancel.nfe', 'view_type': 'form', 'view_mode': 'form', 'target': 'new', 'context': { 'default_edoc_id': self.id } } cert = self.company_id.with_context({'bin_size': False}).nfe_a1_file cert_pfx = base64.decodestring(cert) certificado = Certificado(cert_pfx, self.company_id.nfe_a1_password) id_canc = "ID110111%s%02d" % (self.chave_nfe, self.sequencial_evento) tz = pytz.timezone(self.env.user.partner_id.tz) or pytz.utc dt_evento = datetime.utcnow() dt_evento = pytz.utc.localize(dt_evento).astimezone(tz) cancelamento = { 'idLote': self.id, 'estado': self.company_id.state_id.ibge_code, 'ambiente': 2 if self.ambiente == 'homologacao' else 1, 'eventos': [{ 'Id': id_canc, 'cOrgao': self.company_id.state_id.ibge_code, 'tpAmb': 2 if self.ambiente == 'homologacao' else 1, 'CNPJ': re.sub('[^0-9]', '', self.company_id.cnpj_cpf), 'chNFe': self.chave_nfe, 'dhEvento': dt_evento.strftime('%Y-%m-%dT%H:%M:%S-03:00'), 'nSeqEvento': self.sequencial_evento, 'nProt': self.protocolo_nfe, 'xJust': justificativa, 'tpEvento': '110111', 'descEvento': 'Cancelamento', }], 'modelo': self.model, } resp = recepcao_evento_cancelamento(certificado, **cancelamento) resposta = resp['object'].getchildren()[0] if resposta.cStat == 128 and \ resposta.retEvento.infEvento.cStat in (135, 136, 155): self.state = 'cancel' self.codigo_retorno = resposta.retEvento.infEvento.cStat self.mensagem_retorno = resposta.retEvento.infEvento.xMotivo self.sequencial_evento += 1 else: code, motive = None, None if resposta.cStat == 128: code = resposta.retEvento.infEvento.cStat motive = resposta.retEvento.infEvento.xMotivo else: code = resposta.cStat motive = resposta.xMotivo if code == 573: # Duplicidade, já cancelado return self.action_get_status() return self._create_response_cancel(code, motive, resp, justificativa) self.env['invoice.eletronic.event'].create({ 'code': self.codigo_retorno, 'name': self.mensagem_retorno, 'invoice_eletronic_id': self.id, }) self._create_attachment('canc', self, resp['sent_xml']) self._create_attachment('canc-ret', self, resp['received_xml']) nfe_processada = base64.decodestring(self.nfe_processada) nfe_proc_cancel = gerar_nfeproc_cancel(nfe_processada, resp['received_xml'].encode()) if nfe_proc_cancel: self.nfe_processada = base64.encodestring(nfe_proc_cancel) def action_get_status(self): cert = self.company_id.with_context({'bin_size': False}).nfe_a1_file cert_pfx = base64.decodestring(cert) certificado = Certificado(cert_pfx, self.company_id.nfe_a1_password) consulta = { 'estado': self.company_id.state_id.ibge_code, 'ambiente': 2 if self.ambiente == 'homologacao' else 1, 'modelo': self.model, 'obj': { 'chave_nfe': self.chave_nfe, 'ambiente': 2 if self.ambiente == 'homologacao' else 1, } } resp = consultar_protocolo_nfe(certificado, **consulta) retorno_consulta = resp['object'].getchildren()[0] if retorno_consulta.cStat == 101: self.state = 'cancel' self.codigo_retorno = retorno_consulta.cStat self.mensagem_retorno = retorno_consulta.xMotivo resp['received_xml'] = etree.tostring(retorno_consulta, encoding=str) self.env['invoice.eletronic.event'].create({ 'code': self.codigo_retorno, 'name': self.mensagem_retorno, 'invoice_eletronic_id': self.id, }) self._create_attachment('canc', self, resp['sent_xml']) self._create_attachment('canc-ret', self, resp['received_xml']) nfe_processada = base64.decodestring(self.nfe_processada) nfe_proc_cancel = gerar_nfeproc_cancel( nfe_processada, resp['received_xml'].encode()) if nfe_proc_cancel: self.nfe_processada = base64.encodestring(nfe_proc_cancel) else: message = "%s - %s" % (retorno_consulta.cStat, retorno_consulta.xMotivo) raise UserError(message) def _create_response_cancel(self, code, motive, response, justificativa): message = "%s - %s" % (code, motive) wiz = self.env['wizard.cancel.nfe'].create({ 'edoc_id': self.id, 'justificativa': justificativa, 'state': 'error', 'message': message, 'sent_xml': base64.b64encode(response['sent_xml'].encode('utf-8')), 'sent_xml_name': 'cancelamento-envio.xml', 'received_xml': base64.b64encode(response['received_xml'].encode('utf-8')), 'received_xml_name': 'cancelamento-retorno.xml', }) return { 'name': 'Cancelamento NFe', 'type': 'ir.actions.act_window', 'res_model': 'wizard.cancel.nfe', 'res_id': wiz.id, 'view_type': 'form', 'view_mode': 'form', 'target': 'new', }
class PaymentLinkWizard(models.TransientModel): _name = "payment.link.wizard" _description = "Generate Payment Link" @api.model def default_get(self, fields): res = super(PaymentLinkWizard, self).default_get(fields) res_id = self._context.get('active_id') res_model = self._context.get('active_model') res.update({'res_id': res_id, 'res_model': res_model}) amount_field = 'amount_residual' if res_model == 'account.move' else 'amount_total' if res_id and res_model == 'account.move': record = self.env[res_model].browse(res_id) res.update({ 'description': record.payment_reference, 'amount': record[amount_field], 'currency_id': record.currency_id.id, 'partner_id': record.partner_id.id, 'amount_max': record[amount_field], }) return res res_model = fields.Char('Related Document Model', required=True) res_id = fields.Integer('Related Document ID', required=True) amount = fields.Monetary(currency_field='currency_id', required=True) amount_max = fields.Monetary(currency_field='currency_id') currency_id = fields.Many2one('res.currency') partner_id = fields.Many2one('res.partner') partner_email = fields.Char(related='partner_id.email') link = fields.Char(string='Payment Link', compute='_compute_values') description = fields.Char('Payment Ref') access_token = fields.Char(compute='_compute_values') company_id = fields.Many2one('res.company', compute='_compute_company') available_acquirer_ids = fields.Many2many( comodel_name='payment.acquirer', string="Payment Acquirers Available", compute='_compute_available_acquirer_ids', compute_sudo=True, ) acquirer_id = fields.Many2one( comodel_name='payment.acquirer', string="Force Payment Acquirer", domain="[('id', 'in', available_acquirer_ids)]", help= "Force the customer to pay via the specified payment acquirer. Leave empty to allow the customer to choose among all acquirers." ) has_multiple_acquirers = fields.Boolean( string="Has Multiple Acquirers", compute='_compute_has_multiple_acquirers', ) @api.onchange('amount', 'description') def _onchange_amount(self): if float_compare(self.amount_max, self.amount, precision_rounding=self.currency_id.rounding or 0.01) == -1: raise ValidationError( _("Please set an amount smaller than %s.") % (self.amount_max)) if self.amount <= 0: raise ValidationError( _("The value of the payment amount must be positive.")) @api.depends('amount', 'description', 'partner_id', 'currency_id', 'acquirer_id') def _compute_values(self): for payment_link in self: payment_link.access_token = payment_utils.generate_access_token( payment_link.partner_id.id, payment_link.amount, payment_link.currency_id.id) # must be called after token generation, obvsly - the link needs an up-to-date token self._generate_link() @api.depends('res_model', 'res_id') def _compute_company(self): for link in self: record = self.env[link.res_model].browse(link.res_id) link.company_id = record.company_id if 'company_id' in record else False @api.depends('company_id', 'partner_id', 'currency_id') def _compute_available_acquirer_ids(self): for link in self: link.available_acquirer_ids = link.env[ 'payment.acquirer']._get_compatible_acquirers( company_id=link.company_id.id, partner_id=link.partner_id.id, currency_id=link.currency_id.id) @api.depends('available_acquirer_ids') def _compute_has_multiple_acquirers(self): for link in self: link.has_multiple_acquirers = len(link.available_acquirer_ids) > 1 def _generate_link(self): for payment_link in self: related_document = self.env[payment_link.res_model].browse( payment_link.res_id) base_url = related_document.get_base_url( ) # Don't generate links for the wrong website payment_link.link = f'{base_url}/payment/pay' \ f'?reference={urls.url_quote(payment_link.description)}' \ f'&amount={payment_link.amount}' \ f'¤cy_id={payment_link.currency_id.id}' \ f'&partner_id={payment_link.partner_id.id}' \ f'&company_id={payment_link.company_id.id}' \ f'{"&acquirer_id=" + str(payment_link.acquirer_id.id) if payment_link.acquirer_id else "" }' \ f'&access_token={payment_link.access_token}'
class school_student(models.Model): _name = 'school.student' _description = 'school_student.school_student' _order = "school_id" name = fields.Char(default="Sunny Leaone") school_id = fields.Many2one("school.profile", string="School Name") hobby_list = fields.Many2many( "hobby", "school_hobby_rel", "student_id", "hobby_id", string="Hobby List", ) is_virtual_school = fields.Boolean(related="school_id.is_virtual_class", string="Is Virtual Class", store=True) school_address = fields.Text(related="school_id.address", string="Address", help="This is school address.") currency_id = fields.Many2one("res.currency", string="Currency") student_fees = fields.Monetary(string="Student Fees", index=True) total_fees = fields.Float(string="Total Fees", default=200) ref_id = fields.Reference(selection=[('school.profile', 'School'), ('account.move', 'Invoice')], string="Reference Field", default="school.profile,1") active = fields.Boolean(string="Active", default=True) bdate = fields.Date(string="Date Of Birth") student_age = fields.Char(string="Total Age", compute="_get_age_from_student") def wiz_open(self): return self.env['ir.actions.act_window']._for_xml_id( "school_student.student_fees_update_action") # return {'type': 'ir.actions.act_window', # 'res_model': 'student.feees.update.wizard', # 'view_mode': 'form', # 'target': 'new'} def custom_button_method(self): # self.env.cr.execute("insert into school_student(name, active) values('from button click', True)") # self.env.cr.commit() # self._cr.execute("insert into school_student(name, active) values('from button click', True)") # self._cr.commit() # print("Envi...... ",self.env) # print("user id...... ",self.env.uid) # print("current user...... ",self.env.user) # print("Super user?...... ",self.env.su) # print("Company...... ",self.env.company) # print("Compaies...... ",self.env.companies) # print("Lang...... ",self.env.lang) # print("Cr...... ",self.env.cr) # print("Hello this is custom_button_method called by you....", self) # with_env # with_context # with_user # with_company # sudo # self.env['student.test'].sudo().create({'name':'Student Test Demo.....'}) # new_cr = registry(self.env.cr.dbname).cursor() # partner_id = self.env['res.partner'].with_env(self.env(cr=new_cr)).create({"name":" New Env CR Partner."}) # partner_id.env.cr.commit() # self.custom_new_method(random.randint(1,1000)) # self.custom_method() cli_commands = tl.config.options print(cli_commands) print(cli_commands.get("db_name")) print(cli_commands.get("db_user")) print(cli_commands.get("db_password")) print(cli_commands.get("addons_path")) print(cli_commands.get("dbfilter")) print(cli_commands.get("weblearns")) print(cli_commands.get("weblearns_author")) if tl.config.options.get("weblearns") == "'Tutorials'": tl.config.options['weblearns'] = "Odoo Tutorial" # print(cli_commands.get("weblearns")) # print(cli_commands.get("weblearns_author")) print(tl.config.options['weblearns']) def custom_new_method(self, total_fees): self.total_fees = total_fees def custom_method(self): try: self.ensure_one() print(self.name) print(self.bdate) print(self.school_id.name) except ValueError: pass @api.model def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): res = super(school_student, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu) if view_type == "form": doc = etree.XML(res['arch']) name_field = doc.xpath("//field[@name='name']") if name_field: # Added one label in form view. name_field[0].addnext( etree.Element( 'label', { 'string': 'Hello this is custom label from fields_view_get method' })) #override attribute address_field = doc.xpath("//field[@name='school_address']") if address_field: address_field[0].set("string", "Hello This is School Address.") address_field[0].set("nolabel", "0") res['arch'] = etree.tostring(doc, encoding='unicode') if view_type == 'tree': doc = etree.XML(res['arch']) school_field = doc.xpath("//field[@name='school_id']") if school_field: # Added one field in tree view. school_field[0].addnext( etree.Element('field', { 'string': 'Total Fees', 'name': 'total_fees' })) res['arch'] = etree.tostring(doc, encoding='unicode') return res @api.model def default_get(self, field_list=[]): print("field_list ", field_list) rtn = super(school_student, self).default_get(field_list) print("Befor Edit ", rtn) rtn['student_fees'] = 4000 print("return statement ", rtn) return rtn @api.depends("bdate") def _get_age_from_student(self): """Age Calculation""" today_date = datetime.date.today() for stud in self: if stud.bdate: """ Get only year. """ # bdate = fields.Datetime.to_datetime(stud.bdate).date() # total_age = str(int((today_date - bdate).days / 365)) # stud.student_age = total_age """ Origin of below source code https://gist.github.com/shahri23/1804a3acb7ffb58a1ec8f1eda304af1a """ currentDate = datetime.date.today() deadlineDate = fields.Datetime.to_datetime(stud.bdate).date() print(deadlineDate) daysLeft = currentDate - deadlineDate print(daysLeft) years = ((daysLeft.total_seconds()) / (365.242 * 24 * 3600)) yearsInt = int(years) months = (years - yearsInt) * 12 monthsInt = int(months) days = (months - monthsInt) * (365.242 / 12) daysInt = int(days) hours = (days - daysInt) * 24 hoursInt = int(hours) minutes = (hours - hoursInt) * 60 minutesInt = int(minutes) seconds = (minutes - minutesInt) * 60 secondsInt = int(seconds) stud.student_age = 'You are {0:d} years, {1:d} months, {2:d} days, {3:d} hours, {4:d} \ minutes, {5:d} seconds old.'.format(yearsInt, monthsInt, daysInt, hoursInt, minutesInt, secondsInt) else: stud.student_age = "Not Providated...."