[MERGE] forward port branch saas-14 up to 6956ad54de
This commit is contained in:
@@ -675,7 +675,7 @@ class AccountInvoice(models.Model):
|
|||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
credit_aml = self.env['account.move.line'].browse(credit_aml_id)
|
credit_aml = self.env['account.move.line'].browse(credit_aml_id)
|
||||||
if not credit_aml.currency_id and self.currency_id != self.company_id.currency_id:
|
if not credit_aml.currency_id and self.currency_id != self.company_id.currency_id:
|
||||||
credit_aml.with_context(allow_amount_currency=True).write({
|
credit_aml.with_context(allow_amount_currency=True, check_move_validity=False).write({
|
||||||
'amount_currency': self.company_id.currency_id.with_context(date=credit_aml.date).compute(credit_aml.balance, self.currency_id),
|
'amount_currency': self.company_id.currency_id.with_context(date=credit_aml.date).compute(credit_aml.balance, self.currency_id),
|
||||||
'currency_id': self.currency_id.id})
|
'currency_id': self.currency_id.id})
|
||||||
if credit_aml.payment_id:
|
if credit_aml.payment_id:
|
||||||
|
|||||||
@@ -76,10 +76,8 @@ class AccountConfigSettings(models.TransientModel):
|
|||||||
def set_default_product_taxes(self):
|
def set_default_product_taxes(self):
|
||||||
""" Set the product taxes if they have changed """
|
""" Set the product taxes if they have changed """
|
||||||
ir_values_obj = self.env['ir.values']
|
ir_values_obj = self.env['ir.values']
|
||||||
if self.default_sale_tax_id:
|
ir_values_obj.sudo().set_default('product.template', "taxes_id", [self.default_sale_tax_id.id] if self.default_sale_tax_id else False, for_all_users=True, company_id=self.company_id.id)
|
||||||
ir_values_obj.sudo().set_default('product.template', "taxes_id", [self.default_sale_tax_id.id], for_all_users=True, company_id=self.company_id.id)
|
ir_values_obj.sudo().set_default('product.template', "supplier_taxes_id", [self.default_purchase_tax_id.id] if self.default_purchase_tax_id else False, for_all_users=True, company_id=self.company_id.id)
|
||||||
if self.default_purchase_tax_id:
|
|
||||||
ir_values_obj.sudo().set_default('product.template', "supplier_taxes_id", [self.default_purchase_tax_id.id], for_all_users=True, company_id=self.company_id.id)
|
|
||||||
|
|
||||||
def set_chart_of_accounts(self):
|
def set_chart_of_accounts(self):
|
||||||
""" install a chart of accounts for the given company (if required) """
|
""" install a chart of accounts for the given company (if required) """
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class AccountInvoiceRefund(models.TransientModel):
|
|||||||
for tmpline in refund.move_id.line_ids:
|
for tmpline in refund.move_id.line_ids:
|
||||||
if tmpline.account_id.id == inv.account_id.id:
|
if tmpline.account_id.id == inv.account_id.id:
|
||||||
to_reconcile_lines += tmpline
|
to_reconcile_lines += tmpline
|
||||||
to_reconcile_lines.filtered(lambda l: l.reconciled == False).reconcile()
|
to_reconcile_lines.filtered(lambda l: l.reconciled == False).reconcile()
|
||||||
if mode == 'modify':
|
if mode == 'modify':
|
||||||
invoice = inv.read(inv_obj._get_refund_modify_read_fields())
|
invoice = inv.read(inv_obj._get_refund_modify_read_fields())
|
||||||
invoice = invoice[0]
|
invoice = invoice[0]
|
||||||
|
|||||||
@@ -393,7 +393,7 @@ class AccountVoucherLine(models.Model):
|
|||||||
if product.description_purchase:
|
if product.description_purchase:
|
||||||
values['name'] += '\n' + product.description_purchase
|
values['name'] += '\n' + product.description_purchase
|
||||||
else:
|
else:
|
||||||
values['price_unit'] = product.lst_price
|
values['price_unit'] = price_unit or product.lst_price
|
||||||
taxes = product.taxes_id or account.tax_ids
|
taxes = product.taxes_id or account.tax_ids
|
||||||
if product.description_sale:
|
if product.description_sale:
|
||||||
values['name'] += '\n' + product.description_sale
|
values['name'] += '\n' + product.description_sale
|
||||||
@@ -403,7 +403,7 @@ class AccountVoucherLine(models.Model):
|
|||||||
if company and currency:
|
if company and currency:
|
||||||
if company.currency_id != currency:
|
if company.currency_id != currency:
|
||||||
if type == 'purchase':
|
if type == 'purchase':
|
||||||
values['price_unit'] = product.standard_price
|
values['price_unit'] = price_unit or product.standard_price
|
||||||
values['price_unit'] = values['price_unit'] * currency.rate
|
values['price_unit'] = values['price_unit'] * currency.rate
|
||||||
|
|
||||||
return {'value': values, 'domain': {}}
|
return {'value': values, 'domain': {}}
|
||||||
|
|||||||
@@ -664,7 +664,13 @@ class Import(models.TransientModel):
|
|||||||
}]
|
}]
|
||||||
|
|
||||||
_logger.info('importing %d rows...', len(data))
|
_logger.info('importing %d rows...', len(data))
|
||||||
import_result = self.env[self.res_model].with_context(import_file=True).load(import_fields, data)
|
|
||||||
|
model = self.env[self.res_model].with_context(import_file=True)
|
||||||
|
defer_parent_store = self.env.context.get('defer_parent_store_computation', True)
|
||||||
|
if defer_parent_store and model._parent_store:
|
||||||
|
model = model.with_context(defer_parent_store_computation=True)
|
||||||
|
|
||||||
|
import_result = model.load(import_fields, data)
|
||||||
_logger.info('done')
|
_logger.info('done')
|
||||||
|
|
||||||
# If transaction aborted, RELEASE SAVEPOINT is going to raise
|
# If transaction aborted, RELEASE SAVEPOINT is going to raise
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import urllib2
|
|||||||
import werkzeug.urls
|
import werkzeug.urls
|
||||||
|
|
||||||
from odoo import api, fields, models, registry, _
|
from odoo import api, fields, models, registry, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
from odoo.http import request
|
from odoo.http import request
|
||||||
|
|
||||||
|
|
||||||
@@ -124,6 +125,9 @@ class GoogleService(models.TransientModel):
|
|||||||
client_id = get_param('google_%s_client_id' % (service,), default=False)
|
client_id = get_param('google_%s_client_id' % (service,), default=False)
|
||||||
client_secret = get_param('google_%s_client_secret' % (service,), default=False)
|
client_secret = get_param('google_%s_client_secret' % (service,), default=False)
|
||||||
|
|
||||||
|
if not client_id or not client_secret:
|
||||||
|
raise UserError(_("The account for the Google service '%s' is not configured") % service)
|
||||||
|
|
||||||
headers = {"content-type": "application/x-www-form-urlencoded"}
|
headers = {"content-type": "application/x-www-form-urlencoded"}
|
||||||
data = werkzeug.url_encode({
|
data = werkzeug.url_encode({
|
||||||
'refresh_token': refresh_token,
|
'refresh_token': refresh_token,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import commands
|
import commands
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
import subprocess
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ BANNED_DEVICES = set([
|
|||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
# drivers modules must add to drivers an object with a get_status() method
|
# drivers modules must add to drivers an object with a get_status() method
|
||||||
# so that 'status' can return the status of all active drivers
|
# so that 'status' can return the status of all active drivers
|
||||||
drivers = {}
|
drivers = {}
|
||||||
|
|
||||||
@@ -107,7 +108,7 @@ class Proxy(http.Controller):
|
|||||||
if not (device_id in BANNED_DEVICES):
|
if not (device_id in BANNED_DEVICES):
|
||||||
resp+= "<div class='device' data-device='"+device+"'>"+device_name+"</div>\n"
|
resp+= "<div class='device' data-device='"+device+"'>"+device_name+"</div>\n"
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
if count == 0:
|
if count == 0:
|
||||||
resp += "<div class='device'>No USB Device Found</div>"
|
resp += "<div class='device'>No USB Device Found</div>"
|
||||||
|
|
||||||
@@ -125,7 +126,7 @@ class Proxy(http.Controller):
|
|||||||
""" % subprocess.check_output('lsusb -v', shell=True)
|
""" % subprocess.check_output('lsusb -v', shell=True)
|
||||||
|
|
||||||
return request.make_response(resp,{
|
return request.make_response(resp,{
|
||||||
'Cache-Control': 'no-cache',
|
'Cache-Control': 'no-cache',
|
||||||
'Content-Type': 'text/html; charset=utf-8',
|
'Content-Type': 'text/html; charset=utf-8',
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
'Access-Control-Allow-Methods': 'GET',
|
'Access-Control-Allow-Methods': 'GET',
|
||||||
@@ -166,7 +167,7 @@ class Proxy(http.Controller):
|
|||||||
@http.route('/hw_proxy/payment_request', type='json', auth='none', cors='*')
|
@http.route('/hw_proxy/payment_request', type='json', auth='none', cors='*')
|
||||||
def payment_request(self, price):
|
def payment_request(self, price):
|
||||||
"""
|
"""
|
||||||
The PoS will activate the method payment
|
The PoS will activate the method payment
|
||||||
"""
|
"""
|
||||||
print "payment_request: price:"+str(price)
|
print "payment_request: price:"+str(price)
|
||||||
return 'ok'
|
return 'ok'
|
||||||
@@ -174,7 +175,7 @@ class Proxy(http.Controller):
|
|||||||
@http.route('/hw_proxy/payment_status', type='json', auth='none', cors='*')
|
@http.route('/hw_proxy/payment_status', type='json', auth='none', cors='*')
|
||||||
def payment_status(self):
|
def payment_status(self):
|
||||||
print "payment_status"
|
print "payment_status"
|
||||||
return { 'status':'waiting' }
|
return { 'status':'waiting' }
|
||||||
|
|
||||||
@http.route('/hw_proxy/payment_cancel', type='json', auth='none', cors='*')
|
@http.route('/hw_proxy/payment_cancel', type='json', auth='none', cors='*')
|
||||||
def payment_cancel(self):
|
def payment_cancel(self):
|
||||||
@@ -206,12 +207,12 @@ class Proxy(http.Controller):
|
|||||||
|
|
||||||
@http.route('/hw_proxy/is_scanner_connected', type='json', auth='none', cors='*')
|
@http.route('/hw_proxy/is_scanner_connected', type='json', auth='none', cors='*')
|
||||||
def is_scanner_connected(self, receipt):
|
def is_scanner_connected(self, receipt):
|
||||||
print 'is_scanner_connected?'
|
print 'is_scanner_connected?'
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@http.route('/hw_proxy/scanner', type='json', auth='none', cors='*')
|
@http.route('/hw_proxy/scanner', type='json', auth='none', cors='*')
|
||||||
def scanner(self, receipt):
|
def scanner(self, receipt):
|
||||||
print 'scanner'
|
print 'scanner'
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ class MailController(http.Controller):
|
|||||||
follower_id = None
|
follower_id = None
|
||||||
follower_recs = request.env['mail.followers'].sudo().browse(follower_ids)
|
follower_recs = request.env['mail.followers'].sudo().browse(follower_ids)
|
||||||
res_ids = follower_recs.mapped('res_id')
|
res_ids = follower_recs.mapped('res_id')
|
||||||
request.env[res_model].browse(res_ids).check_access_rule("write")
|
request.env[res_model].browse(res_ids).check_access_rule("read")
|
||||||
for follower in follower_recs:
|
for follower in follower_recs:
|
||||||
is_uid = partner_id == follower.partner_id
|
is_uid = partner_id == follower.partner_id
|
||||||
follower_id = follower.id if is_uid else follower_id
|
follower_id = follower.id if is_uid else follower_id
|
||||||
|
|||||||
@@ -294,8 +294,11 @@ class MailComposer(models.TransientModel):
|
|||||||
'mail_server_id': self.mail_server_id.id,
|
'mail_server_id': self.mail_server_id.id,
|
||||||
'mail_activity_type_id': self.mail_activity_type_id.id,
|
'mail_activity_type_id': self.mail_activity_type_id.id,
|
||||||
}
|
}
|
||||||
|
|
||||||
# mass mailing: rendering override wizard static values
|
# mass mailing: rendering override wizard static values
|
||||||
if mass_mail_mode and self.model:
|
if mass_mail_mode and self.model:
|
||||||
|
if self.model in self.env and hasattr(self.env[self.model], 'message_get_email_values'):
|
||||||
|
mail_values.update(self.env[self.model].browse(res_id).message_get_email_values())
|
||||||
# keep a copy unless specifically requested, reset record name (avoid browsing records)
|
# keep a copy unless specifically requested, reset record name (avoid browsing records)
|
||||||
mail_values.update(notification=not self.auto_delete_message, model=self.model, res_id=res_id, record_name=False)
|
mail_values.update(notification=not self.auto_delete_message, model=self.model, res_id=res_id, record_name=False)
|
||||||
# auto deletion of mail_mail
|
# auto deletion of mail_mail
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ STRIPE_HEADERS = {'Stripe-Version': '2016-03-07'}
|
|||||||
|
|
||||||
# The following currencies are integer only, see https://stripe.com/docs/currencies#zero-decimal
|
# The following currencies are integer only, see https://stripe.com/docs/currencies#zero-decimal
|
||||||
INT_CURRENCIES = [
|
INT_CURRENCIES = [
|
||||||
'BIF', 'XAF', 'XPF', 'CLP', 'KMF', 'DJF', 'GNF', 'JPY', 'MGA', 'PYGí', 'RWF', 'KRW', 'VUV',
|
u'BIF', u'XAF', u'XPF', u'CLP', u'KMF', u'DJF', u'GNF', u'JPY', u'MGA', u'PYGí', u'RWF', u'KRW',
|
||||||
'VND', 'XOF'
|
u'VUV', u'VND', u'XOF'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
@@ -116,10 +116,13 @@ class PaymentTransactionStripe(models.Model):
|
|||||||
def _stripe_form_get_tx_from_data(self, data):
|
def _stripe_form_get_tx_from_data(self, data):
|
||||||
""" Given a data dict coming from stripe, verify it and find the related
|
""" Given a data dict coming from stripe, verify it and find the related
|
||||||
transaction record. """
|
transaction record. """
|
||||||
reference = data['metadata']['reference']
|
reference = data.get('metadata', {}).get('reference')
|
||||||
if not reference:
|
if not reference:
|
||||||
error_msg = _('Stripe: invalid reply received from provider, missing reference')
|
error_msg = _(
|
||||||
_logger.error(error_msg, data['metadata'])
|
'Stripe: invalid reply received from provider, missing reference. Additional message: %s'
|
||||||
|
% data.get('error', {}).get('message', '')
|
||||||
|
)
|
||||||
|
_logger.error(error_msg)
|
||||||
raise ValidationError(error_msg)
|
raise ValidationError(error_msg)
|
||||||
tx = self.search([('reference', '=', reference)])
|
tx = self.search([('reference', '=', reference)])
|
||||||
if not tx:
|
if not tx:
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ odoo.define('payment_stripe.stripe', function(require) {
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var ajax = require('web.ajax');
|
var ajax = require('web.ajax');
|
||||||
|
var core = require('web.core');
|
||||||
|
var _t = core._t;
|
||||||
|
var qweb = core.qweb;
|
||||||
|
ajax.loadXML('/payment_stripe/static/src/xml/stripe_templates.xml', qweb);
|
||||||
|
|
||||||
// The following currencies are integer only, see
|
// The following currencies are integer only, see
|
||||||
// https://stripe.com/docs/currencies#zero-decimal
|
// https://stripe.com/docs/currencies#zero-decimal
|
||||||
var int_currencies = [
|
var int_currencies = [
|
||||||
@@ -33,6 +38,10 @@ odoo.define('payment_stripe.stripe', function(require) {
|
|||||||
}).done(function(data){
|
}).done(function(data){
|
||||||
handler.isTokenGenerate = false;
|
handler.isTokenGenerate = false;
|
||||||
window.location.href = data;
|
window.location.href = data;
|
||||||
|
}).fail(function(){
|
||||||
|
var msg = arguments && arguments[1] && arguments[1].data && arguments[1].data.message;
|
||||||
|
var wizard = $(qweb.render('stripe.error', {'msg': msg || _t('Payment error')}));
|
||||||
|
wizard.appendTo($('body')).modal({'keyboard': true});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<templates id="template" xml:space="preserve">
|
||||||
|
<t t-name="stripe.error">
|
||||||
|
<div class="modal fade">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
|
||||||
|
<h4 class="modal-title">Error</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<t t-esc="msg"></t>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<a href="#" class="btn btn-link btn-xs" data-dismiss="modal">Close</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</templates>
|
||||||
@@ -110,7 +110,8 @@ odoo.define('point_of_sale.Tour', function (require) {
|
|||||||
trigger: ".header-button",
|
trigger: ".header-button",
|
||||||
}, {
|
}, {
|
||||||
content: "confirm closing the frontend",
|
content: "confirm closing the frontend",
|
||||||
trigger: ".header-button",
|
trigger: ".header-button.confirm",
|
||||||
|
run: function() {}, //it's a check,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
tour.register('pos_basic_order', { test: true, url: '/pos/web' }, steps);
|
tour.register('pos_basic_order', { test: true, url: '/pos/web' }, steps);
|
||||||
|
|||||||
@@ -191,7 +191,12 @@ models.Order = models.Order.extend({
|
|||||||
var product_id = line.get_product().id;
|
var product_id = line.get_product().id;
|
||||||
|
|
||||||
if (typeof resume[line_hash] === 'undefined') {
|
if (typeof resume[line_hash] === 'undefined') {
|
||||||
resume[line_hash] = { qty: qty, note: note, product_id: product_id };
|
resume[line_hash] = {
|
||||||
|
qty: qty,
|
||||||
|
note: note,
|
||||||
|
product_id: product_id,
|
||||||
|
product_name_wrapped: line.generate_wrapped_product_name(),
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
resume[line_hash].qty += qty;
|
resume[line_hash].qty += qty;
|
||||||
}
|
}
|
||||||
@@ -222,6 +227,7 @@ models.Order = models.Order.extend({
|
|||||||
add.push({
|
add.push({
|
||||||
'id': curr.product_id,
|
'id': curr.product_id,
|
||||||
'name': this.pos.db.get_product_by_id(curr.product_id).display_name,
|
'name': this.pos.db.get_product_by_id(curr.product_id).display_name,
|
||||||
|
'name_wrapped': curr.product_name_wrapped,
|
||||||
'note': curr.note,
|
'note': curr.note,
|
||||||
'qty': curr.qty,
|
'qty': curr.qty,
|
||||||
});
|
});
|
||||||
@@ -229,6 +235,7 @@ models.Order = models.Order.extend({
|
|||||||
add.push({
|
add.push({
|
||||||
'id': curr.product_id,
|
'id': curr.product_id,
|
||||||
'name': this.pos.db.get_product_by_id(curr.product_id).display_name,
|
'name': this.pos.db.get_product_by_id(curr.product_id).display_name,
|
||||||
|
'name_wrapped': curr.product_name_wrapped,
|
||||||
'note': curr.note,
|
'note': curr.note,
|
||||||
'qty': curr.qty - old.qty,
|
'qty': curr.qty - old.qty,
|
||||||
});
|
});
|
||||||
@@ -236,6 +243,7 @@ models.Order = models.Order.extend({
|
|||||||
rem.push({
|
rem.push({
|
||||||
'id': curr.product_id,
|
'id': curr.product_id,
|
||||||
'name': this.pos.db.get_product_by_id(curr.product_id).display_name,
|
'name': this.pos.db.get_product_by_id(curr.product_id).display_name,
|
||||||
|
'name_wrapped': curr.product_name_wrapped,
|
||||||
'note': curr.note,
|
'note': curr.note,
|
||||||
'qty': old.qty - curr.qty,
|
'qty': old.qty - curr.qty,
|
||||||
});
|
});
|
||||||
@@ -248,6 +256,7 @@ models.Order = models.Order.extend({
|
|||||||
rem.push({
|
rem.push({
|
||||||
'id': old.product_id,
|
'id': old.product_id,
|
||||||
'name': this.pos.db.get_product_by_id(old.product_id).display_name,
|
'name': this.pos.db.get_product_by_id(old.product_id).display_name,
|
||||||
|
'name_wrapped': old.product_name_wrapped,
|
||||||
'note': old.note,
|
'note': old.note,
|
||||||
'qty': old.qty,
|
'qty': old.qty,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,15 @@
|
|||||||
</span>
|
</span>
|
||||||
</t>
|
</t>
|
||||||
|
|
||||||
|
<t t-name="NameWrapped">
|
||||||
|
<t t-foreach="change.name_wrapped.slice(1)" t-as="wrapped_line">
|
||||||
|
<line>
|
||||||
|
<left></left>
|
||||||
|
<right><t t-esc="wrapped_line"/></right>
|
||||||
|
</line>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
|
||||||
<t t-name="OrderChangeReceipt">
|
<t t-name="OrderChangeReceipt">
|
||||||
<receipt
|
<receipt
|
||||||
align='center'
|
align='center'
|
||||||
@@ -33,8 +42,9 @@
|
|||||||
<t t-foreach="changes.cancelled" t-as="change">
|
<t t-foreach="changes.cancelled" t-as="change">
|
||||||
<line>
|
<line>
|
||||||
<left><value><t t-esc="change.qty" /></value></left>
|
<left><value><t t-esc="change.qty" /></value></left>
|
||||||
<right><t t-esc="change.name" /></right>
|
<right><t t-esc="change.name_wrapped[0]" /></right>
|
||||||
</line>
|
</line>
|
||||||
|
<t t-call="NameWrapped"/>
|
||||||
<t t-if="change.note">
|
<t t-if="change.note">
|
||||||
<line>
|
<line>
|
||||||
<left>NOTE</left>
|
<left>NOTE</left>
|
||||||
@@ -55,8 +65,9 @@
|
|||||||
<t t-foreach="changes.new" t-as="change">
|
<t t-foreach="changes.new" t-as="change">
|
||||||
<line>
|
<line>
|
||||||
<left><value><t t-esc="change.qty" /></value></left>
|
<left><value><t t-esc="change.qty" /></value></left>
|
||||||
<right><t t-esc="change.name" /></right>
|
<right><t t-esc="change.name_wrapped[0]" /></right>
|
||||||
</line>
|
</line>
|
||||||
|
<t t-call="NameWrapped"/>
|
||||||
<t t-if="change.note">
|
<t t-if="change.note">
|
||||||
<line>
|
<line>
|
||||||
<left>NOTE</left>
|
<left>NOTE</left>
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ class ProductTemplate(models.Model):
|
|||||||
updated_products.write({'attribute_value_ids': [(4, value_id.id)]})
|
updated_products.write({'attribute_value_ids': [(4, value_id.id)]})
|
||||||
|
|
||||||
# list of values combination
|
# list of values combination
|
||||||
existing_variants = [set(variant.attribute_value_ids.ids) for variant in tmpl_id.product_variant_ids]
|
existing_variants = [set(variant.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant).ids) for variant in tmpl_id.product_variant_ids]
|
||||||
variant_matrix = itertools.product(*(line.value_ids for line in tmpl_id.attribute_line_ids if line.value_ids and line.value_ids[0].attribute_id.create_variant))
|
variant_matrix = itertools.product(*(line.value_ids for line in tmpl_id.attribute_line_ids if line.value_ids and line.value_ids[0].attribute_id.create_variant))
|
||||||
variant_matrix = map(lambda record_list: reduce(lambda x, y: x+y, record_list, self.env['product.attribute.value']), variant_matrix)
|
variant_matrix = map(lambda record_list: reduce(lambda x, y: x+y, record_list, self.env['product.attribute.value']), variant_matrix)
|
||||||
to_create_variants = filter(lambda rec_set: set(rec_set.ids) not in existing_variants, variant_matrix)
|
to_create_variants = filter(lambda rec_set: set(rec_set.ids) not in existing_variants, variant_matrix)
|
||||||
@@ -412,9 +412,9 @@ class ProductTemplate(models.Model):
|
|||||||
variants_to_activate = self.env['product.product']
|
variants_to_activate = self.env['product.product']
|
||||||
variants_to_unlink = self.env['product.product']
|
variants_to_unlink = self.env['product.product']
|
||||||
for product_id in tmpl_id.product_variant_ids:
|
for product_id in tmpl_id.product_variant_ids:
|
||||||
if not product_id.active and product_id.attribute_value_ids in variant_matrix:
|
if not product_id.active and product_id.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant) in variant_matrix:
|
||||||
variants_to_activate |= product_id
|
variants_to_activate |= product_id
|
||||||
elif product_id.attribute_value_ids not in variant_matrix:
|
elif product_id.attribute_value_ids.filtered(lambda r: r.attribute_id.create_variant) not in variant_matrix:
|
||||||
variants_to_unlink |= product_id
|
variants_to_unlink |= product_id
|
||||||
if variants_to_activate:
|
if variants_to_activate:
|
||||||
variants_to_activate.write({'active': True})
|
variants_to_activate.write({'active': True})
|
||||||
|
|||||||
@@ -407,7 +407,7 @@ class Task(models.Model):
|
|||||||
@api.onchange('project_id')
|
@api.onchange('project_id')
|
||||||
def _onchange_project(self):
|
def _onchange_project(self):
|
||||||
default_partner_id = self.env.context.get('default_partner_id')
|
default_partner_id = self.env.context.get('default_partner_id')
|
||||||
default_partner = self.env['res.partner'].browse(default_partner_id) if default_partner_id else None
|
default_partner = self.env['res.partner'].browse(default_partner_id) if default_partner_id else self.env['res.partner']
|
||||||
if self.project_id:
|
if self.project_id:
|
||||||
self.partner_id = self.project_id.partner_id or default_partner
|
self.partner_id = self.project_id.partner_id or default_partner
|
||||||
self.stage_id = self.stage_find(self.project_id.id, [('fold', '=', False)])
|
self.stage_id = self.stage_find(self.project_id.id, [('fold', '=', False)])
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<field name="use_tasks" position="after">
|
<field name="use_tasks" position="after">
|
||||||
<field name="use_issues"/>
|
<field name="use_issues"/>
|
||||||
<field name="issue_ids" invisible="1"/>
|
<field name="issue_count" invisible="1"/>
|
||||||
<field name="issue_needaction_count" invisible="1"/>
|
<field name="issue_needaction_count" invisible="1"/>
|
||||||
</field>
|
</field>
|
||||||
<xpath expr="//div[contains(@class, 'o_kanban_manage_reports')]/div[last()]" position="after">
|
<xpath expr="//div[contains(@class, 'o_kanban_manage_reports')]/div[last()]" position="after">
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<xpath expr="//div[contains(@class, 'o_project_kanban_box')]/div[last()]" position="before">
|
<xpath expr="//div[contains(@class, 'o_project_kanban_box')]/div[last()]" position="before">
|
||||||
<div attrs="{'invisible': [('use_issues', '=', False)]}" class="o_project_kanban_box">
|
<div attrs="{'invisible': [('use_issues', '=', False)]}" class="o_project_kanban_box">
|
||||||
<a name="%(act_project_project_2_project_issue_all)d" type="action">
|
<a name="%(act_project_project_2_project_issue_all)d" type="action">
|
||||||
<span class="o_value"><t t-raw="record.issue_ids.raw_value.length"/></span>
|
<span class="o_value"><t t-raw="record.issue_count.raw_value"/></span>
|
||||||
<span class="o_label"><field name="label_issues"/></span>
|
<span class="o_label"><field name="label_issues"/></span>
|
||||||
<a t-if="record.issue_needaction_count.raw_value" class="o_needaction" name="%(act_project_project_2_project_issue_all)d" type="action" context="{'search_default_message_needaction': 1}">
|
<a t-if="record.issue_needaction_count.raw_value" class="o_needaction" name="%(act_project_project_2_project_issue_all)d" type="action" context="{'search_default_message_needaction': 1}">
|
||||||
<t t-esc="record.issue_needaction_count.raw_value"/>
|
<t t-esc="record.issue_needaction_count.raw_value"/>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<field name="header_line" />
|
<field name="header_line" />
|
||||||
<field name="header_spacing" />
|
<field name="header_spacing" />
|
||||||
<field name="dpi" />
|
<field name="dpi" />
|
||||||
<field name="report_ids" widget="many2many_tags"/>
|
<field name="report_ids" widget="many2many_tags" options="{'not_delete': True}"/>
|
||||||
</group>
|
</group>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
|
|||||||
@@ -244,9 +244,9 @@
|
|||||||
<div>
|
<div>
|
||||||
<field name="customer_lead" class="oe_inline"/> days
|
<field name="customer_lead" class="oe_inline"/> days
|
||||||
</div>
|
</div>
|
||||||
<label for="analytic_tag_ids"/>
|
<label for="analytic_tag_ids" groups="analytic.group_analytic_accounting"/>
|
||||||
<div>
|
<div>
|
||||||
<field name="analytic_tag_ids" widget="many2many_tags"/>
|
<field name="analytic_tag_ids" widget="many2many_tags" groups="analytic.group_analytic_accounting"/>
|
||||||
</div>
|
</div>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class SaleOrder(models.Model):
|
|||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def action_cancel(self):
|
def action_cancel(self):
|
||||||
self.order_line.mapped('procurement_ids').cancel()
|
self.mapped('order_line').mapped('procurement_ids').cancel()
|
||||||
return super(SaleOrder, self).action_cancel()
|
return super(SaleOrder, self).action_cancel()
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
|
|||||||
@@ -201,6 +201,19 @@ class Inventory(models.Model):
|
|||||||
return True
|
return True
|
||||||
prepare_inventory = action_start
|
prepare_inventory = action_start
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def action_inventory_line_tree(self):
|
||||||
|
action = self.env.ref('stock.action_inventory_line_tree').read()[0]
|
||||||
|
action['context'] = {
|
||||||
|
'default_location_id': self.location_id.id,
|
||||||
|
'default_product_id': self.product_id.id,
|
||||||
|
'default_prod_lot_id': self.lot_id.id,
|
||||||
|
'default_package_id': self.package_id.id,
|
||||||
|
'default_partner_id': self.partner_id.id,
|
||||||
|
'default_inventory_id': self.id,
|
||||||
|
}
|
||||||
|
return action
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def _get_inventory_lines_values(self):
|
def _get_inventory_lines_values(self):
|
||||||
# TDE CLEANME: is sql really necessary ? I don't think so
|
# TDE CLEANME: is sql really necessary ? I don't think so
|
||||||
@@ -332,6 +345,8 @@ class InventoryLine(models.Model):
|
|||||||
theoretical_qty = fields.Float(
|
theoretical_qty = fields.Float(
|
||||||
'Theoretical Quantity', compute='_compute_theoretical_qty',
|
'Theoretical Quantity', compute='_compute_theoretical_qty',
|
||||||
digits=dp.get_precision('Product Unit of Measure'), readonly=True, store=True)
|
digits=dp.get_precision('Product Unit of Measure'), readonly=True, store=True)
|
||||||
|
inventory_location_id = fields.Many2one(
|
||||||
|
'stock.location', 'Location', related='inventory_id.location_id', related_sudo=False)
|
||||||
|
|
||||||
@api.one
|
@api.one
|
||||||
@api.depends('location_id', 'product_id', 'package_id', 'product_uom_id', 'company_id', 'prod_lot_id', 'partner_id')
|
@api.depends('location_id', 'product_id', 'package_id', 'product_uom_id', 'company_id', 'prod_lot_id', 'partner_id')
|
||||||
|
|||||||
@@ -156,7 +156,8 @@ class StockMove(models.Model):
|
|||||||
@api.depends('product_id', 'product_uom', 'product_uom_qty')
|
@api.depends('product_id', 'product_uom', 'product_uom_qty')
|
||||||
def _compute_product_qty(self):
|
def _compute_product_qty(self):
|
||||||
if self.product_uom:
|
if self.product_uom:
|
||||||
self.product_qty = self.product_uom._compute_quantity(self.product_uom_qty, self.product_id.uom_id)
|
rounding_method = self._context.get('rounding_method', 'UP')
|
||||||
|
self.product_qty = self.product_uom._compute_quantity(self.product_uom_qty, self.product_id.uom_id, rounding_method=rounding_method)
|
||||||
|
|
||||||
def _set_product_qty(self):
|
def _set_product_qty(self):
|
||||||
""" The meaning of product_qty field changed lately and is now a functional field computing the quantity
|
""" The meaning of product_qty field changed lately and is now a functional field computing the quantity
|
||||||
@@ -950,11 +951,11 @@ class StockMove(models.Model):
|
|||||||
# TDE CLEANME: remove context key + add as parameter
|
# TDE CLEANME: remove context key + add as parameter
|
||||||
if self.env.context.get('source_location_id'):
|
if self.env.context.get('source_location_id'):
|
||||||
defaults['location_id'] = self.env.context['source_location_id']
|
defaults['location_id'] = self.env.context['source_location_id']
|
||||||
new_move = self.copy(defaults)
|
new_move = self.with_context(rounding_method='HALF-UP').copy(defaults)
|
||||||
# ctx = context.copy()
|
# ctx = context.copy()
|
||||||
# TDE CLEANME: used only in write in this file, to clean
|
# TDE CLEANME: used only in write in this file, to clean
|
||||||
# ctx['do_not_propagate'] = True
|
# ctx['do_not_propagate'] = True
|
||||||
self.with_context(do_not_propagate=True).write({'product_uom_qty': self.product_uom_qty - uom_qty})
|
self.with_context(do_not_propagate=True, rounding_method='HALF-UP').write({'product_uom_qty': self.product_uom_qty - uom_qty})
|
||||||
|
|
||||||
if self.move_dest_id and self.propagate and self.move_dest_id.state not in ('done', 'cancel'):
|
if self.move_dest_id and self.propagate and self.move_dest_id.state not in ('done', 'cancel'):
|
||||||
new_move_prop = self.move_dest_id.split(qty)
|
new_move_prop = self.move_dest_id.split(qty)
|
||||||
|
|||||||
@@ -14,6 +14,40 @@
|
|||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="stock_inventory_line_tree2" model="ir.ui.view">
|
||||||
|
<field name="name">stock.inventory.line.tree2</field>
|
||||||
|
<field name="model">stock.inventory.line</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree editable="top" string="Inventory Details" decoration-info="product_qty != theoretical_qty" decoration-danger="theoretical_qty < 0">
|
||||||
|
<field name="product_id" domain="[('type','=','product')]"/>
|
||||||
|
<field name="product_uom_id" string="UoM" groups="product.group_uom"/>
|
||||||
|
<field name="location_id" domain="[('id', 'child_of', inventory_location_id)]" groups="stock.group_stock_multi_locations"/>
|
||||||
|
<field name="prod_lot_id" domain="[('product_id', '=', product_id)]" context="{'default_product_id': product_id}" groups="stock.group_production_lot"/>
|
||||||
|
<field name="package_id" domain="['|', ('location_id','=', False), ('location_id', '=', location_id)]" groups="stock.group_tracking_lot"/>
|
||||||
|
<field name="partner_id" groups="stock.group_tracking_owner"/>
|
||||||
|
<field name="theoretical_qty" readonly="1"/>
|
||||||
|
<field name="product_qty" string="Real Quantity"/>
|
||||||
|
<field name="state" invisible="1"/>
|
||||||
|
<field name="inventory_id" invisible="1"/>
|
||||||
|
<field name="inventory_location_id" invisible="1"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="stock_inventory_line_search" model="ir.ui.view">
|
||||||
|
<field name="name">stock.inventory.line.search</field>
|
||||||
|
<field name="model">stock.inventory.line</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search Inventory Lines">
|
||||||
|
<field name="product_id"/>
|
||||||
|
<field name="location_id" groups="stock.group_stock_multi_locations"/>
|
||||||
|
<field name="prod_lot_id" groups="stock.group_production_lot"/>
|
||||||
|
<field name="package_id" groups="stock.group_tracking_lot"/>
|
||||||
|
<field name="partner_id" groups="stock.group_tracking_owner"/>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
<record id="view_inventory_filter" model="ir.ui.view">
|
<record id="view_inventory_filter" model="ir.ui.view">
|
||||||
<field name="name">stock.inventory.filter</field>
|
<field name="name">stock.inventory.filter</field>
|
||||||
<field name="model">stock.inventory</field>
|
<field name="model">stock.inventory</field>
|
||||||
@@ -56,6 +90,19 @@
|
|||||||
<field name="state" widget="statusbar" statusbar_visible="draft,confirm,done"/>
|
<field name="state" widget="statusbar" statusbar_visible="draft,confirm,done"/>
|
||||||
</header>
|
</header>
|
||||||
<sheet>
|
<sheet>
|
||||||
|
<div class="oe_button_box" name="button_box">
|
||||||
|
<button name="action_inventory_line_tree"
|
||||||
|
class="oe_stat_button"
|
||||||
|
icon="fa-building-o"
|
||||||
|
type="object"
|
||||||
|
help="List view of lines"
|
||||||
|
groups="base.group_no_one"
|
||||||
|
states="confirm">
|
||||||
|
<div class="o_form_field o_stat_info">
|
||||||
|
<span class="o_stat_text">Details</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="oe_title">
|
<div class="oe_title">
|
||||||
<label for="name" class="oe_edit_only"/>
|
<label for="name" class="oe_edit_only"/>
|
||||||
<h1><field name="name" placeholder="e.g. Annual inventory"/></h1>
|
<h1><field name="name" placeholder="e.g. Annual inventory"/></h1>
|
||||||
@@ -181,5 +228,16 @@
|
|||||||
</p>
|
</p>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
<record id="action_inventory_line_tree" model="ir.actions.act_window">
|
||||||
|
<field name="name">Inventory Lines</field>
|
||||||
|
<field name="type">ir.actions.act_window</field>
|
||||||
|
<field name="res_model">stock.inventory.line</field>
|
||||||
|
<field name="view_type">form</field>
|
||||||
|
<field name="view_mode">tree</field>
|
||||||
|
<field name="view_id" ref="stock_inventory_line_tree2"/>
|
||||||
|
<field name="domain">[('inventory_id', '=', active_id)]</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
<menuitem action="action_inventory_form" id="menu_action_inventory_form" parent="menu_stock_inventory_control" sequence="30"/>
|
<menuitem action="action_inventory_form" id="menu_action_inventory_form" parent="menu_stock_inventory_control" sequence="30"/>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -204,7 +204,15 @@ var ViewEditor = Widget.extend({
|
|||||||
initType = "less";
|
initType = "less";
|
||||||
}
|
}
|
||||||
if (!this.options.doNotLoadViews) {
|
if (!this.options.doNotLoadViews) {
|
||||||
initResID = (typeof this.viewKey === "number" ? this.viewKey : _.findWhere(this.views, {xml_id: this.viewKey}).id);
|
if (typeof this.viewKey === "number") {
|
||||||
|
initResID = this.viewKey;
|
||||||
|
} else {
|
||||||
|
var view = _.findWhere(this.views, {xml_id: this.viewKey});
|
||||||
|
if (!view) {
|
||||||
|
view = _.findWhere(this.views, {key: this.viewKey});
|
||||||
|
}
|
||||||
|
initResID = view.id;
|
||||||
|
}
|
||||||
initType = "xml";
|
initType = "xml";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,9 +313,14 @@ class Website(Home):
|
|||||||
|
|
||||||
def get_view_ids(self, xml_ids):
|
def get_view_ids(self, xml_ids):
|
||||||
ids = []
|
ids = []
|
||||||
|
View = request.env["ir.ui.view"].with_context(active_test=False)
|
||||||
for xml_id in xml_ids:
|
for xml_id in xml_ids:
|
||||||
if "." in xml_id:
|
if "." in xml_id:
|
||||||
record_id = request.env.ref(xml_id).id
|
# Get website-specific view if possible
|
||||||
|
record_id = View.search([
|
||||||
|
("website_id", "=", request.website.id),
|
||||||
|
("key", "=", xml_id),
|
||||||
|
]).id or request.env.ref(xml_id).id
|
||||||
else:
|
else:
|
||||||
record_id = int(xml_id)
|
record_id = int(xml_id)
|
||||||
ids.append(record_id)
|
ids.append(record_id)
|
||||||
|
|||||||
@@ -69,6 +69,11 @@ class View(models.Model):
|
|||||||
# assume it's already a view object (WTF?)
|
# assume it's already a view object (WTF?)
|
||||||
return view_id
|
return view_id
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_inheriting_views_arch_domain(self, view_id, model):
|
||||||
|
domain = super(View, self)._get_inheriting_views_arch_domain(view_id, model)
|
||||||
|
return ['|', ('website_id', '=', False), ('website_id', '=', self.env.context.get('website_id'))] + domain
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
@tools.ormcache_context('self._uid', 'xml_id', keys=('website_id',))
|
@tools.ormcache_context('self._uid', 'xml_id', keys=('website_id',))
|
||||||
def get_view_id(self, xml_id):
|
def get_view_id(self, xml_id):
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ class BlogPost(models.Model):
|
|||||||
default='{"background-image": "none", "background-color": "oe_black", "opacity": "0.2", "resize_class": ""}')
|
default='{"background-image": "none", "background-color": "oe_black", "opacity": "0.2", "resize_class": ""}')
|
||||||
blog_id = fields.Many2one('blog.blog', 'Blog', required=True, ondelete='cascade')
|
blog_id = fields.Many2one('blog.blog', 'Blog', required=True, ondelete='cascade')
|
||||||
tag_ids = fields.Many2many('blog.tag', string='Tags')
|
tag_ids = fields.Many2many('blog.tag', string='Tags')
|
||||||
content = fields.Html('Content', default=_default_content, translate=html_translate, sanitize_attributes=False)
|
content = fields.Html('Content', default=_default_content, translate=html_translate, sanitize=False)
|
||||||
teaser = fields.Text('Teaser', compute='_compute_teaser', inverse='_set_teaser')
|
teaser = fields.Text('Teaser', compute='_compute_teaser', inverse='_set_teaser')
|
||||||
teaser_manual = fields.Text(string='Teaser Content')
|
teaser_manual = fields.Text(string='Teaser Content')
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<record id="hr_department_public" model="ir.rule">
|
<record id="hr_department_public" model="ir.rule">
|
||||||
<field name="name">Job department: Public</field>
|
<field name="name">Job department: Public</field>
|
||||||
<field name="model_id" ref="hr.model_hr_department"/>
|
<field name="model_id" ref="hr.model_hr_department"/>
|
||||||
<field name="domain_force">[('jobs_ids.website_published', '=', True)]</field>
|
<field name="domain_force">['|', ('jobs_ids.website_published', '=', True), ('child_ids', 'not in', [])]</field>
|
||||||
<field name="groups" eval="[(4, ref('base.group_public'))]"/>
|
<field name="groups" eval="[(4, ref('base.group_public'))]"/>
|
||||||
<field name="perm_read" eval="True"/>
|
<field name="perm_read" eval="True"/>
|
||||||
<field name="perm_write" eval="False"/>
|
<field name="perm_write" eval="False"/>
|
||||||
|
|||||||
@@ -159,7 +159,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template id="contact" name="Contact">
|
<template id="contact" name="Contact">
|
||||||
<h3 class="page-header">Saleman</h3>
|
<h3 class="page-header">Salesman</h3>
|
||||||
<h5 t-esc="sales_rep.name"/>
|
<h5 t-esc="sales_rep.name"/>
|
||||||
<p><a t-att-href="'mailto:'+sales_rep.email" t-esc="sales_rep.email"/></p>
|
<p><a t-att-href="'mailto:'+sales_rep.email" t-esc="sales_rep.email"/></p>
|
||||||
<p t-esc="sales_rep.phone"/>
|
<p t-esc="sales_rep.phone"/>
|
||||||
|
|||||||
@@ -500,7 +500,7 @@ class WebsiteSale(http.Controller):
|
|||||||
def _checkout_form_save(self, mode, checkout, all_values):
|
def _checkout_form_save(self, mode, checkout, all_values):
|
||||||
Partner = request.env['res.partner']
|
Partner = request.env['res.partner']
|
||||||
if mode[0] == 'new':
|
if mode[0] == 'new':
|
||||||
partner_id = Partner.sudo().create(checkout)
|
partner_id = Partner.sudo().create(checkout).id
|
||||||
elif mode[0] == 'edit':
|
elif mode[0] == 'edit':
|
||||||
partner_id = int(all_values.get('partner_id', 0))
|
partner_id = int(all_values.get('partner_id', 0))
|
||||||
if partner_id:
|
if partner_id:
|
||||||
|
|||||||
@@ -190,7 +190,9 @@ class Product(models.Model):
|
|||||||
def _website_price(self):
|
def _website_price(self):
|
||||||
qty = self._context.get('quantity', 1.0)
|
qty = self._context.get('quantity', 1.0)
|
||||||
partner = self.env.user.partner_id
|
partner = self.env.user.partner_id
|
||||||
pricelist = self.env['website'].get_current_website().get_current_pricelist()
|
current_website = self.env['website'].get_current_website()
|
||||||
|
pricelist = current_website.get_current_pricelist()
|
||||||
|
company_id = current_website.company_id
|
||||||
|
|
||||||
context = dict(self._context, pricelist=pricelist.id, partner=partner)
|
context = dict(self._context, pricelist=pricelist.id, partner=partner)
|
||||||
self2 = self.with_context(context) if self._context != context else self
|
self2 = self.with_context(context) if self._context != context else self
|
||||||
@@ -198,7 +200,7 @@ class Product(models.Model):
|
|||||||
ret = self.env.user.has_group('sale.group_show_price_subtotal') and 'total_excluded' or 'total_included'
|
ret = self.env.user.has_group('sale.group_show_price_subtotal') and 'total_excluded' or 'total_included'
|
||||||
|
|
||||||
for p, p2 in zip(self, self2):
|
for p, p2 in zip(self, self2):
|
||||||
taxes = partner.property_account_position_id.map_tax(p.taxes_id)
|
taxes = partner.property_account_position_id.map_tax(p.taxes_id.filtered(lambda x: x.company_id == company_id))
|
||||||
p.website_price = taxes.compute_all(p2.price, pricelist.currency_id, quantity=qty, product=p2, partner=partner)[ret]
|
p.website_price = taxes.compute_all(p2.price, pricelist.currency_id, quantity=qty, product=p2, partner=partner)[ret]
|
||||||
p.website_public_price = taxes.compute_all(p2.lst_price, quantity=qty, product=p2, partner=partner)[ret]
|
p.website_public_price = taxes.compute_all(p2.lst_price, quantity=qty, product=p2, partner=partner)[ret]
|
||||||
|
|
||||||
|
|||||||
@@ -459,7 +459,7 @@ class Website(models.Model):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
request.session['sale_order_id'] = None
|
request.session['sale_order_id'] = None
|
||||||
return None
|
return self.env['sale.order']
|
||||||
|
|
||||||
return sale_order
|
return sale_order
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,6 @@ class WebsiteSaleDigital(website_account):
|
|||||||
return request.not_found()
|
return request.not_found()
|
||||||
elif attachment["datas"]:
|
elif attachment["datas"]:
|
||||||
data = StringIO(base64.standard_b64decode(attachment["datas"]))
|
data = StringIO(base64.standard_b64decode(attachment["datas"]))
|
||||||
return http.send_file(data, filename=attachment['name'], as_attachment=True)
|
return http.send_file(data, filename=attachment['name'].encode('utf-8'), as_attachment=True)
|
||||||
else:
|
else:
|
||||||
return request.not_found()
|
return request.not_found()
|
||||||
|
|||||||
@@ -36,4 +36,5 @@ Luis González lgonzalez@vauxoo.com https://github.com/luisg123v
|
|||||||
Oriana Maita oriana@vauxoo.com https://github.com/maitaoriana
|
Oriana Maita oriana@vauxoo.com https://github.com/maitaoriana
|
||||||
Gabriela Mogollon gmogollon@vauxoo.com https://github.com/GavyMG
|
Gabriela Mogollon gmogollon@vauxoo.com https://github.com/GavyMG
|
||||||
Jesus Zapata jesus@vauxoo.com https://github.com/JesusZapata
|
Jesus Zapata jesus@vauxoo.com https://github.com/JesusZapata
|
||||||
Germana Oliveira germana@vauxoo.com https://github.com/goliveirab
|
Germana Oliveira germana@vauxoo.com https://github.com/goliveirab
|
||||||
|
Mariano Fernandez mariano@vauxoo.com https://github.com/Batuto
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
Vietnam, 2017-08-15
|
||||||
|
|
||||||
|
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,
|
||||||
|
|
||||||
|
Duong Bao Thang nothingctrl@gmail.com https://github.com/NothingCtrl
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
Indonesia, 2017-08-12
|
||||||
|
|
||||||
|
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,
|
||||||
|
Edi Santoso repodevs@gmail.com https://github.com/repodevs
|
||||||
@@ -159,7 +159,11 @@ database
|
|||||||
- ``%h`` is replaced by the whole hostname the request is made on.
|
- ``%h`` is replaced by the whole hostname the request is made on.
|
||||||
- ``%d`` is replaced by the subdomain the request is made on, with the
|
- ``%d`` is replaced by the subdomain the request is made on, with the
|
||||||
exception of ``www`` (so domain ``odoo.com`` and ``www.odoo.com`` both
|
exception of ``www`` (so domain ``odoo.com`` and ``www.odoo.com`` both
|
||||||
match the database ``odoo``)
|
match the database ``odoo``).
|
||||||
|
|
||||||
|
These operations are case sensitive. Add option ``(?i)`` to match all
|
||||||
|
databases (so domain ``odoo.com`` using ``(?i)%d`` matches the database
|
||||||
|
``Odoo``).
|
||||||
|
|
||||||
.. option:: --db-template <template>
|
.. option:: --db-template <template>
|
||||||
|
|
||||||
|
|||||||
@@ -840,17 +840,13 @@ attributes:
|
|||||||
Possible children of the view element are:
|
Possible children of the view element are:
|
||||||
|
|
||||||
``field``
|
``field``
|
||||||
declares fields to aggregate or to use in kanban *logic*. If the field is
|
declares fields to use in kanban *logic*. If the field is simply displayed in
|
||||||
simply displayed in the kanban view, it does not need to be pre-declared.
|
the kanban view, it does not need to be pre-declared.
|
||||||
|
|
||||||
Possible attributes are:
|
Possible attributes are:
|
||||||
|
|
||||||
``name`` (required)
|
``name`` (required)
|
||||||
the name of the field to fetch
|
the name of the field to fetch
|
||||||
``sum``, ``avg``, ``min``, ``max``, ``count``
|
|
||||||
displays the corresponding aggregation at the top of a kanban column, the
|
|
||||||
field's value is the label of the aggregation (a string). Only one
|
|
||||||
aggregate operation per field is supported.
|
|
||||||
|
|
||||||
``templates``
|
``templates``
|
||||||
defines a list of :ref:`reference/qweb` templates. Cards definition may be
|
defines a list of :ref:`reference/qweb` templates. Cards definition may be
|
||||||
|
|||||||
@@ -512,7 +512,8 @@ class IrModelFields(models.Model):
|
|||||||
if field.state == 'manual' and field.ttype == 'many2many':
|
if field.state == 'manual' and field.ttype == 'many2many':
|
||||||
rel_name = field.relation_table or model._fields[field.name].relation
|
rel_name = field.relation_table or model._fields[field.name].relation
|
||||||
tables_to_drop.add(rel_name)
|
tables_to_drop.add(rel_name)
|
||||||
model._pop_field(field.name)
|
if field.state == 'manual':
|
||||||
|
model._pop_field(field.name)
|
||||||
|
|
||||||
if tables_to_drop:
|
if tables_to_drop:
|
||||||
# drop the relation tables that are not used by other fields
|
# drop the relation tables that are not used by other fields
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class IrQWeb(models.AbstractModel, QWeb):
|
|||||||
# assume cache will be invalidated by third party on write to ir.ui.view
|
# assume cache will be invalidated by third party on write to ir.ui.view
|
||||||
def _get_template_cache_keys(self):
|
def _get_template_cache_keys(self):
|
||||||
""" Return the list of context keys to use for caching ``_get_template``. """
|
""" Return the list of context keys to use for caching ``_get_template``. """
|
||||||
return ['lang', 'inherit_branding', 'editable', 'translatable', 'edit_translations']
|
return ['lang', 'inherit_branding', 'editable', 'translatable', 'edit_translations', 'website_id']
|
||||||
|
|
||||||
# apply ormcache_context decorator unless in dev mode...
|
# apply ormcache_context decorator unless in dev mode...
|
||||||
@tools.conditional(
|
@tools.conditional(
|
||||||
@@ -186,14 +186,14 @@ class IrQWeb(models.AbstractModel, QWeb):
|
|||||||
# in non-xml-debug mode we want assets to be cached forever, and the admin can force a cache clear
|
# 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)
|
# 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'],
|
'xml' not in tools.config['dev_mode'],
|
||||||
tools.ormcache('xmlid', 'options.get("lang", "en_US")', 'css', 'js', 'debug', 'async'),
|
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(self, xmlid, options, css=True, js=True, debug=False, async=False, values=None):
|
||||||
files, remains = self._get_asset_content(xmlid, options)
|
files, remains = self._get_asset_content(xmlid, options)
|
||||||
asset = AssetsBundle(xmlid, files, remains, env=self.env)
|
asset = AssetsBundle(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))
|
return asset.to_html(css=css, js=js, debug=debug, async=async, url_for=(values or {}).get('url_for', lambda url: url))
|
||||||
|
|
||||||
@tools.ormcache('xmlid', 'options.get("lang", "en_US")')
|
@tools.ormcache_context('xmlid', 'options.get("lang", "en_US")', keys=("website_id",))
|
||||||
def _get_asset_content(self, xmlid, options):
|
def _get_asset_content(self, xmlid, options):
|
||||||
options = dict(options,
|
options = dict(options,
|
||||||
inherit_branding=False, inherit_branding_auto=False,
|
inherit_branding=False, inherit_branding_auto=False,
|
||||||
|
|||||||
@@ -429,6 +429,15 @@ actual arch.
|
|||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
# Inheritance mecanism
|
# Inheritance mecanism
|
||||||
#------------------------------------------------------
|
#------------------------------------------------------
|
||||||
|
@api.model
|
||||||
|
def _get_inheriting_views_arch_domain(self, view_id, model):
|
||||||
|
return [
|
||||||
|
['inherit_id', '=', view_id],
|
||||||
|
['model', '=', model],
|
||||||
|
['mode', '=', 'extension'],
|
||||||
|
['active', '=', True],
|
||||||
|
]
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get_inheriting_views_arch(self, view_id, model):
|
def get_inheriting_views_arch(self, view_id, model):
|
||||||
"""Retrieves the architecture of views that inherit from the given view, from the sets of
|
"""Retrieves the architecture of views that inherit from the given view, from the sets of
|
||||||
@@ -444,13 +453,8 @@ actual arch.
|
|||||||
:return: [(view_arch,view_id), ...]
|
:return: [(view_arch,view_id), ...]
|
||||||
"""
|
"""
|
||||||
user_groups = self.env.user.groups_id
|
user_groups = self.env.user.groups_id
|
||||||
|
conditions = self._get_inheriting_views_arch_domain(view_id, model)
|
||||||
|
|
||||||
conditions = [
|
|
||||||
['inherit_id', '=', view_id],
|
|
||||||
['model', '=', model],
|
|
||||||
['mode', '=', 'extension'],
|
|
||||||
['active', '=', True],
|
|
||||||
]
|
|
||||||
if self.pool._init and not self._context.get('load_all_views'):
|
if self.pool._init and not self._context.get('load_all_views'):
|
||||||
# Module init currently in progress, only consider views from
|
# Module init currently in progress, only consider views from
|
||||||
# modules whose code is already loaded
|
# modules whose code is already loaded
|
||||||
@@ -917,7 +921,7 @@ actual arch.
|
|||||||
|
|
||||||
def _read_template_keys(self):
|
def _read_template_keys(self):
|
||||||
""" Return the list of context keys to use for caching ``_read_template``. """
|
""" Return the list of context keys to use for caching ``_read_template``. """
|
||||||
return ['lang', 'inherit_branding', 'editable', 'translatable', 'edit_translations']
|
return ['lang', 'inherit_branding', 'editable', 'translatable', 'edit_translations', 'website_id']
|
||||||
|
|
||||||
# apply ormcache_context decorator unless in dev mode...
|
# apply ormcache_context decorator unless in dev mode...
|
||||||
@api.model
|
@api.model
|
||||||
|
|||||||
@@ -522,7 +522,10 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin)
|
|||||||
IrValues = self.env['ir.values'].sudo()
|
IrValues = self.env['ir.values'].sudo()
|
||||||
for name, model, field in classified['default']:
|
for name, model, field in classified['default']:
|
||||||
if isinstance(self[name], models.BaseModel):
|
if isinstance(self[name], models.BaseModel):
|
||||||
value = self[name].ids
|
if self._fields[name].type == 'many2one':
|
||||||
|
value = self[name].id
|
||||||
|
else:
|
||||||
|
value = self[name].ids
|
||||||
else:
|
else:
|
||||||
value = self[name]
|
value = self[name]
|
||||||
IrValues.set_default(model, field, value)
|
IrValues.set_default(model, field, value)
|
||||||
|
|||||||
@@ -155,6 +155,14 @@ def constrains(*args):
|
|||||||
``@constrains`` only supports simple field names, dotted names
|
``@constrains`` only supports simple field names, dotted names
|
||||||
(fields of relational fields e.g. ``partner_id.customer``) are not
|
(fields of relational fields e.g. ``partner_id.customer``) are not
|
||||||
supported and will be ignored
|
supported and will be ignored
|
||||||
|
|
||||||
|
``@constrains`` will be triggered only if the declared fields in the
|
||||||
|
decorated method are included in the ``create`` or ``write`` call.
|
||||||
|
It implies that fields not present in a view will not trigger a call
|
||||||
|
during a record creation. A override of ``create`` is necessary to make
|
||||||
|
sure a constraint will always be triggered (e.g. to test the absence of
|
||||||
|
value).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return attrsetter('_constrains', args)
|
return attrsetter('_constrains', args)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -781,7 +781,7 @@ class BaseModel(object):
|
|||||||
# avoid broken transaction) and keep going
|
# avoid broken transaction) and keep going
|
||||||
cr.execute('ROLLBACK TO SAVEPOINT model_load_save')
|
cr.execute('ROLLBACK TO SAVEPOINT model_load_save')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
message = (_('Unknown error during import:') + ' %s: %s' % (type(e), unicode(e)))
|
message = (_('Unknown error during import:') + ' %s: %s' % (type(e), unicode(e.message or e.name)))
|
||||||
moreinfo = _('Resolve other errors first')
|
moreinfo = _('Resolve other errors first')
|
||||||
messages.append(dict(info, type='error', message=message, moreinfo=moreinfo))
|
messages.append(dict(info, type='error', message=message, moreinfo=moreinfo))
|
||||||
# Failed for some reason, perhaps due to invalid data supplied,
|
# Failed for some reason, perhaps due to invalid data supplied,
|
||||||
|
|||||||
Reference in New Issue
Block a user