mirror of
https://gitlab.com/flectra-hq/flectra.git
synced 2026-08-19 01:34:43 -05:00
Merge branch 'master-upstream-patch-16102021' into 'master'
[PATCH] Upstream patch - 16102021 See merge request flectra-hq/flectra!463
This commit is contained in:
@@ -4,13 +4,13 @@
|
||||
|
||||
<!-- Account Tax Group -->
|
||||
<record id="tax_group_0" model="account.tax.group">
|
||||
<field name="name">Thuế GTGT 0%</field>
|
||||
<field name="name">VAT 0%</field>
|
||||
</record>
|
||||
<record id="tax_group_5" model="account.tax.group">
|
||||
<field name="name">Thuế GTGT 5%</field>
|
||||
<field name="name">VAT 5%</field>
|
||||
</record>
|
||||
<record id="tax_group_10" model="account.tax.group">
|
||||
<field name="name">Thuế GTGT 10%</field>
|
||||
<field name="name">VAT 10%</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<!-- for purchase -->
|
||||
<record id="tax_purchase_vat10" model="account.tax.template">
|
||||
<field name="chart_template_id" ref="vn_template"/>
|
||||
<field name="name">Thuế GTGT được khấu trừ 10%</field>
|
||||
<field name="description">Thuế GTGT được khấu trừ 10%</field>
|
||||
<field name="name">Deductible VAT 10%</field>
|
||||
<field name="description">Deductible VAT 10%</field>
|
||||
<field name="amount" eval="10"/>
|
||||
<field name="amount_type">percent</field>
|
||||
<field name="type_tax_use">purchase</field>
|
||||
@@ -39,8 +39,8 @@
|
||||
</record>
|
||||
<record id="tax_purchase_vat5" model="account.tax.template">
|
||||
<field name="chart_template_id" ref="vn_template"/>
|
||||
<field name="name">Thuế GTGT được khấu trừ 5%</field>
|
||||
<field name="description">Thuế GTGT được khấu trừ 5%</field>
|
||||
<field name="name">Deductible VAT 5%</field>
|
||||
<field name="description">Deductible VAT 5%</field>
|
||||
<field name="amount" eval="5"/>
|
||||
<field name="amount_type">percent</field>
|
||||
<field name="type_tax_use">purchase</field>
|
||||
@@ -74,8 +74,8 @@
|
||||
</record>
|
||||
<record id="tax_purchase_vat0" model="account.tax.template">
|
||||
<field name="chart_template_id" ref="vn_template"/>
|
||||
<field name="name">Thuế GTGT được khấu trừ 0%</field>
|
||||
<field name="description">Thuế GTGT được khấu trừ 0%</field>
|
||||
<field name="name">Deductible VAT 0%</field>
|
||||
<field name="description">Deductible VAT 0%</field>
|
||||
<field name="amount" eval="0"/>
|
||||
<field name="amount_type">percent</field>
|
||||
<field name="type_tax_use">purchase</field>
|
||||
@@ -107,8 +107,8 @@
|
||||
<!-- for sale -->
|
||||
<record id="tax_sale_vat10" model="account.tax.template">
|
||||
<field name="chart_template_id" ref="vn_template"/>
|
||||
<field name="name">Thuế GTGT phải nộp 10%</field>
|
||||
<field name="description">Thuế GTGT phải nộp 10%</field>
|
||||
<field name="name">Value Added Tax (VAT) 10%</field>
|
||||
<field name="description">Value Added Tax (VAT) 10%</field>
|
||||
<field name="amount" eval="10"/>
|
||||
<field name="amount_type">percent</field>
|
||||
<field name="type_tax_use">sale</field>
|
||||
@@ -142,8 +142,8 @@
|
||||
</record>
|
||||
<record id="tax_sale_vat5" model="account.tax.template">
|
||||
<field name="chart_template_id" ref="vn_template"/>
|
||||
<field name="name">Thuế GTGT phải nộp 5%</field>
|
||||
<field name="description">Thuế GTGT phải nộp 5%</field>
|
||||
<field name="name">Value Added Tax (VAT) 5%</field>
|
||||
<field name="description">Value Added Tax (VAT) 5%</field>
|
||||
<field name="amount" eval="5"/>
|
||||
<field name="amount_type">percent</field>
|
||||
<field name="type_tax_use">sale</field>
|
||||
@@ -177,8 +177,8 @@
|
||||
</record>
|
||||
<record id="tax_sale_vat0" model="account.tax.template">
|
||||
<field name="chart_template_id" ref="vn_template"/>
|
||||
<field name="name">Thuế GTGT phải nộp 0%</field>
|
||||
<field name="description">Thuế GTGT phải nộp 0%</field>
|
||||
<field name="name">Value Added Tax (VAT) 0%</field>
|
||||
<field name="description">Value Added Tax (VAT) 0%</field>
|
||||
<field name="amount" eval="0"/>
|
||||
<field name="amount_type">percent</field>
|
||||
<field name="type_tax_use">sale</field>
|
||||
|
||||
@@ -7,136 +7,136 @@
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_01_vn" model="account.tax.report.line">
|
||||
<field name="name">HHDV mua vào</field>
|
||||
<field name="name">Purchase of Goods and Services</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="1"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_01_01_vn" model="account.tax.report.line">
|
||||
<field name="name">Thuế GTGT HHDV mua vào</field>
|
||||
<field name="name">VAT on purchase of goods and services</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="parent_id" ref="account_tax_report_line_01_vn"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_01_01_01_vn" model="account.tax.report.line">
|
||||
<field name="name">Thuế GTGT HHDV mua vào chịu thuế 0%</field>
|
||||
<field name="tag_name">Thuế GTGT HHDV mua vào chịu thuế 0%</field>
|
||||
<field name="name">VAT on purchase of goods and services 0%</field>
|
||||
<field name="tag_name">VAT on purchase of goods and services 0%</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="parent_id" ref="account_tax_report_line_01_01_vn"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_02_01_01_vn" model="account.tax.report.line">
|
||||
<field name="name">Thuế GTGT HHDV mua vào chịu thuế 5%</field>
|
||||
<field name="tag_name">Thuế GTGT HHDV mua vào chịu thuế 5%</field>
|
||||
<field name="name">VAT on purchase of goods and services 5%</field>
|
||||
<field name="tag_name">VAT on purchase of goods and services 5%</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="2"/>
|
||||
<field name="parent_id" ref="account_tax_report_line_01_01_vn"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_03_01_01_vn" model="account.tax.report.line">
|
||||
<field name="name">Thuế GTGT HHDV mua vào chịu thuế 10%</field>
|
||||
<field name="tag_name">Thuế GTGT HHDV mua vào chịu thuế 10%</field>
|
||||
<field name="name">VAT on purchase of goods and services 10%</field>
|
||||
<field name="tag_name">VAT on purchase of goods and services 10%</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="3"/>
|
||||
<field name="parent_id" ref="account_tax_report_line_01_01_vn"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_02_01_vn" model="account.tax.report.line">
|
||||
<field name="name">Giá trị HHDV mua vào</field>
|
||||
<field name="name">Untaxed Purchase of Goods and Services</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="2"/>
|
||||
<field name="parent_id" ref="account_tax_report_line_01_vn"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_01_02_01_vn" model="account.tax.report.line">
|
||||
<field name="name">Giá trị HHDV mua vào chịu thuế 0%</field>
|
||||
<field name="tag_name">Giá trị HHDV mua vào chịu thuế 0%</field>
|
||||
<field name="name">Untaxed Purchase of Goods and Services taxed 0%</field>
|
||||
<field name="tag_name">Untaxed Purchase of Goods and Services taxed 0%</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="parent_id" ref="account_tax_report_line_02_01_vn"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_02_02_01_vn" model="account.tax.report.line">
|
||||
<field name="name">Giá trị HHDV mua vào chịu thuế 5%</field>
|
||||
<field name="tag_name">Giá trị HHDV mua vào chịu thuế 5%</field>
|
||||
<field name="name">Untaxed Purchase of Goods and Services taxed 5%</field>
|
||||
<field name="tag_name">Untaxed Purchase of Goods and Services taxed 5%</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="2"/>
|
||||
<field name="parent_id" ref="account_tax_report_line_02_01_vn"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_03_02_01_vn" model="account.tax.report.line">
|
||||
<field name="name">Giá trị HHDV mua vào chịu thuế 10%</field>
|
||||
<field name="tag_name">Giá trị HHDV mua vào chịu thuế 10%</field>
|
||||
<field name="name">Untaxed Purchase of Goods and Services taxed 10%</field>
|
||||
<field name="tag_name">Untaxed Purchase of Goods and Services taxed 10%</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="3"/>
|
||||
<field name="parent_id" ref="account_tax_report_line_02_01_vn"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_02_vn" model="account.tax.report.line">
|
||||
<field name="name">HHDV bán ra</field>
|
||||
<field name="name">Sales of Goods and Services</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="2"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_01_02_vn" model="account.tax.report.line">
|
||||
<field name="name">Thuế GTGT HHDV bán ra</field>
|
||||
<field name="name">VAT on sales of goods and services</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="parent_id" ref="account_tax_report_line_02_vn"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_01_01_02_vn" model="account.tax.report.line">
|
||||
<field name="name">Thuế GTGT HHDV bán ra chịu thuế 0%</field>
|
||||
<field name="tag_name">Thuế GTGT HHDV bán ra chịu thuế 0%</field>
|
||||
<field name="name">VAT on sales of goods and services 0%</field>
|
||||
<field name="tag_name">VAT on sales of goods and services 0%</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="parent_id" ref="account_tax_report_line_01_02_vn"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_02_01_02_vn" model="account.tax.report.line">
|
||||
<field name="name">Thuế GTGT HHDV bán ra chịu thuế 5%</field>
|
||||
<field name="tag_name">Thuế GTGT HHDV bán ra chịu thuế 5%</field>
|
||||
<field name="name">VAT on sales of goods and services 5%</field>
|
||||
<field name="tag_name">VAT on sales of goods and services 5%</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="2"/>
|
||||
<field name="parent_id" ref="account_tax_report_line_01_02_vn"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_03_01_02_vn" model="account.tax.report.line">
|
||||
<field name="name">Thuế GTGT HHDV bán ra chịu thuế 10%</field>
|
||||
<field name="tag_name">Thuế GTGT HHDV bán ra chịu thuế 10%</field>
|
||||
<field name="name">VAT on sales of goods and services 10%</field>
|
||||
<field name="tag_name">VAT on sales of goods and services 10%</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="3"/>
|
||||
<field name="parent_id" ref="account_tax_report_line_01_02_vn"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_02_02_vn" model="account.tax.report.line">
|
||||
<field name="name">Giá trị HHDV bán ra</field>
|
||||
<field name="name">Untaxed Sales of Goods and Services</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="2"/>
|
||||
<field name="parent_id" ref="account_tax_report_line_02_vn"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_01_02_02_vn" model="account.tax.report.line">
|
||||
<field name="name">Giá trị HHDV bán ra chịu thuế 0%</field>
|
||||
<field name="tag_name">Giá trị HHDV bán ra chịu thuế 0%</field>
|
||||
<field name="name">Untaxed sales of goods and services taxed 0%</field>
|
||||
<field name="tag_name">Untaxed sales of goods and services taxed 0%</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="parent_id" ref="account_tax_report_line_02_02_vn"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_02_02_02_vn" model="account.tax.report.line">
|
||||
<field name="name">Giá trị HHDV bán ra chịu thuế 5%</field>
|
||||
<field name="tag_name">Giá trị HHDV bán ra chịu thuế 5%</field>
|
||||
<field name="name">Untaxed sales of goods and services taxed 5%</field>
|
||||
<field name="tag_name">Untaxed sales of goods and services taxed 5%</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="2"/>
|
||||
<field name="parent_id" ref="account_tax_report_line_02_02_vn"/>
|
||||
</record>
|
||||
|
||||
<record id="account_tax_report_line_03_02_02_vn" model="account.tax.report.line">
|
||||
<field name="name">Giá trị HHDV bán ra chịu thuế 10%</field>
|
||||
<field name="tag_name">Giá trị HHDV bán ra chịu thuế 10%</field>
|
||||
<field name="name">Untaxed sales of goods and services taxed 10%</field>
|
||||
<field name="tag_name">Untaxed sales of goods and services taxed 10%</field>
|
||||
<field name="report_id" ref="tax_report"/>
|
||||
<field name="sequence" eval="3"/>
|
||||
<field name="parent_id" ref="account_tax_report_line_02_02_vn"/>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+376
-179
File diff suppressed because it is too large
Load Diff
@@ -395,7 +395,7 @@ class StockMove(models.Model):
|
||||
# Then, we collect every relevant moves related to a specific component
|
||||
# to know how many are considered delivered.
|
||||
uom_qty_per_kit = bom_line_data['qty'] / bom_line_data['original_qty']
|
||||
qty_per_kit = bom_line.product_uom_id._compute_quantity(uom_qty_per_kit, bom_line.product_id.uom_id)
|
||||
qty_per_kit = bom_line.product_uom_id._compute_quantity(uom_qty_per_kit, bom_line.product_id.uom_id, round=False)
|
||||
if not qty_per_kit:
|
||||
continue
|
||||
incoming_moves = bom_line_moves.filtered(filters['incoming_moves'])
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from random import randint
|
||||
from datetime import datetime
|
||||
|
||||
from flectra import fields, tools
|
||||
from flectra.addons.stock_account.tests.test_anglo_saxon_valuation_reconciliation_common import ValuationReconciliationTestCommon
|
||||
@@ -272,6 +273,7 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
|
||||
cls.env['res.currency.rate'].create({
|
||||
'rate': 0.5,
|
||||
'currency_id': cls.other_currency.id,
|
||||
'name': datetime.today().date(),
|
||||
})
|
||||
other_cash_journal = cls.env['account.journal'].create({
|
||||
'name': 'Cash Other',
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
<attribute name="invisible">0</attribute>
|
||||
</xpath>
|
||||
<group name="purchase" position="before">
|
||||
<field name="seller_ids" context="{'default_product_tmpl_id':context.get('product_tmpl_id',active_id), 'product_template_invisible_variant': True, 'tree_view_ref':'purchase.product_supplierinfo_tree_view2'}" nolabel="1" attrs="{'invisible': [('product_variant_count','>',1)]}"/>
|
||||
<field name="variant_seller_ids" context="{'default_product_tmpl_id': context.get('product_tmpl_id', active_id), 'tree_view_ref':'purchase.product_supplierinfo_tree_view2'}" nolabel="1" attrs="{'invisible': [('product_variant_count','<=',1)]}"/>
|
||||
<field name="seller_ids" context="{'default_product_tmpl_id':context.get('product_tmpl_id',active_id), 'product_template_invisible_variant': True, 'tree_view_ref':'purchase.product_supplierinfo_tree_view2'}" nolabel="1" attrs="{'invisible': [('product_variant_count','>',1)], 'readonly': [('product_variant_count','>',1)]}"/>
|
||||
<field name="variant_seller_ids" context="{'default_product_tmpl_id': context.get('product_tmpl_id', active_id), 'tree_view_ref':'purchase.product_supplierinfo_tree_view2'}" nolabel="1" attrs="{'invisible': [('product_variant_count','<=',1)], 'readonly': [('product_variant_count','<=',1)]}"/>
|
||||
</group>
|
||||
<group name="bill" position="attributes">
|
||||
<attribute name="groups">purchase.group_purchase_manager</attribute>
|
||||
|
||||
@@ -296,3 +296,50 @@ class TestSaleMrpKitBom(TransactionCase):
|
||||
purchase_price = line.product_id.with_company(line.company_id)._compute_average_price(0, line.product_uom_qty, line.move_ids)
|
||||
self.assertEqual(purchase_price, 92, "The purchase price must be the total cost of the components multiplied by their unit of measure")
|
||||
|
||||
def test_qty_delivered_with_bom(self):
|
||||
"""Check the quantity delivered, when a bom line has a non integer quantity"""
|
||||
|
||||
self.env['ir.model.data'].xmlid_to_object('product.decimal_product_uom').digits = 5
|
||||
|
||||
self.kit = self._create_product('Kit', 'product', 0.00)
|
||||
self.comp = self._create_product('Component', 'product', 0.00)
|
||||
|
||||
# Create BoM for Kit
|
||||
bom_product_form = Form(self.env['mrp.bom'])
|
||||
bom_product_form.product_id = self.kit
|
||||
bom_product_form.product_tmpl_id = self.kit.product_tmpl_id
|
||||
bom_product_form.product_qty = 1.0
|
||||
bom_product_form.type = 'phantom'
|
||||
with bom_product_form.bom_line_ids.new() as bom_line:
|
||||
bom_line.product_id = self.comp
|
||||
bom_line.product_qty = 0.08600
|
||||
self.bom = bom_product_form.save()
|
||||
|
||||
|
||||
self.customer = self.env['res.partner'].create({
|
||||
'name': 'customer',
|
||||
})
|
||||
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.customer.id,
|
||||
'order_line': [
|
||||
(0, 0, {
|
||||
'name': self.kit.name,
|
||||
'product_id': self.kit.id,
|
||||
'product_uom_qty': 10.0,
|
||||
'product_uom': self.kit.uom_id.id,
|
||||
'price_unit': 1,
|
||||
'tax_id': False,
|
||||
})],
|
||||
})
|
||||
so.action_confirm()
|
||||
|
||||
self.assertTrue(so.picking_ids)
|
||||
self.assertEqual(so.order_line.qty_delivered, 0)
|
||||
|
||||
picking = so.picking_ids
|
||||
picking.move_lines.quantity_done = 0.86000
|
||||
picking.button_validate()
|
||||
|
||||
# Checks the delivery amount (must be 10).
|
||||
self.assertEqual(so.order_line.qty_delivered, 10)
|
||||
|
||||
@@ -162,7 +162,8 @@ class SaleOrder(models.Model):
|
||||
@api.onchange('user_id')
|
||||
def onchange_user_id(self):
|
||||
super().onchange_user_id()
|
||||
self.warehouse_id = self.user_id.with_company(self.company_id.id)._get_default_warehouse_id().id
|
||||
if self.state in ['draft','sent']:
|
||||
self.warehouse_id = self.user_id.with_company(self.company_id.id)._get_default_warehouse_id().id
|
||||
|
||||
@api.onchange('partner_shipping_id')
|
||||
def _onchange_partner_shipping_id(self):
|
||||
|
||||
@@ -202,10 +202,11 @@ class ProjectTask(models.Model):
|
||||
# override sale_order_id and make it computed stored field instead of regular field.
|
||||
sale_order_id = fields.Many2one(compute='_compute_sale_order_id', store=True, readonly=False,
|
||||
domain="['|', '|', ('partner_id', '=', partner_id), ('partner_id', 'child_of', commercial_partner_id), ('partner_id', 'parent_of', partner_id)]")
|
||||
# content in related parameter of the field definition is removed to manually define the compute and the search method.
|
||||
project_sale_order_id = fields.Many2one(compute='_compute_project_sale_order_id', search='_search_project_sale_order_id', related=None)
|
||||
analytic_account_id = fields.Many2one('account.analytic.account', related='sale_order_id.analytic_account_id')
|
||||
bill_type = fields.Selection(related="project_id.bill_type")
|
||||
pricing_type = fields.Selection(related="project_id.pricing_type")
|
||||
sale_line_id = fields.Many2one(domain="[('company_id', '=', company_id), ('is_service', '=', True), ('order_partner_id', 'child_of', commercial_partner_id), ('is_expense', '=', False), ('state', 'in', ['sale', 'done']), '|', ('project_id.bill_type', '=', 'customer_task'), ('order_id', '=?', project_sale_order_id)]")
|
||||
is_project_map_empty = fields.Boolean("Is Project map empty", compute='_compute_is_project_map_empty')
|
||||
has_multi_sol = fields.Boolean(compute='_compute_has_multi_sol', compute_sudo=True)
|
||||
allow_billable = fields.Boolean(related="project_id.allow_billable")
|
||||
@@ -271,6 +272,14 @@ class ProjectTask(models.Model):
|
||||
for task in self:
|
||||
task.analytic_account_active = task.analytic_account_active or task.analytic_account_id.active
|
||||
|
||||
@api.depends('project_id.bill_type', 'project_id.sale_order_id')
|
||||
def _compute_project_sale_order_id(self):
|
||||
for task in self:
|
||||
if task.bill_type != 'customer_task':
|
||||
task.project_sale_order_id = task.project_id.sale_order_id
|
||||
else:
|
||||
task.project_sale_order_id = False
|
||||
|
||||
@api.depends('sale_line_id', 'project_id', 'allow_billable', 'non_allow_billable')
|
||||
def _compute_sale_order_id(self):
|
||||
for task in self:
|
||||
@@ -309,6 +318,9 @@ class ProjectTask(models.Model):
|
||||
if not self.sale_line_id:
|
||||
self.sale_line_id = self.project_id.sale_line_id
|
||||
|
||||
def _search_project_sale_order_id(self, operator, value):
|
||||
return [('bill_type', '!=', 'customer_task'), ('project_id.sale_order_id', operator, value)]
|
||||
|
||||
def write(self, values):
|
||||
res = super(ProjectTask, self).write(values)
|
||||
# Done after super to avoid constraints on field recomputation
|
||||
|
||||
@@ -101,9 +101,10 @@ class Project(models.Model):
|
||||
'amount_untaxed_to_invoice': 'to_invoice',
|
||||
'timesheet_cost': 'cost',
|
||||
'expense_cost': 'expense_cost',
|
||||
'expense_amount_untaxed_invoiced': 'expense_amount_untaxed_invoiced',
|
||||
'expense_amount_untaxed_invoiced': 'expense_amount_untaxed_invoiced',
|
||||
'expense_amount_untaxed_to_invoice': 'expense_amount_untaxed_to_invoice',
|
||||
'other_revenues': 'other_revenues'
|
||||
}
|
||||
}
|
||||
profit = dict.fromkeys(list(field_map.values()) + ['other_revenues', 'total'], 0.0)
|
||||
profitability_raw_data = self.env['project.profitability.report'].read_group([('project_id', 'in', self.ids)], ['project_id'] + list(field_map), ['project_id'])
|
||||
for data in profitability_raw_data:
|
||||
|
||||
@@ -132,7 +132,6 @@ class ProfitabilityAnalysis(models.Model):
|
||||
SUM(timesheet_cost) AS timesheet_cost,
|
||||
SUM(expense_cost) AS expense_cost,
|
||||
SUM(other_revenues) AS other_revenues,
|
||||
SUM(downpayment_invoiced) AS downpayment_invoiced,
|
||||
SUM(expense_amount_untaxed_to_invoice) AS expense_amount_untaxed_to_invoice,
|
||||
SUM(expense_amount_untaxed_invoiced) AS expense_amount_untaxed_invoiced,
|
||||
SUM(amount_untaxed_to_invoice) AS amount_untaxed_to_invoice,
|
||||
@@ -148,7 +147,6 @@ class ProfitabilityAnalysis(models.Model):
|
||||
TS.amount AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced,
|
||||
0.0 AS expense_amount_untaxed_to_invoice,
|
||||
0.0 AS expense_amount_untaxed_invoiced,
|
||||
0.0 AS amount_untaxed_to_invoice,
|
||||
@@ -159,7 +157,7 @@ class ProfitabilityAnalysis(models.Model):
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Get the other revenues
|
||||
-- Get the other revenues (products that are not services)
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
@@ -168,7 +166,6 @@ class ProfitabilityAnalysis(models.Model):
|
||||
0.0 AS timesheet_cost,
|
||||
AAL.amount AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced,
|
||||
0.0 AS expense_amount_untaxed_to_invoice,
|
||||
0.0 AS expense_amount_untaxed_invoiced,
|
||||
0.0 AS amount_untaxed_to_invoice,
|
||||
@@ -177,16 +174,12 @@ class ProfitabilityAnalysis(models.Model):
|
||||
FROM project_project P
|
||||
JOIN account_analytic_account AA ON P.analytic_account_id = AA.id
|
||||
JOIN account_analytic_line AAL ON AAL.account_id = AA.id
|
||||
JOIN product_product PP ON PP.id = AAL.product_id
|
||||
JOIN product_template PT ON PT.id = PP.product_tmpl_id
|
||||
LEFT JOIN sale_order_line_invoice_rel SOINV ON SOINV.invoice_line_id = AAL.move_id
|
||||
LEFT JOIN sale_order_line SOL ON SOINV.order_line_id = SOL.id
|
||||
WHERE AAL.amount > 0.0 AND AAL.project_id IS NULL AND P.active = 't'
|
||||
AND P.allow_timesheets = 't'
|
||||
AND PT.service_type = 'manual' -- default value or Milestone service for services products
|
||||
AND PT.service_tracking = 'no' -- default value or not a tracking service for services products
|
||||
AND (SOL.id IS NULL
|
||||
OR (SOL.is_expense IS NOT TRUE AND SOL.is_downpayment IS NOT TRUE))
|
||||
OR (SOL.is_expense IS NOT TRUE AND SOL.is_downpayment IS NOT TRUE AND SOL.is_service IS NOT TRUE))
|
||||
|
||||
UNION ALL
|
||||
|
||||
@@ -199,64 +192,32 @@ class ProfitabilityAnalysis(models.Model):
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
AAL.amount AS expense_cost,
|
||||
0.0 AS downpayment_invoiced,
|
||||
0.0 AS expense_amount_untaxed_to_invoice,
|
||||
0.0 AS expense_amount_untaxed_invoiced,
|
||||
0.0 AS amount_untaxed_to_invoice,
|
||||
0.0 AS amount_untaxed_invoiced,
|
||||
AAL.date AS line_date
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account AA ON P.analytic_account_id = AA.id
|
||||
LEFT JOIN account_analytic_line AAL ON AAL.account_id = AA.id
|
||||
WHERE AAL.amount < 0.0 AND AAL.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Get the invoiced downpayments
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
MY_SOLS.id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
CASE WHEN MY_SOLS.invoice_status = 'invoiced' THEN MY_SOLS.price_reduce ELSE 0.0 END AS downpayment_invoiced,
|
||||
0.0 AS expense_amount_untaxed_to_invoice,
|
||||
0.0 AS expense_amount_untaxed_invoiced,
|
||||
0.0 AS amount_untaxed_to_invoice,
|
||||
0.0 AS amount_untaxed_invoiced,
|
||||
MY_S.date_order AS line_date
|
||||
FROM project_project P
|
||||
LEFT JOIN sale_order_line MY_SOL ON P.sale_line_id = MY_SOL.id
|
||||
LEFT JOIN sale_order MY_S ON MY_SOL.order_id = MY_S.id
|
||||
LEFT JOIN sale_order_line MY_SOLS ON MY_SOLS.order_id = MY_S.id
|
||||
WHERE MY_SOLS.is_downpayment = 't'
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Get the expense costs from sale order line
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
OLIS.id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
OLIS.price_reduce AS expense_cost,
|
||||
0.0 AS downpayment_invoiced,
|
||||
0.0 AS expense_amount_untaxed_to_invoice,
|
||||
0.0 AS expense_amount_untaxed_invoiced,
|
||||
0.0 AS amount_untaxed_to_invoice,
|
||||
0.0 AS amount_untaxed_invoiced,
|
||||
ANLI.date AS line_date
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account ANAC ON P.analytic_account_id = ANAC.id
|
||||
LEFT JOIN account_analytic_line ANLI ON ANAC.id = ANLI.account_id
|
||||
LEFT JOIN sale_order_line OLI ON P.sale_line_id = OLI.id
|
||||
LEFT JOIN sale_order ORD ON OLI.order_id = ORD.id
|
||||
LEFT JOIN sale_order_line OLIS ON ORD.id = OLIS.order_id
|
||||
WHERE OLIS.product_id = ANLI.product_id AND OLIS.is_downpayment = 't' AND ANLI.amount < 0.0 AND ANLI.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
JOIN account_analytic_account AA ON P.analytic_account_id = AA.id
|
||||
JOIN account_analytic_line AAL ON AAL.account_id = AA.id
|
||||
LEFT JOIN account_move_line RINVL ON AAL.move_id = RINVL.id
|
||||
AND RINVL.parent_state = 'posted'
|
||||
AND RINVL.exclude_from_invoice_tab = 'f'
|
||||
-- Check if the AAL is not related to a reversed credit note
|
||||
LEFT JOIN account_move RINV ON RINV.id = RINVL.move_id
|
||||
LEFT JOIN account_move_line INVL ON INVL.move_id = RINV.reversed_entry_id
|
||||
AND INVL.parent_state = 'posted'
|
||||
AND INVL.exclude_from_invoice_tab = 'f'
|
||||
AND INVL.product_id = RINVL.product_id
|
||||
LEFT JOIN sale_order_line_invoice_rel SOINV ON SOINV.invoice_line_id = INVL.id
|
||||
LEFT JOIN sale_order_line SOL ON SOINV.order_line_id = SOL.id
|
||||
AND SOL.product_id = AAL.product_id
|
||||
-- Check if the AAL is not related to a consumed downpayment (when the SOL is fully invoiced - with downpayment discounted.)
|
||||
LEFT JOIN sale_order_line_invoice_rel SOINVDOWN ON SOINVDOWN.invoice_line_id = RINVL.id
|
||||
LEFT JOIN sale_order_line SOLDOWN on SOINVDOWN.order_line_id = SOLDOWN.id AND SOLDOWN.is_downpayment = 't'
|
||||
WHERE AAL.amount < 0.0 AND AAL.project_id IS NULL
|
||||
AND SOL.id IS NULL AND SOLDOWN.id IS NULL -- Not linked to a credit note and not a downpayment
|
||||
AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
|
||||
UNION ALL
|
||||
|
||||
@@ -270,17 +231,16 @@ class ProfitabilityAnalysis(models.Model):
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced,
|
||||
CASE
|
||||
WHEN SOL.qty_delivered_method = 'analytic' THEN (SOL.untaxed_amount_to_invoice / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
|
||||
ELSE 0.0
|
||||
END AS expense_amount_untaxed_to_invoice,
|
||||
CASE
|
||||
WHEN SOL.qty_delivered_method = 'analytic' AND SOL.invoice_status != 'no'
|
||||
WHEN SOL.qty_delivered_method = 'analytic' AND SOL.invoice_status = 'invoiced'
|
||||
THEN
|
||||
CASE
|
||||
WHEN T.expense_policy = 'sales_price'
|
||||
THEN (SOL.price_reduce / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END) * SOL.qty_invoiced
|
||||
THEN (SOL.untaxed_amount_invoiced / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
|
||||
ELSE -AMOUNT_UNTAXED.expense_cost
|
||||
END
|
||||
ELSE 0.0
|
||||
@@ -290,31 +250,80 @@ class ProfitabilityAnalysis(models.Model):
|
||||
ELSE 0.0
|
||||
END AS amount_untaxed_to_invoice,
|
||||
CASE
|
||||
WHEN SOL.qty_delivered_method IN ('timesheet', 'manual') THEN (COALESCE(SOL.untaxed_amount_invoiced, AMOUNT_UNTAXED.downpayment_invoiced) / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
|
||||
WHEN SOL.qty_delivered_method IN ('timesheet', 'manual') THEN (SOL.untaxed_amount_invoiced / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
|
||||
ELSE 0.0
|
||||
END AS amount_untaxed_invoiced,
|
||||
S.date_order AS line_date
|
||||
FROM project_project P
|
||||
JOIN res_company C ON C.id = P.company_id
|
||||
LEFT JOIN (
|
||||
-- Gets SOL linked to timesheets
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
AAL.so_line AS sale_line_id,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
0.0 AS expense_cost
|
||||
FROM account_analytic_line AAL, project_project P
|
||||
WHERE AAL.project_id IS NOT NULL AND P.id = AAL.project_id AND P.active = 't'
|
||||
GROUP BY P.id, AAL.so_line
|
||||
|
||||
UNION
|
||||
-- Service SOL linked to a project task AND not yet timesheeted
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
SOL.id AS sale_line_id,
|
||||
0.0 AS expense_cost
|
||||
FROM sale_order_line SOL
|
||||
JOIN project_task T ON T.sale_line_id = SOL.id
|
||||
JOIN project_project P ON T.project_id = P.id
|
||||
LEFT JOIN account_analytic_line AAL ON AAL.task_id = T.id
|
||||
WHERE SOL.is_service = 't'
|
||||
AND AAL.id IS NULL -- not timesheeted
|
||||
AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, SOL.id
|
||||
UNION
|
||||
-- Service SOL linked to project AND not yet timesheeted
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
SOL.id AS sale_line_id,
|
||||
0.0 AS expense_cost
|
||||
FROM sale_order_line SOL
|
||||
JOIN project_project P ON P.sale_line_id = SOL.id
|
||||
LEFT JOIN account_analytic_line AAL ON AAL.project_id = P.id
|
||||
LEFT JOIN project_task T ON T.sale_line_id = SOL.id
|
||||
WHERE SOL.is_service = 't'
|
||||
AND AAL.id IS NULL -- not timesheeted
|
||||
AND (T.id IS NULL OR T.project_id != P.id) -- not linked to a task in this project
|
||||
AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, SOL.id
|
||||
UNION
|
||||
-- Service SOL linked to analytic account AND not yet timesheeted
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
SOL.id AS sale_line_id,
|
||||
0.0 AS expense_cost
|
||||
FROM sale_order_line SOL
|
||||
JOIN sale_order SO ON SO.id = SOL.order_id
|
||||
JOIN account_analytic_account AA ON AA.id = SO.analytic_account_id
|
||||
JOIN project_project P ON P.analytic_account_id = AA.id
|
||||
LEFT JOIN project_project PSOL ON PSOL.sale_line_id = SOL.id
|
||||
LEFT JOIN project_task TSOL ON TSOL.sale_line_id = SOL.id
|
||||
LEFT JOIN account_analytic_line AAL ON AAL.so_line = SOL.id
|
||||
WHERE SOL.is_service = 't'
|
||||
AND AAL.id IS NULL -- not timesheeted
|
||||
AND TSOL.id IS NULL -- not linked to a task
|
||||
AND PSOL.id IS NULL -- not linked to a project
|
||||
AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, SOL.id
|
||||
UNION
|
||||
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
AAL.so_line AS sale_line_id,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
0.0 AS expense_cost
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account AA ON P.analytic_account_id = AA.id
|
||||
LEFT JOIN account_analytic_line AAL ON AAL.account_id = AA.id
|
||||
@@ -325,8 +334,7 @@ class ProfitabilityAnalysis(models.Model):
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
AAL.so_line AS sale_line_id,
|
||||
SUM(AAL.amount) AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
SUM(AAL.amount) AS expense_cost
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account AA ON P.analytic_account_id = AA.id
|
||||
LEFT JOIN account_analytic_line AAL ON AAL.account_id = AA.id
|
||||
@@ -336,37 +344,31 @@ class ProfitabilityAnalysis(models.Model):
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
MY_SOLS.id AS sale_line_id,
|
||||
0.0 AS expense_cost,
|
||||
CASE WHEN MY_SOLS.invoice_status = 'invoiced' THEN MY_SOLS.price_reduce ELSE 0.0 END AS downpayment_invoiced
|
||||
SOLDOWN.id AS sale_line_id,
|
||||
0.0 AS expense_cost
|
||||
FROM project_project P
|
||||
LEFT JOIN sale_order_line MY_SOL ON P.sale_line_id = MY_SOL.id
|
||||
LEFT JOIN sale_order MY_S ON MY_SOL.order_id = MY_S.id
|
||||
LEFT JOIN sale_order_line MY_SOLS ON MY_SOLS.order_id = MY_S.id
|
||||
WHERE MY_SOLS.is_downpayment = 't'
|
||||
GROUP BY P.id, MY_SOLS.id
|
||||
UNION
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
OLIS.id AS sale_line_id,
|
||||
OLIS.price_reduce AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account ANAC ON P.analytic_account_id = ANAC.id
|
||||
LEFT JOIN account_analytic_line ANLI ON ANAC.id = ANLI.account_id
|
||||
LEFT JOIN sale_order_line OLI ON P.sale_line_id = OLI.id
|
||||
LEFT JOIN sale_order ORD ON OLI.order_id = ORD.id
|
||||
LEFT JOIN sale_order_line OLIS ON ORD.id = OLIS.order_id
|
||||
WHERE OLIS.product_id = ANLI.product_id AND OLIS.is_downpayment = 't' AND ANLI.amount < 0.0 AND ANLI.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, OLIS.id
|
||||
LEFT JOIN sale_order_line SOL ON P.sale_line_id = SOL.id
|
||||
LEFT JOIN sale_order SO ON SO.id = SOL.order_id OR SO.analytic_account_id = P.analytic_account_id
|
||||
LEFT JOIN sale_order_line SOLDOWN ON SOLDOWN.order_id = SO.id AND SOLDOWN.is_downpayment = 't'
|
||||
LEFT JOIN sale_order_line_invoice_rel SOINV ON SOINV.order_line_id = SOLDOWN.id
|
||||
LEFT JOIN account_move_line INVL ON SOINV.invoice_line_id = INVL.id
|
||||
AND INVL.parent_state = 'posted'
|
||||
AND INVL.exclude_from_invoice_tab = 'f'
|
||||
LEFT JOIN account_move RINV ON INVL.move_id = RINV.reversed_entry_id
|
||||
LEFT JOIN account_move_line RINVL ON RINV.id = RINVL.move_id
|
||||
AND RINVL.parent_state = 'posted'
|
||||
AND RINVL.exclude_from_invoice_tab = 'f'
|
||||
AND RINVL.product_id = SOLDOWN.product_id
|
||||
LEFT JOIN account_analytic_line ANLI ON ANLI.move_id = RINVL.id AND ANLI.amount < 0.0
|
||||
WHERE ANLI.id IS NULL -- there are no credit note for this downpayment
|
||||
AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, SOLDOWN.id
|
||||
UNION
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
SOL.id AS sale_line_id,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
0.0 AS expense_cost
|
||||
FROM sale_order_line SOL
|
||||
INNER JOIN project_project P ON SOL.project_id = P.id
|
||||
WHERE P.active = 't' AND P.allow_timesheets = 't'
|
||||
@@ -375,8 +377,7 @@ class ProfitabilityAnalysis(models.Model):
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
SOL.id AS sale_line_id,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
0.0 AS expense_cost
|
||||
FROM sale_order_line SOL
|
||||
INNER JOIN project_task T ON SOL.task_id = T.id
|
||||
INNER JOIN project_project P ON P.id = T.project_id
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# # Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import common
|
||||
from . import common_reporting
|
||||
from . import test_sale_timesheet
|
||||
from . import test_sale_service
|
||||
from . import test_project_billing
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
from flectra.tools import float_is_zero, float_compare
|
||||
from flectra.addons.sale_timesheet.tests.common import TestCommonSaleTimesheet
|
||||
|
||||
|
||||
class TestCommonReporting(TestCommonSaleTimesheet):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
|
||||
# expense product
|
||||
cls.product_expense = cls.env['product.product'].with_context(mail_notrack=True, mail_create_nolog=True).create({
|
||||
'name': "Expense service",
|
||||
'standard_price': 10,
|
||||
'list_price': 20,
|
||||
'type': 'service',
|
||||
'invoice_policy': 'delivery',
|
||||
'expense_policy': 'sales_price',
|
||||
'default_code': 'EXP',
|
||||
'service_type': 'manual',
|
||||
'taxes_id': False,
|
||||
'property_account_income_id': cls.company_data['default_account_revenue'].id,
|
||||
})
|
||||
|
||||
# create Analytic Accounts
|
||||
cls.analytic_account_1 = cls.env['account.analytic.account'].create({
|
||||
'name': 'Test AA 1',
|
||||
'code': 'AA1',
|
||||
'company_id': cls.company_data['company'].id,
|
||||
'partner_id': cls.partner_a.id
|
||||
})
|
||||
cls.analytic_account_2 = cls.env['account.analytic.account'].create({
|
||||
'name': 'Test AA 2',
|
||||
'code': 'AA2',
|
||||
'company_id': cls.company_data['company'].id,
|
||||
'partner_id': cls.partner_a.id
|
||||
})
|
||||
cls.analytic_account_3 = cls.env['account.analytic.account'].create({
|
||||
'name': 'Test AA 3',
|
||||
'code': 'AA3',
|
||||
'company_id': cls.company_data['company'].id,
|
||||
'partner_id': cls.partner_a.id
|
||||
})
|
||||
|
||||
# Sale orders each will create project and a task in a global project (one SO is 'delivered', the other is 'ordered')
|
||||
# and a third one using fixed_price (which is 'delivered')
|
||||
cls.sale_order_1 = cls.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({
|
||||
'partner_id': cls.partner_a.id,
|
||||
'partner_invoice_id': cls.partner_a.id,
|
||||
'partner_shipping_id': cls.partner_a.id,
|
||||
'analytic_account_id': cls.analytic_account_1.id,
|
||||
})
|
||||
cls.so_line_deliver_project = cls.env['sale.order.line'].create({
|
||||
'name': cls.product_delivery_timesheet3.name,
|
||||
'product_id': cls.product_delivery_timesheet3.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': cls.product_delivery_timesheet3.uom_id.id,
|
||||
'price_unit': cls.product_delivery_timesheet3.list_price,
|
||||
'order_id': cls.sale_order_1.id,
|
||||
})
|
||||
cls.so_line_deliver_task = cls.env['sale.order.line'].create({
|
||||
'name': cls.product_delivery_timesheet2.name,
|
||||
'product_id': cls.product_delivery_timesheet2.id,
|
||||
'product_uom_qty': 7,
|
||||
'product_uom': cls.product_delivery_timesheet2.uom_id.id,
|
||||
'price_unit': cls.product_delivery_timesheet2.list_price,
|
||||
'order_id': cls.sale_order_1.id,
|
||||
})
|
||||
|
||||
cls.sale_order_2 = cls.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({
|
||||
'partner_id': cls.partner_a.id,
|
||||
'partner_invoice_id': cls.partner_a.id,
|
||||
'partner_shipping_id': cls.partner_a.id,
|
||||
'analytic_account_id': cls.analytic_account_2.id,
|
||||
})
|
||||
cls.so_line_order_project = cls.env['sale.order.line'].create({
|
||||
'name': cls.product_order_timesheet3.name,
|
||||
'product_id': cls.product_order_timesheet3.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': cls.product_order_timesheet3.uom_id.id,
|
||||
'price_unit': cls.product_order_timesheet3.list_price,
|
||||
'order_id': cls.sale_order_2.id,
|
||||
})
|
||||
cls.so_line_order_task = cls.env['sale.order.line'].create({
|
||||
'name': cls.product_order_timesheet2.name,
|
||||
'product_id': cls.product_order_timesheet2.id,
|
||||
'product_uom_qty': 7,
|
||||
'product_uom': cls.product_order_timesheet2.uom_id.id,
|
||||
'price_unit': cls.product_order_timesheet2.list_price,
|
||||
'order_id': cls.sale_order_2.id,
|
||||
})
|
||||
|
||||
cls.sale_order_3 = cls.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({
|
||||
'partner_id': cls.partner_a.id,
|
||||
'partner_invoice_id': cls.partner_a.id,
|
||||
'partner_shipping_id': cls.partner_a.id,
|
||||
'analytic_account_id': cls.analytic_account_3.id,
|
||||
})
|
||||
cls.so_line_deliver_manual_project = cls.env['sale.order.line'].create({
|
||||
'name': cls.product_delivery_manual3.name,
|
||||
'product_id': cls.product_delivery_manual3.id,
|
||||
'product_uom_qty': 11,
|
||||
'product_uom': cls.product_delivery_manual3.uom_id.id,
|
||||
'price_unit': cls.product_delivery_manual3.list_price,
|
||||
'order_id': cls.sale_order_3.id,
|
||||
})
|
||||
|
||||
def _log_timesheet_user(self, project, unit_amount, task=False):
|
||||
""" Utility method to log timesheet """
|
||||
Timesheet = self.env['account.analytic.line']
|
||||
return Timesheet.create({
|
||||
'name': 'timesheet employee on project_so_1 only',
|
||||
'account_id': project.analytic_account_id.id,
|
||||
'project_id': project.id,
|
||||
'employee_id': self.employee_user.id,
|
||||
'unit_amount': unit_amount,
|
||||
'task_id': task.id if task else False,
|
||||
})
|
||||
|
||||
def _log_timesheet_manager(self, project, unit_amount, task=False):
|
||||
""" Utility method to log timesheet """
|
||||
Timesheet = self.env['account.analytic.line']
|
||||
return Timesheet.create({
|
||||
'name': 'timesheet employee on project_so_1 only',
|
||||
'account_id': project.analytic_account_id.id,
|
||||
'project_id': project.id,
|
||||
'employee_id': self.employee_manager.id,
|
||||
'unit_amount': unit_amount,
|
||||
'task_id': task.id if task else False,
|
||||
})
|
||||
@@ -1,13 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from flectra.addons.sale_timesheet.tests.test_reporting import TestReporting
|
||||
from flectra.tools import float_compare
|
||||
from flectra.addons.sale_timesheet.tests.common_reporting import TestCommonReporting
|
||||
from flectra.tools import float_compare, float_is_zero
|
||||
from flectra.tests import tagged
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestSaleProject(TestReporting):
|
||||
class TestSaleProject(TestCommonReporting):
|
||||
|
||||
def test_project_overview_by_project(self):
|
||||
rounding = self.env.company.currency_id.rounding
|
||||
@@ -34,7 +34,6 @@ class TestSaleProject(TestReporting):
|
||||
project_so = self.so_line_order_project.project_id
|
||||
# log timesheet for billable time
|
||||
timesheet1 = self._log_timesheet_manager(project_so, 10, so_line_deliver_global_project.task_id)
|
||||
|
||||
task_so = self.so_line_order_project.task_id
|
||||
# logged some timesheets: on project only, then on tasks with different employees
|
||||
timesheet2 = self._log_timesheet_user(project_so, 2)
|
||||
@@ -89,7 +88,7 @@ class TestSaleProject(TestReporting):
|
||||
})
|
||||
|
||||
other_revenues = self.env['account.analytic.line'].create({
|
||||
'name': 'pther revenues on project_so',
|
||||
'name': 'other revenues on project_so',
|
||||
'account_id': project_so.analytic_account_id.id,
|
||||
'employee_id': self.employee_user.id,
|
||||
'unit_amount': 1,
|
||||
@@ -103,10 +102,10 @@ class TestSaleProject(TestReporting):
|
||||
|
||||
dashboard_value = timesheet2.unit_amount + timesheet3.unit_amount + timesheet4.unit_amount + timesheet5.unit_amount + timesheet1.unit_amount
|
||||
project_so_timesheet_sold_unit = timesheet3.unit_amount + timesheet4.unit_amount
|
||||
project_rate_non_billable = timesheet5.unit_amount / dashboard_value * 100
|
||||
project_rate_non_billable_project = timesheet2.unit_amount / dashboard_value * 100
|
||||
project_rate_billable_time = timesheet1.unit_amount / dashboard_value * 100
|
||||
project_rate_billable_fixed = project_so_timesheet_sold_unit / dashboard_value * 100
|
||||
project_rate_non_billable = round(timesheet5.unit_amount / dashboard_value * 100, 2)
|
||||
project_rate_non_billable_project = round(timesheet2.unit_amount / dashboard_value * 100, 2)
|
||||
project_rate_billable_time = round(timesheet1.unit_amount / dashboard_value * 100, 2)
|
||||
project_rate_billable_fixed = round(project_so_timesheet_sold_unit / dashboard_value * 100, 2)
|
||||
project_rate_total = project_rate_non_billable + project_rate_non_billable_project + project_rate_billable_time + project_rate_billable_fixed
|
||||
project_invoiced = self.so_line_order_project.price_unit * self.so_line_order_project.product_uom_qty * timesheet1.unit_amount
|
||||
project_timesheet_cost = timesheet2.amount + timesheet3.amount + timesheet4.amount + timesheet5.amount + timesheet1.amount
|
||||
@@ -122,9 +121,11 @@ class TestSaleProject(TestReporting):
|
||||
self.assertEqual(float_compare(vals['dashboard']['rates']['billable_time'], project_rate_billable_time, precision_rounding=rounding), 0, "The rate billable-time should be the one from the SO2 line, as we are in ordered quantity")
|
||||
self.assertEqual(float_compare(vals['dashboard']['rates']['billable_fixed'], project_rate_billable_fixed, precision_rounding=rounding), 0, "The rate billable-fixed should be the one from the SO2 line, as we are in ordered quantity")
|
||||
self.assertEqual(float_compare(vals['dashboard']['rates']['total'], project_rate_total, precision_rounding=rounding), 0, "The total rates should be the one from the SO2 line, as we are in ordered quantity")
|
||||
self.assertEqual(float_compare(vals['dashboard']['profit']['invoiced'], project_invoiced, precision_rounding=rounding), 0, "The amount invoiced should be the one from the SO2 line, as we are in ordered quantity")
|
||||
self.assertEqual(float_compare(vals['dashboard']['profit']['invoiced'], project_invoiced + project_other_revenues.price_total, precision_rounding=rounding), 0, "The amount invoiced should be the one from the SO2 line, as we are in ordered quantity")
|
||||
self.assertEqual(float_compare(vals['dashboard']['profit']['cost'], project_timesheet_cost, precision_rounding=rounding), 0, "The amount cost should be the one from the SO2 line, as we are in ordered quantity")
|
||||
self.assertEqual(float_compare(vals['dashboard']['profit']['expense_cost'], expense.amount, precision_rounding=rounding), 0, "The amount expense-cost should be the one from the SO2 line, as we are in ordered quantity")
|
||||
self.assertEqual(float_compare(vals['dashboard']['profit']['other_revenues'], other_revenues.amount + project_other_revenues.price_total, precision_rounding=rounding), 0, "The amount of the other revenues should be equal to the corresponding account move line and the one from the SO line")
|
||||
self.assertEqual(float_compare(vals['dashboard']['profit']['total'], project_invoiced + project_timesheet_cost + expense.amount + other_revenues.amount + project_other_revenues.price_total, precision_rounding=rounding), 0, "The total amount should be the sum of the SO2 line and the created other_revenues account analytic line")
|
||||
self.assertEqual(float_compare(vals['dashboard']['profit']['other_revenues'], other_revenues.amount, precision_rounding=rounding), 0, "The amount of the other revenues should be equal to the corresponding account move line and the one from the SO line")
|
||||
self.assertEqual(float_compare(vals['dashboard']['profit']['expense_amount_untaxed_to_invoice'], -expense.amount, precision_rounding=rounding), 0, "The amount of expense to re-invoice shoul be equal to the opposite of expense amount")
|
||||
self.assertTrue(float_is_zero(vals['dashboard']['profit']['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The amount of expense re-invoiced should be equal to 0")
|
||||
self.assertEqual(float_compare(vals['dashboard']['profit']['total'], project_invoiced + project_timesheet_cost + other_revenues.amount + project_other_revenues.price_total, precision_rounding=rounding), 0, "The total amount should be the sum of the SO2 line and the created other_revenues account analytic line")
|
||||
self.assertEqual(float_compare(vals['repartition_employee_max'], 11.0, precision_rounding=rounding), 0, "The amount of repartition-employee-max should be the one from SO2 line")
|
||||
|
||||
@@ -1,115 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
||||
from flectra.osv import expression
|
||||
from flectra.tools import float_is_zero, float_compare
|
||||
from flectra.addons.sale_timesheet.tests.common import TestCommonSaleTimesheet
|
||||
from flectra.addons.sale_timesheet.tests.common_reporting import TestCommonReporting
|
||||
from flectra.tests import tagged
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestReporting(TestCommonSaleTimesheet):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
|
||||
# expense product
|
||||
cls.product_expense = cls.env['product.product'].with_context(mail_notrack=True, mail_create_nolog=True).create({
|
||||
'name': "Expense service",
|
||||
'standard_price': 10,
|
||||
'list_price': 20,
|
||||
'type': 'service',
|
||||
'invoice_policy': 'delivery',
|
||||
'expense_policy': 'sales_price',
|
||||
'default_code': 'EXP',
|
||||
'service_type': 'manual',
|
||||
'taxes_id': False,
|
||||
'property_account_income_id': cls.company_data['default_account_revenue'].id,
|
||||
})
|
||||
|
||||
# create Analytic Accounts
|
||||
cls.analytic_account_1 = cls.env['account.analytic.account'].create({
|
||||
'name': 'Test AA 1',
|
||||
'code': 'AA1',
|
||||
'company_id': cls.company_data['company'].id,
|
||||
'partner_id': cls.partner_a.id
|
||||
})
|
||||
cls.analytic_account_2 = cls.env['account.analytic.account'].create({
|
||||
'name': 'Test AA 2',
|
||||
'code': 'AA2',
|
||||
'company_id': cls.company_data['company'].id,
|
||||
'partner_id': cls.partner_a.id
|
||||
})
|
||||
|
||||
# Sale orders each will create project and a task in a global project (one SO is 'delivered', the other is 'ordered')
|
||||
cls.sale_order_1 = cls.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({
|
||||
'partner_id': cls.partner_a.id,
|
||||
'partner_invoice_id': cls.partner_a.id,
|
||||
'partner_shipping_id': cls.partner_a.id,
|
||||
'analytic_account_id': cls.analytic_account_1.id,
|
||||
})
|
||||
cls.so_line_deliver_project = cls.env['sale.order.line'].create({
|
||||
'name': cls.product_delivery_timesheet3.name,
|
||||
'product_id': cls.product_delivery_timesheet3.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': cls.product_delivery_timesheet3.uom_id.id,
|
||||
'price_unit': cls.product_delivery_timesheet3.list_price,
|
||||
'order_id': cls.sale_order_1.id,
|
||||
})
|
||||
cls.so_line_deliver_task = cls.env['sale.order.line'].create({
|
||||
'name': cls.product_delivery_timesheet2.name,
|
||||
'product_id': cls.product_delivery_timesheet2.id,
|
||||
'product_uom_qty': 7,
|
||||
'product_uom': cls.product_delivery_timesheet2.uom_id.id,
|
||||
'price_unit': cls.product_delivery_timesheet2.list_price,
|
||||
'order_id': cls.sale_order_1.id,
|
||||
})
|
||||
|
||||
cls.sale_order_2 = cls.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({
|
||||
'partner_id': cls.partner_a.id,
|
||||
'partner_invoice_id': cls.partner_a.id,
|
||||
'partner_shipping_id': cls.partner_a.id,
|
||||
'analytic_account_id': cls.analytic_account_2.id,
|
||||
})
|
||||
cls.so_line_order_project = cls.env['sale.order.line'].create({
|
||||
'name': cls.product_order_timesheet3.name,
|
||||
'product_id': cls.product_order_timesheet3.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': cls.product_order_timesheet3.uom_id.id,
|
||||
'price_unit': cls.product_order_timesheet3.list_price,
|
||||
'order_id': cls.sale_order_2.id,
|
||||
})
|
||||
cls.so_line_order_task = cls.env['sale.order.line'].create({
|
||||
'name': cls.product_order_timesheet2.name,
|
||||
'product_id': cls.product_order_timesheet2.id,
|
||||
'product_uom_qty': 7,
|
||||
'product_uom': cls.product_order_timesheet2.uom_id.id,
|
||||
'price_unit': cls.product_order_timesheet2.list_price,
|
||||
'order_id': cls.sale_order_2.id,
|
||||
})
|
||||
|
||||
def _log_timesheet_user(self, project, unit_amount, task=False):
|
||||
""" Utility method to log timesheet """
|
||||
Timesheet = self.env['account.analytic.line']
|
||||
return Timesheet.create({
|
||||
'name': 'timesheet employee on project_so_1 only',
|
||||
'account_id': project.analytic_account_id.id,
|
||||
'project_id': project.id,
|
||||
'employee_id': self.employee_user.id,
|
||||
'unit_amount': unit_amount,
|
||||
'task_id': task.id if task else False,
|
||||
})
|
||||
|
||||
def _log_timesheet_manager(self, project, unit_amount, task=False):
|
||||
""" Utility method to log timesheet """
|
||||
Timesheet = self.env['account.analytic.line']
|
||||
return Timesheet.create({
|
||||
'name': 'timesheet employee on project_so_1 only',
|
||||
'account_id': project.analytic_account_id.id,
|
||||
'project_id': project.id,
|
||||
'employee_id': self.employee_manager.id,
|
||||
'unit_amount': unit_amount,
|
||||
'task_id': task.id if task else False,
|
||||
})
|
||||
class TestReporting(TestCommonReporting):
|
||||
|
||||
def test_profitability_report(self):
|
||||
|
||||
@@ -125,31 +23,38 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the global project should be 0.0")
|
||||
|
||||
# confirm sales orders
|
||||
self.sale_order_1.action_confirm()
|
||||
self.sale_order_2.action_confirm()
|
||||
self.sale_order_3.action_confirm()
|
||||
self.env['project.profitability.report'].flush()
|
||||
|
||||
project_so_1 = self.so_line_deliver_project.project_id
|
||||
project_so_2 = self.so_line_order_project.project_id
|
||||
project_so_3 = self.so_line_deliver_manual_project.project_id
|
||||
|
||||
task_so_1 = self.so_line_deliver_project.task_id
|
||||
task_so_2 = self.so_line_order_project.task_id
|
||||
task_so_3 = self.so_line_deliver_manual_project.task_id
|
||||
|
||||
task_in_global_1 = self.so_line_deliver_task.task_id
|
||||
task_in_global_2 = self.so_line_order_task.task_id
|
||||
|
||||
# deliver project should not be impacted, as no timesheet are logged yet
|
||||
project_so_1_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_1.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced'], ['project_id'])[0]
|
||||
project_so_1_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_1.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['amount_untaxed_invoiced'], precision_rounding=rounding), "The invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['amount_untaxed_to_invoice'], precision_rounding=rounding), "The amount to invoice of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['timesheet_unit_amount'], precision_rounding=rounding), "The timesheet unit amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['timesheet_cost'], precision_rounding=rounding), "The timesheet cost of the global project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['timesheet_cost'], precision_rounding=rounding), "The timesheet cost of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the project from SO1 should be 0.0")
|
||||
|
||||
# order project should be to invoice, but nothing has been delivered yet
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_2.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced'], ['project_id'])[0]
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_2.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['amount_untaxed_invoiced'], precision_rounding=rounding), "The invoiced amount of the project from SO2 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_2_stat['amount_untaxed_to_invoice'], self.so_line_order_project.price_unit * self.so_line_order_project.qty_to_invoice, precision_rounding=rounding), 0, "The amount to invoice should be the one from the SO line, as we are in ordered quantity")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['timesheet_unit_amount'], precision_rounding=rounding), "The timesheet unit amount of the project from SO2 should be 0.0")
|
||||
@@ -157,9 +62,21 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the project from SO2 should be 0.0")
|
||||
|
||||
# global project now contain 2 tasks: 'deliver' one which has no effect (no timesheet yet), and the 'order' one which should update the 'to invoice' amount
|
||||
project_global_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', self.project_global.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced'], ['project_id'])[0]
|
||||
# fixed price project should not be impacted, as no delivered quantity are logged yet
|
||||
project_so_3_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_3.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['amount_untaxed_invoiced'], precision_rounding=rounding), "The invoiced amount of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['amount_untaxed_to_invoice'], precision_rounding=rounding), "The amount to invoice of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['timesheet_unit_amount'], precision_rounding=rounding), "The timesheet unit amount of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['timesheet_cost'], precision_rounding=rounding), "The timesheet cost of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the project from SO3 should be 0.0")
|
||||
|
||||
# global project now contain 3 tasks: 'deliver' ones which have no effect (no timesheet or delivered yet), and the 'order' one which should update the 'to invoice' amount
|
||||
project_global_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', self.project_global.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
self.assertTrue(float_is_zero(project_global_stat['amount_untaxed_invoiced'], precision_rounding=rounding), "The invoiced amount of the global project should be 0.0")
|
||||
self.assertEqual(float_compare(project_global_stat['amount_untaxed_to_invoice'], self.so_line_order_task.price_unit * self.so_line_order_task.qty_to_invoice, precision_rounding=rounding), 0, "The amount to invoice of global project should take the task in 'oredered qty' into account")
|
||||
self.assertTrue(float_is_zero(project_global_stat['timesheet_unit_amount'], precision_rounding=rounding), "The timesheet unit amount of the global project should be 0.0")
|
||||
@@ -167,21 +84,22 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the global project should be 0.0")
|
||||
|
||||
# logged some timesheets: on project only, then on tasks with different employees
|
||||
timesheet1 = self._log_timesheet_user(project_so_1, 2)
|
||||
timesheet2 = self._log_timesheet_user(project_so_2, 2)
|
||||
timesheet3 = self._log_timesheet_user(project_so_1, 3, task_so_1)
|
||||
timesheet4 = self._log_timesheet_user(project_so_2, 3, task_so_2)
|
||||
|
||||
timesheet5 = self._log_timesheet_manager(project_so_1, 1, task_so_1)
|
||||
timesheet6 = self._log_timesheet_manager(project_so_2, 1, task_so_2)
|
||||
timesheet7 = self._log_timesheet_manager(self.project_global, 3, task_in_global_1)
|
||||
timesheet8 = self._log_timesheet_manager(self.project_global, 3, task_in_global_2)
|
||||
timesheet9 = self._log_timesheet_user(project_so_3, 4, task_so_3)
|
||||
self.env['project.profitability.report'].flush()
|
||||
|
||||
# deliver project should now have cost and something to invoice
|
||||
project_so_1_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_1.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced'], ['project_id'])[0]
|
||||
project_so_1_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_1.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_1_timesheet_cost = timesheet1.amount + timesheet3.amount + timesheet5.amount
|
||||
project_so_1_timesheet_sold_unit = timesheet1.unit_amount + timesheet3.unit_amount + timesheet5.unit_amount
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['amount_untaxed_invoiced'], precision_rounding=rounding), "The invoiced amount of the project from SO1 should be 0.0")
|
||||
@@ -191,9 +109,10 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the deliver project should be 0.0")
|
||||
|
||||
# order project still have something to invoice but has costs now
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_2.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced'], ['project_id'])[0]
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_2.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_2_timesheet_cost = timesheet2.amount + timesheet4.amount + timesheet6.amount
|
||||
project_so_2_timesheet_sold_unit = timesheet2.unit_amount + timesheet4.unit_amount + timesheet6.unit_amount
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['amount_untaxed_invoiced'], precision_rounding=rounding), "The invoiced amount of the project from SO2 should be 0.0")
|
||||
@@ -203,9 +122,23 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the project from SO2 should be 0.0")
|
||||
|
||||
# fixed price project should ow have cost and nothing to invoice as no delivered quantity are logged yet
|
||||
project_so_3_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_3.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_3_timesheet_cost = timesheet9.amount
|
||||
project_so_3_timesheet_sold_unit = timesheet9.unit_amount
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['amount_untaxed_invoiced'], precision_rounding=rounding), "The invoiced amount of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['amount_untaxed_to_invoice'], precision_rounding=rounding), "The amount to invoice of the project from SO3 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_3_stat['timesheet_unit_amount'], project_so_3_timesheet_sold_unit, precision_rounding=rounding), 0, "The timesheet unit amount of the project from SO3")
|
||||
self.assertEqual(float_compare(project_so_3_stat['timesheet_cost'], project_so_3_timesheet_cost, precision_rounding=rounding), 0, "The timesheet cost of the project from SO3")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the project from SO3 should be 0.0")
|
||||
|
||||
# global project should have more to invoice, and should now have costs
|
||||
project_global_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', self.project_global.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced'], ['project_id'])[0]
|
||||
project_global_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', self.project_global.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_global_timesheet_cost = timesheet7.amount + timesheet8.amount
|
||||
project_global_timesheet_unit = timesheet7.unit_amount + timesheet8.unit_amount
|
||||
project_global_to_invoice = (self.so_line_order_task.price_unit * self.so_line_order_task.product_uom_qty) + (self.so_line_deliver_task.price_unit * timesheet7.unit_amount)
|
||||
@@ -216,6 +149,63 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the global project should be 0.0")
|
||||
|
||||
self.so_line_deliver_manual_project.write({'qty_delivered': 7.0})
|
||||
self.env['project.profitability.report'].flush()
|
||||
|
||||
# deliver project should now have cost and something to invoice
|
||||
project_so_1_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_1.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_1_timesheet_cost = timesheet1.amount + timesheet3.amount + timesheet5.amount
|
||||
project_so_1_timesheet_sold_unit = timesheet1.unit_amount + timesheet3.unit_amount + timesheet5.unit_amount
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['amount_untaxed_invoiced'], precision_rounding=rounding), "The invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_1_stat['amount_untaxed_to_invoice'], self.so_line_deliver_project.price_unit * project_so_1_timesheet_sold_unit, precision_rounding=rounding), 0, "The amount to invoice of the project from SO1 should only include timesheet linked to task")
|
||||
self.assertEqual(float_compare(project_so_1_stat['timesheet_unit_amount'], project_so_1_timesheet_sold_unit, precision_rounding=rounding), 0, "The timesheet unit amount of the project from SO1 should include all timesheet in project")
|
||||
self.assertEqual(float_compare(project_so_1_stat['timesheet_cost'], project_so_1_timesheet_cost, precision_rounding=rounding), 0, "The timesheet cost of the project from SO1 should include all timesheet")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the deliver project should be 0.0")
|
||||
|
||||
# order project still have something to invoice but has costs now
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_2.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_2_timesheet_cost = timesheet2.amount + timesheet4.amount + timesheet6.amount
|
||||
project_so_2_timesheet_sold_unit = timesheet2.unit_amount + timesheet4.unit_amount + timesheet6.unit_amount
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['amount_untaxed_invoiced'], precision_rounding=rounding), "The invoiced amount of the project from SO2 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_2_stat['amount_untaxed_to_invoice'], self.so_line_order_project.price_unit * self.so_line_order_project.qty_to_invoice, precision_rounding=rounding), 0, "The amount to invoice should be the one from the SO line, as we are in ordered quantity")
|
||||
self.assertEqual(float_compare(project_so_2_stat['timesheet_unit_amount'], project_so_2_timesheet_sold_unit, precision_rounding=rounding), 0, "The timesheet unit amount of the project from SO2 should include all timesheet")
|
||||
self.assertEqual(float_compare(project_so_2_stat['timesheet_cost'], project_so_2_timesheet_cost, precision_rounding=rounding), 0, "The timesheet cost of the project from SO2 should include all timesheet")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the project from SO2 should be 0.0")
|
||||
|
||||
# fixed price project should ow have cost and something to invoice as some delivered quantity are logged in
|
||||
project_so_3_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_3.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_3_timesheet_cost = timesheet9.amount
|
||||
project_so_3_timesheet_sold_unit = timesheet9.unit_amount
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['amount_untaxed_invoiced'], precision_rounding=rounding), "The invoiced amount of the project from SO3 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_3_stat['amount_untaxed_to_invoice'], self.so_line_deliver_manual_project.price_unit * self.so_line_deliver_manual_project.qty_to_invoice, precision_rounding=rounding), 0, "The amount to invoice of the project from SO3 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_3_stat['timesheet_unit_amount'], project_so_3_timesheet_sold_unit, precision_rounding=rounding), 0, "The timesheet unit amount of the project from SO3")
|
||||
self.assertEqual(float_compare(project_so_3_stat['timesheet_cost'], project_so_3_timesheet_cost, precision_rounding=rounding), 0, "The timesheet cost of the project from SO3")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the project from SO3 should be 0.0")
|
||||
|
||||
# global project should have more to invoice, and should now have costs
|
||||
project_global_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', self.project_global.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_global_timesheet_cost = timesheet7.amount + timesheet8.amount
|
||||
project_global_timesheet_unit = timesheet7.unit_amount + timesheet8.unit_amount
|
||||
project_global_to_invoice = (self.so_line_order_task.price_unit * self.so_line_order_task.product_uom_qty) + (self.so_line_deliver_task.price_unit * timesheet7.unit_amount)
|
||||
self.assertTrue(float_is_zero(project_global_stat['amount_untaxed_invoiced'], precision_rounding=rounding), "The invoiced amount of the global project should be 0.0")
|
||||
self.assertEqual(float_compare(project_global_stat['amount_untaxed_to_invoice'], project_global_to_invoice, precision_rounding=rounding), 0, "The amount to invoice of global project should take the task in 'oredered qty' and the delivered timesheets into account")
|
||||
self.assertEqual(float_compare(project_global_stat['timesheet_unit_amount'], project_global_timesheet_unit, precision_rounding=rounding), 0, "The timesheet unit amount of the global project should include all timesheet")
|
||||
self.assertEqual(float_compare(project_global_stat['timesheet_cost'], project_global_timesheet_cost, precision_rounding=rounding), 0, "The timesheet cost of the global project should include all timesheet")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the global project should be 0.0")
|
||||
|
||||
InvoiceWizard = self.env['sale.advance.payment.inv'].with_context(mail_notrack=True)
|
||||
|
||||
@@ -236,7 +226,7 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.env['project.profitability.report'].flush()
|
||||
|
||||
# deliver project should now have cost and something invoiced
|
||||
project_so_1_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_1.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced'], ['project_id'])[0]
|
||||
project_so_1_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_1.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_1_timesheet_cost = timesheet1.amount + timesheet3.amount + timesheet5.amount
|
||||
project_so_1_timesheet_sold_unit = timesheet1.unit_amount + timesheet3.unit_amount + timesheet5.unit_amount
|
||||
|
||||
@@ -247,9 +237,10 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the deliver project should be 0.0")
|
||||
|
||||
# order project has still nothing invoiced
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_2.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced'], ['project_id'])[0]
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_2.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_2_timesheet_cost = timesheet2.amount + timesheet4.amount + timesheet6.amount
|
||||
project_so_2_timesheet_sold_unit = timesheet2.unit_amount + timesheet4.unit_amount + timesheet6.unit_amount
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['amount_untaxed_invoiced'], precision_rounding=rounding), "The invoiced amount of the project from SO2 should be 0.0")
|
||||
@@ -259,9 +250,23 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the project from SO2 should be 0.0")
|
||||
|
||||
# fixed price project has still nothing invoice
|
||||
project_so_3_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_3.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_3_timesheet_cost = timesheet9.amount
|
||||
project_so_3_timesheet_sold_unit = timesheet9.unit_amount
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['amount_untaxed_invoiced'], precision_rounding=rounding), "The invoiced amount of the project from SO3 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_3_stat['amount_untaxed_to_invoice'], self.so_line_deliver_manual_project.price_unit * self.so_line_deliver_manual_project.qty_to_invoice, precision_rounding=rounding), 0, "The amount to invoice of the project from SO3 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_3_stat['timesheet_unit_amount'], project_so_3_timesheet_sold_unit, precision_rounding=rounding), 0, "The timesheet unit amount of the project from SO3")
|
||||
self.assertEqual(float_compare(project_so_3_stat['timesheet_cost'], project_so_3_timesheet_cost, precision_rounding=rounding), 0, "The timesheet cost of the project from SO3")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the project from SO3 should be 0.0")
|
||||
|
||||
# global project should have more to invoice, and should now have costs
|
||||
project_global_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', self.project_global.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced'], ['project_id'])[0]
|
||||
project_global_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', self.project_global.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_global_timesheet_cost = timesheet7.amount + timesheet8.amount
|
||||
project_global_timesheet_unit = timesheet7.unit_amount + timesheet8.unit_amount
|
||||
project_global_to_invoice = self.so_line_order_task.price_unit * self.so_line_order_task.product_uom_qty
|
||||
@@ -273,6 +278,7 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the global project should be 0.0")
|
||||
|
||||
# invoice the Sales Order SO2
|
||||
context = {
|
||||
@@ -291,7 +297,7 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.env['project.profitability.report'].flush()
|
||||
|
||||
# deliver project should not be impacted by the invoice of the other SO
|
||||
project_so_1_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_1.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced'], ['project_id'])[0]
|
||||
project_so_1_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_1.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_1_timesheet_cost = timesheet1.amount + timesheet3.amount + timesheet5.amount
|
||||
project_so_1_timesheet_sold_unit = timesheet1.unit_amount + timesheet3.unit_amount + timesheet5.unit_amount
|
||||
self.assertEqual(float_compare(project_so_1_stat['amount_untaxed_invoiced'], self.so_line_deliver_project.price_unit * project_so_1_timesheet_sold_unit, precision_rounding=rounding), 0, "The invoiced amount of the project from SO1 should only include timesheet linked to task")
|
||||
@@ -301,9 +307,10 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the deliver project should be 0.0")
|
||||
|
||||
# order project is now totally invoiced, as we are in ordered qty
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_2.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced'], ['project_id'])[0]
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_2.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_2_timesheet_cost = timesheet2.amount + timesheet4.amount + timesheet6.amount
|
||||
project_so_2_timesheet_sold_unit = timesheet2.unit_amount + timesheet4.unit_amount + timesheet6.unit_amount
|
||||
self.assertEqual(float_compare(project_so_2_stat['amount_untaxed_invoiced'], self.so_line_order_project.price_unit * self.so_line_order_project.product_uom_qty, precision_rounding=rounding), 0, "The invoiced amount should be the one from the SO line, as we are in ordered quantity")
|
||||
@@ -313,9 +320,23 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the project from SO2 should be 0.0")
|
||||
|
||||
# fixed price project has still nothing invoice
|
||||
project_so_3_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_3.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_3_timesheet_cost = timesheet9.amount
|
||||
project_so_3_timesheet_sold_unit = timesheet9.unit_amount
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['amount_untaxed_invoiced'], precision_rounding=rounding), "The invoiced amount of the project from SO3 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_3_stat['amount_untaxed_to_invoice'], self.so_line_deliver_manual_project.price_unit * self.so_line_deliver_manual_project.qty_to_invoice, precision_rounding=rounding), 0, "The amount to invoice of the project from SO3 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_3_stat['timesheet_unit_amount'], project_so_3_timesheet_sold_unit, precision_rounding=rounding), 0, "The timesheet unit amount of the project from SO3")
|
||||
self.assertEqual(float_compare(project_so_3_stat['timesheet_cost'], project_so_3_timesheet_cost, precision_rounding=rounding), 0, "The timesheet cost of the project from SO3")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the project from SO3 should be 0.0")
|
||||
|
||||
# global project should be totally invoiced
|
||||
project_global_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', self.project_global.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced'], ['project_id'])[0]
|
||||
project_global_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', self.project_global.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_global_timesheet_cost = timesheet7.amount + timesheet8.amount
|
||||
project_global_timesheet_unit = timesheet7.unit_amount + timesheet8.unit_amount
|
||||
project_global_invoiced = self.so_line_order_task.price_unit * self.so_line_order_task.product_uom_qty + self.so_line_deliver_task.price_unit * timesheet7.unit_amount
|
||||
@@ -326,6 +347,76 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the global project should be 0.0")
|
||||
|
||||
# invoice the Sales Order SO3
|
||||
context = {
|
||||
"active_model": 'sale.order',
|
||||
"active_ids": [self.sale_order_3.id],
|
||||
"active_id": self.sale_order_3.id,
|
||||
'open_invoices': True,
|
||||
}
|
||||
payment = InvoiceWizard.create({
|
||||
'advance_payment_method': 'delivered',
|
||||
})
|
||||
action_invoice = payment.with_context(context).create_invoices()
|
||||
invoice_id = action_invoice['res_id']
|
||||
invoice_3 = self.env['account.move'].browse(invoice_id)
|
||||
invoice_3.action_post()
|
||||
self.env['project.profitability.report'].flush()
|
||||
|
||||
# deliver project should not be impacted by the invoice of the other SO
|
||||
project_so_1_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_1.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_1_timesheet_cost = timesheet1.amount + timesheet3.amount + timesheet5.amount
|
||||
project_so_1_timesheet_sold_unit = timesheet1.unit_amount + timesheet3.unit_amount + timesheet5.unit_amount
|
||||
self.assertEqual(float_compare(project_so_1_stat['amount_untaxed_invoiced'], self.so_line_deliver_project.price_unit * project_so_1_timesheet_sold_unit, precision_rounding=rounding), 0, "The invoiced amount of the project from SO1 should only include timesheet linked to task")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['amount_untaxed_to_invoice'], precision_rounding=rounding), "The amount to invoice of the project from SO1 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_1_stat['timesheet_unit_amount'], project_so_1_timesheet_sold_unit, precision_rounding=rounding), 0, "The timesheet unit amount of the project from SO1 should include all timesheet in project")
|
||||
self.assertEqual(float_compare(project_so_1_stat['timesheet_cost'], project_so_1_timesheet_cost, precision_rounding=rounding), 0, "The timesheet cost of the project from SO1 should include all timesheet")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the deliver project should be 0.0")
|
||||
|
||||
# order project should not be impacted by the invoice of the other SO
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_2.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_2_timesheet_cost = timesheet2.amount + timesheet4.amount + timesheet6.amount
|
||||
project_so_2_timesheet_sold_unit = timesheet2.unit_amount + timesheet4.unit_amount + timesheet6.unit_amount
|
||||
self.assertEqual(float_compare(project_so_2_stat['amount_untaxed_invoiced'], self.so_line_order_project.price_unit * self.so_line_order_project.product_uom_qty, precision_rounding=rounding), 0, "The invoiced amount should be the one from the SO line, as we are in ordered quantity")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['amount_untaxed_to_invoice'], precision_rounding=rounding), "The amount to invoice should be the one 0.0, as all ordered quantity is invoiced")
|
||||
self.assertEqual(float_compare(project_so_2_stat['timesheet_unit_amount'], project_so_2_timesheet_sold_unit, precision_rounding=rounding), 0, "The timesheet unit amount of the project from SO2 should include all timesheet")
|
||||
self.assertEqual(float_compare(project_so_2_stat['timesheet_cost'], project_so_2_timesheet_cost, precision_rounding=rounding), 0, "The timesheet cost of the project from SO2 should include all timesheet")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the project from SO2 should be 0.0")
|
||||
|
||||
# fixed price project is now partially invoiced, as we are in delivered qty
|
||||
project_so_3_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_3.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_3_timesheet_cost = timesheet9.amount
|
||||
project_so_3_timesheet_sold_unit = timesheet9.unit_amount
|
||||
self.assertEqual(float_compare(project_so_3_stat['amount_untaxed_invoiced'], self.so_line_deliver_manual_project.price_unit * self.so_line_deliver_manual_project.qty_delivered, precision_rounding=rounding), 0, "The invoiced amount of the project from SO3 should be the delivered quantity * the unit price")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['amount_untaxed_to_invoice'], precision_rounding=rounding), "The amount to invoice of the project from SO3 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_3_stat['timesheet_unit_amount'], project_so_3_timesheet_sold_unit, precision_rounding=rounding), 0, "The timesheet unit amount of the project from SO3")
|
||||
self.assertEqual(float_compare(project_so_3_stat['timesheet_cost'], project_so_3_timesheet_cost, precision_rounding=rounding), 0, "The timesheet cost of the project from SO3")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO3 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_3_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the project from SO3 should be 0.0")
|
||||
|
||||
# global project should be totally invoiced
|
||||
project_global_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', self.project_global.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_global_timesheet_cost = timesheet7.amount + timesheet8.amount
|
||||
project_global_timesheet_unit = timesheet7.unit_amount + timesheet8.unit_amount
|
||||
project_global_invoiced = self.so_line_order_task.price_unit * self.so_line_order_task.product_uom_qty + self.so_line_deliver_task.price_unit * timesheet7.unit_amount
|
||||
self.assertEqual(float_compare(project_global_stat['amount_untaxed_invoiced'], project_global_invoiced, precision_rounding=rounding), 0, "The invoiced amount of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['amount_untaxed_to_invoice'], precision_rounding=rounding), "The amount to invoice of global project should take the task in 'oredered qty' and the delivered timesheets into account")
|
||||
self.assertEqual(float_compare(project_global_stat['timesheet_unit_amount'], project_global_timesheet_unit, precision_rounding=rounding), 0, "The timesheet unit amount of the global project should include all timesheet")
|
||||
self.assertEqual(float_compare(project_global_stat['timesheet_cost'], project_global_timesheet_cost, precision_rounding=rounding), 0, "The timesheet cost of the global project should include all timesheet")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the global project should be 0.0")
|
||||
|
||||
# simulate the auto creation of the SO line for expense, like we confirm a vendor bill.
|
||||
so_line_expense = self.env['sale.order.line'].create({
|
||||
@@ -362,7 +453,7 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.env['project.profitability.report'].flush()
|
||||
|
||||
# deliver project should now have expense cost, and expense to reinvoice as there is a still open sales order linked to the AA1
|
||||
project_so_1_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_1.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced'], ['project_id'])[0]
|
||||
project_so_1_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_1.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_1_timesheet_cost = timesheet1.amount + timesheet3.amount + timesheet5.amount
|
||||
project_so_1_timesheet_sold_unit = timesheet1.unit_amount + timesheet3.unit_amount + timesheet5.unit_amount
|
||||
self.assertEqual(float_compare(project_so_1_stat['amount_untaxed_invoiced'], self.so_line_deliver_project.price_unit * project_so_1_timesheet_sold_unit, precision_rounding=rounding), 0, "The invoiced amount of the project from SO1 should only include timesheet linked to task")
|
||||
@@ -372,9 +463,10 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.assertEqual(float_compare(project_so_1_stat['expense_amount_untaxed_to_invoice'], -1 * expense1.amount, precision_rounding=rounding), 0, "The expense cost to reinvoice of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_1_stat['expense_cost'], expense1.amount, precision_rounding=rounding), 0, "The expense cost of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_1_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the deliver project should be 0.0")
|
||||
|
||||
# order project is not impacted by the expenses
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_2.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced'], ['project_id'])[0]
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', project_so_2.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_so_2_timesheet_cost = timesheet2.amount + timesheet4.amount + timesheet6.amount
|
||||
project_so_2_timesheet_sold_unit = timesheet2.unit_amount + timesheet4.unit_amount + timesheet6.unit_amount
|
||||
self.assertEqual(float_compare(project_so_2_stat['amount_untaxed_invoiced'], self.so_line_order_project.price_unit * self.so_line_order_project.product_uom_qty, precision_rounding=rounding), 0, "The invoiced amount should be the one from the SO line, as we are in ordered quantity")
|
||||
@@ -384,9 +476,10 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_cost'], precision_rounding=rounding), "The expense cost of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the project from SO2 should be 0.0")
|
||||
|
||||
# global project should have an expense, but not reinvoiceable
|
||||
project_global_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', self.project_global.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced'], ['project_id'])[0]
|
||||
project_global_stat = self.env['project.profitability.report'].read_group([('project_id', 'in', self.project_global.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'], ['project_id'])[0]
|
||||
project_global_timesheet_cost = timesheet7.amount + timesheet8.amount
|
||||
project_global_timesheet_unit = timesheet7.unit_amount + timesheet8.unit_amount
|
||||
project_global_invoiced = self.so_line_order_task.price_unit * self.so_line_order_task.product_uom_qty + self.so_line_deliver_task.price_unit * timesheet7.unit_amount
|
||||
@@ -397,3 +490,338 @@ class TestReporting(TestCommonSaleTimesheet):
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding), "The expense cost to reinvoice of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding), "The expense invoiced amount of the project from SO1 should be 0.0")
|
||||
self.assertEqual(float_compare(project_global_stat['expense_cost'], expense2.amount, precision_rounding=rounding), 0, "The expense cost of the global project should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_global_stat['other_revenues'], precision_rounding=rounding), "The other revenues of the global project should be 0.0")
|
||||
|
||||
def test_reversed_downpayment(self):
|
||||
# this test suppose everything is in the same currency as the current one
|
||||
currency = self.env.company.currency_id
|
||||
rounding = currency.rounding
|
||||
|
||||
self.sale_order_2.action_confirm()
|
||||
context = {
|
||||
'active_model': 'sale.order',
|
||||
'active_ids': self.sale_order_2.ids,
|
||||
'active_id': self.sale_order_2.id,
|
||||
'default_journal_id': self.company_data['default_journal_sale'].id,
|
||||
'open_invoices': True,
|
||||
}
|
||||
# Let's do an invoice for a deposit of 100
|
||||
downpayment = self.env['sale.advance.payment.inv'].with_context(context).create({
|
||||
'advance_payment_method': 'percentage',
|
||||
'amount': 10,
|
||||
})
|
||||
action_invoice = downpayment.create_invoices()
|
||||
invoice_id = action_invoice['res_id']
|
||||
invoice_downpayment = self.env['account.move'].browse(invoice_id)
|
||||
invoice_downpayment.action_post()
|
||||
posted_invoice_res_ids = [invoice_id]
|
||||
downpayment2 = self.env['sale.advance.payment.inv'].with_context(context).create({
|
||||
'advance_payment_method': 'percentage',
|
||||
'amount': 25,
|
||||
})
|
||||
action_invoice = downpayment2.create_invoices()
|
||||
invoice_downpayment2 = self.env['account.move'].search(expression.AND([action_invoice['domain'], [('id', 'not in', posted_invoice_res_ids)]]))
|
||||
invoice_downpayment2.action_post()
|
||||
posted_invoice_res_ids += invoice_downpayment2.ids
|
||||
milestone_to_invoice = self.so_line_order_project.price_unit * self.so_line_order_project.qty_to_invoice
|
||||
timesheets_to_invoice = self.so_line_order_task.price_unit * self.so_line_order_task.qty_to_invoice
|
||||
total_product_price = milestone_to_invoice + timesheets_to_invoice
|
||||
credit_note_wizard = self.env['account.move.reversal'].with_context({
|
||||
'active_ids': invoice_downpayment2.ids,
|
||||
'active_id': invoice_downpayment2.id,
|
||||
'active_model': 'account.move'
|
||||
}).create({
|
||||
'refund_method': 'refund',
|
||||
'reason': 'reason test create',
|
||||
})
|
||||
action_moves = credit_note_wizard.reverse_moves()
|
||||
credit_id = action_moves['res_id']
|
||||
invoice_credit = self.env['account.move'].browse(credit_id)
|
||||
invoice_credit.action_post()
|
||||
posted_invoice_res_ids += invoice_credit.ids
|
||||
project_so_2 = self.so_line_order_project.project_id
|
||||
task_so_2 = self.so_line_order_project.task_id
|
||||
task_in_global_2 = self.so_line_order_task.task_id
|
||||
|
||||
self.env['project.profitability.report'].flush()
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group(
|
||||
[('project_id', 'in', project_so_2.ids)],
|
||||
['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'],
|
||||
['project_id']
|
||||
)[0]
|
||||
self.assertEqual(float_compare(project_so_2_stat['amount_untaxed_invoiced'], 0.1 * total_product_price, precision_rounding=rounding), 0,
|
||||
"The invoiced amount is the amount of downpayments not reversed")
|
||||
self.assertEqual(float_compare(project_so_2_stat['amount_untaxed_to_invoice'], milestone_to_invoice - 0.1 * total_product_price, precision_rounding=rounding), 0,
|
||||
"The amount to invoice is the milestone product minus the downpayment not reversed")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['timesheet_unit_amount'], precision_rounding=rounding),
|
||||
"The timesheet unit amount of the project from SO2 is 0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['timesheet_cost'], precision_rounding=rounding),
|
||||
"The timesheet cost of the project from SO2 is 0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding),
|
||||
"The expense cost to reinvoice of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding),
|
||||
"The expense invoiced amount of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_cost'], precision_rounding=rounding),
|
||||
"The expense cost of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['other_revenues'], precision_rounding=rounding),
|
||||
"The other revenues of the project from SO2 should be 0.0")
|
||||
|
||||
# logged some timesheets: on project only, then on tasks with different employees
|
||||
timesheet2 = self._log_timesheet_user(project_so_2, 2)
|
||||
timesheet4 = self._log_timesheet_user(project_so_2, 3, task_so_2)
|
||||
timesheet6 = self._log_timesheet_manager(project_so_2, 2, task_so_2)
|
||||
timesheet8 = self._log_timesheet_manager(self.project_global, 3, task_in_global_2)
|
||||
|
||||
# simulate the auto creation of the SO line for expense, like we confirm a vendor bill.
|
||||
so_line_expense = self.env['sale.order.line'].create({
|
||||
'name': self.product_expense.name,
|
||||
'product_id': self.product_expense.id,
|
||||
'product_uom_qty': 0.0,
|
||||
'product_uom': self.product_expense.uom_id.id,
|
||||
'price_unit': self.product_expense.list_price, # reinvoice at sales price
|
||||
'order_id': self.sale_order_2.id,
|
||||
'is_expense': True,
|
||||
})
|
||||
|
||||
expense1 = self.env['account.analytic.line'].create({
|
||||
'name': 'expense on project_so_2',
|
||||
'account_id': project_so_2.analytic_account_id.id,
|
||||
'so_line': so_line_expense.id,
|
||||
'employee_id': self.employee_user.id,
|
||||
'unit_amount': 4,
|
||||
'amount': 4 * self.product_expense.list_price * -1,
|
||||
'product_id': self.product_expense.id,
|
||||
'product_uom_id': self.product_expense.uom_id.id,
|
||||
})
|
||||
|
||||
self.env['project.profitability.report'].flush()
|
||||
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group(
|
||||
[('project_id', 'in', project_so_2.ids)],
|
||||
['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'],
|
||||
['project_id']
|
||||
)[0]
|
||||
project_so_2_timesheet_cost = timesheet2.amount + timesheet4.amount + timesheet6.amount
|
||||
project_so_2_timesheet_sold_unit = timesheet2.unit_amount + timesheet4.unit_amount + timesheet6.unit_amount
|
||||
self.assertEqual(float_compare(project_so_2_stat['amount_untaxed_invoiced'], 0.1 * total_product_price, precision_rounding=rounding), 0,
|
||||
"The invoiced amount of the project from SO2 should only include downpayment not reversed")
|
||||
self.assertEqual(float_compare(project_so_2_stat['amount_untaxed_to_invoice'], milestone_to_invoice - 0.1 * total_product_price, precision_rounding=rounding), 0,
|
||||
"The amount to invoice of the project from SO2 should include the milestone to invoice minus the downpayment")
|
||||
self.assertEqual(float_compare(project_so_2_stat['timesheet_unit_amount'], project_so_2_timesheet_sold_unit, precision_rounding=rounding), 0,
|
||||
"The timesheet unit amount of the project from SO2 should include all timesheet in project")
|
||||
self.assertEqual(float_compare(project_so_2_stat['timesheet_cost'], project_so_2_timesheet_cost, precision_rounding=rounding), 0,
|
||||
"The timesheet cost of the project from SO2 should include all timesheet")
|
||||
self.assertEqual(float_compare(project_so_2_stat['expense_amount_untaxed_to_invoice'], -expense1.amount, precision_rounding=rounding), 0,
|
||||
"The expense cost to reinvoice of the project from SO2 should be the expense amount")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding),
|
||||
"The expense invoiced amount of the project from SO2 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_2_stat['expense_cost'], expense1.amount, precision_rounding=rounding), 0,
|
||||
"The expense cost of the project from SO1 should be expense amount")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['other_revenues'], precision_rounding=rounding),
|
||||
"The other revenues of the project from SO2 should be 0.0")
|
||||
|
||||
# invoice the Sales Order SO2
|
||||
context = {
|
||||
"active_model": 'sale.order',
|
||||
"active_ids": self.sale_order_2.ids,
|
||||
"active_id": self.sale_order_2.id,
|
||||
'open_invoices': True,
|
||||
'mail_notrack': True,
|
||||
}
|
||||
payment = self.env['sale.advance.payment.inv'].with_context(mail_notrack=True).create({
|
||||
'advance_payment_method': 'delivered',
|
||||
})
|
||||
action_invoice = payment.with_context(context).create_invoices()
|
||||
invoice_payment = self.env['account.move'].search(expression.AND([action_invoice['domain'], [('id', 'not in', posted_invoice_res_ids)]]))
|
||||
invoice_payment.action_post()
|
||||
self.env['project.profitability.report'].flush()
|
||||
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group(
|
||||
[('project_id', 'in', project_so_2.ids)],
|
||||
['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'],
|
||||
['project_id']
|
||||
)[0]
|
||||
self.assertEqual(float_compare(project_so_2_stat['amount_untaxed_invoiced'], milestone_to_invoice, precision_rounding=rounding), 0,
|
||||
"The invoiced amount of the project from SO2 should only include timesheet linked to task")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['amount_untaxed_to_invoice'], precision_rounding=rounding),
|
||||
"The amount to invoice of the project from SO2 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_2_stat['timesheet_unit_amount'], project_so_2_timesheet_sold_unit, precision_rounding=rounding), 0,
|
||||
"The timesheet unit amount of the project from SO2 should include all timesheet in project")
|
||||
self.assertEqual(float_compare(project_so_2_stat['timesheet_cost'], project_so_2_timesheet_cost, precision_rounding=rounding), 0,
|
||||
"The timesheet cost of the project from SO2 should include all timesheet")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding),
|
||||
"The expense to invoice amount of the project from SO2 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_2_stat['expense_amount_untaxed_invoiced'], -1 * expense1.amount, precision_rounding=rounding), 0,
|
||||
"The expense cost reinvoiced of the project from SO2 should be the expense amount")
|
||||
self.assertEqual(float_compare(project_so_2_stat['expense_cost'], expense1.amount, precision_rounding=rounding), 0,
|
||||
"The expense cost of the project from SO2 should be the expense amount")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['other_revenues'], precision_rounding=rounding),
|
||||
"The other revenues of the project from SO2 should be 0.0")
|
||||
|
||||
credit_note_wizard = self.env['account.move.reversal'].with_context({
|
||||
'active_ids': invoice_payment.ids,
|
||||
'active_id': invoice_payment.id,
|
||||
'active_model': 'account.move'
|
||||
}).create({
|
||||
'refund_method': 'refund',
|
||||
'reason': 'reason test create',
|
||||
})
|
||||
action_moves = credit_note_wizard.reverse_moves()
|
||||
credit_id = action_moves['res_id']
|
||||
invoice_credit = self.env['account.move'].browse(credit_id)
|
||||
invoice_credit.action_post()
|
||||
self.env['project.profitability.report'].flush()
|
||||
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group(
|
||||
[('project_id', 'in', project_so_2.ids)],
|
||||
['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'],
|
||||
['project_id']
|
||||
)[0]
|
||||
self.assertEqual(float_compare(project_so_2_stat['amount_untaxed_invoiced'], 0.1 * total_product_price, precision_rounding=rounding), 0,
|
||||
"The invoiced amount of the project from SO2 should only include downpayment not reversed")
|
||||
self.assertEqual(float_compare(project_so_2_stat['amount_untaxed_to_invoice'], milestone_to_invoice - 0.1 * total_product_price, precision_rounding=rounding), 0,
|
||||
"The amount to invoice of the project from SO2 should include the milestone to invoice minus the downpayment")
|
||||
self.assertEqual(float_compare(project_so_2_stat['timesheet_unit_amount'], project_so_2_timesheet_sold_unit, precision_rounding=rounding), 0,
|
||||
"The timesheet unit amount of the project from SO2 should include all timesheet in project")
|
||||
self.assertEqual(float_compare(project_so_2_stat['timesheet_cost'], project_so_2_timesheet_cost, precision_rounding=rounding), 0,
|
||||
"The timesheet cost of the project from SO2 should include all timesheet")
|
||||
self.assertEqual(float_compare(project_so_2_stat['expense_amount_untaxed_to_invoice'], -expense1.amount, precision_rounding=rounding), 0,
|
||||
"The expense cost to reinvoice of the project from SO2 should be the expense amount")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding),
|
||||
"The expense invoiced amount of the project from SO2 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_2_stat['expense_cost'], expense1.amount, precision_rounding=rounding), 0,
|
||||
"The expense cost of the project from SO1 should be expense amount")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['other_revenues'], precision_rounding=rounding),
|
||||
"The other revenues of the project from SO2 should be 0.0")
|
||||
|
||||
def test_milestone_no_tracking(self):
|
||||
# this test suppose everything is in the same currency as the current one
|
||||
currency = self.env.company.currency_id
|
||||
rounding = currency.rounding
|
||||
so_line_deliver_no_task = self.env['sale.order.line'].create({
|
||||
'name': self.product_delivery_manual1.name,
|
||||
'product_id': self.product_delivery_manual1.id,
|
||||
'product_uom_qty': 50,
|
||||
'product_uom': self.product_delivery_manual1.uom_id.id,
|
||||
'price_unit': self.product_delivery_manual1.list_price,
|
||||
'order_id': self.sale_order_2.id,
|
||||
})
|
||||
so_line_deliver_no_task.write({'qty_delivered': 1.0})
|
||||
self.sale_order_2.action_confirm()
|
||||
milestone_to_invoice = self.so_line_order_project.price_unit * self.so_line_order_project.qty_to_invoice
|
||||
milestone_no_task = so_line_deliver_no_task.price_unit * so_line_deliver_no_task.qty_to_invoice
|
||||
self.env['project.profitability.report'].flush()
|
||||
|
||||
project_so_2 = self.so_line_order_project.project_id
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group(
|
||||
[('project_id', 'in', project_so_2.ids)],
|
||||
['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'],
|
||||
['project_id']
|
||||
)[0]
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['amount_untaxed_invoiced'], precision_rounding=rounding),
|
||||
"The invoiced amount of the project from SO2 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_2_stat['amount_untaxed_to_invoice'], milestone_to_invoice + milestone_no_task, precision_rounding=rounding), 0,
|
||||
"The amount to invoice of the project from SO2 should include the milestone to invoice")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['timesheet_unit_amount'], precision_rounding=rounding),
|
||||
"The timesheet unit amount of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['timesheet_cost'], precision_rounding=rounding),
|
||||
"The timesheet cost of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding),
|
||||
"The expense cost to reinvoice of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding),
|
||||
"The expense invoiced amount of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_cost'], precision_rounding=rounding),
|
||||
"The expense cost of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['other_revenues'], precision_rounding=rounding),
|
||||
"The other revenues of the project from SO2 should be 0.0")
|
||||
|
||||
task_using_milestone_not_tracked = self.env['project.task'].create({
|
||||
'name': 'Task with milestone not tracked',
|
||||
'project_id': project_so_2.id,
|
||||
'partner_id': project_so_2.partner_id.id,
|
||||
'sale_line_id': so_line_deliver_no_task.id,
|
||||
})
|
||||
self.env['project.profitability.report'].flush()
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group(
|
||||
[('project_id', 'in', project_so_2.ids)],
|
||||
['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'],
|
||||
['project_id']
|
||||
)[0]
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['amount_untaxed_invoiced'], precision_rounding=rounding),
|
||||
"The invoiced amount of the project from SO2 should be 0.0")
|
||||
self.assertEqual(float_compare(project_so_2_stat['amount_untaxed_to_invoice'], milestone_to_invoice + milestone_no_task, precision_rounding=rounding), 0,
|
||||
"The amount to invoice of the project from SO2 should include the milestone to invoice linked to the project or project's task")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['timesheet_unit_amount'], precision_rounding=rounding),
|
||||
"The timesheet unit amount of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['timesheet_cost'], precision_rounding=rounding),
|
||||
"The timesheet cost of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding),
|
||||
"The expense cost to reinvoice of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding),
|
||||
"The expense invoiced amount of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_cost'], precision_rounding=rounding),
|
||||
"The expense cost of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['other_revenues'], precision_rounding=rounding),
|
||||
"The other revenues of the project from SO2 should be 0.0")
|
||||
|
||||
timesheet = self._log_timesheet_user(project_so_2, 3, task_using_milestone_not_tracked)
|
||||
self.env['project.profitability.report'].flush()
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group(
|
||||
[('project_id', 'in', project_so_2.ids)],
|
||||
['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'],
|
||||
['project_id']
|
||||
)[0]
|
||||
project_so_2_timesheet_cost = timesheet.amount
|
||||
project_so_2_timesheet_sold_unit = timesheet.unit_amount
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['amount_untaxed_invoiced'], precision_rounding=rounding),
|
||||
"The invoiced amount of the project from SO2 should only include downpayment not reversed")
|
||||
self.assertEqual(float_compare(project_so_2_stat['amount_untaxed_to_invoice'], milestone_to_invoice + milestone_no_task, precision_rounding=rounding), 0,
|
||||
"The amount to invoice of the project from SO2 should include the milestone to invoice minus the downpayment")
|
||||
self.assertEqual(float_compare(project_so_2_stat['timesheet_unit_amount'], project_so_2_timesheet_sold_unit, precision_rounding=rounding), 0,
|
||||
"The timesheet unit amount of the project from SO2 should include all timesheet in project")
|
||||
self.assertEqual(float_compare(project_so_2_stat['timesheet_cost'], project_so_2_timesheet_cost, precision_rounding=rounding), 0,
|
||||
"The timesheet cost of the project from SO2 should include all timesheet")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding),
|
||||
"The expense cost to reinvoice of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding),
|
||||
"The expense invoiced amount of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_cost'], precision_rounding=rounding),
|
||||
"The expense cost of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['other_revenues'], precision_rounding=rounding),
|
||||
"The other revenues of the project from SO2 should be 0.0")
|
||||
# invoice the Sales Order SO2
|
||||
context = {
|
||||
"active_model": 'sale.order',
|
||||
"active_ids": self.sale_order_2.ids,
|
||||
"active_id": self.sale_order_2.id,
|
||||
'open_invoices': True,
|
||||
'mail_notrack': True,
|
||||
}
|
||||
payment = self.env['sale.advance.payment.inv'].with_context(mail_notrack=True).create({
|
||||
'advance_payment_method': 'delivered',
|
||||
})
|
||||
action_invoice = payment.with_context(context).create_invoices()
|
||||
invoice_payment = self.env['account.move'].browse(action_invoice['res_id'])
|
||||
invoice_payment.action_post()
|
||||
self.env['project.profitability.report'].flush()
|
||||
project_so_2_stat = self.env['project.profitability.report'].read_group(
|
||||
[('project_id', 'in', project_so_2.ids)],
|
||||
['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_unit_amount', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_to_invoice', 'expense_amount_untaxed_invoiced', 'other_revenues'],
|
||||
['project_id']
|
||||
)[0]
|
||||
self.assertEqual(float_compare(project_so_2_stat['amount_untaxed_invoiced'], milestone_to_invoice + milestone_no_task, precision_rounding=rounding), 0,
|
||||
"The invoiced amount of the project from SO2 should include the milestone invoiced")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['amount_untaxed_to_invoice'], precision_rounding=rounding),
|
||||
"The amount to invoice of the project from SO2 should include the milestone to invoice")
|
||||
self.assertEqual(float_compare(project_so_2_stat['timesheet_unit_amount'], project_so_2_timesheet_sold_unit, precision_rounding=rounding), 0,
|
||||
"The timesheet unit amount of the project from SO2 should include all timesheet in project")
|
||||
self.assertEqual(float_compare(project_so_2_stat['timesheet_cost'], project_so_2_timesheet_cost, precision_rounding=rounding), 0,
|
||||
"The timesheet cost of the project from SO2 should include all timesheet")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_to_invoice'], precision_rounding=rounding),
|
||||
"The expense cost to reinvoice of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_amount_untaxed_invoiced'], precision_rounding=rounding),
|
||||
"The expense invoiced amount of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['expense_cost'], precision_rounding=rounding),
|
||||
"The expense cost of the project from SO2 should be 0.0")
|
||||
self.assertTrue(float_is_zero(project_so_2_stat['other_revenues'], precision_rounding=rounding),
|
||||
"The other revenues of the project from SO2 should be 0.0")
|
||||
|
||||
@@ -241,6 +241,14 @@
|
||||
Re-invoiced costs
|
||||
</td>
|
||||
</tr>
|
||||
<tr t-if="display_cost & (dashboard['profit']['expense_amount_untaxed_to_invoice'] != 0)">
|
||||
<td class="o_timesheet_plan_dashboard_cell">
|
||||
<t t-esc="dashboard['profit']['expense_amount_untaxed_to_invoice']" t-options='{"widget": "monetary", "display_currency": currency}'/>
|
||||
</td>
|
||||
<td title="Costs from expenses that still need to be reinvoiced to your customer (provided that the Analytic Account of the Project was set on the Expense).">
|
||||
To re-invoice costs
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="o_timesheet_plan_dashboard_total">
|
||||
<b>
|
||||
|
||||
@@ -1270,17 +1270,16 @@ class StockMove(models.Model):
|
||||
|
||||
# Find a candidate move line to update or create a new one.
|
||||
for reserved_quant, quantity in quants:
|
||||
to_update = self.move_line_ids.filtered(lambda ml: ml._reservation_is_updatable(quantity, reserved_quant))
|
||||
to_update = next((line for line in self.move_line_ids if line._reservation_is_updatable(quantity, reserved_quant)), False)
|
||||
if to_update:
|
||||
uom_quantity = self.product_id.uom_id._compute_quantity(quantity, to_update[0].product_uom_id, rounding_method='HALF-UP')
|
||||
uom_quantity = self.product_id.uom_id._compute_quantity(quantity, to_update.product_uom_id, rounding_method='HALF-UP')
|
||||
uom_quantity = float_round(uom_quantity, precision_digits=rounding)
|
||||
uom_quantity_back_to_product_uom = to_update[0].product_uom_id._compute_quantity(uom_quantity, self.product_id.uom_id, rounding_method='HALF-UP')
|
||||
uom_quantity_back_to_product_uom = to_update.product_uom_id._compute_quantity(uom_quantity, self.product_id.uom_id, rounding_method='HALF-UP')
|
||||
if to_update and float_compare(quantity, uom_quantity_back_to_product_uom, precision_digits=rounding) == 0:
|
||||
to_update[0].with_context(bypass_reservation_update=True).product_uom_qty += uom_quantity
|
||||
to_update.with_context(bypass_reservation_update=True).product_uom_qty += uom_quantity
|
||||
else:
|
||||
if self.product_id.tracking == 'serial':
|
||||
for i in range(0, int(quantity)):
|
||||
self.env['stock.move.line'].create(self._prepare_move_line_vals(quantity=1, reserved_quant=reserved_quant))
|
||||
self.env['stock.move.line'].create([self._prepare_move_line_vals(quantity=1, reserved_quant=reserved_quant) for i in range(int(quantity))])
|
||||
else:
|
||||
self.env['stock.move.line'].create(self._prepare_move_line_vals(quantity=quantity, reserved_quant=reserved_quant))
|
||||
return taken_quantity
|
||||
@@ -1652,7 +1651,7 @@ class StockMove(models.Model):
|
||||
else:
|
||||
moves_state_to_write['confirmed'].add(move.id)
|
||||
for state, moves_ids in moves_state_to_write.items():
|
||||
self.browse(moves_ids).write({'state': state})
|
||||
self.browse(moves_ids).filtered(lambda m: m.state != state).state = state
|
||||
|
||||
@api.model
|
||||
def _consuming_picking_types(self):
|
||||
|
||||
@@ -77,7 +77,7 @@ class ProductionLot(models.Model):
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
self._check_create()
|
||||
return super(ProductionLot, self).create(vals_list)
|
||||
return super(ProductionLot, self.with_context(mail_create_nosubscribe=True)).create(vals_list)
|
||||
|
||||
def write(self, vals):
|
||||
if 'company_id' in vals:
|
||||
|
||||
@@ -460,7 +460,7 @@
|
||||
<field name="name" string="Transfer" filter_domain="['|', ('name', 'ilike', self), ('origin', 'ilike', self)]"/>
|
||||
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
|
||||
<field name="origin"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_id" filter_domain="['|', '|', ('product_id.default_code', 'ilike', self), ('product_id.name', 'ilike', self), ('product_id.barcode', 'ilike', self)]"/>
|
||||
<field name="picking_type_id"/>
|
||||
<filter name="my_transfers" string="My Transfers" domain="[('user_id', '=', uid)]"/>
|
||||
<filter string="Starred" name="starred" domain="[('priority', '=', '1')]"/>
|
||||
|
||||
@@ -1660,8 +1660,11 @@ var SnippetsMenu = Widget.extend({
|
||||
* when the DOM elements must be in an editable environment to be
|
||||
* considered (@see noCheck), this is true if the DOM elements'
|
||||
* parent must also be in an editable environment to be considered.
|
||||
* @param {string} excludeParent
|
||||
* jQuery selector that the parents of DOM elements must *not* match
|
||||
* to be considered as potential snippet.
|
||||
*/
|
||||
_computeSelectorFunctions: function (selector, exclude, target, noCheck, isChildren) {
|
||||
_computeSelectorFunctions: function (selector, exclude, target, noCheck, isChildren, excludeParent) {
|
||||
var self = this;
|
||||
|
||||
exclude += `${exclude && ', '}.o_snippet_not_selectable`;
|
||||
@@ -1675,6 +1678,12 @@ var SnippetsMenu = Widget.extend({
|
||||
return oldFilter.apply(this) && $(this).find(target).length !== 0;
|
||||
};
|
||||
}
|
||||
if (excludeParent) {
|
||||
const oldFilter = filterFunc;
|
||||
filterFunc = function () {
|
||||
return oldFilter.apply(this) && !$(this).parent().is(excludeParent);
|
||||
};
|
||||
}
|
||||
|
||||
// Prepare the functions
|
||||
var functions = {
|
||||
@@ -1735,10 +1744,12 @@ var SnippetsMenu = Widget.extend({
|
||||
this.templateOptions = [];
|
||||
var selectors = [];
|
||||
var $styles = $html.find('[data-selector]');
|
||||
const snippetAdditionDropIn = $styles.filter('#so_snippet_addition').data('drop-in');
|
||||
$styles.each(function () {
|
||||
var $style = $(this);
|
||||
var selector = $style.data('selector');
|
||||
var exclude = $style.data('exclude') || '';
|
||||
const excludeParent = $style.attr('id') === "so_content_addition" ? snippetAdditionDropIn : '';
|
||||
var target = $style.data('target');
|
||||
var noCheck = $style.data('no-check');
|
||||
var optionID = $style.data('js') || $style.data('option-name'); // used in tour js as selector
|
||||
@@ -1749,7 +1760,7 @@ var SnippetsMenu = Widget.extend({
|
||||
'base_target': target,
|
||||
'selector': self._computeSelectorFunctions(selector, exclude, target, noCheck),
|
||||
'$el': $style,
|
||||
'drop-near': $style.data('drop-near') && self._computeSelectorFunctions($style.data('drop-near'), '', false, noCheck, true),
|
||||
'drop-near': $style.data('drop-near') && self._computeSelectorFunctions($style.data('drop-near'), '', false, noCheck, true, excludeParent),
|
||||
'drop-in': $style.data('drop-in') && self._computeSelectorFunctions($style.data('drop-in'), '', false, noCheck),
|
||||
'data': _.extend({string: $style.attr('string')}, $style.data()),
|
||||
};
|
||||
|
||||
@@ -173,7 +173,7 @@ class Slide(models.Model):
|
||||
slide_resource_ids = fields.One2many('slide.slide.resource', 'slide_id', string="Additional Resource for this slide")
|
||||
slide_resource_downloadable = fields.Boolean('Allow Download', default=True, help="Allow the user to download the content of the slide.")
|
||||
mime_type = fields.Char('Mime-type')
|
||||
html_content = fields.Html("HTML Content", help="Custom HTML content for slides of type 'Web Page'.", translate=True, sanitize_form=False)
|
||||
html_content = fields.Html("HTML Content", help="Custom HTML content for slides of type 'Web Page'.", translate=True, sanitize_attributes=False, sanitize_form=False)
|
||||
# website
|
||||
website_id = fields.Many2one(related='channel_id.website_id', readonly=True)
|
||||
date_published = fields.Datetime('Publish Date', readonly=True, tracking=1)
|
||||
|
||||
Reference in New Issue
Block a user