1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import json
from ast import literal_eval
from collections import defaultdict
from odoo import models
class Project(models.Model):
_inherit = 'project.project'
def _add_purchase_items(self, profitability_items, with_action=True):
domain = self._get_add_purchase_items_domain()
with_action = with_action and (
self.env.user.has_group('account.group_account_invoice')
or self.env.user.has_group('account.group_account_readonly')
)
self._get_costs_items_from_purchase(domain, profitability_items, with_action=with_action)
def _get_add_purchase_items_domain(self):
purchase_order_line_invoice_line_ids = self._get_already_included_profitability_invoice_line_ids()
return [
('move_type', 'in', ['in_invoice', 'in_refund']),
('parent_state', 'in', ['draft', 'posted']),
('price_subtotal', '>', 0),
('id', 'not in', purchase_order_line_invoice_line_ids),
]
def _get_costs_items_from_purchase(self, domain, profitability_items, with_action=True):
""" This method is used in sale_project and project_purchase. Since project_account is the only common module (except project), we create the method here. """
# calculate the cost of bills without a purchase order
account_move_lines = self.env['account.move.line'].sudo().search_fetch(
domain + [('analytic_distribution', 'in', self.account_id.ids)],
['price_subtotal', 'parent_state', 'currency_id', 'analytic_distribution', 'move_type', 'move_id'],
)
if account_move_lines:
# Get conversion rate from currencies to currency of the current company
amount_invoiced = amount_to_invoice = 0.0
for move_line in account_move_lines:
price_subtotal = move_line.currency_id._convert(
from_amount=move_line.price_subtotal, to_currency=self.currency_id,
)
# an analytic account can appear several time in an analytic distribution with different repartition percentage
analytic_contribution = sum(
percentage for ids, percentage in move_line.analytic_distribution.items()
if str(self.account_id.id) in ids.split(',')
) / 100.
if move_line.parent_state == 'draft':
if move_line.move_type == 'in_invoice':
amount_to_invoice -= price_subtotal * analytic_contribution
else: # move_line.move_type == 'in_refund'
amount_to_invoice += price_subtotal * analytic_contribution
else: # move_line.parent_state == 'posted'
if move_line.move_type == 'in_invoice':
amount_invoiced -= price_subtotal * analytic_contribution
else: # move_line.move_type == 'in_refund'
amount_invoiced += price_subtotal * analytic_contribution
# don't display the section if the final values are both 0 (bill -> vendor credit)
if amount_invoiced != 0 or amount_to_invoice != 0:
costs = profitability_items['costs']
section_id = 'other_purchase_costs'
bills_costs = {
'id': section_id,
'sequence': self._get_profitability_sequence_per_invoice_type()[section_id],
'billed': amount_invoiced,
'to_bill': amount_to_invoice,
}
if with_action:
bills_costs['action'] = self._get_action_for_profitability_section(account_move_lines.move_id.ids, section_id)
costs['data'].append(bills_costs)
costs['total']['billed'] += amount_invoiced
costs['total']['to_bill'] += amount_to_invoice
def _get_action_for_profitability_section(self, record_ids, name):
self.ensure_one()
args = [name, [('id', 'in', record_ids)]]
if len(record_ids) == 1:
args.append(record_ids[0])
return {'name': 'action_profitability_items', 'type': 'object', 'args': json.dumps(args)}
def _get_profitability_labels(self):
return {
**super()._get_profitability_labels(),
'other_purchase_costs': self.env._('Vendor Bills'),
'other_revenues_aal': self.env._('Other Revenues'),
'other_costs_aal': self.env._('Other Costs'),
}
def _get_profitability_sequence_per_invoice_type(self):
return {
**super()._get_profitability_sequence_per_invoice_type(),
'other_purchase_costs': 11,
'other_revenues_aal': 14,
'other_costs_aal': 15,
}
def action_profitability_items(self, section_name, domain=None, res_id=False):
if section_name in ['other_revenues_aal', 'other_costs_aal']:
action = self.env["ir.actions.actions"]._for_xml_id("analytic.account_analytic_line_action_entries")
action['domain'] = domain
action['context'] = {
'group_by_date': True,
}
if res_id:
action['views'] = [(False, 'form')]
action['view_mode'] = 'form'
action['res_id'] = res_id
else:
pivot_view_id = self.env['ir.model.data']._xmlid_to_res_id('project_account.project_view_account_analytic_line_pivot')
graph_view_id = self.env['ir.model.data']._xmlid_to_res_id('project_account.project_view_account_analytic_line_graph')
action['views'] = [(pivot_view_id, view_type) if view_type == 'pivot' else (graph_view_id, view_type) if view_type == 'graph' else (view_id, view_type)
for (view_id, view_type) in action['views']]
return action
if section_name == 'other_purchase_costs':
action = self.env["ir.actions.actions"]._for_xml_id("account.action_move_in_invoice_type")
action['domain'] = domain or []
if res_id:
action['views'] = [(False, 'form')]
action['view_mode'] = 'form'
action['res_id'] = res_id
return action
return super().action_profitability_items(section_name, domain, res_id)
def _get_domain_aal_with_no_move_line(self):
""" this method is used in order to overwrite the domain in sale_timesheet module. Since the field 'project_id' is added to the "analytic line" model
in the hr_timesheet module, we can't add the condition ('project_id', '=', False) here. """
return [('account_id', '=', self.account_id.id), ('move_line_id', '=', False), ('category', '!=', 'manufacturing_order')]
def _get_items_from_aal(self, with_action=True):
domain = self._get_domain_aal_with_no_move_line()
aal_other_search = self.env['account.analytic.line'].sudo().search_read(domain, ['id', 'amount', 'currency_id'])
if not aal_other_search:
return {
'revenues': {'data': [], 'total': {'invoiced': 0.0, 'to_invoice': 0.0}},
'costs': {'data': [], 'total': {'billed': 0.0, 'to_bill': 0.0}},
}
# dict of form { company : {costs : float, revenues: float}}
dict_amount_per_currency_id = defaultdict(lambda: {'costs': 0.0, 'revenues': 0.0})
set_currency_ids = {self.currency_id.id}
cost_ids = []
revenue_ids = []
for aal in aal_other_search:
set_currency_ids.add(aal['currency_id'][0])
aal_amount = aal['amount']
if aal_amount < 0.0:
dict_amount_per_currency_id[aal['currency_id'][0]]['costs'] += aal_amount
cost_ids.append(aal['id'])
else:
dict_amount_per_currency_id[aal['currency_id'][0]]['revenues'] += aal_amount
revenue_ids.append(aal['id'])
total_revenues = total_costs = 0.0
for currency_id, dict_amounts in dict_amount_per_currency_id.items():
currency = self.env['res.currency'].browse(currency_id).with_prefetch(dict_amount_per_currency_id)
total_revenues += currency._convert(dict_amounts['revenues'], self.currency_id, self.company_id)
total_costs += currency._convert(dict_amounts['costs'], self.currency_id, self.company_id)
# we dont know what part of the numbers has already been billed or not, so we have no choice but to put everything under the billed/invoiced columns.
# The to bill/to invoice ones will simply remain 0
profitability_sequence_per_invoice_type = self._get_profitability_sequence_per_invoice_type()
revenues = {'id': 'other_revenues_aal', 'sequence': profitability_sequence_per_invoice_type['other_revenues_aal'], 'invoiced': total_revenues, 'to_invoice': 0.0}
costs = {'id': 'other_costs_aal', 'sequence': profitability_sequence_per_invoice_type['other_costs_aal'], 'billed': total_costs, 'to_bill': 0.0}
if with_action and self.env.user.has_group('account.group_account_readonly'):
costs['action'] = self._get_action_for_profitability_section(cost_ids, 'other_costs_aal')
revenues['action'] = self._get_action_for_profitability_section(revenue_ids, 'other_revenues_aal')
return {
'revenues': {'data': [revenues], 'total': {'invoiced': total_revenues, 'to_invoice': 0.0}},
'costs': {'data': [costs], 'total': {'billed': total_costs, 'to_bill': 0.0}},
}
def action_open_analytic_items(self):
action = self.env['ir.actions.act_window']._for_xml_id('analytic.account_analytic_line_action_entries')
action['domain'] = [('account_id', '=', self.account_id.id)]
context = literal_eval(action['context'])
action['context'] = {
**context,
'create': self.env.context.get('from_embedded_action', False),
'default_account_id': self.account_id.id,
}
return action
|