[MERGE] forward port branch saas-14 up to c0e7f9b0b1
This commit is contained in:
@@ -998,7 +998,7 @@ class AccountMoveLine(models.Model):
|
||||
second_line_dict['amount_currency'] = -second_line_dict['amount_currency']
|
||||
|
||||
# Create the move
|
||||
writeoff_move = self.env['account.move'].create({
|
||||
writeoff_move = self.env['account.move'].with_context(apply_taxes=True).create({
|
||||
'journal_id': vals['journal_id'],
|
||||
'date': vals['date'],
|
||||
'state': 'draft',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import json
|
||||
import urllib
|
||||
import urllib2
|
||||
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.exceptions import UserError
|
||||
@@ -9,10 +9,10 @@ from odoo.exceptions import UserError
|
||||
|
||||
def geo_find(addr):
|
||||
url = 'https://maps.googleapis.com/maps/api/geocode/json?sensor=false&address='
|
||||
url += urllib.quote(addr.encode('utf8'))
|
||||
url += urllib2.quote(addr.encode('utf8'))
|
||||
|
||||
try:
|
||||
result = json.load(urllib.urlopen(url))
|
||||
result = json.load(urllib2.urlopen(url))
|
||||
except Exception as e:
|
||||
raise UserError(_('Cannot contact geolocation servers. Please make sure that your Internet connection is up and running (%s).') % e)
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ class ResPartner(models.Model):
|
||||
country_code = partner.commercial_partner_id.country_id.code
|
||||
if country_code:
|
||||
if not check_func(country_code.lower(), partner.vat):
|
||||
msg = self._construct_constraint_msg(country_code.lower())
|
||||
msg = partner._construct_constraint_msg(country_code.lower())
|
||||
raise ValidationError(msg)
|
||||
|
||||
def _construct_constraint_msg(self, country_code):
|
||||
|
||||
@@ -10,5 +10,6 @@ class CalendarBusController(BusController):
|
||||
# --------------------------
|
||||
def _poll(self, dbname, channels, last, options):
|
||||
if request.session.uid:
|
||||
channels = list(channels)
|
||||
channels.append((request.db, 'calendar.alarm', request.env.user.partner_id.id))
|
||||
return super(CalendarBusController, self)._poll(dbname, channels, last, options)
|
||||
|
||||
@@ -10,7 +10,7 @@ class Partner(models.Model):
|
||||
|
||||
team_id = fields.Many2one('crm.team', string='Sales Channel', oldname='section_id')
|
||||
opportunity_ids = fields.One2many('crm.lead', 'partner_id', string='Opportunities', domain=[('type', '=', 'opportunity')])
|
||||
meeting_ids = fields.Many2many('calendar.event', 'calendar_event_res_partner_rel', 'res_partner_id', 'calendar_event_id', string='Meetings')
|
||||
meeting_ids = fields.Many2many('calendar.event', 'calendar_event_res_partner_rel', 'res_partner_id', 'calendar_event_id', string='Meetings', copy=False)
|
||||
opportunity_count = fields.Integer("Opportunity", compute='_compute_opportunity_count')
|
||||
meeting_count = fields.Integer("# Meetings", compute='_compute_meeting_count')
|
||||
activities_count = fields.Integer("Activities", compute='_compute_activities_count')
|
||||
|
||||
@@ -139,7 +139,11 @@ class GoogleService(models.TransientModel):
|
||||
if error.code == 400: # invalid grant
|
||||
with registry(request.session.db).cursor() as cur:
|
||||
self.env(cur)['res.users'].browse(self.env.uid).write({'google_%s_rtoken' % service: False})
|
||||
error_key = json.loads(error.read()).get("error", "nc")
|
||||
try:
|
||||
error_file = error.read()
|
||||
error_key = json.loads(error_file).get("error", "nc")
|
||||
except:
|
||||
error_key = error
|
||||
_logger.exception("Bad google request : %s !", error_key)
|
||||
error_msg = _("Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired [%s]") % error_key
|
||||
raise self.env['res.config.settings'].get_config_warning(error_msg)
|
||||
|
||||
@@ -513,7 +513,7 @@ class GoogleCalendar(models.AbstractModel):
|
||||
if type == "write":
|
||||
res = CalendarEvent.browse(event['id']).write(result)
|
||||
elif type == "copy":
|
||||
result['recurrence'] = True
|
||||
result['recurrency'] = True
|
||||
res = CalendarEvent.browse([event['id']]).write(result)
|
||||
elif type == "create":
|
||||
res = CalendarEvent.create(result).id
|
||||
|
||||
@@ -55,9 +55,7 @@ index_template = """
|
||||
<p>
|
||||
If you need to grant remote debugging access to a developer, you can do it <a href='/remote_connect'>here</a>.
|
||||
</p>
|
||||
<p>
|
||||
If you need to display the current customer basket on another device, you can do it <a href='/point_of_sale/display'>here</a>.
|
||||
</p>
|
||||
%s
|
||||
<p>
|
||||
The PosBox software installed on this posbox is <b>version 16</b>,
|
||||
the posbox version number is independent from Odoo. You can upgrade
|
||||
@@ -72,10 +70,21 @@ index_template = """
|
||||
|
||||
|
||||
class PosboxHomepage(odoo.addons.web.controllers.main.Home):
|
||||
|
||||
def get_hw_screen_message(self):
|
||||
return """
|
||||
<p>
|
||||
The activate the customer display feature, you will need to reinstall the PosBox software.
|
||||
You can find the latest images on the <a href="http://nightly.odoo.com/master/posbox/">Odoo Nightly builds</a> website.
|
||||
Make sure to download at least the version 16.<br/>
|
||||
Odoo version 11, or above, is required to use the customer display feature.
|
||||
</p>
|
||||
"""
|
||||
|
||||
@http.route('/', type='http', auth='none', website=True)
|
||||
def index(self):
|
||||
#return request.render('hw_posbox_homepage.index',mimetype='text/html')
|
||||
return index_template
|
||||
return index_template % self.get_hw_screen_message()
|
||||
|
||||
@http.route('/wifi', type='http', auth='none', website=True)
|
||||
def wifi(self):
|
||||
|
||||
@@ -119,7 +119,10 @@ class Scanner(Thread):
|
||||
def get_devices(self):
|
||||
try:
|
||||
if not evdev:
|
||||
return None
|
||||
return []
|
||||
|
||||
if not os.path.isdir(self.input_dir):
|
||||
return []
|
||||
|
||||
new_devices = [device for device in listdir(self.input_dir)
|
||||
if join(self.input_dir, device) not in [dev.evdev.fn for dev in self.open_devices]]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from odoo import http
|
||||
from odoo.tools import config
|
||||
from odoo.addons.web.controllers import main as web
|
||||
from openerp.addons.hw_posbox_homepage.controllers import main as homepage
|
||||
|
||||
import logging
|
||||
import netifaces as ni
|
||||
@@ -16,6 +17,14 @@ self_port = str(config['xmlrpc_port'] or 8069)
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class Homepage(homepage.PosboxHomepage):
|
||||
|
||||
def get_hw_screen_message(self):
|
||||
return """
|
||||
<p>
|
||||
If you need to display the current customer basket on another device, you can do it <a href='/point_of_sale/display'>here</a>.
|
||||
</p>
|
||||
"""
|
||||
|
||||
class HardwareScreen(web.Home):
|
||||
|
||||
|
||||
@@ -327,8 +327,12 @@ class MrpProduction(models.Model):
|
||||
return self.env['stock.move']
|
||||
if bom_line.product_id.type not in ['product', 'consu']:
|
||||
return self.env['stock.move']
|
||||
if self.bom_id.routing_id and self.bom_id.routing_id.location_id:
|
||||
source_location = self.bom_id.routing_id.location_id
|
||||
if self.routing_id:
|
||||
routing = self.routing_id
|
||||
else:
|
||||
routing = self.bom_id.routing_id
|
||||
if routing and routing.location_id:
|
||||
source_location = routing.location_id
|
||||
else:
|
||||
source_location = self.location_src_id
|
||||
original_quantity = self.product_qty - self.qty_produced
|
||||
|
||||
@@ -631,7 +631,7 @@ td {
|
||||
margin-right: 0;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
width: 188px;
|
||||
width: 186px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
border-top: 1px solid;
|
||||
|
||||
@@ -335,7 +335,7 @@ class ResCountryGroup(models.Model):
|
||||
class PricelistItem(models.Model):
|
||||
_name = "product.pricelist.item"
|
||||
_description = "Pricelist item"
|
||||
_order = "applied_on, min_quantity desc, categ_id desc"
|
||||
_order = "applied_on, min_quantity desc, categ_id desc, id"
|
||||
|
||||
product_tmpl_id = fields.Many2one(
|
||||
'product.template', 'Product Template', ondelete='cascade',
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<search string="Product">
|
||||
<field name="name" string="Product" filter_domain="['|','|',('default_code','ilike',self),('name','ilike',self),('barcode','ilike',self)]"/>
|
||||
<field name="categ_id" domain="[('categ_id', 'child_of', self)]"/>
|
||||
<field name="categ_id" filter_domain="[('categ_id', 'child_of', self)]"/>
|
||||
<separator/>
|
||||
<filter string="Services" name="services" domain="[('type','=','service')]"/>
|
||||
<filter string="Products" name="consumable" domain="[('type', 'in', ['consu', 'product'])]"/>
|
||||
|
||||
@@ -35,7 +35,6 @@ class ProductProduct(models.Model):
|
||||
|
||||
def _calc_price(self, bom):
|
||||
price = 0.0
|
||||
workcenter_cost = 0.0
|
||||
result, result2 = bom.explode(self, 1)
|
||||
for sbom, sbom_data in result2:
|
||||
if not sbom.attribute_value_ids:
|
||||
@@ -44,9 +43,8 @@ class ProductProduct(models.Model):
|
||||
if bom.routing_id:
|
||||
total_cost = 0.0
|
||||
for order in bom.routing_id.operation_ids:
|
||||
total_cost += (order.time_cycle_manual/60) * order.workcenter_id.costs_hour
|
||||
workcenter_cost = total_cost / len(bom.routing_id.operation_ids)
|
||||
price += bom.product_uom_id._compute_price(workcenter_cost, bom.product_id.uom_id)
|
||||
total_cost += (order.time_cycle/60) * order.workcenter_id.costs_hour
|
||||
price += bom.product_uom_id._compute_price(total_cost, bom.product_id.uom_id)
|
||||
# Convert on product UoM quantities
|
||||
if price > 0:
|
||||
price = bom.product_uom_id._compute_price(price / bom.product_qty, self.uom_id)
|
||||
|
||||
@@ -104,7 +104,7 @@ class ProductProduct(models.Model):
|
||||
#Cost price is calculated afterwards as it is a property
|
||||
sqlstr = """
|
||||
select
|
||||
sum(l.price_unit * l.quantity)/sum(nullif(l.quantity,0)) as avg_unit_price,
|
||||
sum(l.price_unit * l.quantity)/nullif(sum(l.quantity),0) as avg_unit_price,
|
||||
sum(l.quantity) as num_qty,
|
||||
sum(l.quantity * (l.price_subtotal/(nullif(l.quantity,0)))) as total,
|
||||
sum(l.quantity * pt.list_price) as sale_expected
|
||||
|
||||
@@ -253,7 +253,10 @@ class SaleOrder(models.Model):
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if vals.get('name', _('New')) == _('New'):
|
||||
vals['name'] = self.env['ir.sequence'].next_by_code('sale.order') or _('New')
|
||||
if 'company_id' in vals:
|
||||
vals['name'] = self.env['ir.sequence'].with_context(force_company=vals['company_id']).next_by_code('sale.order') or _('New')
|
||||
else:
|
||||
vals['name'] = self.env['ir.sequence'].next_by_code('sale.order') or _('New')
|
||||
|
||||
# Makes sure partner_invoice_id', 'partner_shipping_id' and 'pricelist_id' are defined
|
||||
if any(f not in vals for f in ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id']):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import unittest
|
||||
from odoo.addons.stock_landed_costs.tests.common import TestStockLandedCostsCommon
|
||||
|
||||
class TestLandedCosts(TestStockLandedCostsCommon):
|
||||
@@ -45,6 +45,10 @@ class TestLandedCosts(TestStockLandedCostsCommon):
|
||||
'location_dest_id': self.customer_location_id})
|
||||
|
||||
def test_00_landed_costs_on_incoming_shipment(self):
|
||||
chart_of_accounts = self.env.user.company_id.chart_template_id
|
||||
generic_coa = self.env.ref('l10n_generic_coa.configurable_chart_template')
|
||||
if chart_of_accounts != generic_coa:
|
||||
raise unittest.SkipTest('Skip this test as it works only with %s (%s loaded)' % (generic_coa.name, chart_of_accounts.name))
|
||||
""" Test landed cost on incoming shipment """
|
||||
#
|
||||
# (A) Purchase product
|
||||
@@ -94,6 +98,11 @@ class TestLandedCosts(TestStockLandedCostsCommon):
|
||||
self.assertEqual(account_entry['debit'], 430.0, 'Wrong Account Entry')
|
||||
|
||||
def test_01_negative_landed_costs_on_incoming_shipment(self):
|
||||
chart_of_accounts = self.env.user.company_id.chart_template_id
|
||||
generic_coa = self.env.ref('l10n_generic_coa.configurable_chart_template')
|
||||
if chart_of_accounts != generic_coa:
|
||||
raise unittest.SkipTest('Skip this test as it works only with %s (%s loaded)' % (generic_coa.name, chart_of_accounts.name))
|
||||
|
||||
""" Test negative landed cost on incoming shipment """
|
||||
#
|
||||
# (A) Purchase Product
|
||||
|
||||
@@ -665,7 +665,7 @@ class Database(http.Controller):
|
||||
request.session.authenticate(name, post['login'], password)
|
||||
return http.local_redirect('/web/')
|
||||
except Exception, e:
|
||||
error = "Database creation error: %s" % e
|
||||
error = "Database creation error: %s" % str(e) or repr(e)
|
||||
return self._render_template(error=error)
|
||||
|
||||
@http.route('/web/database/duplicate', type='http', auth="none", methods=['POST'], csrf=False)
|
||||
@@ -676,7 +676,7 @@ class Database(http.Controller):
|
||||
dispatch_rpc('db', 'duplicate_database', [master_pwd, name, new_name])
|
||||
return http.local_redirect('/web/database/manager')
|
||||
except Exception, e:
|
||||
error = "Database duplication error: %s" % e
|
||||
error = "Database duplication error: %s" % str(e) or repr(e)
|
||||
return self._render_template(error=error)
|
||||
|
||||
@http.route('/web/database/drop', type='http', auth="none", methods=['POST'], csrf=False)
|
||||
@@ -686,7 +686,7 @@ class Database(http.Controller):
|
||||
request._cr = None # dropping a database leads to an unusable cursor
|
||||
return http.local_redirect('/web/database/manager')
|
||||
except Exception, e:
|
||||
error = "Database deletion error: %s" % e
|
||||
error = "Database deletion error: %s" % str(e) or repr(e)
|
||||
return self._render_template(error=error)
|
||||
|
||||
@http.route('/web/database/backup', type='http', auth="none", methods=['POST'], csrf=False)
|
||||
@@ -704,7 +704,7 @@ class Database(http.Controller):
|
||||
return response
|
||||
except Exception, e:
|
||||
_logger.exception('Database.backup')
|
||||
error = "Database backup error: %s" % e
|
||||
error = "Database backup error: %s" % str(e) or repr(e)
|
||||
return self._render_template(error=error)
|
||||
|
||||
@http.route('/web/database/restore', type='http', auth="none", methods=['POST'], csrf=False)
|
||||
@@ -714,7 +714,7 @@ class Database(http.Controller):
|
||||
dispatch_rpc('db', 'restore', [master_pwd, name, data, str2bool(copy)])
|
||||
return http.local_redirect('/web/database/manager')
|
||||
except Exception, e:
|
||||
error = "Database restore error: %s" % e
|
||||
error = "Database restore error: %s" % str(e) or repr(e)
|
||||
return self._render_template(error=error)
|
||||
|
||||
@http.route('/web/database/change_password', type='http', auth="none", methods=['POST'], csrf=False)
|
||||
@@ -723,7 +723,7 @@ class Database(http.Controller):
|
||||
dispatch_rpc('db', 'change_admin_password', [master_pwd, master_pwd_new])
|
||||
return http.local_redirect('/web/database/manager')
|
||||
except Exception, e:
|
||||
error = "Master password update error: %s" % e
|
||||
error = "Master password update error: %s" % str(e) or repr(e)
|
||||
return self._render_template(error=error)
|
||||
|
||||
@http.route('/web/database/list', type='json', auth='none')
|
||||
|
||||
@@ -1118,7 +1118,7 @@ data.Editor = Class.extend({
|
||||
}).bind(this));
|
||||
|
||||
// Activate the overlay
|
||||
$style_button.toggleClass("hidden", $ul.children(":not(.divider):not(.hidden)").length === 0);
|
||||
$style_button.toggleClass("hidden", $ul.children(":not(.o_main_header):not(.divider):not(.hidden)").length === 0);
|
||||
this.$overlay.toggleClass("oe_active", !!focus);
|
||||
|
||||
function _do_action_focus(style, $dest) {
|
||||
|
||||
@@ -576,15 +576,18 @@ odoo.define('web_editor.snippets.options', function (require) {
|
||||
registry.background = SnippetOption.extend({
|
||||
start: function () {
|
||||
var res = this._super.apply(this, arguments);
|
||||
this.$target.off(".background-option")
|
||||
.on("background-color-event.background-option", (function (e, type) {
|
||||
e.stopPropagation();
|
||||
if (e.currentTarget !== e.target) return;
|
||||
this.$el.find("li:first > a").trigger(type);
|
||||
}).bind(this));
|
||||
this.bind_bg_events();
|
||||
return res;
|
||||
},
|
||||
|
||||
bind_bg_events: function () {
|
||||
this.$target.off(".background-option")
|
||||
.on("background-color-event.background-option", (function (e, type) {
|
||||
e.stopPropagation();
|
||||
if (e.currentTarget !== e.target) return;
|
||||
this.$el.find("li:first > a").trigger(type);
|
||||
}).bind(this));
|
||||
},
|
||||
background: function (type, value, $li) {
|
||||
if (value && value.length) {
|
||||
this.$target.css("background-image", "url(" + value + ")");
|
||||
|
||||
@@ -238,3 +238,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
.o_tooltip {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,7 +328,9 @@ animation.registry.parallax = animation.Class.extend({
|
||||
if (!this.$bg || !this.$bg.length) {
|
||||
this.$bg = this.$("> .s_parallax_bg");
|
||||
if (!this.$bg.length) {
|
||||
this.$bg = $("<span/>", {"class": "s_parallax_bg"}).prependTo(this.$target);
|
||||
this.$bg = $('<span/>', {
|
||||
class: 's_parallax_bg' + (this.$target.hasClass('oe_custom_bg') ? ' oe_custom_bg' : ''),
|
||||
}).prependTo(this.$target);
|
||||
}
|
||||
}
|
||||
var urlTarget = this.$target.css("background-image");
|
||||
|
||||
@@ -228,6 +228,9 @@ options.registry.carousel = options.registry.slider.extend({
|
||||
s_option.$target = self.$target.find(".item.active");
|
||||
s_option.set_active();
|
||||
s_option.$target.trigger("snippet-option-change", [s_option]);
|
||||
if (opt_name === 'background') {
|
||||
s_option.bind_bg_events();
|
||||
}
|
||||
});
|
||||
});
|
||||
this.$target.trigger('slid.bs.carousel');
|
||||
@@ -342,7 +345,6 @@ options.registry.parallax = options.Class.extend({
|
||||
_refresh: function () {
|
||||
_.defer((function () {
|
||||
this.$target.data("snippet-view")._rebuild();
|
||||
this._update_target_to_bg();
|
||||
}).bind(this));
|
||||
},
|
||||
_toggle_refresh_callback: function (on) {
|
||||
|
||||
@@ -407,7 +407,6 @@ div.carousel {
|
||||
background-size: cover;
|
||||
background-attachment: scroll;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
&.s_parallax_is_fixed > .s_parallax_bg {
|
||||
background-attachment: fixed;
|
||||
|
||||
@@ -528,7 +528,7 @@
|
||||
</template>
|
||||
|
||||
<template id="s_parallax" name="Parallax">
|
||||
<section class="parallax s_parallax oe_custom_bg oe_img_bg"
|
||||
<section class="parallax s_parallax oe_img_bg"
|
||||
style="background-image: url(/web/image/website.s_background_image_04);"
|
||||
data-scroll-background-ratio="1.6"><div class="oe_structure"/></section>
|
||||
</template>
|
||||
|
||||
@@ -152,7 +152,7 @@ class WebsiteForum(http.Controller):
|
||||
|
||||
question_ids = Post.search(domain, limit=self._post_per_page, offset=pager['offset'], order=sorting)
|
||||
|
||||
values = self._prepare_forum_values(forum=forum, searches=post)
|
||||
values = self._prepare_forum_values(forum=forum, searches=post, header={'ask_hide': not forum.active})
|
||||
values.update({
|
||||
'main_object': tag or forum,
|
||||
'question_ids': question_ids,
|
||||
|
||||
@@ -152,11 +152,14 @@ class website_account(website_account):
|
||||
order.check_access_rule('read')
|
||||
except AccessError:
|
||||
return request.render("website.403")
|
||||
order_invoice_lines = {il.product_id.id: il.invoice_id for il in order.invoice_ids.mapped('invoice_line_ids')}
|
||||
|
||||
order_sudo = order.sudo()
|
||||
order_invoice_lines = {il.product_id.id: il.invoice_id for il in order_sudo.invoice_ids.mapped('invoice_line_ids')}
|
||||
|
||||
history = request.session.get('my_orders_history', [])
|
||||
|
||||
values = {
|
||||
'order': order.sudo(),
|
||||
'order': order_sudo,
|
||||
'order_invoice_lines': order_invoice_lines,
|
||||
}
|
||||
values.update(get_records_pager(history, order))
|
||||
|
||||
@@ -61,28 +61,24 @@ class PaymentTransaction(models.Model):
|
||||
|
||||
def _confirm_so(self, acquirer_name=False):
|
||||
for tx in self:
|
||||
try:
|
||||
# check tx state, confirm the potential SO
|
||||
if tx.sale_order_id and tx.sale_order_id.state in ['draft', 'sent']:
|
||||
# verify SO/TX match, excluding tx.fees which are currently not included in SO
|
||||
amount_matches = float_compare(tx.amount, tx.sale_order_id.amount_total, 2) == 0
|
||||
if amount_matches:
|
||||
if not acquirer_name:
|
||||
acquirer_name = tx.sale_order_id.payment_acquirer_id.provider or 'unknown'
|
||||
if tx.state == 'authorized' and tx.acquirer_id.auto_confirm == 'authorize':
|
||||
_logger.info('<%s> transaction authorized, auto-confirming order %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id)
|
||||
tx.sale_order_id.with_context(send_email=True).action_confirm()
|
||||
if tx.state == 'done' and tx.acquirer_id.auto_confirm in ['confirm_so', 'generate_and_pay_invoice']:
|
||||
_logger.info('<%s> transaction completed, auto-confirming order %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id)
|
||||
tx.sale_order_id.with_context(send_email=True).action_confirm()
|
||||
# check tx state, confirm the potential SO
|
||||
if tx.sale_order_id and tx.sale_order_id.state in ['draft', 'sent']:
|
||||
# verify SO/TX match, excluding tx.fees which are currently not included in SO
|
||||
amount_matches = float_compare(tx.amount, tx.sale_order_id.amount_total, 2) == 0
|
||||
if amount_matches:
|
||||
if not acquirer_name:
|
||||
acquirer_name = tx.sale_order_id.payment_acquirer_id.provider or 'unknown'
|
||||
if tx.state == 'authorized' and tx.acquirer_id.auto_confirm == 'authorize':
|
||||
_logger.info('<%s> transaction authorized, auto-confirming order %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id)
|
||||
tx.sale_order_id.with_context(send_email=True).action_confirm()
|
||||
if tx.state == 'done' and tx.acquirer_id.auto_confirm in ['confirm_so', 'generate_and_pay_invoice']:
|
||||
_logger.info('<%s> transaction completed, auto-confirming order %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id)
|
||||
tx.sale_order_id.with_context(send_email=True).action_confirm()
|
||||
|
||||
if tx.acquirer_id.auto_confirm == 'generate_and_pay_invoice':
|
||||
self._generate_and_pay_invoice(tx, acquirer_name)
|
||||
elif tx.state not in ['cancel', 'error'] and tx.sale_order_id.state == 'draft':
|
||||
_logger.info('<%s> transaction pending/to confirm manually, sending quote email for order %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id)
|
||||
tx.sale_order_id.force_quotation_send()
|
||||
else:
|
||||
_logger.warning('<%s> transaction MISMATCH for order %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id)
|
||||
|
||||
except Exception:
|
||||
_logger.exception('Fail to confirm the order or send the confirmation email%s', tx and ' for the transaction %s' % tx.reference or '')
|
||||
if tx.acquirer_id.auto_confirm == 'generate_and_pay_invoice':
|
||||
self._generate_and_pay_invoice(tx, acquirer_name)
|
||||
elif tx.state not in ['cancel', 'error'] and tx.sale_order_id.state == 'draft':
|
||||
_logger.info('<%s> transaction pending/to confirm manually, sending quote email for order %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id)
|
||||
tx.sale_order_id.force_quotation_send()
|
||||
else:
|
||||
_logger.warning('<%s> transaction MISMATCH for order %s (ID %s)', acquirer_name, tx.sale_order_id.name, tx.sale_order_id.id)
|
||||
|
||||
@@ -245,7 +245,9 @@ class WebsiteSale(http.Controller):
|
||||
|
||||
ProductAttribute = request.env['product.attribute']
|
||||
if products:
|
||||
attributes = ProductAttribute.search([('attribute_line_ids.product_tmpl_id', 'in', products.ids)])
|
||||
# get all products without limit
|
||||
selected_products = Product.search(domain, limit=False)
|
||||
attributes = ProductAttribute.search([('attribute_line_ids.product_tmpl_id', 'in', selected_products.ids)])
|
||||
else:
|
||||
attributes = ProductAttribute.browse(attributes_ids)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Canada, France, 8-2-2015
|
||||
Canada, France, 2017-05-12
|
||||
|
||||
Savoir-faire Linux agrees to the terms of the Odoo Corporate Contributor License
|
||||
Agreement v1.0.
|
||||
@@ -8,22 +8,25 @@ declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Maxime Chambreuil partners@savoirfairelinux.com https://github.com/savoirfairelinux
|
||||
Jérome Boisvert-Chouinard jerome.boisvertchouinard@savoirfairelinux.com https://github.com/jbchouinard
|
||||
|
||||
List of contributors:
|
||||
|
||||
Agathe Mollé agathe.molle@savoirfairelinux.com https://github.com/Ehtaga
|
||||
Bruno Joliveau bruno.joliveau@savoirfairelinux.com https://github.com/bjoliveau
|
||||
David Cormier david.cormier@savoirfairelinux.com https://github.com/cormier
|
||||
David Dufresne david.dufresne@savoirfairelinux.com https://github.com/dufresnedavid
|
||||
Davin Baragiotta davin.baragiotta@savoirfairelinux.com https://github.com/giotta
|
||||
El Hadji Dem elhadji.dem@savoirfairelinux.com https://github.com/ehdem
|
||||
Guillaume Auger guillaume.auger@savoirfairelinux.com https://github.com/jehog
|
||||
Jérome Boisvert-Chouinard jerome.boisvertchouinard@savoirfairelinux.com https://github.com/jbchouinard
|
||||
Joao Alfredo Gama Batista joao.gama@savoirfairelinux.com https://github.com/joaoalf
|
||||
Jordi Riera jordi.riera@savoirfairelinux.com https://github.com/foutoucour
|
||||
Julien Roux julien.roux@savoirfairelinux.com https://github.com/jrouxsfl
|
||||
Loïc Faure-Lacroix loic.lacroix@savoirfairelinux.com https://github.com/llacroix
|
||||
Maxime Chambreuil maxime.chambreuil@savoirfairelinux.com https://github.com/max3903
|
||||
Pierre Gault pierre.gault@savoirfairelinux.com https://github.com/gaultp
|
||||
Julie Moussu julie.mousse@savoirfairelinux.com https://github.com/JulieSFL
|
||||
Pierre Lamarche pierre.lamarche@savoirfairelinux.com https://github.com/plamarche
|
||||
Sandy Carter sandy.carter@savoirfairelinux.com https://github.com/bwrsandman
|
||||
Vincent Vinet vincent.vinet@savoirfairelinux.com https://github.com/veloutin
|
||||
David Cormier david.cormier@savoirfairelinux.com https://github.com/cormier (up to 2017-05-12)
|
||||
Davin Baragiotta davin.baragiotta@savoirfairelinux.com https://github.com/giotta (up to 2017-05-12)
|
||||
El Hadji Dem elhadji.dem@savoirfairelinux.com https://github.com/ehdem (up to 2017-05-12)
|
||||
Guillaume Auger guillaume.auger@savoirfairelinux.com https://github.com/jehog (up to 2017-05-12)
|
||||
Jordi Riera jordi.riera@savoirfairelinux.com https://github.com/foutoucour (up to 2017-05-12)
|
||||
Julien Roux julien.roux@savoirfairelinux.com https://github.com/jrouxsfl (up to 2017-05-12)
|
||||
Loïc Faure-Lacroix loic.lacroix@savoirfairelinux.com https://github.com/llacroix (up to 2017-05-12)
|
||||
Maxime Chambreuil maxime.chambreuil@savoirfairelinux.com https://github.com/max3903 (up to 2017-05-12)
|
||||
Pierre Gault pierre.gault@savoirfairelinux.com https://github.com/gaultp (up to 2017-05-12)
|
||||
Sandy Carter sandy.carter@savoirfairelinux.com https://github.com/bwrsandman (up to 2017-05-12)
|
||||
Vincent Vinet vincent.vinet@savoirfairelinux.com https://github.com/veloutin (up to 2017-05-12)
|
||||
|
||||
@@ -9,4 +9,4 @@ declaration.
|
||||
Signed,
|
||||
|
||||
Jesús Ventosinos Mayor jesus@pexego.es https://github.com/jesusVMayor
|
||||
|
||||
Jesús Ventosinos Mayor jesus@comunitea.com https://github.com/jesusVMayor
|
||||
|
||||
+1
-1
@@ -569,7 +569,7 @@ class BaseModel(object):
|
||||
field = cls._fields.get(name)
|
||||
if not field:
|
||||
_logger.warning("method %s.%s: @constrains parameter %r is not a field name", cls._name, attr, name)
|
||||
elif not (field.store or field.inverse):
|
||||
elif not (field.store or field.inverse or field.inherited):
|
||||
_logger.warning("method %s.%s: @constrains parameter %r is not writeable", cls._name, attr, name)
|
||||
methods.append(func)
|
||||
|
||||
|
||||
+7
-25
@@ -57,10 +57,15 @@ def check(f):
|
||||
elif isinstance(kwargs, dict):
|
||||
if 'context' in kwargs:
|
||||
ctx = kwargs['context']
|
||||
elif 'kwargs' in kwargs:
|
||||
elif 'kwargs' in kwargs and kwargs['kwargs'].get('context'):
|
||||
# http entry points such as call_kw()
|
||||
ctx = kwargs['kwargs'].get('context')
|
||||
|
||||
else:
|
||||
try:
|
||||
from odoo.http import request
|
||||
ctx = request.env.context
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
uid = 1
|
||||
if args and isinstance(args[0], (long, int)):
|
||||
@@ -74,29 +79,6 @@ def check(f):
|
||||
# queries (as in IntegrityError) will invalidate the current one.
|
||||
cr = False
|
||||
|
||||
if hasattr(src, '__call__'):
|
||||
# callable. We need to find the right parameters to call
|
||||
# the orm._sql_message(self, cr, uid, ids, context) function,
|
||||
# or we skip..
|
||||
# our signature is f(registry, dbname [,uid, obj, method, args])
|
||||
try:
|
||||
if args and len(args) > 1:
|
||||
# TODO self doesn't exist, but was already wrong before (it was not a registry but just the object_service.
|
||||
obj = self.get(args[1])
|
||||
if len(args) > 3 and isinstance(args[3], (long, int, list)):
|
||||
ids = args[3]
|
||||
else:
|
||||
ids = []
|
||||
cr = odoo.sql_db.db_connect(dbname).cursor()
|
||||
return src(obj, cr, uid, ids, context=(ctx or {}))
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
if cr: cr.close()
|
||||
|
||||
return False # so that the original SQL error will
|
||||
# be returned, it is the best we have.
|
||||
|
||||
try:
|
||||
cr = odoo.sql_db.db_connect(dbname).cursor()
|
||||
res = translate(cr, name=False, source_type=ttype,
|
||||
|
||||
Reference in New Issue
Block a user