def get_access_token(self, scope=None): Config = self.env['ir.config_parameter'].sudo() google_drive_refresh_token = Config.get_param('google_drive_refresh_token') user_is_admin = self.env['res.users'].browse(self.env.user.id)._is_admin() if not google_drive_refresh_token: if user_is_admin: dummy, action_id = self.env['ir.model.data'].get_object_reference('base_setup', 'action_general_configuration') msg = _("You haven't configured 'Authorization Code' generated from google, Please generate and configure it .") raise RedirectWarning(msg, action_id, _('Go to the configuration panel')) else: raise UserError(_("Google Drive is not yet configured. Please contact your administrator.")) google_drive_client_id = Config.get_param('google_drive_client_id') google_drive_client_secret = Config.get_param('google_drive_client_secret') #For Getting New Access Token With help of old Refresh Token data = { 'client_id': google_drive_client_id, 'refresh_token': google_drive_refresh_token, 'client_secret': google_drive_client_secret, 'grant_type': "refresh_token", 'scope': scope or 'https://www.googleapis.com/auth/drive' } headers = {"Content-type": "application/x-www-form-urlencoded"} try: req = requests.post(GOOGLE_TOKEN_ENDPOINT, data=data, headers=headers, timeout=TIMEOUT) req.raise_for_status() except requests.HTTPError: if user_is_admin: dummy, action_id = self.env['ir.model.data'].get_object_reference('base_setup', 'action_general_configuration') msg = _("Something went wrong during the token generation. Please request again an authorization code .") raise RedirectWarning(msg, action_id, _('Go to the configuration panel')) else: raise UserError(_("Google Drive is not yet configured. Please contact your administrator.")) return req.json().get('access_token')
def _validate_fiscalyear_lock(self, values): if values.get('fiscalyear_lock_date'): draft_entries = self.env['account.move'].search([ ('company_id', 'in', self.ids), ('state', '=', 'draft'), ('date', '<=', values['fiscalyear_lock_date']) ]) if draft_entries: error_msg = _( 'There are still unposted entries in the period you want to lock. You should either post or delete them.' ) action_error = { 'view_mode': 'tree', 'name': 'Unposted Entries', 'res_model': 'account.move', 'type': 'ir.actions.act_window', 'domain': [('id', 'in', draft_entries.ids)], 'search_view_id': [ self.env.ref('account.view_account_move_filter').id, 'search' ], 'views': [[self.env.ref('account.view_move_tree').id, 'list'], [self.env.ref('account.view_move_form').id, 'form']], } raise RedirectWarning(error_msg, action_error, _('Show unposted entries')) unreconciled_statement_lines = self.env[ 'account.bank.statement.line'].search([ ('company_id', 'in', self.ids), ('is_reconciled', '=', False), ('date', '<=', values['fiscalyear_lock_date']), ('move_id.state', 'in', ('draft', 'posted')), ]) if unreconciled_statement_lines: error_msg = _( "There are still unreconciled bank statement lines in the period you want to lock." "You should either reconcile or delete them.") action_error = { 'type': 'ir.actions.client', 'tag': 'bank_statement_reconciliation_view', 'context': { 'statement_line_ids': unreconciled_statement_lines.ids, 'company_ids': self.ids }, } raise RedirectWarning( error_msg, action_error, _('Show Unreconciled Bank Statement Line'))
def do_print_checks(self): check_layout = self.company_id.account_check_printing_layout redirect_action = self.env.ref('account.action_account_config') if not check_layout or check_layout == 'disabled': msg = _("You have to choose a check layout. For this, go in Invoicing/Accounting Settings, search for 'Checks layout' and set one.") raise RedirectWarning(msg, redirect_action.id, _('Go to the configuration panel')) report_action = self.env.ref(check_layout, False) if not report_action: msg = _("Something went wrong with Check Layout, please select another layout in Invoicing/Accounting Settings and try again.") raise RedirectWarning(msg, redirect_action.id, _('Go to the configuration panel')) self.write({'is_move_sent': True}) return report_action.report_action(self)
def get_chart_of_accounts_or_fail(self): account = self.env['account.account'].search( [('company_id', '=', self.id)], limit=1) if len(account) == 0: action = self.env.ref('account.action_account_config') msg = _( "We cannot find a chart of accounts for this company, you should configure it. \n" "Please go to Account Configuration and select or install a fiscal localization." ) raise RedirectWarning(msg, action.id, _("Go to the configuration panel")) return account
def _get_default_category_id(self): if self._context.get('categ_id') or self._context.get('default_categ_id'): return self._context.get('categ_id') or self._context.get('default_categ_id') category = self.env.ref('product.product_category_all', raise_if_not_found=False) if not category: category = self.env['product.category'].search([], limit=1) if category: return category.id else: err_msg = _('You must define at least one product category in order to be able to create products.') redir_msg = _('Go to Internal Categories') raise RedirectWarning(err_msg, self.env.ref('product.product_category_action_form').id, redir_msg)
def get_config_warning(self, msg): """ Helper: return a Warning exception with the given message where the %(field:xxx)s and/or %(menu:yyy)s are replaced by the human readable field's name and/or menuitem's full path. Usage: ------ Just include in your error message %(field:model_name.field_name)s to obtain the human readable field's name, and/or %(menu:module_name.menuitem_xml_id)s to obtain the menuitem's full path. Example of use: --------------- from flectra.addons.base.models.res_config import get_warning_config raise get_warning_config(cr, _("Error: this action is prohibited. You should check the field %(field:sale.config.settings.fetchmail_lead)s in %(menu:sales_team.menu_sale_config)s."), context=context) This will return an exception containing the following message: Error: this action is prohibited. You should check the field Create leads from incoming mails in Settings/Configuration/Sales. What if there is another substitution in the message already? ------------------------------------------------------------- You could have a situation where the error message you want to upgrade already contains a substitution. Example: Cannot find any account journal of %s type for this company.\n\nYou can create one in the menu: \nConfiguration\Journals\Journals. What you want to do here is simply to replace the path by %menu:account.menu_account_config)s, and leave the rest alone. In order to do that, you can use the double percent (%%) to escape your new substitution, like so: Cannot find any account journal of %s type for this company.\n\nYou can create one in the %%(menu:account.menu_account_config)s. """ self = self.sudo() # Process the message # 1/ find the menu and/or field references, put them in a list regex_path = r'%\(((?:menu|field):[a-z_\.]*)\)s' references = re.findall(regex_path, msg, flags=re.I) # 2/ fetch the menu and/or field replacement values (full path and # human readable field's name) and the action_id if any values = {} action_id = None for item in references: ref_type, ref = item.split(':') if ref_type == 'menu': values[item], action_id = self.get_option_path(ref) elif ref_type == 'field': values[item] = self.get_option_name(ref) # 3/ substitute and return the result if (action_id): return RedirectWarning(msg % values, action_id, _('Go to the configuration panel')) return UserError(msg % values)
def _get_journal_letter(self, counterpart_partner=False): """ Regarding the AFIP responsibility of the company and the type of journal (sale/purchase), get the allowed letters. Optionally, receive the counterpart partner (customer/supplier) and get the allowed letters to work with him. This method is used to populate document types on journals and also to filter document types on specific invoices to/from customer/supplier """ self.ensure_one() letters_data = { 'issued': { '1': ['A', 'B', 'E', 'M'], '3': [], '4': ['C'], '5': [], '6': ['C', 'E'], '9': ['I'], '10': [], '13': ['C', 'E'], '99': [] }, 'received': { '1': ['A', 'B', 'C', 'E', 'M', 'I'], '3': ['B', 'C', 'I'], '4': ['B', 'C', 'I'], '5': ['B', 'C', 'I'], '6': ['A', 'B', 'C', 'I'], '9': ['E'], '10': ['E'], '13': ['A', 'B', 'C', 'I'], '99': ['B', 'C', 'I'] }, } if not self.company_id.l10n_ar_afip_responsibility_type_id: action = self.env.ref('base.action_res_company_form') msg = _( 'Can not create chart of account until you configure your company AFIP Responsibility and VAT.' ) raise RedirectWarning(msg, action.id, _('Go to Companies')) letters = letters_data[ 'issued' if self.type == 'sale' else 'received'][ self.company_id.l10n_ar_afip_responsibility_type_id.code] if counterpart_partner: counterpart_letters = letters_data[ 'issued' if self.type == 'purchase' else 'received'].get( counterpart_partner.l10n_ar_afip_responsibility_type_id. code, []) letters = list(set(letters) & set(counterpart_letters)) return letters
def default_get(self, fields): res = super(ImportMwsProducts, self).default_get(fields) context = dict(self._context) params = context.get('params', {}) active_id = context.get('active_id') or params.get('id') model = 'multi.channel.sale' if (active_id and (context.get('active_model') == model or params.get('model') == model)): context['channel_id'] = active_id report_status = self.channel_id.with_context( context).mws_ensure_report() report_id = report_status.get('report_id') if report_status.get('message'): action_id = self.env.ref( 'amazon_flectra_bridge.action_reports').id raise RedirectWarning(report_status.get('message'), action_id, _('Go to the report panel.')) return res
def _onchange_partner_journal(self): """ This method is used when the invoice is created from the sale or subscription """ expo_journals = ['FEERCEL', 'FEEWS', 'FEERCELP'] for rec in self.filtered( lambda x: x.company_id.country_id.code == "AR" and x.journal_id .type == 'sale' and x.l10n_latam_use_documents and x.partner_id .l10n_ar_afip_responsibility_type_id): res_code = rec.partner_id.l10n_ar_afip_responsibility_type_id.code domain = [('company_id', '=', rec.company_id.id), ('l10n_latam_use_documents', '=', True), ('type', '=', 'sale')] journal = self.env['account.journal'] msg = False if res_code in [ '9', '10' ] and rec.journal_id.l10n_ar_afip_pos_system not in expo_journals: # if partner is foregin and journal is not of expo, we try to change to expo journal journal = journal.search( domain + [('l10n_ar_afip_pos_system', 'in', expo_journals)], limit=1) msg = _( 'You are trying to create an invoice for foreign partner but you don\'t have an exportation journal' ) elif res_code not in [ '9', '10' ] and rec.journal_id.l10n_ar_afip_pos_system in expo_journals: # if partner is NOT foregin and journal is for expo, we try to change to local journal journal = journal.search( domain + [('l10n_ar_afip_pos_system', 'not in', expo_journals)], limit=1) msg = _( 'You are trying to create an invoice for domestic partner but you don\'t have a domestic market journal' ) if journal: rec.journal_id = journal.id elif msg: # Throw an error to user in order to proper configure the journal for the type of operation action = self.env.ref('account.action_account_journal_form') raise RedirectWarning(msg, action.id, _('Go to Journals'))
def unlink(self): """ If some tasks to unlink have some timesheets entries, these timesheets entries must be unlinked first. In this case, a warning message is displayed through a RedirectWarning and allows the user to see timesheets entries to unlink. """ tasks_with_timesheets = self.filtered(lambda t: t.timesheet_ids) if tasks_with_timesheets: if len(tasks_with_timesheets) > 1: warning_msg = _( "These tasks have some timesheet entries referencing them. Before removing these tasks, you have to remove these timesheet entries." ) else: warning_msg = _( "This task has some timesheet entries referencing it. Before removing this task, you have to remove these timesheet entries." ) raise RedirectWarning( warning_msg, self.env.ref('hr_timesheet.timesheet_action_task').id, _('See timesheet entries'), {'active_ids': tasks_with_timesheets.ids}) return super(Task, self).unlink()
def _update_reserved_quantity(self, product_id, location_id, quantity, lot_id=None, package_id=None, owner_id=None, strict=False): """ Increase the reserved quantity, i.e. increase `reserved_quantity` for the set of quants sharing the combination of `product_id, location_id` if `strict` is set to False or sharing the *exact same characteristics* otherwise. Typically, this method is called when reserving a move or updating a reserved move line. When reserving a chained move, the strict flag should be enabled (to reserve exactly what was brought). When the move is MTS,it could take anything from the stock, so we disable the flag. When editing a move line, we naturally enable the flag, to reflect the reservation according to the edition. :return: a list of tuples (quant, quantity_reserved) showing on which quant the reservation was done and how much the system was able to reserve on it """ self = self.sudo() rounding = product_id.uom_id.rounding quants = self._gather(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict) reserved_quants = [] if float_compare(quantity, 0, precision_rounding=rounding) > 0: # if we want to reserve available_quantity = sum( quants.filtered(lambda q: float_compare( q.quantity, 0, precision_rounding=rounding) > 0).mapped( 'quantity')) - sum(quants.mapped('reserved_quantity')) if float_compare(quantity, available_quantity, precision_rounding=rounding) > 0: raise UserError( _( 'It is not possible to reserve more products of %s than you have in stock.', product_id.display_name)) elif float_compare(quantity, 0, precision_rounding=rounding) < 0: # if we want to unreserve available_quantity = sum(quants.mapped('reserved_quantity')) if float_compare(abs(quantity), available_quantity, precision_rounding=rounding) > 0: action_fix_unreserve = self.env.ref( 'stock.stock_quant_stock_move_line_desynchronization', raise_if_not_found=False) if action_fix_unreserve and self.user_has_groups( 'base.group_system'): raise RedirectWarning( _( """It is not possible to unreserve more products of %s than you have in stock. The correction could unreserve some operations with problematics products.""", product_id.display_name), action_fix_unreserve.id, _('Automated action to fix it')) else: raise UserError( _( 'It is not possible to unreserve more products of %s than you have in stock. Contact an administrator.', product_id.display_name)) else: return reserved_quants for quant in quants: if float_compare(quantity, 0, precision_rounding=rounding) > 0: max_quantity_on_quant = quant.quantity - quant.reserved_quantity if float_compare(max_quantity_on_quant, 0, precision_rounding=rounding) <= 0: continue max_quantity_on_quant = min(max_quantity_on_quant, quantity) quant.reserved_quantity += max_quantity_on_quant reserved_quants.append((quant, max_quantity_on_quant)) quantity -= max_quantity_on_quant available_quantity -= max_quantity_on_quant else: max_quantity_on_quant = min(quant.reserved_quantity, abs(quantity)) quant.reserved_quantity -= max_quantity_on_quant reserved_quants.append((quant, -max_quantity_on_quant)) quantity += max_quantity_on_quant available_quantity += max_quantity_on_quant if float_is_zero( quantity, precision_rounding=rounding) or float_is_zero( available_quantity, precision_rounding=rounding): break return reserved_quants
def _import_facturx(self, tree, invoice): """ Decodes a factur-x invoice into an invoice. :param tree: the factur-x tree to decode. :param invoice: the invoice to update or an empty recordset. :returns: the invoice where the factur-x data was imported. """ def _find_value(xpath, element=tree): return self._find_value(xpath, element, tree.nsmap) amount_total_import = None default_move_type = False if invoice._context.get('default_journal_id'): journal = self.env['account.journal'].browse( self.env.context['default_journal_id']) default_move_type = 'out_invoice' if journal.type == 'sale' else 'in_invoice' elif invoice._context.get('default_move_type'): default_move_type = self._context['default_move_type'] elif invoice.move_type in self.env['account.move'].get_invoice_types( include_receipts=True): # in case an attachment is saved on a draft invoice previously created, we might # have lost the default value in context but the type was already set default_move_type = invoice.move_type if not default_move_type: raise UserError( _("No information about the journal or the type of invoice is passed" )) if default_move_type == 'entry': return # Total amount. elements = tree.xpath('//ram:GrandTotalAmount', namespaces=tree.nsmap) total_amount = elements and float(elements[0].text) or 0.0 # Refund type. # There is two modes to handle refund in Factur-X: # a) type_code == 380 for invoice, type_code == 381 for refund, all positive amounts. # b) type_code == 380, negative amounts in case of refund. # To handle both, we consider the 'a' mode and switch to 'b' if a negative amount is encountered. elements = tree.xpath('//rsm:ExchangedDocument/ram:TypeCode', namespaces=tree.nsmap) type_code = elements[0].text default_move_type.replace('_refund', '_invoice') if type_code == '381': default_move_type = 'out_refund' if default_move_type == 'out_invoice' else 'in_refund' refund_sign = -1 else: # Handle 'b' refund mode. if total_amount < 0: default_move_type = 'out_refund' if default_move_type == 'out_invoice' else 'in_refund' refund_sign = -1 if 'refund' in default_move_type else 1 # Write the type as the journal entry is already created. invoice.move_type = default_move_type # self could be a single record (editing) or be empty (new). with Form(invoice.with_context( default_move_type=default_move_type)) as invoice_form: self_ctx = self.with_company(invoice.company_id) # Partner (first step to avoid warning 'Warning! You must first select a partner.'). partner_type = invoice_form.journal_id.type == 'purchase' and 'SellerTradeParty' or 'BuyerTradeParty' invoice_form.partner_id = self_ctx._retrieve_partner( name=self._find_value('//ram:' + partner_type + '/ram:Name', tree, namespaces=tree.nsmap), mail=self._find_value('//ram:' + partner_type + '//ram:URIID[@schemeID=\'SMTP\']', tree, namespaces=tree.nsmap), vat=self._find_value('//ram:' + partner_type + '/ram:SpecifiedTaxRegistration/ram:ID', tree, namespaces=tree.nsmap), ) # Reference. elements = tree.xpath('//rsm:ExchangedDocument/ram:ID', namespaces=tree.nsmap) if elements: invoice_form.ref = elements[0].text # Name. elements = tree.xpath( '//ram:BuyerOrderReferencedDocument/ram:IssuerAssignedID', namespaces=tree.nsmap) if elements: invoice_form.payment_reference = elements[0].text # Comment. elements = tree.xpath('//ram:IncludedNote/ram:Content', namespaces=tree.nsmap) if elements: invoice_form.narration = elements[0].text # Total amount. elements = tree.xpath('//ram:GrandTotalAmount', namespaces=tree.nsmap) if elements: # Currency. if elements[0].attrib.get('currencyID'): currency_str = elements[0].attrib['currencyID'] currency = self.env.ref('base.%s' % currency_str.upper(), raise_if_not_found=False) if currency and not currency.active: error_msg = _( 'The currency (%s) of the document you are uploading is not active in this database.\n' 'Please activate it before trying again to import.', currency.name) error_action = { 'view_mode': 'form', 'res_model': 'res.currency', 'type': 'ir.actions.act_window', 'target': 'new', 'res_id': currency.id, 'views': [[False, 'form']] } raise RedirectWarning(error_msg, error_action, _('Display the currency')) if currency != self.env.company.currency_id and currency.active: invoice_form.currency_id = currency # Store xml total amount. amount_total_import = total_amount * refund_sign # Date. elements = tree.xpath( '//rsm:ExchangedDocument/ram:IssueDateTime/udt:DateTimeString', namespaces=tree.nsmap) if elements: date_str = elements[0].text date_obj = datetime.strptime(date_str, DEFAULT_FACTURX_DATE_FORMAT) invoice_form.invoice_date = date_obj.strftime( DEFAULT_SERVER_DATE_FORMAT) # Due date. elements = tree.xpath( '//ram:SpecifiedTradePaymentTerms/ram:DueDateDateTime/udt:DateTimeString', namespaces=tree.nsmap) if elements: date_str = elements[0].text date_obj = datetime.strptime(date_str, DEFAULT_FACTURX_DATE_FORMAT) invoice_form.invoice_date_due = date_obj.strftime( DEFAULT_SERVER_DATE_FORMAT) # Invoice lines. elements = tree.xpath('//ram:IncludedSupplyChainTradeLineItem', namespaces=tree.nsmap) if elements: for element in elements: with invoice_form.invoice_line_ids.new( ) as invoice_line_form: # Sequence. line_elements = element.xpath( './/ram:AssociatedDocumentLineDocument/ram:LineID', namespaces=tree.nsmap) if line_elements: invoice_line_form.sequence = int( line_elements[0].text) # Product. name = _find_value( './/ram:SpecifiedTradeProduct/ram:Name', element) if name: invoice_line_form.name = name invoice_line_form.product_id = self_ctx._retrieve_product( default_code=_find_value( './/ram:SpecifiedTradeProduct/ram:SellerAssignedID', element), name=_find_value( './/ram:SpecifiedTradeProduct/ram:Name', element), barcode=_find_value( './/ram:SpecifiedTradeProduct/ram:GlobalID', element)) # Quantity. line_elements = element.xpath( './/ram:SpecifiedLineTradeDelivery/ram:BilledQuantity', namespaces=tree.nsmap) if line_elements: invoice_line_form.quantity = float( line_elements[0].text) # Price Unit. line_elements = element.xpath( './/ram:GrossPriceProductTradePrice/ram:ChargeAmount', namespaces=tree.nsmap) if line_elements: quantity_elements = element.xpath( './/ram:GrossPriceProductTradePrice/ram:BasisQuantity', namespaces=tree.nsmap) if quantity_elements: invoice_line_form.price_unit = float( line_elements[0].text) / float( quantity_elements[0].text) else: invoice_line_form.price_unit = float( line_elements[0].text) else: line_elements = element.xpath( './/ram:NetPriceProductTradePrice/ram:ChargeAmount', namespaces=tree.nsmap) if line_elements: quantity_elements = element.xpath( './/ram:NetPriceProductTradePrice/ram:BasisQuantity', namespaces=tree.nsmap) if quantity_elements: invoice_line_form.price_unit = float( line_elements[0].text) / float( quantity_elements[0].text) else: invoice_line_form.price_unit = float( line_elements[0].text) # Discount. line_elements = element.xpath( './/ram:AppliedTradeAllowanceCharge/ram:CalculationPercent', namespaces=tree.nsmap) if line_elements: invoice_line_form.discount = float( line_elements[0].text) # Taxes tax_element = element.xpath( './/ram:SpecifiedLineTradeSettlement/ram:ApplicableTradeTax/ram:RateApplicablePercent', namespaces=tree.nsmap) invoice_line_form.tax_ids.clear() for eline in tax_element: tax = self_ctx._retrieve_tax( amount=eline.text, type_tax_use=invoice_form.journal_id.type) if tax: invoice_line_form.tax_ids.add(tax) elif amount_total_import: # No lines in BASICWL. with invoice_form.invoice_line_ids.new() as invoice_line_form: invoice_line_form.name = invoice_form.comment or '/' invoice_line_form.quantity = 1 invoice_line_form.price_unit = amount_total_import return invoice_form.save()