[MERGE] forward port branch 11.0 up to 152519cd65
This commit is contained in:
@@ -35,22 +35,6 @@ class ProductTemplate(models.Model):
|
||||
domain=[('deprecated', '=', False)],
|
||||
help="The expense is accounted for when a vendor bill is validated, except in anglo-saxon accounting with perpetual inventory valuation in which case the expense (Cost of Goods Sold account) is recognized at the customer invoice validation. If the field is empty, it uses the one defined in the product category.")
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
#TODO: really? i don't see the reason we'd need that constraint..
|
||||
check = self.ids and 'uom_po_id' in vals
|
||||
if check:
|
||||
self._cr.execute("SELECT id, uom_po_id FROM product_template WHERE id IN %s", [tuple(self.ids)])
|
||||
uoms = dict(self._cr.fetchall())
|
||||
res = super(ProductTemplate, self).write(vals)
|
||||
if check:
|
||||
self._cr.execute("SELECT id, uom_po_id FROM product_template WHERE id IN %s", [tuple(self.ids)])
|
||||
if dict(self._cr.fetchall()) != uoms:
|
||||
products = self.env['product.product'].search([('product_tmpl_id', 'in', self.ids)])
|
||||
if self.env['account.move.line'].search_count([('product_id', 'in', products.ids)]):
|
||||
raise UserError(_('You can not change the unit of measure of a product that has been already used in an account journal item. If you need to change the unit of measure, you may deactivate this product.'))
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def _get_product_accounts(self):
|
||||
return {
|
||||
|
||||
@@ -155,5 +155,5 @@ class ResUsers(models.Model):
|
||||
sup = super(ResUsers, self)
|
||||
if not default or not default.get('email'):
|
||||
# avoid sending email to the user we are duplicating
|
||||
sup = super(ResUsers, self.with_context(reset_password=False))
|
||||
sup = super(ResUsers, self.with_context(no_reset_password=True))
|
||||
return sup.copy(default=default)
|
||||
|
||||
@@ -5,3 +5,11 @@ from . import controllers
|
||||
from . import models
|
||||
from . import report
|
||||
from . import wizard
|
||||
|
||||
from odoo import api, SUPERUSER_ID
|
||||
|
||||
|
||||
def uninstall_hook(cr, registry):
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
teams = env['crm.team'].search([('dashboard_graph_model', '=', 'crm.opportunity.report')])
|
||||
teams.update({'dashboard_graph_model': None})
|
||||
|
||||
@@ -53,4 +53,5 @@
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'auto_install': False,
|
||||
'uninstall_hook': 'uninstall_hook',
|
||||
}
|
||||
|
||||
@@ -442,7 +442,8 @@ class Lead(models.Model):
|
||||
'res_id': self.id,
|
||||
'views': [(form_view.id, 'form'),],
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'inline'
|
||||
'target': 'inline',
|
||||
'context': {'default_type': 'opportunity'}
|
||||
}
|
||||
|
||||
# ----------------------------------------
|
||||
|
||||
@@ -56,8 +56,7 @@
|
||||
<group string="Assign these opportunities to">
|
||||
<field name="team_id"/>
|
||||
<field name="user_ids" widget="many2many_tags" domain="[('share', '=', False)]"/>
|
||||
<!-- Uncomment me in trunk -->
|
||||
<!-- <field name="force_assignation" /> -->
|
||||
<field name="force_assignation"/>
|
||||
</group>
|
||||
<label for="opportunity_ids" string="Leads with existing duplicates (for information)" help="Leads that you selected that have duplicates. If the list is empty, it means that no duplicates were found" attrs="{'invisible': [('deduplicate', '=', False)]}"/>
|
||||
<group attrs="{'invisible': [('deduplicate', '=', False)]}">
|
||||
|
||||
@@ -124,3 +124,7 @@ class SaleOrderLine(models.Model):
|
||||
if not line.product_id or not line.product_uom or not line.product_uom_qty:
|
||||
return 0.0
|
||||
line.product_qty = line.product_uom._compute_quantity(line.product_uom_qty, line.product_id.uom_id)
|
||||
|
||||
def _is_delivery(self):
|
||||
self.ensure_one()
|
||||
return self.is_delivery
|
||||
|
||||
@@ -41,5 +41,12 @@
|
||||
]
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.rule" id="event_registration_portal">
|
||||
<field name="name">Event/Registration: Portal</field>
|
||||
<field name="model_id" ref="model_event_registration"/>
|
||||
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
|
||||
<field name="domain_force">['|', ('email', '=', user.partner_id.email), ('partner_id', '=', user.partner_id.id)]
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -5,7 +5,8 @@ access_event_event_portal,event.event.portal,model_event_event,,1,0,0,0
|
||||
access_event_event_user,event.event.user,model_event_event,event.group_event_user,1,0,0,0
|
||||
access_event_event_manager,event.event.manager,model_event_event,event.group_event_manager,1,1,1,1
|
||||
access_event_registration,event.registration,model_event_registration,event.group_event_user,1,1,1,1
|
||||
access_event_registration_portal,event.registration,model_event_registration,,0,0,0,0
|
||||
access_event_registration_employee,event.registration,model_event_registration,base.group_user,1,0,0,0
|
||||
access_event_registration_portal,event.registration,model_event_registration,base.group_portal,1,0,0,0
|
||||
access_event_mail,event.mail,model_event_mail,event.group_event_user,1,0,0,0
|
||||
access_event_mail_manager,event.mail manager,model_event_mail,event.group_event_manager,1,1,1,1
|
||||
access_event_mail_registration,event.mail.registration,model_event_mail_registration,event.group_event_user,1,0,0,0
|
||||
|
||||
|
@@ -26,6 +26,7 @@ this event.
|
||||
'data/event_sale_data.xml',
|
||||
'report/event_event_templates.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'security/event_security.xml',
|
||||
'wizard/event_edit_registration.xml',
|
||||
],
|
||||
'demo': ['data/event_demo.xml'],
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="0">
|
||||
<record id="sales_team.group_sale_salesman" model="res.groups">
|
||||
<field name="implied_ids" eval="[(4, ref('event.group_event_user'))]"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -2,7 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
from odoo.addons import decimal_precision as dp
|
||||
|
||||
@@ -5,6 +5,7 @@ import logging
|
||||
import re
|
||||
|
||||
from email.utils import formataddr
|
||||
from openerp.http import request
|
||||
|
||||
from odoo import _, api, fields, models, modules, SUPERUSER_ID, tools
|
||||
from odoo.exceptions import UserError, AccessError
|
||||
@@ -288,11 +289,12 @@ class Message(models.Model):
|
||||
|
||||
# 2. Attachments as SUPERUSER, because could receive msg and attachments for doc uid cannot see
|
||||
attachments_data = attachments.sudo().read(['id', 'datas_fname', 'name', 'mimetype'])
|
||||
safari = request and request.httprequest.user_agent.browser == 'safari'
|
||||
attachments_tree = dict((attachment['id'], {
|
||||
'id': attachment['id'],
|
||||
'filename': attachment['datas_fname'],
|
||||
'name': attachment['name'],
|
||||
'mimetype': attachment['mimetype'],
|
||||
'mimetype': 'application/octet-stream' if safari and 'video' in attachment['mimetype'] else attachment['mimetype'],
|
||||
}) for attachment in attachments_data)
|
||||
|
||||
# 3. Tracking values
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
name="action_mass_mailing_attendees"
|
||||
icon="fa-envelope-o"
|
||||
attrs="{'invisible': [('seats_expected', '=', 0)]}">
|
||||
Mail Attendees
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<span class="o_stat_text">Mail Attendees</span>
|
||||
</div>
|
||||
</button>
|
||||
</button>
|
||||
</field>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<span t-att-res-id="bom_line['product_id'].id" res-model="product.product" view-type="form" t-esc="bom_line['product_id'].name"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bom_line['product_uom_qty']"/> <span t-esc="bom_line['product_uom'].name" groups="product.group_uom"/>
|
||||
<span t-esc="bom_line['product_uom_qty']" t-esc-options='{"widget": "float", "decimal_precision": "Product Unit of Measure"}'/> <span t-esc="bom_line['product_uom'].name" groups="product.group_uom"/>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span t-esc="bom_line['price_unit']" t-options='{"widget": "monetary", "display_currency": currency}'/>
|
||||
|
||||
@@ -332,10 +332,11 @@ class PosConfig(models.Model):
|
||||
def name_get(self):
|
||||
result = []
|
||||
for config in self:
|
||||
if (not config.session_ids) or (config.session_ids[0].state == 'closed'):
|
||||
last_session = self.env['pos.session'].search([('config_id', '=', config.id)], limit=1)
|
||||
if (not last_session) or (last_session.state == 'closed'):
|
||||
result.append((config.id, config.name + ' (' + _('not used') + ')'))
|
||||
continue
|
||||
result.append((config.id, config.name + ' (' + config.session_ids[0].user_id.name + ')'))
|
||||
result.append((config.id, config.name + ' (' + last_session.user_id.name + ')'))
|
||||
return result
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -215,7 +215,7 @@ var ProxyDevice = core.Class.extend(mixins.PropertiesMixin,{
|
||||
var self = this;
|
||||
|
||||
function status(){
|
||||
self.connection.rpc('/hw_proxy/status_json',{},{timeout:2500})
|
||||
self.connection.rpc('/hw_proxy/status_json',{},{shadow: true, timeout:2500})
|
||||
.then(function(driver_status){
|
||||
self.set_connection_status('connected',driver_status);
|
||||
},function(){
|
||||
@@ -239,7 +239,7 @@ var ProxyDevice = core.Class.extend(mixins.PropertiesMixin,{
|
||||
callbacks[i](params);
|
||||
}
|
||||
if(this.get('status').status !== 'disconnected'){
|
||||
return this.connection.rpc('/hw_proxy/' + name, params || {});
|
||||
return this.connection.rpc('/hw_proxy/' + name, params || {}, {shadow: true});
|
||||
}else{
|
||||
return (new $.Deferred()).reject();
|
||||
}
|
||||
@@ -588,7 +588,7 @@ var BarcodeReader = core.Class.extend({
|
||||
this.remote_active = 1;
|
||||
|
||||
function waitforbarcode(){
|
||||
return self.proxy.connection.rpc('/hw_proxy/scanner',{},{timeout:7500})
|
||||
return self.proxy.connection.rpc('/hw_proxy/scanner',{},{shadow: true, timeout:7500})
|
||||
.then(function(barcode){
|
||||
if(!self.remote_scanning){
|
||||
self.remote_active = 0;
|
||||
|
||||
@@ -141,7 +141,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-if="widget.editable" class="o_kanban_card_manage_settings row">
|
||||
<div t-if="widget.editable" class="o_kanban_card_manage_settings row" groups="point_of_sale.group_pos_manager">
|
||||
<div class="col-xs-12 text-right">
|
||||
<a type="edit">Settings</a>
|
||||
</div>
|
||||
|
||||
@@ -27,11 +27,14 @@ var Printer = core.Class.extend(mixins.PropertiesMixin,{
|
||||
function send_printing_job(){
|
||||
if(self.receipt_queue.length > 0){
|
||||
var r = self.receipt_queue.shift();
|
||||
self.connection.rpc('/hw_proxy/print_xml_receipt',{receipt: r},{timeout: 5000})
|
||||
var options = {shadow: true, timeout: 5000};
|
||||
self.connection.rpc('/hw_proxy/print_xml_receipt', {receipt: r}, options)
|
||||
.then(function(){
|
||||
send_printing_job();
|
||||
},function(){
|
||||
},function(error, event){
|
||||
self.receipt_queue.unshift(r);
|
||||
console.log('There was an error while trying to print the order:');
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -55,7 +58,7 @@ models.load_models({
|
||||
|
||||
for(var i = 0; i < printers.length; i++){
|
||||
if(active_printers[printers[i].id]){
|
||||
var url = printers[i].proxy_ip;
|
||||
var url = printers[i].proxy_ip || '';
|
||||
if(url.indexOf('//') < 0){
|
||||
url = 'http://'+url;
|
||||
}
|
||||
|
||||
@@ -964,12 +964,12 @@ class Task(models.Model):
|
||||
@api.multi
|
||||
def message_get_suggested_recipients(self):
|
||||
recipients = super(Task, self).message_get_suggested_recipients()
|
||||
for task in self.filtered('partner_id'):
|
||||
reason = _('Customer Email') if task.partner_id.email else _('Customer')
|
||||
for task in self:
|
||||
if task.partner_id:
|
||||
reason = _('Customer Email') if task.partner_id.email else _('Customer')
|
||||
task._message_add_suggested_recipient(recipients, partner=task.partner_id, reason=reason)
|
||||
elif task.email_from:
|
||||
task._message_add_suggested_recipient(recipients, partner=task.email_from, reason=reason)
|
||||
task._message_add_suggested_recipient(recipients, email=task.email_from, reason=_('Customer Email'))
|
||||
return recipients
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -402,6 +402,8 @@ class PurchaseOrder(models.Model):
|
||||
for pick in order.picking_ids.filtered(lambda r: r.state != 'cancel'):
|
||||
pick.action_cancel()
|
||||
|
||||
order.order_line.write({'move_dest_ids':[(5,0,0)]})
|
||||
|
||||
self.write({'state': 'cancel'})
|
||||
|
||||
@api.multi
|
||||
@@ -947,7 +949,8 @@ class ProcurementRule(models.Model):
|
||||
cache[domain] = po
|
||||
if not po:
|
||||
vals = self._prepare_purchase_order(product_id, product_qty, product_uom, origin, values, partner)
|
||||
po = self.env['purchase.order'].sudo().create(vals)
|
||||
company_id = values.get('company_id') and values['company_id'].id or self.env.user.company_id.id
|
||||
po = self.env['purchase.order'].with_context(force_company=company_id).sudo().create(vals)
|
||||
cache[domain] = po
|
||||
elif not po.origin or origin not in po.origin.split(', '):
|
||||
if po.origin:
|
||||
|
||||
@@ -1239,3 +1239,8 @@ class SaleOrderLine(models.Model):
|
||||
discount = (new_list_price - price) / new_list_price * 100
|
||||
if discount > 0:
|
||||
self.discount = discount
|
||||
|
||||
|
||||
def _is_delivery(self):
|
||||
self.ensure_one()
|
||||
return False
|
||||
@@ -347,7 +347,7 @@
|
||||
</div>
|
||||
<h2>Invoicing</h2>
|
||||
<div class="row mt16 o_settings_container">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" title="This default value is applied to any new product created. This can be changed in the product detail form.">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box hidden" title="This default value is applied to any new product created. This can be changed in the product detail form.">
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="default_invoice_policy"/>
|
||||
<div class="text-muted">
|
||||
|
||||
@@ -48,6 +48,8 @@ tour.register('sale_tour', {
|
||||
in_modal: false,
|
||||
run: function (actions) {
|
||||
actions.auto();
|
||||
// There might be a modal because of the view:
|
||||
// sale.order.form.editable.list, enabled by some groups
|
||||
if ($(".modal-footer .btn-primary").length) {
|
||||
actions.auto(".modal-footer .btn-primary");
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class SaleOrder(models.Model):
|
||||
for order in self:
|
||||
dates_list = []
|
||||
order_datetime = fields.Datetime.from_string(order.date_order)
|
||||
for line in order.order_line.filtered(lambda x: x.state != 'cancel'):
|
||||
for line in order.order_line.filtered(lambda x: x.state != 'cancel' and not x._is_delivery()):
|
||||
dt = order_datetime + timedelta(days=line.customer_lead or 0.0)
|
||||
dates_list.append(dt)
|
||||
if dates_list:
|
||||
|
||||
@@ -585,3 +585,23 @@ class ProductCategory(models.Model):
|
||||
category = category.parent_id
|
||||
routes |= category.route_ids
|
||||
self.total_route_ids = routes
|
||||
|
||||
|
||||
class ProductUoM(models.Model):
|
||||
_inherit = 'product.uom'
|
||||
|
||||
def write(self, values):
|
||||
# Users can not update the factor if open stock moves are based on it
|
||||
if 'factor' in values or 'factor_inv' in values or 'category_id' in values:
|
||||
stock_move_lines = self.env['stock.move.line'].search_count([
|
||||
('product_uom_id.category_id', '=', self.category_id.id),
|
||||
('state', '!=', 'cancel'),
|
||||
])
|
||||
|
||||
if stock_move_lines:
|
||||
raise UserError(_(
|
||||
"You cannot change the ratio of this unit of mesure as some"
|
||||
" products with this UoM have already been moved or are "
|
||||
"currently reserved."
|
||||
))
|
||||
return super(ProductUoM, self).write(values)
|
||||
|
||||
@@ -83,6 +83,21 @@ class Location(models.Model):
|
||||
if 'usage' in values and values['usage'] == 'view':
|
||||
if self.mapped('quant_ids'):
|
||||
raise UserError(_("This location's usage cannot be changed to view as it contains products."))
|
||||
if 'usage' in values or 'scrap_location' in values:
|
||||
modified_locations = self.filtered(
|
||||
lambda l: l.usage != values.get('usage') or
|
||||
l.scrap_location != values.get('scrap_location')
|
||||
)
|
||||
reserved_quantities = self.env['stock.move.line'].search_count([
|
||||
('location_id', 'in', modified_locations.ids),
|
||||
('product_qty', '>', 0),
|
||||
])
|
||||
if reserved_quantities:
|
||||
raise UserError(_(
|
||||
"You cannot change the location type or its use as a scrap"
|
||||
" location as there are products reserved in this location."
|
||||
" Please unreserve the products first."
|
||||
))
|
||||
return super(Location, self).write(values)
|
||||
|
||||
def name_get(self):
|
||||
|
||||
@@ -787,7 +787,7 @@ class StockMove(models.Model):
|
||||
group_id = False
|
||||
return {
|
||||
'company_id': self.company_id,
|
||||
'date_planned': self.date,
|
||||
'date_planned': self.date_expected,
|
||||
'move_dest_ids': self,
|
||||
'group_id': group_id,
|
||||
'route_ids': self.route_ids,
|
||||
|
||||
@@ -439,6 +439,12 @@ class QuantPackage(models.Model):
|
||||
if move_lines_to_remove:
|
||||
move_lines_to_remove.write({'result_package_id': False})
|
||||
else:
|
||||
move_line_to_modify = self.env['stock.move.line'].search([
|
||||
('package_id', '=', package.id),
|
||||
('state', 'in', ('assigned', 'partially_available')),
|
||||
('product_qty', '!=', 0),
|
||||
])
|
||||
move_line_to_modify.write({'package_id': False})
|
||||
package.mapped('quant_ids').write({'package_id': False})
|
||||
|
||||
def action_view_picking(self):
|
||||
|
||||
@@ -397,7 +397,7 @@ class Warehouse(models.Model):
|
||||
|
||||
pull_rules_list = supplier_wh._get_supply_pull_rules_values(
|
||||
[self.Routing(output_location, transit_location, supplier_wh.out_type_id)],
|
||||
values={'route_id': inter_wh_route.id, 'propagate_warehouse_id': self.id})
|
||||
values={'route_id': inter_wh_route.id})
|
||||
pull_rules_list += self._get_supply_pull_rules_values(
|
||||
[self.Routing(transit_location, input_location, self.in_type_id)],
|
||||
values={'route_id': inter_wh_route.id, 'propagate_warehouse_id': supplier_wh.id})
|
||||
|
||||
@@ -9,6 +9,7 @@ from . import test_quant
|
||||
from . import test_inventory
|
||||
from . import test_move
|
||||
from . import test_move2
|
||||
from . import test_robustness
|
||||
from . import test_packing
|
||||
from . import test_packing_neg
|
||||
from . import test_proc_rule
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestRobustness(TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestRobustness, self).setUp()
|
||||
self.stock_location = self.env.ref('stock.stock_location_stock')
|
||||
self.customer_location = self.env.ref('stock.stock_location_customers')
|
||||
self.uom_unit = self.env.ref('product.product_uom_unit')
|
||||
self.uom_dozen = self.env.ref('product.product_uom_dozen')
|
||||
self.product1 = self.env['product.product'].create({
|
||||
'name': 'Product A',
|
||||
'type': 'product',
|
||||
'categ_id': self.env.ref('product.product_category_all').id,
|
||||
})
|
||||
|
||||
def test_uom_factor(self):
|
||||
""" Changing the factor of a unit of measure shouldn't be allowed while
|
||||
quantities are reserved, else the existing move lines won't be consistent
|
||||
with the `reserved_quantity` on quants.
|
||||
"""
|
||||
# make some stock
|
||||
self.env['stock.quant']._update_available_quantity(
|
||||
self.product1,
|
||||
self.stock_location,
|
||||
12,
|
||||
)
|
||||
|
||||
# reserve a dozen
|
||||
move1 = self.env['stock.move'].create({
|
||||
'name': 'test_uom_rounding',
|
||||
'location_id': self.stock_location.id,
|
||||
'location_dest_id': self.customer_location.id,
|
||||
'product_id': self.product1.id,
|
||||
'product_uom': self.uom_dozen.id,
|
||||
'product_uom_qty': 1,
|
||||
})
|
||||
move1._action_confirm()
|
||||
move1._action_assign()
|
||||
self.assertEqual(move1.state, 'assigned')
|
||||
quant = self.env['stock.quant']._gather(
|
||||
self.product1,
|
||||
self.stock_location,
|
||||
)
|
||||
|
||||
# assert the reservation
|
||||
self.assertEqual(quant.reserved_quantity, 12)
|
||||
self.assertEqual(move1.product_qty, 12)
|
||||
|
||||
# change the factor
|
||||
with self.assertRaises(UserError):
|
||||
with self.cr.savepoint():
|
||||
move1.product_uom.factor = 0.05
|
||||
|
||||
# assert the reservation
|
||||
self.assertEqual(quant.reserved_quantity, 12)
|
||||
self.assertEqual(move1.state, 'assigned')
|
||||
self.assertEqual(move1.product_qty, 12)
|
||||
|
||||
# unreserve
|
||||
move1._do_unreserve()
|
||||
|
||||
def test_location_usage(self):
|
||||
""" Changing the usage of a location shouldn't be allowed while
|
||||
quantities are reserved, else the existing move lines won't be
|
||||
consistent with the `reserved_quantity` on the quants.
|
||||
"""
|
||||
# change stock usage
|
||||
self.stock_location.scrap_location = True
|
||||
|
||||
# make some stock
|
||||
self.env['stock.quant']._update_available_quantity(
|
||||
self.product1,
|
||||
self.stock_location,
|
||||
1,
|
||||
)
|
||||
|
||||
# reserve a unit
|
||||
move1 = self.env['stock.move'].create({
|
||||
'name': 'test_location_archive',
|
||||
'location_id': self.stock_location.id,
|
||||
'location_dest_id': self.customer_location.id,
|
||||
'product_id': self.product1.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1,
|
||||
})
|
||||
move1._action_confirm()
|
||||
move1._action_assign()
|
||||
self.assertEqual(move1.state, 'assigned')
|
||||
quant = self.env['stock.quant']._gather(
|
||||
self.product1,
|
||||
self.stock_location,
|
||||
)
|
||||
|
||||
# assert the reservation
|
||||
self.assertEqual(quant.reserved_quantity, 0) # reservation is bypassed in scrap location
|
||||
self.assertEqual(move1.product_qty, 1)
|
||||
|
||||
# change the stock usage
|
||||
with self.assertRaises(UserError):
|
||||
with self.cr.savepoint():
|
||||
self.stock_location.scrap_location = False
|
||||
|
||||
# unreserve
|
||||
move1._do_unreserve()
|
||||
|
||||
def test_package_unpack(self):
|
||||
""" Unpack a package that contains quants with a reservation
|
||||
should also remove the package on the reserved move lines.
|
||||
"""
|
||||
package = self.env['stock.quant.package'].create({
|
||||
'name': 'Shell Helix HX7 10W30',
|
||||
})
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(
|
||||
self.product1,
|
||||
self.stock_location,
|
||||
10,
|
||||
package_id=package
|
||||
)
|
||||
|
||||
# reserve a dozen
|
||||
move1 = self.env['stock.move'].create({
|
||||
'name': 'test_uom_rounding',
|
||||
'location_id': self.stock_location.id,
|
||||
'location_dest_id': self.customer_location.id,
|
||||
'product_id': self.product1.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 10,
|
||||
})
|
||||
move1._action_confirm()
|
||||
move1._action_assign()
|
||||
|
||||
move1.result_package_id = False
|
||||
|
||||
package.unpack()
|
||||
|
||||
# unreserve
|
||||
move1._do_unreserve()
|
||||
self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location)), 1)
|
||||
self.assertEqual(len(self.env['stock.quant']._gather(self.product1, self.stock_location, package_id=package)), 0)
|
||||
|
||||
self.assertEqual(self.env['stock.quant']._gather(self.product1, self.stock_location).reserved_quantity, 0)
|
||||
@@ -228,6 +228,215 @@ class TestWarehouse(TestStockCommon):
|
||||
quant = self.env['stock.quant'].search([('product_id', '=', productA.id), ('location_id', '=', location_loss.id)])
|
||||
self.assertEqual(len(quant), 1)
|
||||
|
||||
def test_resupply_route(self):
|
||||
""" Simulate a resupply chain between warehouses.
|
||||
Stock -> transit -> Dist. -> transit -> Shop -> Customer
|
||||
Create the move from Shop to Customer and ensure that all the pull
|
||||
rules are triggered in order to complete the move chain to Stock.
|
||||
"""
|
||||
warehouse_stock = self.env['stock.warehouse'].create({
|
||||
'name': 'Stock.',
|
||||
'code': 'STK',
|
||||
})
|
||||
|
||||
warehouse_distribution = self.env['stock.warehouse'].create({
|
||||
'name': 'Dist.',
|
||||
'code': 'DIST',
|
||||
'default_resupply_wh_id': warehouse_stock.id,
|
||||
'resupply_wh_ids': [(6, 0, [warehouse_stock.id])]
|
||||
})
|
||||
|
||||
warehouse_shop = self.env['stock.warehouse'].create({
|
||||
'name': 'Shop',
|
||||
'code': 'SHOP',
|
||||
'default_resupply_wh_id': warehouse_distribution.id,
|
||||
'resupply_wh_ids': [(6, 0, [warehouse_distribution.id])]
|
||||
})
|
||||
|
||||
route_stock_to_dist = warehouse_distribution.resupply_route_ids
|
||||
route_dist_to_shop = warehouse_shop.resupply_route_ids
|
||||
|
||||
# Change the procure_method on the pull rules between dist and shop
|
||||
# warehouses. Since mto and resupply routes are both on product it will
|
||||
# select one randomly between them and if it select the resupply it is
|
||||
# 'make to stock' and it will not create the picking between stock and
|
||||
# dist warehouses.
|
||||
route_dist_to_shop.pull_ids.write({'procure_method': 'make_to_order'})
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Fakir',
|
||||
'type': 'product',
|
||||
'route_ids': [(4, route_id) for route_id in [route_stock_to_dist.id, route_dist_to_shop.id, self.env.ref('stock.route_warehouse0_mto').id]],
|
||||
})
|
||||
|
||||
picking_out = self.env['stock.picking'].create({
|
||||
'partner_id': self.env.ref('base.res_partner_2').id,
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id,
|
||||
'location_id': warehouse_shop.lot_stock_id.id,
|
||||
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
|
||||
})
|
||||
self.env['stock.move'].create({
|
||||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom': product.uom_id.id,
|
||||
'picking_id': picking_out.id,
|
||||
'location_id': warehouse_shop.lot_stock_id.id,
|
||||
'location_dest_id': self.env.ref('stock.stock_location_customers').id,
|
||||
'warehouse_id': warehouse_shop.id,
|
||||
'procure_method': 'make_to_order',
|
||||
})
|
||||
picking_out.action_confirm()
|
||||
|
||||
moves = self.env['stock.move'].search([('product_id', '=', product.id)])
|
||||
# Shop/Stock -> Customer
|
||||
# Transit -> Shop/Stock
|
||||
# Dist/Stock -> Transit
|
||||
# Transit -> Dist/Stock
|
||||
# Stock/Stock -> Transit
|
||||
self.assertEqual(len(moves), 5, 'Invalid moves number.')
|
||||
self.assertTrue(self.env['stock.move'].search([('location_id', '=', warehouse_stock.lot_stock_id.id)]))
|
||||
self.assertTrue(self.env['stock.move'].search([('location_dest_id', '=', warehouse_distribution.lot_stock_id.id)]))
|
||||
self.assertTrue(self.env['stock.move'].search([('location_id', '=', warehouse_distribution.lot_stock_id.id)]))
|
||||
self.assertTrue(self.env['stock.move'].search([('location_dest_id', '=', warehouse_shop.lot_stock_id.id)]))
|
||||
self.assertTrue(self.env['stock.move'].search([('location_id', '=', warehouse_shop.lot_stock_id.id)]))
|
||||
|
||||
def test_mutiple_resupply_warehouse(self):
|
||||
""" Simulate the following situation:
|
||||
- 2 shops with stock are resupply by 2 distinct warehouses
|
||||
- Shop Namur is resupply by the warehouse stock Namur
|
||||
- Shop Wavre is resupply by the warehouse stock Wavre
|
||||
- Simulate 2 moves for the same product but in different shop.
|
||||
This test ensure that the move are supplied by the correct distribution
|
||||
warehouse.
|
||||
"""
|
||||
customer_location = self.env.ref('stock.stock_location_customers')
|
||||
|
||||
warehouse_distribution_wavre = self.env['stock.warehouse'].create({
|
||||
'name': 'Stock Wavre.',
|
||||
'code': 'WV',
|
||||
})
|
||||
|
||||
warehouse_shop_wavre = self.env['stock.warehouse'].create({
|
||||
'name': 'Shop Wavre',
|
||||
'code': 'SHWV',
|
||||
'default_resupply_wh_id': warehouse_distribution_wavre.id,
|
||||
'resupply_wh_ids': [(6, 0, [warehouse_distribution_wavre.id])]
|
||||
})
|
||||
|
||||
warehouse_distribution_namur = self.env['stock.warehouse'].create({
|
||||
'name': 'Stock Namur.',
|
||||
'code': 'NM',
|
||||
})
|
||||
|
||||
warehouse_shop_namur = self.env['stock.warehouse'].create({
|
||||
'name': 'Shop Namur',
|
||||
'code': 'SHNM',
|
||||
'default_resupply_wh_id': warehouse_distribution_namur.id,
|
||||
'resupply_wh_ids': [(6, 0, [warehouse_distribution_namur.id])]
|
||||
})
|
||||
|
||||
route_shop_namur = warehouse_shop_namur.resupply_route_ids
|
||||
route_shop_wavre = warehouse_shop_wavre.resupply_route_ids
|
||||
|
||||
# The product contains the 2 resupply routes.
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Fakir',
|
||||
'type': 'product',
|
||||
'route_ids': [(4, route_id) for route_id in [route_shop_namur.id, route_shop_wavre.id, self.env.ref('stock.route_warehouse0_mto').id]],
|
||||
})
|
||||
|
||||
# Add 1 quant in each distribution warehouse.
|
||||
self.env['stock.quant']._update_available_quantity(product, warehouse_distribution_wavre.lot_stock_id, 1.0)
|
||||
self.env['stock.quant']._update_available_quantity(product, warehouse_distribution_namur.lot_stock_id, 1.0)
|
||||
|
||||
# Create the move for the shop Namur. Should create a resupply from
|
||||
# distribution warehouse Namur.
|
||||
picking_out_namur = self.env['stock.picking'].create({
|
||||
'partner_id': self.env.ref('base.res_partner_2').id,
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id,
|
||||
'location_id': warehouse_shop_namur.lot_stock_id.id,
|
||||
'location_dest_id': customer_location.id,
|
||||
})
|
||||
self.env['stock.move'].create({
|
||||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom': product.uom_id.id,
|
||||
'picking_id': picking_out_namur.id,
|
||||
'location_id': warehouse_shop_namur.lot_stock_id.id,
|
||||
'location_dest_id': customer_location.id,
|
||||
'warehouse_id': warehouse_shop_namur.id,
|
||||
'procure_method': 'make_to_order',
|
||||
})
|
||||
picking_out_namur.action_confirm()
|
||||
|
||||
# Validate the picking
|
||||
# Dist. warehouse Namur -> transit Location -> Shop Namur
|
||||
picking_stock_transit = self.env['stock.picking'].search([('location_id', '=', warehouse_distribution_namur.lot_stock_id.id)])
|
||||
self.assertTrue(picking_stock_transit)
|
||||
picking_stock_transit.action_assign()
|
||||
picking_stock_transit.move_lines[0].quantity_done = 1.0
|
||||
picking_stock_transit.action_done()
|
||||
|
||||
picking_transit_shop_namur = self.env['stock.picking'].search([('location_dest_id', '=', warehouse_shop_namur.lot_stock_id.id)])
|
||||
self.assertTrue(picking_transit_shop_namur)
|
||||
picking_transit_shop_namur.action_assign()
|
||||
picking_transit_shop_namur.move_lines[0].quantity_done = 1.0
|
||||
picking_transit_shop_namur.action_done()
|
||||
|
||||
picking_out_namur.action_assign()
|
||||
picking_out_namur.move_lines[0].quantity_done = 1.0
|
||||
picking_out_namur.action_done()
|
||||
|
||||
# Check that the correct quantity has been provided to customer
|
||||
self.assertEqual(self.env['stock.quant']._gather(product, customer_location).quantity, 1)
|
||||
# Ensure there still no quants in distribution warehouse
|
||||
self.assertEqual(len(self.env['stock.quant']._gather(product, warehouse_distribution_namur.lot_stock_id)), 0)
|
||||
|
||||
# Create the move for the shop Wavre. Should create a resupply from
|
||||
# distribution warehouse Wavre.
|
||||
picking_out_wavre = self.env['stock.picking'].create({
|
||||
'partner_id': self.env.ref('base.res_partner_2').id,
|
||||
'picking_type_id': self.env.ref('stock.picking_type_out').id,
|
||||
'location_id': warehouse_shop_wavre.lot_stock_id.id,
|
||||
'location_dest_id': customer_location.id,
|
||||
})
|
||||
self.env['stock.move'].create({
|
||||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom': product.uom_id.id,
|
||||
'picking_id': picking_out_wavre.id,
|
||||
'location_id': warehouse_shop_wavre.lot_stock_id.id,
|
||||
'location_dest_id': customer_location.id,
|
||||
'warehouse_id': warehouse_shop_wavre.id,
|
||||
'procure_method': 'make_to_order',
|
||||
})
|
||||
picking_out_wavre.action_confirm()
|
||||
|
||||
# Validate the picking
|
||||
# Dist. warehouse Wavre -> transit Location -> Shop Wavre
|
||||
picking_stock_transit = self.env['stock.picking'].search([('location_id', '=', warehouse_distribution_wavre.lot_stock_id.id)])
|
||||
self.assertTrue(picking_stock_transit)
|
||||
picking_stock_transit.action_assign()
|
||||
picking_stock_transit.move_lines[0].quantity_done = 1.0
|
||||
picking_stock_transit.action_done()
|
||||
|
||||
picking_transit_shop_wavre = self.env['stock.picking'].search([('location_dest_id', '=', warehouse_shop_wavre.lot_stock_id.id)])
|
||||
self.assertTrue(picking_transit_shop_wavre)
|
||||
picking_transit_shop_wavre.action_assign()
|
||||
picking_transit_shop_wavre.move_lines[0].quantity_done = 1.0
|
||||
picking_transit_shop_wavre.action_done()
|
||||
|
||||
picking_out_wavre.action_assign()
|
||||
picking_out_wavre.move_lines[0].quantity_done = 1.0
|
||||
picking_out_wavre.action_done()
|
||||
|
||||
# Check that the correct quantity has been provided to customer
|
||||
self.assertEqual(self.env['stock.quant']._gather(product, customer_location).quantity, 2)
|
||||
# Ensure there still no quants in distribution warehouse
|
||||
self.assertEqual(len(self.env['stock.quant']._gather(product, warehouse_distribution_wavre.lot_stock_id)), 0)
|
||||
|
||||
class TestResupply(TestStockCommon):
|
||||
def setUp(self):
|
||||
|
||||
@@ -146,11 +146,13 @@ class ProductProduct(models.Model):
|
||||
'account_id': debit_account_id,
|
||||
'debit': abs(diff * qty_available),
|
||||
'credit': 0,
|
||||
'product_id': product.id,
|
||||
}), (0, 0, {
|
||||
'name': _('Standard Price changed - %s') % (product.display_name),
|
||||
'account_id': credit_account_id,
|
||||
'debit': 0,
|
||||
'credit': abs(diff * qty_available),
|
||||
'product_id': product.id,
|
||||
})],
|
||||
}
|
||||
move = AccountMove.create(move_vals)
|
||||
|
||||
@@ -41,7 +41,8 @@ var CrashManager = core.Class.extend({
|
||||
core.bus.trigger('connection_lost');
|
||||
this.connection_lost = true;
|
||||
var timeinterval = setInterval(function() {
|
||||
ajax.jsonRpc('/web/webclient/version_info').then(function() {
|
||||
var options = {shadow: true};
|
||||
ajax.jsonRpc('/web/webclient/version_info', 'call', {}, options).then(function () {
|
||||
clearInterval(timeinterval);
|
||||
core.bus.trigger('connection_restored');
|
||||
self.connection_lost = false;
|
||||
|
||||
@@ -2579,6 +2579,11 @@ var BasicModel = AbstractModel.extend({
|
||||
relationField: field.relation_field,
|
||||
viewType: view ? view.type : fieldInfo.viewType,
|
||||
});
|
||||
// set existing changes to the list
|
||||
if (record._changes && record._changes[fieldName]) {
|
||||
list._changes = self.localData[record._changes[fieldName]]._changes;
|
||||
record._changes[fieldName] = list.id;
|
||||
}
|
||||
record.data[fieldName] = list.id;
|
||||
if (!fieldInfo.__no_fetch) {
|
||||
var def = self._readUngroupedList(list).then(function () {
|
||||
|
||||
@@ -143,10 +143,12 @@ QUnit.module('relational_fields', {
|
||||
user: {
|
||||
fields: {
|
||||
name: {string: "Name", type: "char"},
|
||||
partner_ids: {string: "one2many partners field", type: "one2many", relation: 'partner', relation_field: 'user_id'},
|
||||
},
|
||||
records: [{
|
||||
id: 17,
|
||||
name: "Aline",
|
||||
partner_ids: [1, 2],
|
||||
}, {
|
||||
id: 19,
|
||||
name: "Christine",
|
||||
@@ -9474,6 +9476,94 @@ QUnit.module('relational_fields', {
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.test('onchange affecting inline unopened list view', function (assert) {
|
||||
// when we got onchange result for fields of record that were not
|
||||
// already available because they were in a inline view not already
|
||||
// opened, in a given configuration the change were applied ignoring
|
||||
// existing data, thus a line of a one2many field inside a one2many
|
||||
// field could be duplicated unexplectedly
|
||||
assert.expect(5);
|
||||
|
||||
var numUserOnchange = 0;
|
||||
|
||||
this.data.user.onchanges = {
|
||||
partner_ids: function (obj) {
|
||||
if (numUserOnchange === 0) {
|
||||
// simulate proper server onchange after save of modal with new record
|
||||
obj.partner_ids = [
|
||||
[5],
|
||||
[1, 1, {
|
||||
display_name: 'first record',
|
||||
turtles: [
|
||||
[5],
|
||||
[1, 2, {'display_name': 'donatello'}],
|
||||
],
|
||||
}],
|
||||
[1, 2, {
|
||||
display_name: 'second record',
|
||||
turtles: [
|
||||
[5],
|
||||
obj.partner_ids[1][2].turtles[0],
|
||||
],
|
||||
}],
|
||||
];
|
||||
}
|
||||
numUserOnchange++;
|
||||
},
|
||||
};
|
||||
|
||||
var form = createView({
|
||||
View: FormView,
|
||||
model: 'user',
|
||||
data: this.data,
|
||||
arch: '<form><sheet><group>' +
|
||||
'<field name="partner_ids">' +
|
||||
'<form>'+
|
||||
'<field name="turtles">' +
|
||||
'<tree editable="bottom">' +
|
||||
'<field name="display_name"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</form>' +
|
||||
'<tree>' +
|
||||
'<field name="display_name"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</group></sheet></form>',
|
||||
res_id: 17,
|
||||
});
|
||||
|
||||
// add a turtle on second partner
|
||||
form.$buttons.find('.o_form_button_edit').click();
|
||||
form.$('.o_data_row:eq(1)').click();
|
||||
$('.modal .o_field_x2many_list_row_add a').click();
|
||||
$('.modal input[name="display_name"]').val('michelangelo').change();
|
||||
$('.modal .btn-primary').click();
|
||||
// open first partner so changes from previous action are applied
|
||||
form.$('.o_data_row:eq(0)').click();
|
||||
$('.modal .btn-primary').click();
|
||||
form.$buttons.find('.o_form_button_save').click();
|
||||
|
||||
assert.strictEqual(numUserOnchange, 2,
|
||||
'there should 2 and only 2 onchange from closing the partner modal');
|
||||
|
||||
form.$('.o_data_row:eq(0)').click();
|
||||
assert.strictEqual($('.modal .o_data_row').length, 1,
|
||||
'only 1 turtle for first partner');
|
||||
assert.strictEqual($('.modal .o_data_row').text(), 'donatello',
|
||||
'first partner turtle is donatello');
|
||||
$('.modal .o_form_button_cancel').click();
|
||||
|
||||
form.$('.o_data_row:eq(1)').click();
|
||||
assert.strictEqual($('.modal .o_data_row').length, 1,
|
||||
'only 1 turtle for second partner');
|
||||
assert.strictEqual($('.modal .o_data_row').text(), 'michelangelo',
|
||||
'second partner turtle is michelangelo');
|
||||
$('.modal .o_form_button_cancel').click();
|
||||
|
||||
form.destroy();
|
||||
});
|
||||
|
||||
QUnit.module('FieldMany2Many');
|
||||
|
||||
QUnit.test('many2many kanban: edition', function (assert) {
|
||||
|
||||
@@ -253,7 +253,7 @@ class Web_Editor(http.Controller):
|
||||
## @param bundles - True if the bundles views must be fetched (default to False)
|
||||
## @param bundles_restriction - Names of the bundle in which to look for less files (if empty, search in all of them)
|
||||
## @returns a dictionary with views info in the views key and style info in the less key
|
||||
@http.route("/web_editor/get_assets_editor_resources", type="json", auth="user")
|
||||
@http.route("/web_editor/get_assets_editor_resources", type="json", auth="user", website=True)
|
||||
def get_assets_editor_resources(self, key, get_views=True, get_less=True, bundles=False, bundles_restriction=[]):
|
||||
# Related views must be fetched if the user wants the views and/or the style
|
||||
views = request.env["ir.ui.view"].get_related_views(key, bundles=bundles)
|
||||
|
||||
@@ -108,9 +108,14 @@ function getMatchedCSSRules(a) {
|
||||
// The css generates all the attributes separately and not in simplified form.
|
||||
// In order to have a better compatibility (outlook for example) we simplify the css tags.
|
||||
// e.g. border-left-style: none; border-bottom-s .... will be simplified in border-style = none
|
||||
_.each([['margin'], ['padding'], ['border', 'style']], function (attr) {
|
||||
var p = attr[0];
|
||||
var e = attr[1] ? '-' + attr[1] : '';
|
||||
_.each([
|
||||
{property: 'margin'},
|
||||
{property: 'padding'},
|
||||
{property: 'border', propertyEnd: '-style', defaultValue: 'none'},
|
||||
], function (propertyInfo) {
|
||||
var p = propertyInfo.property;
|
||||
var e = propertyInfo.propertyEnd || '';
|
||||
var defVal = propertyInfo.defaultValue || 0;
|
||||
|
||||
if (style[p+'-top'+e] || style[p+'-right'+e] || style[p+'-bottom'+e] || style[p+'-left'+e]) {
|
||||
if (style[p+'-top'+e] === style[p+'-right'+e] && style[p+'-top'+e] === style[p+'-bottom'+e] && style[p+'-top'+e] === style[p+'-left'+e]) {
|
||||
@@ -119,7 +124,7 @@ function getMatchedCSSRules(a) {
|
||||
}
|
||||
else {
|
||||
// keep => property: [top value] [right value] [bottom value] [left value];
|
||||
style[p+e] = (style[p+'-top'+e] || 0) + ' ' + (style[p+'-right'+e] || 0) + ' ' + (style[p+'-bottom'+e] || 0) + ' ' + (style[p+'-left'+e] || 0);
|
||||
style[p+e] = (style[p+'-top'+e] || defVal) + ' ' + (style[p+'-right'+e] || defVal) + ' ' + (style[p+'-bottom'+e] || defVal) + ' ' + (style[p+'-left'+e] || defVal);
|
||||
if (style[p+e].indexOf('inherit') !== -1 || style[p+e].indexOf('initial') !== -1) {
|
||||
// keep => property-top: [top value]; property-right: [right value]; property-bottom: [bottom value]; property-left: [left value];
|
||||
delete style[p+e];
|
||||
|
||||
@@ -175,7 +175,7 @@ class WebsiteEventController(http.Controller):
|
||||
'event': event,
|
||||
'main_object': event,
|
||||
'range': range,
|
||||
'registrable': event._is_event_registrable()
|
||||
'registrable': event.sudo()._is_event_registrable()
|
||||
}
|
||||
return request.render("website_event.event_description_full", values)
|
||||
|
||||
|
||||
@@ -480,7 +480,7 @@
|
||||
<t t-if="event.state != 'draft'" itemprop="availability" content="http://schema.org/Discontinued">
|
||||
Event registration is closed.
|
||||
</t>
|
||||
<t t-if="uid">
|
||||
<t t-if="request.env.user.has_group('event.group_event_manager')">
|
||||
<a t-attf-href="/web#id=#{event.id}&view_type=form&model=event.event">
|
||||
<i class="fa fa-plus-circle"></i>
|
||||
<em>Configure and Launch Event Registration</em>
|
||||
|
||||
@@ -8,6 +8,7 @@ sAnimation.registry.follow_alias = sAnimation.Class.extend({
|
||||
start: function () {
|
||||
var self = this;
|
||||
this.is_user = false;
|
||||
var unsubscribePage = window.location.search.slice(1).split('&').indexOf("unsubscribe") >= 0;
|
||||
this._rpc({
|
||||
route: '/groups/is_member',
|
||||
params: {
|
||||
@@ -19,6 +20,9 @@ sAnimation.registry.follow_alias = sAnimation.Class.extend({
|
||||
self.is_user = data.is_user;
|
||||
self.email = data.email;
|
||||
self.$target.find('.js_mg_link').attr('href', '/groups/' + self.$target.data('id'));
|
||||
if (unsubscribePage && self.is_user) {
|
||||
self.$target.find(".js_mg_follow_form").remove();
|
||||
}
|
||||
self.toggle_subscription(data.is_member ? 'on' : 'off', data.email);
|
||||
self.$target.removeClass("hidden");
|
||||
});
|
||||
|
||||
@@ -43,7 +43,7 @@ class ResConfigSettings(models.TransientModel):
|
||||
|
||||
order_mail_template = fields.Many2one('mail.template', string='Order Confirmation Email',
|
||||
default=_default_order_mail_template, domain="[('model', '=', 'sale.order')]",
|
||||
help="Email sent to customer at the end of the checkout process")
|
||||
help="Email sent to customer at the end of the checkout process", readonly=True)
|
||||
|
||||
automatic_invoice = fields.Boolean("Automatic Invoice",
|
||||
help="The invoice is generated automatically and available in the customer portal "
|
||||
|
||||
@@ -9,7 +9,7 @@ require('website_sale.website_sale');
|
||||
$('.oe_website_sale #add_to_cart, .oe_website_sale #products_grid .a-submit')
|
||||
.off('click')
|
||||
.removeClass('a-submit')
|
||||
.click(function (event) {
|
||||
.click(_.debounce(function (event) {
|
||||
var $form = $(this).closest('form');
|
||||
var quantity = parseFloat($form.find('input[name="add_qty"]').val() || 1);
|
||||
var product_id = parseInt($form.find('input[type="hidden"][name="product_id"], input[type="radio"][name="product_id"]:checked').first().val(),10);
|
||||
@@ -96,6 +96,6 @@ $('.oe_website_sale #add_to_cart, .oe_website_sale #products_grid .a-submit')
|
||||
});
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}, 200, true));
|
||||
|
||||
});
|
||||
|
||||
@@ -189,7 +189,7 @@ class Property(models.Model):
|
||||
if not values:
|
||||
return
|
||||
|
||||
if not default_value:
|
||||
if default_value is None:
|
||||
domain = self._get_domain(name, model)
|
||||
if domain is None:
|
||||
raise Exception()
|
||||
|
||||
@@ -174,7 +174,7 @@ class IrQWeb(models.AbstractModel, QWeb):
|
||||
value=ast.Call(
|
||||
func=ast.Attribute(
|
||||
value=ast.Name(id='self', ctx=ast.Load()),
|
||||
attr='_get_asset',
|
||||
attr='_get_asset_nodes',
|
||||
ctx=ast.Load()
|
||||
),
|
||||
args=[
|
||||
@@ -286,15 +286,28 @@ class IrQWeb(models.AbstractModel, QWeb):
|
||||
|
||||
# method called by computing code
|
||||
|
||||
def get_asset_bundle(self, xmlid, files, remains=None, env=None):
|
||||
return AssetsBundle(xmlid, files, remains=remains, env=env)
|
||||
|
||||
# compatibility to remove after v11 - DEPRECATED
|
||||
@tools.conditional(
|
||||
'xml' not in tools.config['dev_mode'],
|
||||
tools.ormcache_context('xmlid', 'options.get("lang", "en_US")', 'css', 'js', 'debug', 'async', keys=("website_id",)),
|
||||
)
|
||||
def _get_asset(self, xmlid, options, css=True, js=True, debug=False, async=False, values=None):
|
||||
files, remains = self._get_asset_content(xmlid, options)
|
||||
asset = self.get_asset_bundle(xmlid, files, remains, env=self.env)
|
||||
return asset.to_html(css=css, js=js, debug=debug, async=async, url_for=(values or {}).get('url_for', lambda url: url))
|
||||
|
||||
@tools.conditional(
|
||||
# in non-xml-debug mode we want assets to be cached forever, and the admin can force a cache clear
|
||||
# by restarting the server after updating the source code (or using the "Clear server cache" in debug tools)
|
||||
'xml' not in tools.config['dev_mode'],
|
||||
tools.ormcache_context('xmlid', 'options.get("lang", "en_US")', 'css', 'js', 'debug', 'async', keys=("website_id",)),
|
||||
)
|
||||
def _get_asset(self, xmlid, options, css=True, js=True, debug=False, async=False, values=None):
|
||||
def _get_asset_nodes(self, xmlid, options, css=True, js=True, debug=False, async=False, values=None):
|
||||
files, remains = self._get_asset_content(xmlid, options)
|
||||
asset = AssetsBundle(xmlid, files, env=self.env)
|
||||
asset = self.get_asset_bundle(xmlid, files, env=self.env)
|
||||
remains = [node for node in remains if (css and node[0] == 'link') or (js and node[0] != 'link')]
|
||||
return remains + asset.to_node(css=css, js=js, debug=debug, async=async)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user