def _get_profitability_values(self, project): costs_revenues = project.analytic_account_id and project.allow_billable timesheets = project.allow_timesheets and self.user_has_groups( 'hr_timesheet.group_hr_timesheet_user') if not (self.user_has_groups('project.group_project_manager') and (costs_revenues or timesheets)): return {} profitability = project._get_profitability_common() return { 'analytic_account_id': project.analytic_account_id, 'costs': format_amount(self.env, -profitability['costs'], self.env.company.currency_id), 'revenues': format_amount(self.env, profitability['revenues'], self.env.company.currency_id), 'margin': profitability['margin'], 'margin_formatted': format_amount(self.env, profitability['margin'], self.env.company.currency_id), 'margin_percentage': formatLang( self.env, not float_utils.float_is_zero(profitability['costs'], precision_digits=2) and -(profitability['margin'] / profitability['costs']) * 100 or 0.0, digits=0), }
def _get_profitability_values(self, project): costs_revenues = project.analytic_account_id and project.allow_billable if not (self.user_has_groups('project.group_project_manager') and costs_revenues): return {} profitability_items = project._get_profitability_items(False) costs = sum(profitability_items['costs']['total'].values()) revenues = sum(profitability_items['revenues']['total'].values()) margin = revenues + costs return { 'analytic_account_id': project.analytic_account_id, 'costs': costs, 'costs_formatted': format_amount(self.env, -costs, project.currency_id), 'revenues': revenues, 'revenues_formatted': format_amount(self.env, revenues, project.currency_id), 'margin': margin, 'margin_formatted': format_amount(self.env, margin, project.currency_id), 'margin_percentage': formatLang( self.env, not float_utils.float_is_zero(costs, precision_digits=2) and (margin / -costs) * 100 or 0.0, digits=0), }
def _get_profitability_items(self): if not self.user_has_groups('project.group_project_manager'): return {'data': []} data = [] if self.allow_billable: profitability = self._get_profitability_common() margin_color = False if not float_is_zero(profitability['margin'], precision_digits=0): margin_color = profitability['margin'] > 0 and 'green' or 'red' data += [{ 'name': _("Revenues"), 'value': format_amount(self.env, profitability['revenues'], self.env.company.currency_id) }, { 'name': _("Costs"), 'value': format_amount(self.env, profitability['costs'], self.env.company.currency_id) }, { 'name': _("Margin"), 'color': margin_color, 'value': format_amount(self.env, profitability['margin'], self.env.company.currency_id) }] return { 'action': self.allow_billable and self.allow_timesheets and "action_view_timesheet", 'allow_billable': self.allow_billable, 'data': data, }
def _get_profitability_items(self): if not self.user_has_groups('project.group_project_manager'): return {'data': []} profitability = self.get_profitability_common() data = [] if self.allow_timesheets and self.user_has_groups('hr_timesheet.group_hr_timesheet_user'): data += [{ 'name': _("Timesheets"), 'value': self.env.ref('sale_timesheet.project_profitability_timesheet_panel')._render({ 'timesheet_unit_amount': float_round(profitability['timesheet_unit_amount'], precision_digits=2), 'timesheet_uom': self.env.company._timesheet_uom_text(), 'is_timesheet_uom_hour': self.env.company._is_timesheet_hour_uom(), 'percentage_billable': formatLang(self.env, profitability['timesheet_percentage_billable'], digits=0), }, engine='ir.qweb'), }] if self.allow_billable: margin_color = False if not float_is_zero(profitability['margin'], precision_digits=0): margin_color = profitability['margin'] > 0 and 'green' or 'red' data += [{ 'name': _("Revenues"), 'value': format_amount(self.env, profitability['revenues'], self.env.company.currency_id) }, { 'name': _("Costs"), 'value': format_amount(self.env, profitability['costs'], self.env.company.currency_id) }, { 'name': _("Margin"), 'color': margin_color, 'value': format_amount(self.env, profitability['margin'], self.env.company.currency_id) }] return { 'action': self.allow_billable and self.allow_timesheets and "action_view_timesheet", 'allow_billable': self.allow_billable, 'data': data, }
def _compute_rule_tip(self): base_selection_vals = { elem[0]: elem[1] for elem in self._fields['base']._description_selection(self.env) } self.rule_tip = False for item in self: if item.compute_price != 'formula': continue base_amount = 100 discount_factor = (100 - item.price_discount) / 100 discounted_price = base_amount * discount_factor if item.price_round: discounted_price = tools.float_round( discounted_price, precision_rounding=item.price_round) surcharge = tools.format_amount(item.env, item.price_surcharge, item.currency_id) item.rule_tip = _( "%(base)s with a %(discount)s %% discount and %(surcharge)s extra fee\n" "Example: %(amount)s * %(discount_charge)s + %(price_surcharge)s → %(total_amount)s", base=base_selection_vals[item.base], discount=item.price_discount, surcharge=surcharge, amount=tools.format_amount(item.env, 100, item.currency_id), discount_charge=discount_factor, price_surcharge=surcharge, total_amount=tools.format_amount( item.env, discounted_price + item.price_surcharge, item.currency_id), )
def _onchange_partner_invoice_id(self): for so in self: partner = so.partner_invoice_id.commercial_partner_id if partner.credit_limit and partner.credit_limit <= partner.credit: m = 'Partner outstanding receivables %s is above their credit limit of %s' \ % (tools.format_amount(self.env, partner.credit, so.currency_id), tools.format_amount(self.env, partner.credit_limit, so.currency_id)) return { 'warning': { 'title': 'Sale Credit Limit', 'message': m } }
def _compute_tax_string(self): for record in self: currency = record.currency_id res = record.taxes_id.compute_all(record.list_price) joined = [] included = res['total_included'] if currency.compare_amounts(included, record.list_price): joined.append(_('%s Incl. Taxes', format_amount(self.env, included, currency))) excluded = res['total_excluded'] if currency.compare_amounts(excluded, record.list_price): joined.append(_('%s Excl. Taxes', format_amount(self.env, excluded, currency))) if joined: record.tax_string = f"(= {', '.join(joined)})" else: record.tax_string = " "
def _get_common_eval_context(self): """ Evaluation context used in all rendering engines. Contains * ``user``: current user browse record; * ``ctx```: current context; * various formatting tools; """ return { 'format_date': lambda date, date_format=False, lang_code=False: format_date( self.env, date, date_format, lang_code), 'format_datetime': lambda dt, tz=False, dt_format=False, lang_code=False: format_datetime(self.env, dt, tz, dt_format, lang_code), 'format_time': lambda time, tz=False, time_format=False, lang_code=False: format_time(self.env, time, tz, time_format, lang_code), 'format_amount': lambda amount, currency, lang_code=False: tools.format_amount( self.env, amount, currency, lang_code), 'format_duration': lambda value: tools.format_duration(value), 'user': self.env.user, 'ctx': self._context, }
def _render_eval_context(self): """ Evaluation context used in all rendering engines. Contains * ``user``: current user browse record; * ``ctx```: current context; * various formatting tools; """ render_context = { 'format_date': lambda date, date_format=False, lang_code=False: format_date( self.env, date, date_format, lang_code), 'format_datetime': lambda dt, tz=False, dt_format=False, lang_code=False: format_datetime(self.env, dt, tz, dt_format, lang_code), 'format_time': lambda time, tz=False, time_format=False, lang_code=False: format_time(self.env, time, tz, time_format, lang_code), 'format_amount': lambda amount, currency, lang_code=False: tools.format_amount( self.env, amount, currency, lang_code), 'format_duration': lambda value: tools.format_duration(value), 'user': self.env.user, 'ctx': self._context, 'is_html_empty': is_html_empty, } render_context.update(copy.copy(template_env_globals)) return render_context
def _render_jinja_eval_context(self): """ Prepare jinja evaluation context, containing for all rendering * ``user``: current user browse record; * ``ctx```: current context, named ctx to avoid clash with jinja internals that already uses context; * various formatting tools; """ render_context = { 'format_date': lambda date, date_format=False, lang_code=False: format_date( self.env, date, date_format, lang_code), 'format_datetime': lambda dt, tz=False, dt_format=False, lang_code=False: format_datetime(self.env, dt, tz, dt_format, lang_code), 'format_amount': lambda amount, currency, lang_code=False: tools.format_amount( self.env, amount, currency, lang_code), 'format_duration': lambda value: tools.format_duration(value), 'user': self.env.user, 'ctx': self._context, } return render_context
def _get_sent_message(self): """ Return the message stating that the transaction has been requested. Note: self.ensure_one() :return: The 'transaction sent' message :rtype: str """ self.ensure_one() # Choose the message based on the payment flow if self.operation in ('online_redirect', 'online_direct'): message = _( "A transaction with reference %(ref)s has been initiated (%(acq_name)s).", ref=self.reference, acq_name=self.acquirer_id.name ) elif self.operation == 'refund': formatted_amount = format_amount(self.env, -self.amount, self.currency_id) message = _( "A refund request of %(amount)s has been sent. The payment will be created soon. " "Refund transaction reference: %(ref)s (%(acq_name)s).", amount=formatted_amount, ref=self.reference, acq_name=self.acquirer_id.name ) else: # 'online_token' message = _( "A transaction with reference %(ref)s has been initiated using the payment method " "%(token_name)s (%(acq_name)s).", ref=self.reference, token_name=self.token_id.name, acq_name=self.acquirer_id.name ) return message
def name_get(self): res = super().name_get() with_price_unit = self.env.context.get('with_price_unit') if with_price_unit: names = dict(res) result = [] sols_by_so_dict = defaultdict( lambda: self.env[self._name] ) # key: (sale_order_id, product_id), value: sale order line for line in self: sols_by_so_dict[line.order_id.id, line.product_id.id] += line for sols in sols_by_so_dict.values(): if len(sols) > 1 and all(sols.mapped('is_service')): result += [ (line.id, '%s - %s' % (names.get(line.id), format_amount(self.env, line.price_unit, line.currency_id))) for line in sols ] else: result += [(line.id, names.get(line.id)) for line in sols] return result return res
def name_get(self): res = super(SaleOrderLine, self).name_get() with_remaining_hours = self.env.context.get('with_remaining_hours') with_price_unit = self.env.context.get('with_price_unit') if with_remaining_hours or with_price_unit: names = dict(res) result = [] uom_hour = with_remaining_hours and self.env.ref('uom.product_uom_hour') uom_day = with_remaining_hours and self.env.ref('uom.product_uom_day') sols_by_so_dict = with_price_unit and defaultdict(lambda: self.env[self._name]) # key: (sale_order_id, product_id), value: sale order line for line in self: if with_remaining_hours: name = names.get(line.id) if line.remaining_hours_available: company = self.env.company encoding_uom = company.timesheet_encode_uom_id remaining_time = '' if encoding_uom == uom_hour: hours, minutes = divmod(abs(line.remaining_hours) * 60, 60) round_minutes = minutes / 30 minutes = math.ceil(round_minutes) if line.remaining_hours >= 0 else math.floor(round_minutes) if minutes > 1: minutes = 0 hours += 1 else: minutes = minutes * 30 remaining_time = ' ({sign}{hours:02.0f}:{minutes:02.0f})'.format( sign='-' if line.remaining_hours < 0 else '', hours=hours, minutes=minutes) elif encoding_uom == uom_day: remaining_days = company.project_time_mode_id._compute_quantity(line.remaining_hours, encoding_uom, round=False) remaining_time = ' ({qty:.02f} {unit})'.format( qty=remaining_days, unit=_('days') if abs(remaining_days) > 1 else _('day') ) name = '{name}{remaining_time}'.format( name=name, remaining_time=remaining_time ) if with_price_unit: names[line.id] = name if not with_price_unit: result.append((line.id, name)) if with_price_unit: sols_by_so_dict[line.order_id.id, line.product_id.id] += line if with_price_unit: for sols in sols_by_so_dict.values(): if len(sols) > 1: result += [( line.id, '%s - %s' % ( names.get(line.id), format_amount(self.env, line.price_unit, line.currency_id)) ) for line in sols] else: result.append((sols.id, names.get(sols.id))) return result return res
def _render_template(self, template_txt, model, res_ids, post_process=False): """ Render the given template text, replace mako expressions ``${expr}`` with the result of evaluating these expressions with an evaluation context containing: - ``user``: Model of the current user - ``object``: record of the document record this mail is related to - ``context``: the context passed to the mail composition wizard :param str template_txt: the template text to render :param str model: model name of the document record this mail is related to. :param int res_ids: list of ids of document records those mails are related to. """ multi_mode = True if isinstance(res_ids, int): multi_mode = False res_ids = [res_ids] results = dict.fromkeys(res_ids, u"") # try to load the template try: mako_env = mako_safe_template_env if self.env.context.get('safe') else mako_template_env template = mako_env.from_string(tools.ustr(template_txt)) except Exception: _logger.info("Failed to load template %r", template_txt, exc_info=True) return multi_mode and results or results[res_ids[0]] # prepare template variables records = self.env[model].browse(it for it in res_ids if it) # filter to avoid browsing [None] res_to_rec = dict.fromkeys(res_ids, None) for record in records: res_to_rec[record.id] = record variables = { 'format_date': lambda date, date_format=False, lang_code=False: format_date(self.env, date, date_format, lang_code), 'format_datetime': lambda dt, tz=False, dt_format=False, lang_code=False: format_datetime(self.env, dt, tz, dt_format, lang_code), 'format_amount': lambda amount, currency, lang_code=False: tools.format_amount(self.env, amount, currency, lang_code), 'format_duration': lambda value: tools.format_duration(value), 'user': self.env.user, 'ctx': self._context, # context kw would clash with mako internals } for res_id, record in res_to_rec.items(): variables['object'] = record try: render_result = template.render(variables) except Exception as e: _logger.info("Failed to render template %r using values %r" % (template, variables), exc_info=True) raise UserError(_("Failed to render template %r using values %r") % (template, variables) + "\n\n%s: %s" % (type(e).__name__, str(e))) if render_result == u"False": render_result = u"" results[res_id] = render_result if post_process: for res_id, result in results.items(): results[res_id] = self.render_post_process(result) return multi_mode and results or results[res_ids[0]]
def _check_amount_and_confirm_order(self): """ Confirm the sales order based on the amount of a transaction. Confirm the sales orders only if the transaction amount is equal to the total amount of the sales orders. Neither partial payments nor grouped payments (paying multiple sales orders in one transaction) are not supported. :return: The confirmed sales orders. :rtype: a `sale.order` recordset """ confirmed_orders = self.env['sale.order'] for tx in self: # We only support the flow where exactly one quotation is linked to a transaction and # vice versa. if len(tx.sale_order_ids) == 1: quotation = tx.sale_order_ids.filtered(lambda so: so.state in ('draft', 'sent')) if quotation and len(quotation.transaction_ids) == 1: # Check if the SO is fully paid if quotation.currency_id.compare_amounts( tx.amount, quotation.amount_total) == 0: quotation.with_context( send_email=True).action_confirm() confirmed_orders |= quotation else: _logger.warning( '<%(provider)s> transaction AMOUNT MISMATCH for order %(so_name)s ' '(ID %(so_id)s): expected %(so_amount)s, got %(tx_amount)s', { 'provider': tx.provider, 'so_name': quotation.name, 'so_id': quotation.id, 'so_amount': format_amount(quotation.env, quotation.amount_total, quotation.currency_id), 'tx_amount': format_amount(tx.env, tx.amount, tx.currency_id), }, ) return confirmed_orders
def _construct_tax_string(self, price): currency = self.currency_id res = self.taxes_id.compute_all(price, product=self, partner=self.env['res.partner']) joined = [] included = res['total_included'] if currency.compare_amounts(included, price): joined.append( _('%s Incl. Taxes', format_amount(self.env, included, currency))) excluded = res['total_excluded'] if currency.compare_amounts(excluded, price): joined.append( _('%s Excl. Taxes', format_amount(self.env, excluded, currency))) if joined: tax_string = f"(= {', '.join(joined)})" else: tax_string = " " return tax_string
def _get_received_message(self): """ Return the message stating that the transaction has been received by the provider. Note: self.ensure_one() """ self.ensure_one() formatted_amount = format_amount(self.env, self.amount, self.currency_id) if self.state == 'pending': message = _( "The transaction with reference %(ref)s for %(amount)s is pending (%(acq_name)s).", ref=self.reference, amount=formatted_amount, acq_name=self.acquirer_id.name ) elif self.state == 'authorized': message = _( "The transaction with reference %(ref)s for %(amount)s has been authorized " "(%(acq_name)s).", ref=self.reference, amount=formatted_amount, acq_name=self.acquirer_id.name ) elif self.state == 'done': message = _( "The transaction with reference %(ref)s for %(amount)s has been confirmed " "(%(acq_name)s).", ref=self.reference, amount=formatted_amount, acq_name=self.acquirer_id.name ) if self.payment_id: message += "<br />" + _( "The related payment is posted: %s", self.payment_id._get_payment_chatter_link() ) elif self.state == 'error': message = _( "The transaction with reference %(ref)s for %(amount)s encountered an error" " (%(acq_name)s).", ref=self.reference, amount=formatted_amount, acq_name=self.acquirer_id.name ) if self.state_message: message += "<br />" + _("Error: %s", self.state_message) else: message = _( "The transaction with reference %(ref)s for %(amount)s is canceled (%(acq_name)s).", ref=self.reference, amount=formatted_amount, acq_name=self.acquirer_id.name ) if self.state_message: message += "<br />" + _("Reason: %s", self.state_message) return message
def get_account_balance(self): if not self.user_has_groups('base.group_erp_manager'): raise AccessError(_("You can't access account balance.")) if not self.env.company.adyen_account_id: return {} balance_fields = {'balance': 'balance', 'onHoldBalance': 'on_hold', 'pendingBalance': 'pending'} balances = self.env['adyen.account.balance'].sudo().search([ ('adyen_account_id', '=', self.env.company.adyen_account_id.id) ]) delta = fields.Datetime.now() - timedelta(hours=1) if not balances or any(b.write_date <= delta for b in balances): response = {} try: response = self.env.company.adyen_account_id._adyen_rpc('v1/account_holder_balance', { 'accountHolderCode': self.env.company.adyen_account_id.account_holder_code, }) except UserError as e: _logger.warning(_('Cannot update account balance, showing previous values: %s', e)) balances.write({ f: 0 for f in balance_fields.values() }) for total_balance, adyen_balances in response.get('totalBalance', {}).items(): for balance in adyen_balances: currency_id = self.env['res.currency'].search([('name', '=', balance.get('currency'))]) bal = balances.filtered(lambda b: b.currency_id == currency_id) if not bal: bal = self.env['adyen.account.balance'].sudo().create({ 'adyen_account_id': self.env.company.adyen_account_id.id, 'currency_id': currency_id.id, }) balances |= bal bal[balance_fields.get(total_balance)] = to_major_currency(balance.get('value', 0), currency_id.decimal_places) warning_delta = fields.Datetime.now() - timedelta(hours=2) return [{ 'currency': b.currency_id.name, 'balance': format_amount(self.env, b.balance, b.currency_id), 'payout_date': format_datetime(self.env, self.env.company.adyen_account_id.next_scheduled_payout, dt_format='short'), 'last_update_warning': b.write_date <= warning_delta, 'last_update': format_datetime(self.env, b.write_date), } for b in balances]
def _get_profitability_values(self, project): costs_revenues = project.analytic_account_id and project.allow_billable timesheets = project.allow_timesheets and self.user_has_groups( 'hr_timesheet.group_hr_timesheet_user') if not (self.user_has_groups('project.group_project_manager') and (costs_revenues or timesheets)): return {} this_month = fields.Date.context_today(self) + relativedelta(day=1) previous_month = this_month + relativedelta(months=-1, day=1) result = { 'allow_timesheets': timesheets, 'allow_costs_and_revenues': costs_revenues, 'analytic_account_id': project.analytic_account_id, 'month': format_date(self.env, this_month, date_format='LLLL y'), 'previous_month': format_date(self.env, previous_month, date_format='LLLL'), 'is_timesheet_uom_hour': self.env.company._is_timesheet_hour_uom(), 'timesheet_uom': self.env.company._timesheet_uom_text(), 'timesheet_unit_amount': '', 'previous_timesheet_unit_amount': '', 'timesheet_trend': '', 'costs': '', 'revenues': '', 'margin': '', 'margin_formatted': '', 'margin_percentage': '', 'billing_rate': '', } if timesheets: timesheets_per_month = self.env[ 'account.analytic.line'].read_group( [('project_id', '=', project.id), ('date', '>=', previous_month)], ['date', 'unit_amount'], ['date:month']) timesheet_unit_amount = { ts['__range']['date']['from']: ts['unit_amount'] for ts in timesheets_per_month } this_amount = timesheet_unit_amount.get( this_month.replace(day=1).strftime(DEFAULT_SERVER_DATE_FORMAT), 0.0) previous_amount = timesheet_unit_amount.get( previous_month.replace( day=1).strftime(DEFAULT_SERVER_DATE_FORMAT), 0.0) result.update({ 'timesheet_unit_amount': formatLang( self.env, project._convert_project_uom_to_timesheet_encode_uom( this_amount), digits=0), 'previous_timesheet_unit_amount': formatLang( self.env, project._convert_project_uom_to_timesheet_encode_uom( previous_amount), digits=0), 'timesheet_trend': formatLang(self.env, previous_amount > 0 and ((this_amount / previous_amount) - 1) * 100 or 0.0, digits=0), }) if costs_revenues: profitability = project._get_profitability_common( costs_revenues=True, timesheets=False) result.update({ 'costs': format_amount(self.env, -profitability['costs'], self.env.company.currency_id), 'revenues': format_amount(self.env, profitability['revenues'], self.env.company.currency_id), 'margin': profitability['margin'], 'margin_formatted': format_amount(self.env, profitability['margin'], self.env.company.currency_id), 'margin_percentage': formatLang( self.env, not float_utils.float_is_zero(profitability['costs'], precision_digits=2) and -(profitability['margin'] / profitability['costs']) * 100 or 0.0, digits=0), 'billing_rate': formatLang( self.env, not float_utils.float_is_zero(profitability['costs'], precision_digits=2) and -(profitability['revenues'] / profitability['costs']) * 100 or 0.0, digits=0), }) return result
def _get_profitability_values(self, project): if not (self.user_has_groups('project.group_project_manager') and (project.analytic_account_id and project.allow_billable or project.allow_timesheets)): return {} profitability = project.get_profitability_common() start_of_month = fields.Date.context_today(self) + relativedelta(day=1) timesheets_this_month = self.env[ 'project.profitability.report'].read_group( [('project_id', '=', project.id), ('line_date', '>=', start_of_month)], ['project_id', 'timesheet_unit_amount'], ['project_id']) timesheets_previous_month = self.env[ 'project.profitability.report'].read_group( [('project_id', '=', project.id), ('line_date', '>=', start_of_month + relativedelta(months=-1, day=1)), ('line_date', '<', start_of_month)], ['project_id', 'timesheet_unit_amount'], ['project_id']) timesheet_unit_amount = timesheets_this_month and timesheets_this_month[ 0]['timesheet_unit_amount'] or 0.0 previous_timesheet_unit_amount = timesheets_previous_month and timesheets_previous_month[ 0]['timesheet_unit_amount'] or 0.0 return { 'allow_timesheets': project.allow_timesheets, 'analytic_account_id': project.analytic_account_id, 'month': start_of_month.strftime('%B %Y'), 'previous_month': (start_of_month + relativedelta(months=-1, day=1)).strftime('%B'), 'is_timesheet_uom_hour': self.env.company._is_timesheet_hour_uom(), 'timesheet_uom': self.env.company._timesheet_uom_text(), 'timesheet_unit_amount': formatLang(self.env, project._convert_project_uom_to_timesheet_encode_uom( timesheet_unit_amount), digits=0), 'previous_timesheet_unit_amount': formatLang(self.env, project._convert_project_uom_to_timesheet_encode_uom( previous_timesheet_unit_amount), digits=0), 'timesheet_trend': formatLang( self.env, previous_timesheet_unit_amount > 0 and ((timesheet_unit_amount / previous_timesheet_unit_amount) - 1) * 100 or 0.0, digits=0), 'costs': format_amount(self.env, -profitability['costs'], self.env.company.currency_id), 'revenues': format_amount(self.env, profitability['revenues'], self.env.company.currency_id), 'margin': profitability['margin'], 'margin_formatted': format_amount(self.env, profitability['margin'], self.env.company.currency_id), 'margin_percentage': formatLang( self.env, not float_utils.float_is_zero(profitability['costs'], precision_digits=2) and -(profitability['margin'] / profitability['costs']) * 100 or 0.0, digits=0), 'billing_rate': formatLang( self.env, not float_utils.float_is_zero(profitability['costs'], precision_digits=2) and -(profitability['revenues'] / profitability['costs']) * 100 or 0.0, digits=0), }