[PATCH] Upstream patch

This commit is contained in:
Parthiv Patel
2021-08-21 11:55:06 +00:00
parent a2d7d22e03
commit 7dac4a4e19
17 changed files with 260 additions and 34 deletions
+2 -2
View File
@@ -3603,7 +3603,7 @@ class AccountMoveLine(models.Model):
# ONCHANGE METHODS
# -------------------------------------------------------------------------
@api.onchange('amount_currency', 'currency_id', 'debit', 'credit', 'tax_ids', 'account_id', 'price_unit')
@api.onchange('amount_currency', 'currency_id', 'debit', 'credit', 'tax_ids', 'account_id', 'price_unit', 'quantity')
def _onchange_mark_recompute_taxes(self):
''' Recompute the dynamic onchange based on taxes.
If the edited line is a tax line, don't recompute anything as the user must be able to
@@ -4907,7 +4907,7 @@ class AccountMoveLine(models.Model):
'move_id': move_line.id,
'user_id': move_line.move_id.invoice_user_id.id or self._uid,
'partner_id': move_line.partner_id.id,
'company_id': move_line.analytic_account_id.company_id.id or self.env.company.id,
'company_id': move_line.analytic_account_id.company_id.id or move_line.move_id.company_id.id,
})
return result
+3 -1
View File
@@ -183,7 +183,9 @@ class AccountTax(models.Model):
@api.returns('self', lambda value: value.id)
def copy(self, default=None):
default = dict(default or {}, name=_("%s (Copy)", self.name))
default = dict(default or {})
if 'name' not in default:
default['name'] = _("%s (Copy)") % self.name
return super(AccountTax, self).copy(default=default)
def name_get(self):
+4 -3
View File
@@ -168,10 +168,11 @@ class AccountFiscalPosition(models.Model):
# This can be easily overridden to apply more complex fiscal rules
PartnerObj = self.env['res.partner']
partner = PartnerObj.browse(partner_id)
delivery = PartnerObj.browse(delivery_id)
# If partner and delivery have the same vat prefix, use invoicing
if not delivery or (delivery.vat and partner.vat and delivery.vat[:2] == partner.vat[:2]):
# if no delivery use invoicing
if delivery_id:
delivery = PartnerObj.browse(delivery_id)
else:
delivery = partner
# partner manually set fiscal position always win
@@ -1,6 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<flectra>
<data noupdate="0">
<!--
There are 4 groups
The way the groups work is different when `account_accountant` is installed or not.
Invoicing only:
```
group_account_invoice ⇨ group_account_manager (only those two should be used)
group_account_readonly ⇨ group_account_user (those two are only visible in debug)
```
Invoicing + Accounting:
```
group_account_invoice ⬂
group_account_readonly ⇨ group_account_user ⇨ group_account_manager
```
`group_account_user` is almost (a bit more than) the sum of `group_account_invoice` and `group_account_readonly`
`group_account_manager` is the king (except when Consolidation is installed, then there is a super-king)
`group_account_invoice` can create/edit invoices, refunds, payments, etc but cannot see accounting related stuff (journal entries, reports, reconciliation,...)
`group_account_readonly` can see (and only see) everything, including the journal entries, advanced configuration, reports
`group_account_user` is the accountant: he can do everything except advanced config (accounting periods for instance)
`group_account_manager` can edit some config that `group_account_user` cannot do
When you have only Invoicing installed, only `group_account_invoice` and `group_account_manager` should be used. The others are giving a shallow access to Accounting features, but we want to remove that.
-->
<record model="ir.module.category" id="base.module_category_accounting_accounting">
<field name="description">Helps you handle your accounting needs, if you are not an accountant, we suggest you to install only the Invoicing.</field>
@@ -154,15 +154,3 @@ class TestFiscalPosition(common.SavepointCase):
mapped_taxes = self.fp2m.map_tax(self.src_tax)
self.assertEqual(mapped_taxes, self.dst1_tax | self.dst2_tax)
def test_30_fp_country_delivery(self):
"""
Customer is in Belgium
Delivery is in France
Check if fiscal position is France
"""
self.george.vat = False
self.assertEqual(
self.fp.get_fiscal_position(self.ben.id, self.george.id).id,
self.fr_b2c.id,
"FR B2C should be set")
+1 -1
View File
@@ -823,7 +823,7 @@
<filter string="Won" name="won" domain="['&amp;', ('active', '=', True), ('stage_id.is_won', '=', True)]"/>
<filter string="Lost" name="lost" domain="['&amp;', ('active', '=', False), ('probability', '=', 0)]"/>
<separator/>
<filter invisible="1" string="Overdue Opportunities" name="overdue_opp" domain="[('date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"/>
<filter invisible="1" string="Overdue Opportunities" name="overdue_opp" domain="['&amp;', ('date_closed', '=', False), ('date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"/>
<filter invisible="1" string="Late Activities" name="activities_overdue"
domain="[('activity_ids.date_deadline', '&lt;', context_today().strftime('%Y-%m-%d'))]"
help="Show all opportunities for which the next action date is before today"/>
+1 -1
View File
@@ -13,4 +13,4 @@ class AccountMove(models.Model):
@api.model
def _l10n_in_get_shipping_partner_gstin(self, shipping_partner):
return shipping_partner.l10n_in_shipping_gstin or shipping_partner.vat
return shipping_partner.l10n_in_shipping_gstin
+17 -7
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import api, fields, models, _
from flectra import api, fields, models, tools
class MrpRoutingWorkcenter(models.Model):
@@ -49,14 +49,24 @@ class MrpRoutingWorkcenter(models.Model):
for operation in manual_ops:
operation.time_cycle = operation.time_cycle_manual
for operation in self - manual_ops:
data = self.env['mrp.workorder'].read_group([
data = self.env['mrp.workorder'].search([
('operation_id', '=', operation.id),
('qty_produced', '>', 0),
('state', '=', 'done')], ['operation_id', 'duration', 'qty_produced'], ['operation_id'],
limit=operation.time_mode_batch)
count_data = dict((item['operation_id'][0], (item['duration'], item['qty_produced'])) for item in data)
if count_data.get(operation.id) and count_data[operation.id][1]:
operation.time_cycle = (count_data[operation.id][0] / count_data[operation.id][1]) * (operation.workcenter_id.capacity or 1.0)
('state', '=', 'done')],
limit=operation.time_mode_batch,
order="date_finished desc")
# To compute the time_cycle, we can take the total duration of previous operations
# but for the quantity, we will take in consideration the qty_produced like if the capacity was 1.
# So producing 50 in 00:10 with capacity 2, for the time_cycle, we assume it is 25 in 00:10
# When recomputing the expected duration, the capacity is used again to divide the qty to produce
# so that if we need 50 with capacity 2, it will compute the expected of 25 which is 00:10
total_duration = 0 # Can be 0 since it's not an invalid duration for BoM
cycle_number = 0 # Never 0 unless infinite item['workcenter_id'].capacity
for item in data:
total_duration += item['duration']
cycle_number += tools.float_round((item['qty_produced'] / item['workcenter_id'].capacity or 1.0), precision_digits=0, rounding_method='UP')
if cycle_number:
operation.time_cycle = total_duration / cycle_number
else:
operation.time_cycle = operation.time_cycle_manual
+53
View File
@@ -84,6 +84,20 @@ class TestMrpCommon(common2.TestStockCommon):
'time_stop': 5,
'time_efficiency': 80,
})
cls.workcenter_2 = cls.env['mrp.workcenter'].create({
'name': 'Simple Workcenter',
'capacity': 1,
'time_start': 0,
'time_stop': 0,
'time_efficiency': 100,
})
cls.workcenter_3 = cls.env['mrp.workcenter'].create({
'name': 'Double Workcenter',
'capacity': 2,
'time_start': 0,
'time_stop': 0,
'time_efficiency': 100,
})
cls.bom_1 = cls.env['mrp.bom'].create({
'product_id': cls.product_4.id,
@@ -129,6 +143,45 @@ class TestMrpCommon(common2.TestStockCommon):
(0, 0, {'product_id': cls.product_4.id, 'product_qty': 8}),
(0, 0, {'product_id': cls.product_2.id, 'product_qty': 12})
]})
cls.bom_4 = cls.env['mrp.bom'].create({
'product_id': cls.product_6.id,
'product_tmpl_id': cls.product_6.product_tmpl_id.id,
'consumption': 'flexible',
'product_qty': 1.0,
'operation_ids': [
(0, 0, {'name': 'Rub it gently with a cloth', 'workcenter_id': cls.workcenter_2.id,
'time_mode_batch': 1, 'time_mode': "auto", 'sequence': 1}),
],
'type': 'normal',
'bom_line_ids': [
(0, 0, {'product_id': cls.product_1.id, 'product_qty': 1}),
]})
cls.bom_5 = cls.env['mrp.bom'].create({
'product_id': cls.product_6.id,
'product_tmpl_id': cls.product_6.product_tmpl_id.id,
'consumption': 'flexible',
'product_qty': 1.0,
'operation_ids': [
(0, 0, {'name': 'Rub it gently with a cloth two at once', 'workcenter_id': cls.workcenter_3.id,
'time_mode_batch': 2, 'time_mode': "auto", 'sequence': 1}),
],
'type': 'normal',
'bom_line_ids': [
(0, 0, {'product_id': cls.product_1.id, 'product_qty': 1}),
]})
cls.bom_6 = cls.env['mrp.bom'].create({
'product_id': cls.product_6.id,
'product_tmpl_id': cls.product_6.product_tmpl_id.id,
'consumption': 'flexible',
'product_qty': 1.0,
'operation_ids': [
(0, 0, {'name': 'Rub it gently with a cloth two at once', 'workcenter_id': cls.workcenter_3.id,
'time_mode_batch': 1, 'time_mode': "auto", 'sequence': 1}),
],
'type': 'normal',
'bom_line_ids': [
(0, 0, {'product_id': cls.product_1.id, 'product_qty': 1}),
]})
cls.stock_location_14 = cls.env['stock.location'].create({
'name': 'Shelf 2',
+131
View File
@@ -2053,3 +2053,134 @@ class TestMrpOrder(TestMrpCommon):
mo4 = mo_form.save()
self.assertEqual(len(mo4.move_finished_ids), 1, 'Wrong number of finish product moves created')
self.assertEqual(mo4.move_finished_ids.product_id, product1, 'Wrong product to produce in finished product move')
def test_compute_tracked_time_1(self):
"""
Checks that the Duration Computation (`time_mode` of mrp.routing.workcenter) with value `auto` with Based On
(`time_mode_batch`) set to 1 actually compute the time based on the last 1 operation, and not more.
Create a first production in 15 minutes (expected should go from 60 to 15
Create a second one in 10 minutes (expected should NOT go from 15 to 12.5, it should go from 15 to 10)
"""
# First production, the default is 60 and there is 0 productions of that operation
production_form = Form(self.env['mrp.production'])
production_form.bom_id = self.bom_4
production = production_form.save()
self.assertEqual(production.workorder_ids[0].duration_expected, 60.0, "Default duration is 0+0+1*60.0")
production.action_confirm()
production.button_plan()
# Production planned, time to start, I produce all the 1 product
production_form.qty_producing = 1
with production_form.workorder_ids.edit(0) as wo:
wo.duration = 15 # in 15 minutes
production = production_form.save()
production.button_mark_done()
# It is saved and done, registered in the db. There are now 1 productions of that operation
# Same production, let's see what the duration_expected is, last prod was 15 minutes for 1 item
production_form = Form(self.env['mrp.production'])
production_form.bom_id = self.bom_4
production = production_form.save()
self.assertEqual(production.workorder_ids[0].duration_expected, 15.0, "Duration is now 0+0+1*15")
production.action_confirm()
production.button_plan()
# Production planned, time to start, I produce all the 1 product
production_form.qty_producing = 1
with production_form.workorder_ids.edit(0) as wo:
wo.duration = 10 # In 10 minutes this time
production = production_form.save()
production.button_mark_done()
# It is saved and done, registered in the db. There are now 2 productions of that operation
# Same production, let's see what the duration_expected is, last prod was 10 minutes for 1 item
# Total average time would be 12.5 but we compute the duration based on the last 1 item
production_form = Form(self.env['mrp.production'])
production_form.bom_id = self.bom_4
production = production_form.save()
self.assertNotEqual(production.workorder_ids[0].duration_expected, 12.5, "Duration expected is based on the last 1 production, not last 2")
self.assertEqual(production.workorder_ids[0].duration_expected, 10.0, "Duration is now 0+0+1*10")
def test_compute_tracked_time_2_under_capacity(self):
"""
Test that when tracking the 2 last production, if we make one with under capacity, and one with normal capacity,
the two are equivalent (1 done with capacity 2 in 10mn = 2 done with capacity 2 in 10mn)
"""
production_form = Form(self.env['mrp.production'])
production_form.bom_id = self.bom_5
production = production_form.save()
production.action_confirm()
production.button_plan()
# Production planned, time to start, I produce all the 1 product
production_form.qty_producing = 1
with production_form.workorder_ids.edit(0) as wo:
wo.duration = 10 # in 10 minutes
production = production_form.save()
production.button_mark_done()
# It is saved and done, registered in the db. There are now 1 productions of that operation
# Same production, let's see what the duration_expected is, last prod was 10 minutes for 1 item
production_form = Form(self.env['mrp.production'])
production_form.bom_id = self.bom_5
production_form.product_qty = 2 # We want to produce 2 items (the capacity) now
production = production_form.save()
self.assertNotEqual(production.workorder_ids[0].duration_expected, 20.0, "We made 1 item with capacity 2 in 10mn -> so 2 items shouldn't be double that")
self.assertEqual(production.workorder_ids[0].duration_expected, 10.0, "Producing 1 or 2 items with capacity 2 is the same duration")
production.action_confirm()
production.button_plan()
# Production planned, time to start, I produce all the 2 product
production_form.qty_producing = 2
with production_form.workorder_ids.edit(0) as wo:
wo.duration = 10 # In 10 minutes this time
production = production_form.save()
production.button_mark_done()
# It is saved and done, registered in the db. There are now 2 productions of that operation but they have the same duration
production_form = Form(self.env['mrp.production'])
production_form.bom_id = self.bom_5
production = production_form.save()
self.assertNotEqual(production.workorder_ids[0].duration_expected, 15, "Producing 1 or 2 in 10mn with capacity 2 take the same amount of time : 10mn")
self.assertEqual(production.workorder_ids[0].duration_expected, 10.0, "Duration is indeed (10+10)/2")
def test_capacity_duration_expected(self):
"""
Test that the duration expected is correctly computed when dealing with below or above capacity
1 -> 10mn
2 -> 10mn
3 -> 20mn
4 -> 20mn
5 -> 30mn
...
"""
production_form = Form(self.env['mrp.production'])
production_form.bom_id = self.bom_6
production = production_form.save()
production.action_confirm()
production.button_plan()
# Production planned, time to start, I produce all the 1 product
production_form.qty_producing = 1
with production_form.workorder_ids.edit(0) as wo:
wo.duration = 10 # in 10 minutes
production = production_form.save()
production.button_mark_done()
production_form = Form(self.env['mrp.production'])
production_form.bom_id = self.bom_6
production = production_form.save()
# production_form.product_qty = 1 [BY DEFAULT]
self.assertEqual(production.workorder_ids[0].duration_expected, 10.0, "Produce 1 with capacity 2, expected is 10mn for each run -> 10mn")
production_form.product_qty = 2
production = production_form.save()
self.assertEqual(production.workorder_ids[0].duration_expected, 10.0, "Produce 2 with capacity 2, expected is 10mn for each run -> 10mn")
production_form.product_qty = 3
production = production_form.save()
self.assertEqual(production.workorder_ids[0].duration_expected, 20.0, "Produce 3 with capacity 2, expected is 10mn for each run -> 20mn")
production_form.product_qty = 4
production = production_form.save()
self.assertEqual(production.workorder_ids[0].duration_expected, 20.0, "Produce 4 with capacity 2, expected is 10mn for each run -> 20mn")
production_form.product_qty = 5
production = production_form.save()
self.assertEqual(production.workorder_ids[0].duration_expected, 30.0, "Produce 5 with capacity 2, expected is 10mn for each run -> 30mn")
@@ -323,7 +323,7 @@ flectra.define('point_of_sale.PaymentScreen', function (require) {
const isPaymentSuccessful = await payment_terminal.send_payment_request(line.cid);
if (isPaymentSuccessful) {
line.set_payment_status('done');
line.can_be_reversed = this.payment_interface.supports_reversals;
line.can_be_reversed = payment_terminal.supports_reversals;
} else {
line.set_payment_status('retry');
}
+1 -1
View File
@@ -650,7 +650,7 @@
<div class="oe_title pr-0">
<h1 class="d-flex flex-row justify-content-between">
<field name="priority" widget="priority" class="mr-3"/>
<field name="name" class="o_task_name text-truncate" placeholder="Task Title..."/>
<field name="name" class="o_task_name text-truncate" placeholder="Task Title..." default_focus="1" />
<field name="kanban_state" widget="state_selection" class="ml-auto"/>
</h1>
</div>
+8 -1
View File
@@ -272,7 +272,7 @@ class PurchaseOrderLine(models.Model):
qty_received_method = fields.Selection(selection_add=[('stock_moves', 'Stock Moves')])
move_ids = fields.One2many('stock.move', 'purchase_line_id', string='Reservation', readonly=True, copy=False)
orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Orderpoint')
orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Orderpoint', copy=False)
move_dest_ids = fields.One2many('stock.move', 'created_purchase_line_id', 'Downstream Moves')
product_description_variants = fields.Char('Custom Description')
propagate_cancel = fields.Boolean('Propagate cancellation', default=True)
@@ -442,8 +442,15 @@ class PurchaseOrderLine(models.Model):
res.append(extra_move_vals)
return res
def _check_orderpoint_picking_type(self):
warehouse_loc = self.order_id.picking_type_id.warehouse_id.view_location_id
if self.orderpoint_id and not warehouse_loc.parent_path in self.orderpoint_id.location_id.parent_path:
raise UserError(_('For the product %s, the warehouse of the operation type (%s) is inconsistent with the location (%s) of the reordering rule (%s). Change the operation type or cancel the request for quotation.',
self.product_id.display_name, self.order_id.picking_type_id.display_name, self.orderpoint_id.location_id.display_name, self.orderpoint_id.display_name))
def _prepare_stock_move_vals(self, picking, price_unit, product_uom_qty, product_uom):
self.ensure_one()
self._check_orderpoint_picking_type()
product = self.product_id.with_context(lang=self.order_id.dest_address_id.lang or self.env.user.lang)
description_picking = product._get_description(self.order_id.picking_type_id)
if self.product_description_variants:
@@ -7,6 +7,7 @@ from datetime import timedelta as td
from flectra import SUPERUSER_ID
from flectra.tests import Form
from flectra.tests.common import SavepointCase
from flectra.exceptions import UserError
class TestReorderingRule(SavepointCase):
@@ -38,7 +39,8 @@ class TestReorderingRule(SavepointCase):
"""
warehouse_1 = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1)
warehouse_1.write({'reception_steps': 'two_steps'})
warehouse_2 = self.env['stock.warehouse'].create({'name': 'WH 2', 'code': 'WH2', 'company_id': self.env.company.id, 'partner_id': self.env.company.partner_id.id, 'reception_steps': 'one_step'})
# create reordering rule
orderpoint_form = Form(self.env['stock.warehouse.orderpoint'])
orderpoint_form.warehouse_id = warehouse_1
@@ -63,6 +65,12 @@ class TestReorderingRule(SavepointCase):
# Check purchase order created or not
purchase_order = self.env['purchase.order'].search([('partner_id', '=', self.partner.id)])
self.assertTrue(purchase_order, 'No purchase order created.')
# Check the picking type on the purchase order
purchase_order.picking_type_id = warehouse_2.in_type_id
with self.assertRaises(UserError):
purchase_order.button_confirm()
purchase_order.picking_type_id = warehouse_1.in_type_id
# On the po generated, the source document should be the name of the reordering rule
self.assertEqual(order_point.name, purchase_order.origin, 'Source document on purchase order should be the name of the reordering rule.')
+1 -1
View File
@@ -63,7 +63,7 @@ class Location(models.Model):
_sql_constraints = [('barcode_company_uniq', 'unique (barcode,company_id)', 'The barcode for a location must be unique per company !')]
@api.depends('name', 'location_id.complete_name')
@api.depends('name', 'location_id.complete_name', 'usage')
def _compute_complete_name(self):
for location in self:
if location.location_id and location.usage != 'view':
@@ -25,7 +25,7 @@
<h6 class="o_wevent_sidebar_title">
<t t-if="country">
<i class="fa fa-flag mr-2"/>Events: <span t-esc="country.name"/>
<img class="img-fluid" t-att-src="website.image_url(country, 'image')" alt=""/>
<img class="img-fluid" t-att-src="country.image_url" alt=""/>
</t>
<t t-else="">
<i class="fa fa-globe mr-2"/>Upcoming Events
@@ -8,7 +8,7 @@
<xpath expr="//field[@name='slide_type']" position="after">
<field name="survey_id"
attrs="{'invisible': [('slide_type', '!=', 'certification')], 'required': [('slide_type', '=', 'certification')]}"
domain="[('certification', '=', True)]"/>
domain="[('certification', '=', True)]" context="{'default_certification': True, 'default_scoring_type': 'scoring_without_answers'}"/>
</xpath>
</field>
</record>