[MERGE] forward port branch 11.0 up to 7e33b54771
This commit is contained in:
@@ -503,16 +503,19 @@ class AccountInvoice(models.Model):
|
||||
"""
|
||||
res = super(AccountInvoice, self).default_get(default_fields)
|
||||
|
||||
if not res.get('type', False) == 'out_invoice' or not 'company_id' in res:
|
||||
if res.get('type', False) not in ('out_invoice', 'in_refund') or not 'company_id' in res:
|
||||
return res
|
||||
|
||||
company = self.env['res.company'].browse(res['company_id'])
|
||||
if company.partner_id:
|
||||
partner_bank_result = self.env['res.partner.bank'].search([('partner_id', '=', company.partner_id.id)], limit=1)
|
||||
if partner_bank_result:
|
||||
res['partner_bank_id'] = partner_bank_result.id
|
||||
partner_bank_result = self._get_partner_bank_id(res['company_id'])
|
||||
if partner_bank_result:
|
||||
res['partner_bank_id'] = partner_bank_result.id
|
||||
return res
|
||||
|
||||
def _get_partner_bank_id(self, company_id):
|
||||
company = self.env['res.company'].browse(company_id)
|
||||
if company.partner_id:
|
||||
return self.env['res.partner.bank'].search([('partner_id', '=', company.partner_id.id)], limit=1)
|
||||
|
||||
@api.model
|
||||
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
|
||||
def get_view_id(xid, name):
|
||||
@@ -1369,6 +1372,11 @@ class AccountInvoice(models.Model):
|
||||
values['payment_term_id'] = False
|
||||
values['refund_invoice_id'] = invoice.id
|
||||
|
||||
if values['type'] == 'in_refund':
|
||||
partner_bank_result = self._get_partner_bank_id(values['company_id'])
|
||||
if partner_bank_result:
|
||||
values['partner_bank_id'] = partner_bank_result.id
|
||||
|
||||
if date:
|
||||
values['date'] = date
|
||||
if description:
|
||||
|
||||
@@ -143,8 +143,9 @@ class AccountMove(models.Model):
|
||||
def post(self):
|
||||
invoice = self._context.get('invoice', False)
|
||||
self._post_validate()
|
||||
# Create the analytic lines in batch is faster as it leads to less cache invalidation.
|
||||
self.mapped('line_ids').create_analytic_lines()
|
||||
for move in self:
|
||||
move.line_ids.create_analytic_lines()
|
||||
if move.name == '/':
|
||||
new_name = False
|
||||
journal = move.journal_id
|
||||
|
||||
@@ -108,3 +108,39 @@ class TestAccountSupplierInvoice(AccountingTestCase):
|
||||
#I cancel the account move which is in posted state and verifies that it gives warning message
|
||||
with self.assertRaises(Warning):
|
||||
invoice.move_id.button_cancel()
|
||||
|
||||
def test_vendor_bill_refund(self):
|
||||
invoice_account = self.env['account.account'].search(
|
||||
[('user_type_id', '=', self.env.ref('account.data_account_type_receivable').id)], limit=1)
|
||||
invoice_line_account = self.env['account.account'].search(
|
||||
[('user_type_id', '=', self.env.ref('account.data_account_type_expenses').id)], limit=1)
|
||||
|
||||
if self.env.ref('base.main_partner').bank_account_count > 0:
|
||||
bank = self.env['res.partner.bank'].search([('partner_id', '=', self.env.ref('base.main_partner').id)], limit=1)
|
||||
|
||||
else:
|
||||
bank = self.env['res.partner.bank'].create({
|
||||
'acc_number': '12345678910',
|
||||
'partner_id': self.env.ref('base.main_partner').id,
|
||||
})
|
||||
|
||||
invoice_id = self.env['account.invoice'].create({
|
||||
'name': 'invoice test refund',
|
||||
'reference_type': 'none',
|
||||
'partner_id': self.env.ref("base.res_partner_2").id,
|
||||
'account_id': invoice_account.id,
|
||||
'currency_id': self.env.ref('base.USD').id,
|
||||
'type': 'in_invoice',
|
||||
})
|
||||
self.env['account.invoice.line'].create({
|
||||
'product_id': self.env.ref("product.product_product_4").id,
|
||||
'quantity': 1,
|
||||
'price_unit': 15.0,
|
||||
'invoice_id': invoice_id.id,
|
||||
'name': 'something',
|
||||
'account_id': invoice_line_account.id,
|
||||
})
|
||||
|
||||
refund_invoices = invoice_id.refund()
|
||||
|
||||
self.assertEqual(refund_invoices.partner_bank_id, bank)
|
||||
|
||||
@@ -18,7 +18,5 @@ class AccountMove(models.Model):
|
||||
|
||||
@api.multi
|
||||
def post(self):
|
||||
for move in self:
|
||||
for depreciation_line in move.asset_depreciation_ids:
|
||||
depreciation_line.post_lines_and_close_asset()
|
||||
self.mapped('asset_depreciation_ids').post_lines_and_close_asset()
|
||||
return super(AccountMove, self).post()
|
||||
|
||||
@@ -500,8 +500,12 @@ class AccountAssetDepreciationLine(models.Model):
|
||||
@api.multi
|
||||
def create_move(self, post_move=True):
|
||||
created_moves = self.env['account.move']
|
||||
# `line.move_id` was invalidated from the cache at each iteration
|
||||
# To prevent to refetch `move_id` of all lines at each iteration just to check a UserError,
|
||||
# we use an intermediar dict which stores the information the UserError check requires.
|
||||
line_moves = {line: line.move_id for line in self}
|
||||
for line in self:
|
||||
if line.move_id:
|
||||
if line_moves[line]:
|
||||
raise UserError(_('This depreciation is already linked to a journal entry! Please post or delete it.'))
|
||||
move_vals = self._prepare_move(line)
|
||||
move = self.env['account.move'].create(move_vals)
|
||||
@@ -547,6 +551,7 @@ class AccountAssetDepreciationLine(models.Model):
|
||||
'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)],
|
||||
}
|
||||
return move_vals
|
||||
line_moves[line] = move
|
||||
|
||||
def _prepare_move_grouped(self):
|
||||
category_id = self[0].asset_id.category_id # we can suppose that all lines have the same category
|
||||
@@ -602,12 +607,19 @@ class AccountAssetDepreciationLine(models.Model):
|
||||
@api.multi
|
||||
def post_lines_and_close_asset(self):
|
||||
# we re-evaluate the assets to determine whether we can close them
|
||||
# `message_post` invalidates the (whole) cache
|
||||
# preprocess the assets and lines in which a message should be posted,
|
||||
# and then post in batch will prevent the re-fetch of the same data over and over.
|
||||
assets_to_close = self.env['account.asset.asset']
|
||||
for line in self:
|
||||
line.log_message_when_posted()
|
||||
asset = line.asset_id
|
||||
if asset.currency_id.is_zero(asset.value_residual):
|
||||
asset.message_post(body=_("Document closed."))
|
||||
asset.write({'state': 'close'})
|
||||
assets_to_close |= asset
|
||||
self.log_message_when_posted()
|
||||
assets_to_close.write({'state': 'close'})
|
||||
for asset in assets_to_close:
|
||||
asset.message_post(body=_("Document closed."))
|
||||
|
||||
|
||||
@api.multi
|
||||
def log_message_when_posted(self):
|
||||
@@ -620,6 +632,10 @@ class AccountAssetDepreciationLine(models.Model):
|
||||
message += '%s</div>' % values
|
||||
return message
|
||||
|
||||
# `message_post` invalidates the (whole) cache
|
||||
# preprocess the assets in which messages should be posted,
|
||||
# and then post in batch will prevent the re-fetch of the same data over and over.
|
||||
assets_to_post = {}
|
||||
for line in self:
|
||||
if line.move_id and line.move_id.state == 'draft':
|
||||
partner_name = line.asset_id.partner_id.name
|
||||
@@ -628,7 +644,10 @@ class AccountAssetDepreciationLine(models.Model):
|
||||
if partner_name:
|
||||
msg_values[_('Partner')] = partner_name
|
||||
msg = _format_message(_('Depreciation line posted.'), msg_values)
|
||||
line.asset_id.message_post(body=msg)
|
||||
assets_to_post.setdefault(line.asset_id, []).append(msg)
|
||||
for asset, messages in assets_to_post.items():
|
||||
for msg in messages:
|
||||
asset.message_post(body=msg)
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
|
||||
@@ -56,20 +56,18 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
|
||||
this.isChromeMobile = isMobile && navigator.userAgent.match(/Chrome/i);
|
||||
|
||||
// Creates an input who will receive the barcode scanner value.
|
||||
if (this.isChromeMobile) {
|
||||
this.$barcodeInput = $('<input/>', {
|
||||
name: 'barcode',
|
||||
type: 'text',
|
||||
css: {
|
||||
'position': 'fixed',
|
||||
'top': '50%',
|
||||
'transform': 'translateY(-50%)',
|
||||
'z-index': '-1',
|
||||
},
|
||||
});
|
||||
// Avoid to show autocomplete for a non appearing input
|
||||
this.$barcodeInput.attr('autocomplete', 'off');
|
||||
}
|
||||
this.$barcodeInput = $('<input/>', {
|
||||
name: 'barcode',
|
||||
type: 'text',
|
||||
css: {
|
||||
'position': 'fixed',
|
||||
'top': '50%',
|
||||
'transform': 'translateY(-50%)',
|
||||
'z-index': '-1',
|
||||
},
|
||||
});
|
||||
// Avoid to show autocomplete for a non appearing input
|
||||
this.$barcodeInput.attr('autocomplete', 'off');
|
||||
|
||||
this.__blurBarcodeInput = _.debounce(this._blurBarcodeInput, this.inputTimeOut);
|
||||
},
|
||||
@@ -220,7 +218,8 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
|
||||
* @param {jQuery.Event} e keydown event
|
||||
*/
|
||||
_listenBarcodeScanner: function (e) {
|
||||
if (!$('input:text:focus, textarea:focus, [contenteditable]:focus').length) {
|
||||
if ($(document.activeElement).not('input:text, textarea, [contenteditable], ' +
|
||||
'[type="email"], [type="number"], [type="password"], [type="tel"]').length) {
|
||||
$('body').append(this.$barcodeInput);
|
||||
this.$barcodeInput.focus();
|
||||
}
|
||||
@@ -263,11 +262,9 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
|
||||
* @private
|
||||
*/
|
||||
_blurBarcodeInput: function () {
|
||||
if (this.$barcodeInput) {
|
||||
// Close the virtual keyboard on mobile browsers
|
||||
// FIXME: actually we can't prevent keyboard from opening
|
||||
this.$barcodeInput.val('').blur();
|
||||
}
|
||||
// Close the virtual keyboard on mobile browsers
|
||||
// FIXME: actually we can't prevent keyboard from opening
|
||||
this.$barcodeInput.val('').blur();
|
||||
},
|
||||
|
||||
start: function(prevent_key_repeat){
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
odoo.define('barcodes.barcode_mobile_tests', function () {
|
||||
"use strict";
|
||||
|
||||
QUnit.module('Barcodes', {}, function () {
|
||||
|
||||
QUnit.module('Barcodes Mobile');
|
||||
|
||||
QUnit.test('barcode field automatically focus behavior', function (assert) {
|
||||
assert.expect(10);
|
||||
|
||||
// Mock Chrome mobile environment
|
||||
var barcodeEvents = odoo.__DEBUG__.services["barcodes.BarcodeEvents"].BarcodeEvents;
|
||||
var __isChromeMobile = barcodeEvents.isChromeMobile;
|
||||
barcodeEvents.isChromeMobile = true;
|
||||
// Rebind keyboard events
|
||||
barcodeEvents.stop();
|
||||
barcodeEvents.start();
|
||||
|
||||
var $form = $(
|
||||
'<form>' +
|
||||
'<input name="email" type="email"/>' +
|
||||
'<input name="number" type="number"/>' +
|
||||
'<input name="password" type="password"/>' +
|
||||
'<input name="tel" type="tel"/>' +
|
||||
'<input name="text"/>' +
|
||||
'<input name="explicit_text" type="text"/>' +
|
||||
'<textarea></textarea>' +
|
||||
'<div contenteditable="true"></div>' +
|
||||
'<select name="select">' +
|
||||
'<option value="option1">Option 1</option>' +
|
||||
'<option value="option2">Option 2</option>' +
|
||||
'</select>' +
|
||||
'</form>');
|
||||
$('#qunit-fixture').append($form);
|
||||
|
||||
// Some elements doesn't need to keep the focus
|
||||
$('body').keydown();
|
||||
assert.strictEqual(document.activeElement.name, 'barcode',
|
||||
"hidden barcode input should have the focus");
|
||||
|
||||
var $element = $form.find('select');
|
||||
$element.focus().keydown();
|
||||
assert.strictEqual(document.activeElement.name, 'barcode',
|
||||
"hidden barcode input should have the focus");
|
||||
|
||||
// Those elements absolutely need to keep the focus:
|
||||
// inputs elements:
|
||||
var keepFocusedElements = ['email', 'number', 'password', 'tel',
|
||||
'text', 'explicit_text'];
|
||||
for (var i = 0; i < keepFocusedElements.length; ++i) {
|
||||
$element = $form.find('input[name=' + keepFocusedElements[i] + ']');
|
||||
$element.focus().keydown();
|
||||
assert.strictEqual(document.activeElement, $element[0],
|
||||
"input " + keepFocusedElements[i] + " should keep focus");
|
||||
}
|
||||
// textarea element
|
||||
$element = $form.find('textarea');
|
||||
$element.focus().keydown();
|
||||
assert.strictEqual(document.activeElement, $element[0],
|
||||
"textarea should keep focus");
|
||||
// contenteditable elements
|
||||
$element = $form.find('[contenteditable=true]');
|
||||
$element.focus().keydown();
|
||||
assert.strictEqual(document.activeElement, $element[0],
|
||||
"contenteditable should keep focus");
|
||||
|
||||
$('#qunit-fixture').empty();
|
||||
barcodeEvents.isChromeMobile = __isChromeMobile;
|
||||
// Rebind keyboard events
|
||||
barcodeEvents.stop();
|
||||
barcodeEvents.start();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -16,4 +16,10 @@
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="qunit_mobile_suite" name="barcode_mobile_tests" inherit_id="web.qunit_mobile_suite">
|
||||
<xpath expr="//t[@t-set='head']" position="inside">
|
||||
<script type="text/javascript" src="/barcodes/static/tests/barcode_mobile_tests.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -150,7 +150,7 @@ class HardwareScreen(web.Home):
|
||||
</head>
|
||||
<body class="original_body">
|
||||
<div hidden class="shadow"></div>
|
||||
<div class="container">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
<h1>Odoo Point of Sale</h1>
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
$("head").append($parsedHTML.find(".resources").html());
|
||||
}
|
||||
|
||||
$(".container").html($parsedHTML.find('.pos-customer_facing_display').html());
|
||||
$(".container").attr('class', 'container').addClass($parsedHTML.find('.pos-customer_facing_display').attr('class'));
|
||||
$(".container-fluid").html($parsedHTML.find('.pos-customer_facing_display').html());
|
||||
$(".container-fluid").attr('class', 'container-fluid').addClass($parsedHTML.find('.pos-customer_facing_display').attr('class'));
|
||||
|
||||
var d = $('.pos_orderlines_list');
|
||||
d.scrollTop(d.prop("scrollHeight"));
|
||||
|
||||
@@ -223,11 +223,13 @@ class XmlDeclaration(models.TransientModel):
|
||||
if dispatchmode:
|
||||
mode1 = 'out_invoice'
|
||||
mode2 = 'in_refund'
|
||||
extrf = "29"
|
||||
declcode = self._get_expedition_code(extendedmode)
|
||||
declform = self._get_expedition_form(extendedmode)
|
||||
else:
|
||||
mode1 = 'in_invoice'
|
||||
mode2 = 'out_refund'
|
||||
extrf = "19"
|
||||
declcode = self._get_reception_code(extendedmode)
|
||||
declform = self._get_reception_form(extendedmode)
|
||||
|
||||
@@ -272,7 +274,7 @@ class XmlDeclaration(models.TransientModel):
|
||||
invoicelines = self.env['account.invoice.line'].browse(invoicelines_ids)
|
||||
|
||||
for inv_line in invoicelines:
|
||||
linekey = self._get_intrastat_linekey(declcode, inv_line, dispatchmode, extendedmode)
|
||||
linekey = self._get_intrastat_linekey(extrf, inv_line, dispatchmode, extendedmode)
|
||||
if linekey is None:
|
||||
continue
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from datetime import datetime
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import Warning
|
||||
from odoo.tools import pycompat, DEFAULT_SERVER_DATE_FORMAT
|
||||
from odoo.tools import float_is_zero, pycompat, DEFAULT_SERVER_DATE_FORMAT
|
||||
|
||||
|
||||
class AccountFrFec(models.TransientModel):
|
||||
@@ -183,14 +183,16 @@ class AccountFrFec(models.TransientModel):
|
||||
|
||||
sql_query += '''
|
||||
GROUP BY aml.account_id, aat.type
|
||||
HAVING sum(aml.balance) != 0
|
||||
HAVING round(sum(aml.balance), %s) != 0
|
||||
AND aat.type not in ('receivable', 'payable')
|
||||
'''
|
||||
formatted_date_from = self.date_from.replace('-', '')
|
||||
date_from = datetime.strptime(self.date_from, DEFAULT_SERVER_DATE_FORMAT)
|
||||
formatted_date_year = date_from.year
|
||||
currency_digits = 2
|
||||
|
||||
self._cr.execute(
|
||||
sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id))
|
||||
sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id, currency_digits))
|
||||
|
||||
for row in self._cr.fetchall():
|
||||
listrow = list(row)
|
||||
@@ -203,6 +205,8 @@ class AccountFrFec(models.TransientModel):
|
||||
current_amount = float(listrow[11].replace(',', '.')) - float(listrow[12].replace(',', '.'))
|
||||
unaffected_earnings_amount = float(unaffected_earnings_results[11].replace(',', '.')) - float(unaffected_earnings_results[12].replace(',', '.'))
|
||||
listrow_amount = current_amount + unaffected_earnings_amount
|
||||
if float_is_zero(listrow_amount, precision_digits=currency_digits):
|
||||
continue
|
||||
if listrow_amount > 0:
|
||||
listrow[11] = str(listrow_amount).replace('.', ',')
|
||||
listrow[12] = '0,00'
|
||||
@@ -270,11 +274,11 @@ class AccountFrFec(models.TransientModel):
|
||||
|
||||
sql_query += '''
|
||||
GROUP BY aml.account_id, aat.type, rp.ref, rp.id
|
||||
HAVING sum(aml.balance) != 0
|
||||
HAVING round(sum(aml.balance), %s) != 0
|
||||
AND aat.type in ('receivable', 'payable')
|
||||
'''
|
||||
self._cr.execute(
|
||||
sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id))
|
||||
sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id, currency_digits))
|
||||
|
||||
for row in self._cr.fetchall():
|
||||
listrow = list(row)
|
||||
|
||||
@@ -200,6 +200,7 @@
|
||||
<field name="tax_exigibility">on_payment</field>
|
||||
<field name="cash_basis_account" ref="cuenta118_01"/>
|
||||
<field name="tag_ids" eval="[(6,0,[ref('tag_diot_8')])]"/>
|
||||
<field name="sequence" eval="10"/>
|
||||
</record>
|
||||
|
||||
<record id="tax17" model="account.tax.template">
|
||||
@@ -215,6 +216,7 @@
|
||||
<field name="tax_group_id" ref="tax_group_iva_8"/>
|
||||
<field name="tax_exigibility">on_payment</field>
|
||||
<field name="cash_basis_account" ref="cuenta208_01"/>
|
||||
<field name="sequence" eval="10"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -368,6 +368,7 @@ class MrpWorkorder(models.Model):
|
||||
move_line.product_uom_qty += self.qty_producing
|
||||
move_line.qty_done += self.qty_producing
|
||||
else:
|
||||
location_dest_id = production_move.location_dest_id.get_putaway_strategy(self.product_id).id or production_move.location_dest_id.id
|
||||
move_line.create({'move_id': production_move.id,
|
||||
'product_id': production_move.product_id.id,
|
||||
'lot_id': self.final_lot_id.id,
|
||||
@@ -376,7 +377,7 @@ class MrpWorkorder(models.Model):
|
||||
'qty_done': self.qty_producing,
|
||||
'workorder_id': self.id,
|
||||
'location_id': production_move.location_id.id,
|
||||
'location_dest_id': production_move.location_dest_id.id,
|
||||
'location_dest_id': location_dest_id,
|
||||
})
|
||||
else:
|
||||
production_move.quantity_done += self.qty_producing
|
||||
|
||||
@@ -6,6 +6,52 @@ from odoo.exceptions import except_orm
|
||||
|
||||
|
||||
class TestWarehouse(common.TestMrpCommon):
|
||||
def setUp(self):
|
||||
super(TestWarehouse, self).setUp()
|
||||
|
||||
self.stock_location = self.env.ref('stock.stock_location_stock')
|
||||
self.depot_location = self.env['stock.location'].create({
|
||||
'name': 'Depot',
|
||||
'usage': 'internal',
|
||||
'location_id': self.stock_location.id,
|
||||
})
|
||||
putaway = self.env['product.putaway'].create({
|
||||
'name': 'putaway stock->depot',
|
||||
'fixed_location_ids': [(0, 0, {
|
||||
'category_id': self.env.ref('product.product_category_all').id,
|
||||
'fixed_location_id': self.depot_location.id,
|
||||
})]
|
||||
})
|
||||
self.stock_location.write({
|
||||
'putaway_strategy_id': putaway.id,
|
||||
})
|
||||
|
||||
self.laptop = self.env.ref("product.product_product_25")
|
||||
graphics_card = self.env.ref("product.product_product_24")
|
||||
unit = self.env.ref("product.product_uom_unit")
|
||||
mrp_routing = self.env.ref("mrp.mrp_routing_0")
|
||||
|
||||
bom_laptop = self.env['mrp.bom'].create({
|
||||
'product_tmpl_id': self.laptop.product_tmpl_id.id,
|
||||
'product_qty': 1,
|
||||
'product_uom_id': unit.id,
|
||||
'bom_line_ids': [(0, 0, {
|
||||
'product_id': graphics_card.id,
|
||||
'product_qty': 1,
|
||||
'product_uom_id': unit.id
|
||||
})],
|
||||
'routing_id': mrp_routing.id
|
||||
})
|
||||
|
||||
# Return a new Manufacturing Order for laptop
|
||||
def new_mo_laptop():
|
||||
return self.env['mrp.production'].create({
|
||||
'product_id': self.laptop.id,
|
||||
'product_qty': 1,
|
||||
'product_uom_id': unit.id,
|
||||
'bom_id': bom_laptop.id
|
||||
})
|
||||
self.new_mo_laptop = new_mo_laptop
|
||||
|
||||
def test_manufacturing_route(self):
|
||||
warehouse_1_stock_manager = self.warehouse_1.sudo(self.user_stock_manager)
|
||||
@@ -100,3 +146,69 @@ class TestWarehouse(common.TestMrpCommon):
|
||||
|
||||
# scrap_move = production_3.move_raw_ids.filtered(lambda x: x.product_id == self.product_2 and x.scrapped)
|
||||
# self.assertTrue(scrap_move, "There are no any scrap move created for production order.")
|
||||
|
||||
def test_putaway_after_manufacturing_1(self):
|
||||
""" This test checks a manufactured product without tracking will go to
|
||||
location defined in putaway strategy.
|
||||
"""
|
||||
mo_laptop = self.new_mo_laptop()
|
||||
|
||||
mo_laptop.button_plan()
|
||||
workorder = mo_laptop.workorder_ids[0]
|
||||
|
||||
workorder.button_start()
|
||||
workorder.record_production()
|
||||
mo_laptop.button_mark_done()
|
||||
|
||||
# We check if the laptop go in the depot and not in the stock
|
||||
move = mo_laptop.move_finished_ids
|
||||
location_dest = move.move_line_ids.location_dest_id
|
||||
self.assertEqual(location_dest.id, self.depot_location.id)
|
||||
self.assertNotEqual(location_dest.id, self.stock_location.id)
|
||||
|
||||
def test_putaway_after_manufacturing_2(self):
|
||||
""" This test checks a tracked manufactured product will go to location
|
||||
defined in putaway strategy.
|
||||
"""
|
||||
self.laptop.tracking = 'serial'
|
||||
mo_laptop = self.new_mo_laptop()
|
||||
|
||||
mo_laptop.button_plan()
|
||||
workorder = mo_laptop.workorder_ids[0]
|
||||
|
||||
workorder.button_start()
|
||||
serial = self.env['stock.production.lot'].create({'product_id': self.laptop.id})
|
||||
workorder.final_lot_id = serial
|
||||
workorder.record_production()
|
||||
mo_laptop.button_mark_done()
|
||||
|
||||
# We check if the laptop go in the depot and not in the stock
|
||||
move = mo_laptop.move_finished_ids
|
||||
location_dest = move.move_line_ids.location_dest_id
|
||||
self.assertEqual(location_dest.id, self.depot_location.id)
|
||||
self.assertNotEqual(location_dest.id, self.stock_location.id)
|
||||
|
||||
def test_putaway_after_manufacturing_3(self):
|
||||
""" This test checks a tracked manufactured product will go to location
|
||||
defined in putaway strategy when the production is recorded with
|
||||
product.produce wizard.
|
||||
"""
|
||||
self.laptop.tracking = 'serial'
|
||||
mo_laptop = self.new_mo_laptop()
|
||||
serial = self.env['stock.production.lot'].create({'product_id': self.laptop.id})
|
||||
|
||||
product_produce = self.env['mrp.product.produce'].with_context({
|
||||
'active_id': mo_laptop.id,
|
||||
'active_ids': [mo_laptop.id],
|
||||
}).create({
|
||||
'product_qty': 1.0,
|
||||
'lot_id': serial.id,
|
||||
})
|
||||
product_produce.do_produce()
|
||||
mo_laptop.button_mark_done()
|
||||
|
||||
# We check if the laptop go in the depot and not in the stock
|
||||
move = mo_laptop.move_finished_ids
|
||||
location_dest = move.move_line_ids.location_dest_id
|
||||
self.assertEqual(location_dest.id, self.depot_location.id)
|
||||
self.assertNotEqual(location_dest.id, self.stock_location.id)
|
||||
|
||||
@@ -133,6 +133,7 @@ class MrpProductProduce(models.TransientModel):
|
||||
existing_move_line.product_uom_qty += self.product_qty
|
||||
existing_move_line.qty_done += self.product_qty
|
||||
else:
|
||||
location_dest_id = produce_move.location_dest_id.get_putaway_strategy(self.product_id).id or produce_move.location_dest_id.id
|
||||
vals = {
|
||||
'move_id': produce_move.id,
|
||||
'product_id': produce_move.product_id.id,
|
||||
@@ -142,7 +143,7 @@ class MrpProductProduce(models.TransientModel):
|
||||
'qty_done': self.product_qty,
|
||||
'lot_id': self.lot_id.id,
|
||||
'location_id': produce_move.location_id.id,
|
||||
'location_dest_id': produce_move.location_dest_id.id,
|
||||
'location_dest_id': location_dest_id,
|
||||
}
|
||||
self.env['stock.move.line'].create(vals)
|
||||
|
||||
|
||||
@@ -856,6 +856,7 @@ class PosOrder(models.Model):
|
||||
for pack_lot in pack_lots:
|
||||
lot_id, qty = pack_lot['lot_id'], pack_lot['qty']
|
||||
self.env['stock.move.line'].create({
|
||||
'picking_id': move.picking_id.id,
|
||||
'move_id': move.id,
|
||||
'product_id': move.product_id.id,
|
||||
'product_uom_id': move.product_uom.id,
|
||||
|
||||
@@ -35,6 +35,7 @@ body .pos-customer_facing_display {
|
||||
font-weight: 300;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
-webkit-display: flex;
|
||||
-moz-display: flex;
|
||||
-ms-display: flex;
|
||||
|
||||
@@ -95,6 +95,7 @@ body {
|
||||
font-weight: 300;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
.flex-display();
|
||||
.flex-direction(row);
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class PortalWizardUser(models.TransientModel):
|
||||
partners_error_empty |= wizard_user.partner_id
|
||||
elif email in emails:
|
||||
partners_error_emails |= wizard_user.partner_id
|
||||
user = self.env['res.users'].sudo().with_context(active_test=False).search([('login', '=', email)])
|
||||
user = self.env['res.users'].sudo().with_context(active_test=False).search([('login', '=ilike', email)])
|
||||
if user:
|
||||
partners_error_user |= wizard_user.partner_id
|
||||
emails.append(email)
|
||||
|
||||
@@ -79,10 +79,11 @@ class MrpStockReport(models.TransientModel):
|
||||
res_model = ''
|
||||
ref = ''
|
||||
res_id = False
|
||||
if move_line.picking_id:
|
||||
picking_id = move_line.picking_id or move_line.move_id.picking_id
|
||||
if picking_id:
|
||||
res_model = 'stock.picking'
|
||||
res_id = move_line.picking_id.id
|
||||
ref = move_line.picking_id.name
|
||||
res_id = picking_id.id
|
||||
ref = picking_id.name
|
||||
elif move_line.move_id.inventory_id:
|
||||
res_model = 'stock.inventory'
|
||||
res_id = move_line.move_id.inventory_id.id
|
||||
|
||||
@@ -173,7 +173,7 @@ var QWeb2 = {
|
||||
new_dict[as_first] = index === 0;
|
||||
new_dict[as_last] = index + 1 === size;
|
||||
new_dict[as_parity] = (index % 2 == 1 ? 'odd' : 'even');
|
||||
if (cur.constructor === Object) {
|
||||
if (cur && cur.constructor === Object) {
|
||||
this.extend(new_dict, cur);
|
||||
}
|
||||
new_dict[as] = cur;
|
||||
|
||||
@@ -346,7 +346,7 @@ var FormRenderer = BasicRenderer.extend({
|
||||
var visible_buttons = buttons_partition[1];
|
||||
|
||||
// Get the unfolded buttons according to window size
|
||||
var nb_buttons = [2, 2, 4, 6][config.device.size_class] || 7;
|
||||
var nb_buttons = this._renderButtonBoxNbButtons();
|
||||
var unfolded_buttons = visible_buttons.slice(0, nb_buttons).concat(invisible_buttons);
|
||||
|
||||
// Get the folded buttons
|
||||
@@ -385,6 +385,13 @@ var FormRenderer = BasicRenderer.extend({
|
||||
this._registerModifiers(node, this.state, $result);
|
||||
return $result;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {integer}
|
||||
*/
|
||||
_renderButtonBoxNbButtons: function () {
|
||||
return [2, 2, 4, 6][config.device.size_class] || 7;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} node
|
||||
|
||||
@@ -291,12 +291,10 @@ var PivotModel = AbstractModel.extend({
|
||||
// if we don't have the key 'groupBy' in params. In that case,
|
||||
// we want to have the full open state for the groupbys.
|
||||
self._updateTree(old_row.root, self.data.main_row.root);
|
||||
self._updateMainGroupBys(old_row, self.data.main_row);
|
||||
}
|
||||
|
||||
if (!('pivot_column_groupby' in (params.context || {}))) {
|
||||
self._updateTree(old_col.root, self.data.main_col.root);
|
||||
self._updateMainGroupBys(old_col, self.data.main_col);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -747,19 +745,6 @@ var PivotModel = AbstractModel.extend({
|
||||
this._traverseTree(root.children[i], f, arg1, arg2, arg3);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Updates the groupBys that the main[row | col] holds
|
||||
*
|
||||
* @private
|
||||
* @param {Object} old: The main abstract header before reload
|
||||
* @param {Object} main: The main abstract header after reload
|
||||
*
|
||||
*/
|
||||
_updateMainGroupBys: function (old, main) {
|
||||
var new_groupby_length = this._getHeaderDepth(main.root) - 1;
|
||||
main.root.groupbys = old.root.groupbys;
|
||||
main.root.groupbys.splice(new_groupby_length);
|
||||
},
|
||||
/**
|
||||
* @param {Object} old_tree
|
||||
* @param {Object} new_tree
|
||||
|
||||
@@ -1036,6 +1036,41 @@ QUnit.module('Views', {
|
||||
pivot.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('Empty results keep groupbys', function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
var pivot = createView({
|
||||
View: PivotView,
|
||||
model: "partner",
|
||||
data: this.data,
|
||||
arch: '<pivot/>',
|
||||
});
|
||||
|
||||
// Set a column groupby
|
||||
pivot.$('thead .o_pivot_header_cell_closed').click();
|
||||
pivot.$('.o_field_selection li[data-field=customer] a').click();
|
||||
|
||||
// Set a domain for empty results
|
||||
pivot.update({domain: [['id', '=', false]]});
|
||||
|
||||
var expectedContext = {pivot_column_groupby: undefined,
|
||||
pivot_measures: undefined,
|
||||
pivot_row_groupby: undefined};
|
||||
assert.deepEqual(pivot.getContext(), expectedContext,
|
||||
'Column groupby not lost after empty results');
|
||||
|
||||
// Set a domain for not empty results
|
||||
pivot.update({domain: [['product_id', '=', 37]]});
|
||||
|
||||
var expectedContext = {pivot_column_groupby: ['customer'],
|
||||
pivot_measures: ['__count'],
|
||||
pivot_row_groupby: []};
|
||||
assert.deepEqual(pivot.getContext(), expectedContext,
|
||||
'Column groupby not lost after reload after empty results');
|
||||
|
||||
pivot.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('correctly uses pivot_ keys from the context', function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
|
||||
@@ -426,7 +426,7 @@ class Partner(models.Model):
|
||||
as if they were related fields """
|
||||
commercial_partner = self.commercial_partner_id
|
||||
if commercial_partner != self:
|
||||
sync_vals = commercial_partner._update_fields_values(self._commercial_fields())
|
||||
sync_vals = commercial_partner.with_prefetch()._update_fields_values(self._commercial_fields())
|
||||
self.write(sync_vals)
|
||||
|
||||
@api.multi
|
||||
|
||||
Reference in New Issue
Block a user