[MERGE] forward port branch saas-11 up to e86983112e
This commit is contained in:
@@ -51,6 +51,7 @@
|
||||
<field name="prefix">A</field>
|
||||
<field eval="1" name="number_next"/>
|
||||
<field eval="1" name="number_increment"/>
|
||||
<field eval="False" name="company_id"/>
|
||||
</record>
|
||||
<!--
|
||||
Invoice requests (deprecated)
|
||||
|
||||
@@ -769,8 +769,9 @@ class AccountInvoice(models.Model):
|
||||
will be grouped together if the journal has the 'group line' option. Of course a module
|
||||
can add fields to invoice lines that would need to be tested too before merging lines
|
||||
or not."""
|
||||
return "%s-%s-%s-%s-%s-%s" % (
|
||||
return "%s-%s-%s-%s-%s-%s-%s" % (
|
||||
invoice_line['account_id'],
|
||||
invoice_line.get('tax_ids', 'False'),
|
||||
invoice_line.get('tax_line_id', 'False'),
|
||||
invoice_line.get('product_id', 'False'),
|
||||
invoice_line.get('analytic_account_id', 'False'),
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="product_id"
|
||||
context="{'partner_id': parent.partner_id}"/>
|
||||
context="parent and {'partner_id': parent.partner_id}"/>
|
||||
<label for="quantity"/>
|
||||
<div>
|
||||
<field name="quantity" class="oe_inline"/>
|
||||
|
||||
@@ -193,6 +193,10 @@ class Employee(models.Model):
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
if 'address_home_id' in vals:
|
||||
account_id = vals.get('bank_account_id') or self.bank_account_id.id
|
||||
if account_id:
|
||||
self.env['res.partner.bank'].browse(account_id).partner_id = vals['address_home_id']
|
||||
tools.image_resize_images(vals)
|
||||
return super(Employee, self).write(vals)
|
||||
|
||||
|
||||
@@ -16,8 +16,9 @@ class Partner(models.Model):
|
||||
|
||||
try:
|
||||
employee_group = self.env.ref('base.group_user')
|
||||
for user in employee_group.users:
|
||||
suggestions.append((user.partner_id.id, user.name, user.email))
|
||||
hr_suggestions = [{'id': user.partner_id.id, 'name': user.name, 'email': user.email}
|
||||
for user in employee_group.users]
|
||||
suggestions.append(hr_suggestions)
|
||||
return suggestions
|
||||
except AccessError:
|
||||
return suggestions
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
<field name="country_id" options='{"no_open": True, "no_create": True}'/>
|
||||
<field name="identification_id" groups="hr.group_hr_user"/>
|
||||
<field name="passport_id" groups="hr.group_hr_user"/>
|
||||
<field name="bank_account_id"/>
|
||||
<field name="bank_account_id" context="{'default_partner_id': address_home_id}"/>
|
||||
</group>
|
||||
<group string="Contact Information">
|
||||
<field name="address_home_id"
|
||||
|
||||
@@ -82,7 +82,7 @@ class Usb(Escpos):
|
||||
|
||||
def _raw(self, msg):
|
||||
""" Print any command sent in raw format """
|
||||
if len(msg) != self.device.write(self.out_ep, msg, self.interface):
|
||||
if len(msg) != self.device.write(self.out_ep, msg, self.interface, timeout=5000):
|
||||
self.device.write(self.out_ep, self.errorText, self.interface)
|
||||
raise TicketNotPrinted()
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class AccountInvoice(models.Model):
|
||||
def _get_reference_type(self):
|
||||
"""Add BBA Structured Communication Type and change labels from 'reference' into 'communication' """
|
||||
res = super(AccountInvoice, self)._get_reference_type()
|
||||
res[[i for i, x in enumerate(res) if x[0] == 'none'][0]] = ('none', 'Free Communication')
|
||||
res[[i for i, x in enumerate(res) if x[0] == 'none'][0]] = ('none', _('Free Communication'))
|
||||
res.append(('bba', 'BBA Structured Communication'))
|
||||
return res
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ class TestMassMailing(models.TransientModel):
|
||||
|
||||
email_to = fields.Char(string='Recipients', required=True,
|
||||
help='Comma-separated list of email addresses.', default=lambda self: self.env['mail.message']._get_default_from())
|
||||
mass_mailing_id = fields.Many2one('mail.mass_mailing', string='Mailing', required=True)
|
||||
mass_mailing_id = fields.Many2one('mail.mass_mailing', string='Mailing', required=True, ondelete='cascade')
|
||||
|
||||
@api.multi
|
||||
def send_mail_test(self):
|
||||
|
||||
@@ -316,22 +316,18 @@ class Picking(models.Model):
|
||||
self.state = 'cancel'
|
||||
elif all(move.state in ['cancel', 'done'] for move in self.move_lines):
|
||||
self.state = 'done'
|
||||
elif self.move_type == 'one':
|
||||
ordered_moves = self.move_lines.filtered(
|
||||
lambda move: move.state not in ['cancel', 'done']
|
||||
).sorted(
|
||||
key=lambda move: (move.state == 'assigned' and 2) or (move.state == 'waiting' and 1) or 0, reverse=False
|
||||
)
|
||||
self.state = ordered_moves[0].state
|
||||
else:
|
||||
filtered_moves = self.move_lines.filtered(lambda move: move.state not in ['cancel', 'done'])
|
||||
if not all(move.state == 'assigned' for move in filtered_moves) and any(move.state == 'assigned' for move in filtered_moves):
|
||||
self.state = 'partially_available'
|
||||
elif any(move.partially_available for move in filtered_moves):
|
||||
# We sort our moves by importance of state: "confirmed" should be first, then we'll have
|
||||
# "waiting" and finally "assigned" at the end.
|
||||
moves_todo = self.move_lines\
|
||||
.filtered(lambda move: move.state not in ['cancel', 'done'])\
|
||||
.sorted(key=lambda move: (move.state == 'assigned' and 2) or (move.state == 'waiting' and 1) or 0)
|
||||
if self.move_type == 'one':
|
||||
self.state = moves_todo[0].state
|
||||
elif moves_todo[0].state != 'assigned' and any(x.partially_available or x.state == 'assigned' for x in moves_todo):
|
||||
self.state = 'partially_available'
|
||||
else:
|
||||
ordered_moves = filtered_moves.sorted(key=lambda move: (move.state == 'assigned' and 2) or (move.state == 'waiting' and 1) or 0, reverse=True)
|
||||
self.state = ordered_moves[0].state
|
||||
self.state = moves_todo[-1].state
|
||||
|
||||
@api.one
|
||||
@api.depends('move_lines.priority')
|
||||
|
||||
@@ -1210,7 +1210,6 @@ class TestStockFlow(TestStockCommon):
|
||||
inventory5.action_done()
|
||||
self.assertEqual(inventory5_lines_count, inventory4_lines_count + 1, "The new product is not taken into account in the inventory valuation.")
|
||||
self.assertTrue(product.id in inventory5.line_ids.mapped('product_id').ids, "The new product is not take into account in the inventory valuation.")
|
||||
|
||||
def test_30_check_with_no_incoming_lot(self):
|
||||
""" Picking in without lots and picking out with"""
|
||||
|
||||
@@ -1278,7 +1277,6 @@ class TestStockFlow(TestStockCommon):
|
||||
self.assertEqual(sum([x.qty for x in quants if x.lot_id.id == lot2.id]), 1.0, 'Wrong sum of quants with lot 2')
|
||||
self.assertEqual(sum([x.qty for x in quants if x.lot_id.id == lot3.id]), 2.0, 'Wrong sum of quants with lot 3')
|
||||
|
||||
|
||||
def test_40_pack_in_pack(self):
|
||||
""" Put a pack in pack"""
|
||||
picking_out = self.PickingObj.create({
|
||||
@@ -1536,3 +1534,194 @@ class TestStockFlow(TestStockCommon):
|
||||
# We should also make sure that when matching stock moves with pack operations, it takes the correct
|
||||
self.assertEqual(len(picking_out.move_lines[0].linked_move_operation_ids), 2, 'We should only have 2 links beween the move and the 2 operations')
|
||||
self.assertEqual(len(picking_out.move_lines[0].quant_ids), 2, 'We should have exactly 2 quants in the end')
|
||||
|
||||
def test_70_picking_state_all_at_once_reserve(self):
|
||||
""" This test will check that the state of the picking is correctly computed according
|
||||
to the state of its move lines and its move type.
|
||||
"""
|
||||
# move_type: direct == partial, one == all at once
|
||||
# picking: confirmed == waiting availability, partially_available = partially available
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# "all at once" and "reserve" scenario
|
||||
# -----------------------------------------------------------
|
||||
# get one product in stock
|
||||
inventory = self.env['stock.inventory'].create({
|
||||
'name': 'Inventory Product Table',
|
||||
'filter': 'partial',
|
||||
'line_ids': [(0, 0, {
|
||||
'product_id': self.productA.id,
|
||||
'product_uom_id': self.productA.uom_id.id,
|
||||
'product_qty': 1,
|
||||
'location_id': self.stock_location
|
||||
})]
|
||||
})
|
||||
inventory.action_done()
|
||||
|
||||
# create a "all at once" delivery order for two products
|
||||
picking_out = self.PickingObj.create({
|
||||
'partner_id': self.partner_agrolite_id,
|
||||
'picking_type_id': self.picking_type_out,
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.customer_location})
|
||||
picking_out.move_type = 'one'
|
||||
|
||||
self.MoveObj.create({
|
||||
'name': self.productA.name,
|
||||
'product_id': self.productA.id,
|
||||
'product_uom_qty': 2,
|
||||
'product_uom': self.productA.uom_id.id,
|
||||
'picking_id': picking_out.id,
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.customer_location})
|
||||
|
||||
# validate this delivery order, it should be in the waiting state
|
||||
picking_out.action_assign()
|
||||
self.assertEquals(picking_out.state, "confirmed")
|
||||
|
||||
# receive one product in stock
|
||||
inventory = self.env['stock.inventory'].create({
|
||||
'name': 'Inventory Product Table',
|
||||
'filter': 'partial',
|
||||
'line_ids': [(0, 0, {
|
||||
'product_id': self.productA.id,
|
||||
'product_uom_id': self.productA.uom_id.id,
|
||||
'product_qty': 2,
|
||||
'location_id': self.stock_location
|
||||
})]
|
||||
})
|
||||
inventory.action_done()
|
||||
|
||||
# recheck availability of the delivery order, it should be assigned
|
||||
picking_out.action_assign()
|
||||
self.assertEquals(picking_out.state, "assigned")
|
||||
|
||||
def test_71_picking_state_all_at_once_force_assign(self):
|
||||
""" This test will check that the state of the picking is correctly computed according
|
||||
to the state of its move lines and its move type.
|
||||
"""
|
||||
# move_type: direct == partial, one == all at once
|
||||
# picking: confirmed == waiting availability, partially_available = partially available
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# "all at once" and "force assign" scenario
|
||||
# -----------------------------------------------------------
|
||||
# create a "all at once" delivery order for two products
|
||||
picking_out = self.PickingObj.create({
|
||||
'partner_id': self.partner_agrolite_id,
|
||||
'picking_type_id': self.picking_type_out,
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.customer_location})
|
||||
picking_out.move_type = 'direct'
|
||||
|
||||
self.MoveObj.create({
|
||||
'name': self.productA.name,
|
||||
'product_id': self.productA.id,
|
||||
'product_uom_qty': 2,
|
||||
'product_uom': self.productA.uom_id.id,
|
||||
'picking_id': picking_out.id,
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.customer_location})
|
||||
|
||||
# validate this delivery order, it should be in the waiting state
|
||||
picking_out.action_assign()
|
||||
self.assertEquals(picking_out.state, "confirmed")
|
||||
|
||||
# force assign on the delivery order, it should be assigned
|
||||
picking_out.force_assign()
|
||||
self.assertEquals(picking_out.state, "assigned")
|
||||
|
||||
def test_72_picking_state_partial_reserve(self):
|
||||
""" This test will check that the state of the picking is correctly computed according
|
||||
to the state of its move lines and its move type.
|
||||
"""
|
||||
# move_type: direct == partial, one == all at once
|
||||
# picking: confirmed == waiting availability, partially_available = partially available
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# "partial" and "reserve" scenario
|
||||
# -----------------------------------------------------------
|
||||
# get one product in stock
|
||||
inventory = self.env['stock.inventory'].create({
|
||||
'name': 'Inventory Product Table',
|
||||
'filter': 'partial',
|
||||
'line_ids': [(0, 0, {
|
||||
'product_id': self.productA.id,
|
||||
'product_uom_id': self.productA.uom_id.id,
|
||||
'product_qty': 1,
|
||||
'location_id': self.stock_location
|
||||
})]
|
||||
})
|
||||
inventory.action_done()
|
||||
|
||||
# create a "partial" delivery order for two products
|
||||
picking_out = self.PickingObj.create({
|
||||
'partner_id': self.partner_agrolite_id,
|
||||
'picking_type_id': self.picking_type_out,
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.customer_location})
|
||||
picking_out.move_type = 'direct'
|
||||
|
||||
self.MoveObj.create({
|
||||
'name': self.productA.name,
|
||||
'product_id': self.productA.id,
|
||||
'product_uom_qty': 2,
|
||||
'product_uom': self.productA.uom_id.id,
|
||||
'picking_id': picking_out.id,
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.customer_location})
|
||||
|
||||
# validate this delivery order, it should be in partially available
|
||||
picking_out.action_assign()
|
||||
self.assertEquals(picking_out.state, "partially_available")
|
||||
|
||||
# receive one product in stock
|
||||
inventory = self.env['stock.inventory'].create({
|
||||
'name': 'Inventory Product Table',
|
||||
'filter': 'partial',
|
||||
'line_ids': [(0, 0, {
|
||||
'product_id': self.productA.id,
|
||||
'product_uom_id': self.productA.uom_id.id,
|
||||
'product_qty': 2,
|
||||
'location_id': self.stock_location
|
||||
})]
|
||||
})
|
||||
inventory.action_done()
|
||||
|
||||
# recheck availability of the delivery order, it should be assigned
|
||||
picking_out.action_assign()
|
||||
self.assertEquals(picking_out.state, "assigned")
|
||||
|
||||
def test_73_picking_state_partial_force_assign(self):
|
||||
""" This test will check that the state of the picking is correctly computed according
|
||||
to the state of its move lines and its move type.
|
||||
"""
|
||||
# move_type: direct == partial, one == all at once
|
||||
# picking: confirmed == waiting availability, partially_available = partially available
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# "partial" and "force assign" scenario
|
||||
# -----------------------------------------------------------
|
||||
picking_out = self.PickingObj.create({
|
||||
'partner_id': self.partner_agrolite_id,
|
||||
'picking_type_id': self.picking_type_out,
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.customer_location})
|
||||
picking_out.move_type = 'direct'
|
||||
|
||||
self.MoveObj.create({
|
||||
'name': self.productA.name,
|
||||
'product_id': self.productA.id,
|
||||
'product_uom_qty': 2,
|
||||
'product_uom': self.productA.uom_id.id,
|
||||
'picking_id': picking_out.id,
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.customer_location})
|
||||
|
||||
# validate this delivery order, it should be in the waiting state
|
||||
picking_out.action_assign()
|
||||
self.assertEquals(picking_out.state, "confirmed")
|
||||
|
||||
# force assign on the delivery order, it should be assigned
|
||||
picking_out.force_assign()
|
||||
self.assertEquals(picking_out.state, "assigned")
|
||||
@@ -40,15 +40,15 @@
|
||||
<table class="o_group o_inner_group o_label_nowrap">
|
||||
<tr>
|
||||
<td class="o_td_label"><label for="old_pwd" class="o_form_label">Old Password</label></td>
|
||||
<td width="100%"><input type="password" class="o_form_field o_form_input" name="old_pwd" minlength="1" autofocus="autofocus"/></td>
|
||||
<td width="100%"><input type="password" class="o_form_field o_form_input" name="old_pwd" minlength="1" autofocus="autofocus" autocomplete="current-password"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="o_td_label"><label for="new_password" class="o_form_label">New Password</label></td>
|
||||
<td width="100%"><input type="password" class="o_form_field o_form_input" name="new_password" minlength="1"/></td>
|
||||
<td width="100%"><input type="password" class="o_form_field o_form_input" name="new_password" minlength="1" autocomplete="new-password"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="o_td_label"><label for="confirm_pwd" class="o_form_label">Confirm New Password</label></td>
|
||||
<td width="100%"><input type="password" class="o_form_field o_form_input" name="confirm_pwd" minlength="1"/></td>
|
||||
<td width="100%"><input type="password" class="o_form_field o_form_input" name="confirm_pwd" minlength="1" autocomplete="new-password"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -625,6 +625,7 @@
|
||||
t-att-tabindex="widget.node.attrs.tabindex"
|
||||
t-att-autofocus="widget.node.attrs.autofocus"
|
||||
t-att-placeholder="widget.node.attrs.placeholder"
|
||||
t-att-autocomplete="widget.password ? 'new-password' : widget.node.attrs.autocomplete"
|
||||
t-att-maxlength="widget.field.size"/>
|
||||
</t>
|
||||
<t t-name="FormSelection">
|
||||
|
||||
@@ -262,7 +262,7 @@
|
||||
|
||||
<div class="form-group field-password">
|
||||
<label for="password" class="control-label">Password</label>
|
||||
<input type="password" name="password" id="password" class="form-control" required="required" t-att-autofocus="'autofocus' if login else None" maxlength="4096"/>
|
||||
<input type="password" name="password" id="password" class="form-control" required="required" autocomplete="current-password" t-att-autofocus="'autofocus' if login else None" maxlength="4096"/>
|
||||
</div>
|
||||
|
||||
<p class="alert alert-danger" t-if="error">
|
||||
|
||||
@@ -6,10 +6,6 @@ var website = require('website.website');
|
||||
var base = require('web_editor.base');
|
||||
require('website_sale.website_sale');
|
||||
|
||||
if(!$('.js_add_cart_variants[data-attribute_value_ids]').length) {
|
||||
return $.Deferred().reject("DOM doesn't contain '.js_add_cart_variants[data-attribute_value_ids]'");
|
||||
}
|
||||
|
||||
$('.oe_website_sale #add_to_cart, .oe_website_sale #products_grid .a-submit')
|
||||
.off('click')
|
||||
.removeClass('a-submit')
|
||||
@@ -28,9 +24,14 @@ $('.oe_website_sale #add_to_cart, .oe_website_sale #products_grid .a-submit')
|
||||
|
||||
$modal.find('img:first').attr("src", "/web/image/product.product/" + product_id + "/image_medium");
|
||||
|
||||
// disable opacity on the <form> if currently active (in case the product is
|
||||
// not published), as it interferes with bs modals
|
||||
$form.addClass('css_options');
|
||||
|
||||
$modal.appendTo($form)
|
||||
.modal()
|
||||
.on('hidden.bs.modal', function () {
|
||||
$form.removeClass('css_options'); // possibly reactivate opacity (see above)
|
||||
$(this).remove();
|
||||
});
|
||||
|
||||
|
||||
@@ -31,3 +31,4 @@ Cyril Gaudin cyril.gaudin@camptocamp.com https://github.com/cyrilgdn
|
||||
Denis Leemann denis.leemann@camptocamp.com https://github.com/leemannd
|
||||
Akim Juillerat akim.juillerat@camptocamp.com https://github.com/grindtildeath
|
||||
Damien Crier damien.crier@camptocamp.com https://github.com/damdam-s
|
||||
Simone Orsi simahawk@gmail.com https://github.com/simahawk
|
||||
|
||||
@@ -11,3 +11,4 @@ Laurent Destailleur ldestailleur@teclib.com https://github.com/eldy
|
||||
List of contributors:
|
||||
|
||||
Laurent Destailleur ldestailleur@teclib.com https://github.com/eldy
|
||||
David Halgand dhalgand@teclib.com https://github.com/halgandd
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Nantes, 2016-11-04
|
||||
|
||||
I hereby agree to the terms of the Odoo Individual Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
David Halgand david.halgand@gmail.com https://github.com/halgandd
|
||||
@@ -956,6 +956,7 @@ class IrActionsServer(models.Model):
|
||||
:return: an action_id to be executed, or False is finished correctly without
|
||||
return action
|
||||
"""
|
||||
res = False
|
||||
for action in self:
|
||||
eval_context = self._get_eval_context(action)
|
||||
condition = action.condition
|
||||
|
||||
@@ -77,6 +77,12 @@ class test_inherits(common.TransactionCase):
|
||||
self.assertIn(daughter, partner_demo.daughter_ids)
|
||||
|
||||
# search the partner from the daughter record
|
||||
partners = self.env['res.partner'].search([('daughter_ids', 'like', 'not existing daugther')])
|
||||
self.assertFalse(partners)
|
||||
partners = self.env['res.partner'].search([('daughter_ids', 'not like', 'not existing daugther')])
|
||||
self.assertIn(partner_demo, partners)
|
||||
partners = self.env['res.partner'].search([('daughter_ids', '!=', False)])
|
||||
self.assertIn(partner_demo, partners)
|
||||
partners = self.env['res.partner'].search([('daughter_ids', 'in', daughter.ids)])
|
||||
self.assertIn(partner_demo, partners)
|
||||
|
||||
|
||||
@@ -957,7 +957,14 @@ class expression(object):
|
||||
|
||||
if call_null:
|
||||
o2m_op = 'in' if operator in NEGATIVE_TERM_OPERATORS else 'not in'
|
||||
push(create_substitution_leaf(leaf, ('id', o2m_op, select_distinct_from_where_not_null(cr, field.inverse_name, comodel._table)), model))
|
||||
# determine ids from field.inverse_name
|
||||
if comodel._fields[field.inverse_name].store:
|
||||
ids1 = select_distinct_from_where_not_null(cr, field.inverse_name, comodel._table)
|
||||
else:
|
||||
domain = [(field.inverse_name, '!=', False)]
|
||||
recs = comodel.search(domain).sudo().with_context(prefetch_fields=False)
|
||||
ids1 = recs.mapped(field.inverse_name).ids
|
||||
push(create_substitution_leaf(leaf, ('id', o2m_op, ids1), model))
|
||||
|
||||
elif field.type == 'many2many':
|
||||
rel_table, rel_id1, rel_id2 = field.relation, field.column1, field.column2
|
||||
|
||||
Reference in New Issue
Block a user