[MERGE] Forwardport of branch saas-6 up to c649bb0df
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
from openerp.tests.common import TransactionCase
|
||||
|
||||
class TestProductIdChange(TransactionCase):
|
||||
"""Test that when an included tax is mapped by a fiscal position, the included tax must be
|
||||
subtracted to the price of the product.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestProductIdChange, self).setUp()
|
||||
self.fiscal_position_model = self.registry('account.fiscal.position')
|
||||
self.fiscal_position_tax_model = self.registry('account.fiscal.position.tax')
|
||||
self.tax_model = self.registry('account.tax')
|
||||
self.pricelist_model = self.registry('product.pricelist')
|
||||
self.res_partner_model = self.registry('res.partner')
|
||||
self.product_tmpl_model = self.registry('product.template')
|
||||
self.product_model = self.registry('product.product')
|
||||
self.invoice_line_model = self.registry('account.invoice.line')
|
||||
|
||||
def test_product_id_change(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
partner_id = self.res_partner_model.create(cr, uid, dict(name="George"))
|
||||
tax_include_id = self.tax_model.create(cr, uid, dict(name="Include tax",
|
||||
type='percent',
|
||||
amount='0.21',
|
||||
price_include=True))
|
||||
tax_exclude_id = self.tax_model.create(cr, uid, dict(name="Exclude tax",
|
||||
type='percent',
|
||||
amount='0.00'))
|
||||
product_tmpl_id = self.product_tmpl_model.create(cr, uid, dict(name="Voiture",
|
||||
list_price='121',
|
||||
standard_price='121',
|
||||
taxes_id=[(6, 0, [tax_include_id])],
|
||||
supplier_taxes_id=[(6, 0, [tax_include_id])]))
|
||||
product_id = self.product_model.create(cr, uid, dict(product_tmpl_id=product_tmpl_id))
|
||||
product = self.product_model.browse(cr, uid, [product_id])
|
||||
fp_id = self.fiscal_position_model.create(cr, uid, dict(name="fiscal position",
|
||||
sequence=1))
|
||||
fp_tax_id = self.fiscal_position_tax_model.create(cr, uid, dict(position_id=fp_id,
|
||||
tax_src_id=tax_include_id,
|
||||
tax_dest_id=tax_exclude_id))
|
||||
|
||||
res = self.invoice_line_model.product_id_change(cr, uid, [], product.id, product.uom_id.id,
|
||||
qty=1, type='out_invoice', partner_id=partner_id,
|
||||
fposition_id=fp_id)
|
||||
self.assertEquals(100, res['value']['price_unit'], "The included tax must be subtracted to the price")
|
||||
|
||||
res = self.invoice_line_model.product_id_change(cr, uid, [], product.id, product.uom_id.id,
|
||||
qty=1, type='in_invoice', partner_id=partner_id,
|
||||
fposition_id=fp_id)
|
||||
self.assertEquals(100, res['value']['price_unit'], "The included tax must be subtracted to the price")
|
||||
@@ -36,7 +36,11 @@ class AuthSignupHome(openerp.addons.web.controllers.main.Home):
|
||||
self.do_signup(qcontext)
|
||||
return super(AuthSignupHome, self).web_login(*args, **kw)
|
||||
except (SignupError, AssertionError), e:
|
||||
qcontext['error'] = _(e.message)
|
||||
if request.env["res.users"].sudo().search([("login", "=", qcontext.get("login"))]):
|
||||
qcontext["error"] = _("Another user is already registered using this email address.")
|
||||
else:
|
||||
_logger.error(e.message)
|
||||
qcontext['error'] = _("Could not create a new account.")
|
||||
|
||||
return request.render('auth_signup.signup', qcontext)
|
||||
|
||||
|
||||
@@ -1469,7 +1469,7 @@ class calendar_event(osv.Model):
|
||||
for arg in args:
|
||||
new_arg = arg
|
||||
|
||||
if arg[0] in ('start_date', 'start_datetime', 'start',) and arg[1] == ">=":
|
||||
if arg[0] in ('stop_date', 'stop_datetime', 'stop',) and arg[1] == ">=":
|
||||
if context.get('virtual_id', True):
|
||||
new_args += ['|', '&', ('recurrency', '=', 1), ('final_date', arg[1], arg[2])]
|
||||
elif arg[0] == "id":
|
||||
|
||||
@@ -131,6 +131,7 @@
|
||||
<group>
|
||||
<!-- Preload all the partner's information -->
|
||||
<field name="partner_id" string="Customer"
|
||||
on_change="on_change_partner_id(partner_id)"
|
||||
options='{"create_name_field": "name"}'
|
||||
context="{'default_name': contact_name, 'default_street': street, 'default_city': city, 'default_state_id': state_id, 'default_zip': zip, 'default_country_id': country_id, 'default_function': function, 'default_phone': phone, 'default_mobile': mobile, 'default_fax': fax, 'default_email': email_from, 'default_user_id': user_id, 'default_team_id': team_id}" groups="base.group_no_one"/>
|
||||
<field name="partner_name" string="Company Name"/>
|
||||
|
||||
@@ -121,6 +121,7 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
||||
context = {}
|
||||
|
||||
lead_obj = self.pool['crm.lead']
|
||||
partner_obj = self.pool['res.partner']
|
||||
|
||||
w = self.browse(cr, uid, ids, context=context)[0]
|
||||
opp_ids = [o.id for o in w.opportunity_ids]
|
||||
@@ -144,6 +145,9 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
||||
lead_ids = context.get('active_ids', [])
|
||||
vals.update({'lead_ids': lead_ids, 'user_ids': [w.user_id.id]})
|
||||
self._convert_opportunity(cr, uid, ids, vals, context=context)
|
||||
for lead in lead_obj.browse(cr, uid, lead_ids, context=context):
|
||||
if lead.partner_id and lead.partner_id.user_id != lead.user_id:
|
||||
partner_obj.write(cr, uid, [lead.partner_id.id], {'user_id': lead.user_id.id}, context=context)
|
||||
|
||||
return self.pool.get('crm.lead').redirect_opportunity_view(cr, uid, lead_ids[0], context=context)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import logging
|
||||
from openerp import api, fields, models, _
|
||||
from openerp.exceptions import UserError
|
||||
from openerp.exceptions import UserError, ValidationError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -52,6 +52,25 @@ class DeliveryCarrier(models.Model):
|
||||
fixed_price = fields.Float(compute='_compute_fixed_price', inverse='_set_product_fixed_price', store=True, string='Fixed Price',help="Keep empty if the pricing depends on the advanced pricing per destination")
|
||||
shipping_enabled = fields.Boolean(string="Shipping enabled", default=True, help="Uncheck this box to disable package shipping while validating Delivery Orders")
|
||||
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
display_delivery = self.env.context.get('display_delivery', False)
|
||||
order_id = self.env.context.get('order_id', False)
|
||||
if display_delivery and order_id:
|
||||
order = self.env['sale.order'].browse(order_id)
|
||||
currency = order.pricelist_id.currency_id.name or ''
|
||||
res = []
|
||||
for carrier_id in self.ids:
|
||||
try:
|
||||
r = self.read([carrier_id], ['name', 'price'])[0]
|
||||
res.append((r['id'], r['name'] + ' (' + (str(r['price'])) + ' ' + currency + ')'))
|
||||
except ValidationError:
|
||||
r = self.read([carrier_id], ['name'])[0]
|
||||
res.append((r['id'], r['name']))
|
||||
else:
|
||||
res = super(DeliveryCarrier, self).name_get()
|
||||
return res
|
||||
|
||||
@api.depends('product_id.list_price', 'product_id.product_tmpl_id.list_price')
|
||||
def _compute_fixed_price(self):
|
||||
for carrier in self:
|
||||
@@ -234,6 +253,8 @@ class DeliveryCarrier(models.Model):
|
||||
quantity += qty
|
||||
total = (order.amount_total or 0.0) - total_delivery
|
||||
|
||||
total = order.currency_id.with_context(date=order.date_order).compute(total, order.company_id.currency_id)
|
||||
|
||||
return self.get_price_from_picking(total, weight, volume, quantity)
|
||||
|
||||
def get_price_from_picking(self, total, weight, volume, quantity):
|
||||
|
||||
@@ -171,7 +171,8 @@
|
||||
</group>
|
||||
</xpath>
|
||||
<div name="button_box" position="inside">
|
||||
<button type="object" name="open_website_url" class="oe_stat_button" icon='fa-truck' string="Tracking" attrs="{'invisible': ['|',('carrier_tracking_ref','=',False),('delivery_type','in', ['fixed', 'base_on_rule'])]}" />
|
||||
<button type="object" name="open_website_url" class="oe_stat_button" icon='fa-truck' string="Tracking"
|
||||
attrs="{'invisible': ['|','|',('carrier_tracking_ref','=',False),('carrier_id', '=', False),('delivery_type','=','grid')]}" />
|
||||
</div>
|
||||
<xpath expr="/form/header/button[last()]" position="after">
|
||||
<button name="send_to_shipper" string="Send to Shipper" type="object" attrs="{'invisible':['|','|','|',('carrier_tracking_ref','!=',False),('delivery_type','in', ['fixed', 'base_on_rule']),('delivery_type','=',False),('state','not in',('done'))]}"/>
|
||||
|
||||
@@ -232,12 +232,12 @@ class google_calendar(osv.AbstractModel):
|
||||
"start": {
|
||||
type: start_date,
|
||||
vstype: None,
|
||||
'timeZone': context.get('tz', 'UTC'),
|
||||
'timeZone': context.get('tz') or 'UTC',
|
||||
},
|
||||
"end": {
|
||||
type: final_date,
|
||||
vstype: None,
|
||||
'timeZone': context.get('tz', 'UTC'),
|
||||
'timeZone': context.get('tz') or 'UTC',
|
||||
},
|
||||
"attendees": attendee_list,
|
||||
"reminders": {
|
||||
|
||||
@@ -22,7 +22,7 @@ class payslip_details_report(report_sxw.rml_parse):
|
||||
if not rule_categories:
|
||||
return []
|
||||
if rule_categories[0].parent_id:
|
||||
rule_categories.insert(0, rule_categories[0].parent_id)
|
||||
rule_categories = rule_categories[0].parent_id | rule_categories
|
||||
get_recursive_parent(rule_categories)
|
||||
return rule_categories
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import controllers
|
||||
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
{
|
||||
'name': 'Blackbox Hardware Driver',
|
||||
'version': '1.0',
|
||||
'category': 'Hardware Drivers',
|
||||
'sequence': 6,
|
||||
'summary': 'Hardware Driver for Belgian Fiscal Data Modules',
|
||||
'website': 'https://www.odoo.com/page/point-of-sale',
|
||||
'description': """
|
||||
Fiscal Data Module Hardware Driver
|
||||
==================================
|
||||
|
||||
This module allows a Point Of Sale client to communicate with a
|
||||
connected Belgian Fiscal Data Module.
|
||||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'depends': ['hw_proxy'],
|
||||
'external_dependencies': {'python': ['serial']},
|
||||
'test': [
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import main
|
||||
@@ -0,0 +1,162 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import serial
|
||||
from os import listdir
|
||||
from threading import Thread, Lock
|
||||
|
||||
from openerp import http
|
||||
|
||||
import openerp.addons.hw_proxy.controllers.main as hw_proxy
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class Blackbox(Thread):
|
||||
def __init__(self):
|
||||
Thread.__init__(self)
|
||||
self.blackbox_lock = Lock()
|
||||
self.set_status('connecting')
|
||||
self.device_path = self._find_device_path_by_probing()
|
||||
|
||||
def set_status(self, status, messages=[]):
|
||||
self.status = {
|
||||
'status': status,
|
||||
'messages': messages
|
||||
}
|
||||
|
||||
def get_status(self):
|
||||
return self.status
|
||||
|
||||
# There is no real way to find a serial device, all you can really
|
||||
# find is the name of the serial to usb interface, which in the
|
||||
# case of the blackbox is not defined because it doesn't always
|
||||
# come with it's own interface (eg. Retail Cleancash SC-B). So, in
|
||||
# order to differentiate between other devices like this, what
|
||||
# we'll do is probe every serial device with an FDM status
|
||||
# request. The first device to give an answer that makes sense
|
||||
# wins.
|
||||
def _find_device_path_by_probing(self):
|
||||
path = "/dev/serial/by-id/"
|
||||
probe_message = self._wrap_low_level_message_around("S000")
|
||||
|
||||
try:
|
||||
devices = listdir(path)
|
||||
except OSError:
|
||||
_logger.warning(path + " doesn't exist")
|
||||
else:
|
||||
for device in listdir(path):
|
||||
path_to_device = path + device
|
||||
_logger.debug("Probing " + device)
|
||||
|
||||
if self._send_to_blackbox(probe_message, 21, path_to_device, just_wait_for_ack=True):
|
||||
_logger.info(device + " will be used as the blackbox")
|
||||
self.set_status("connected", [device])
|
||||
return path_to_device
|
||||
|
||||
_logger.warning("Blackbox could not be found")
|
||||
self.set_status("error", ["Couldn't find the Fiscal Data Module"])
|
||||
return ""
|
||||
|
||||
def _lrc(self, msg):
|
||||
lrc = 0
|
||||
|
||||
for character in msg:
|
||||
byte = ord(character)
|
||||
lrc = (lrc + byte) & 0xFF
|
||||
|
||||
lrc = ((lrc ^ 0xFF) + 1) & 0xFF
|
||||
|
||||
return lrc
|
||||
|
||||
def _wrap_low_level_message_around(self, high_level_message):
|
||||
bcc = self._lrc(high_level_message)
|
||||
high_level_message_bytes = map(ord, high_level_message)
|
||||
|
||||
low_level_message = bytearray()
|
||||
low_level_message.append(0x02)
|
||||
low_level_message.extend(high_level_message_bytes)
|
||||
low_level_message.append(0x03)
|
||||
low_level_message.append(bcc)
|
||||
|
||||
return low_level_message
|
||||
|
||||
def _send_and_wait_for_ack(self, packet, serial):
|
||||
ack = 0
|
||||
MAX_RETRIES = 1
|
||||
|
||||
while ack != 0x06 and int(chr(packet[4])) < MAX_RETRIES:
|
||||
serial.write(packet)
|
||||
ack = serial.read(1)
|
||||
|
||||
# This violates the principle that we do high level
|
||||
# client-side and low level posbox-side but the retry
|
||||
# counter is always in a fixed position in the high level
|
||||
# message so it's safe to do it. Also it would be a pain
|
||||
# to have to throw this all the way back to js just so it
|
||||
# can increment the retry counter and then try again.
|
||||
packet = packet[:4] + str(int(packet[4]) + 1) + packet[5:]
|
||||
|
||||
if ack:
|
||||
ack = ord(ack)
|
||||
else:
|
||||
_logger.warning("did not get ACK, retrying...")
|
||||
ack = 0
|
||||
|
||||
if ack == 0x06:
|
||||
return True
|
||||
else:
|
||||
_logger.error("retried " + str(MAX_RETRIES) + " times without receiving ACK, is blackbox properly connected?")
|
||||
return False
|
||||
|
||||
def _send_to_blackbox(self, packet, response_size, device_path, just_wait_for_ack=False):
|
||||
if not device_path:
|
||||
return ""
|
||||
|
||||
ser = serial.Serial(port=device_path,
|
||||
baudrate=19200,
|
||||
timeout=3)
|
||||
MAX_NACKS = 1
|
||||
got_response = False
|
||||
sent_nacks = 0
|
||||
|
||||
if self._send_and_wait_for_ack(packet, ser):
|
||||
if just_wait_for_ack:
|
||||
return True
|
||||
|
||||
while not got_response and sent_nacks < MAX_NACKS:
|
||||
stx = ser.read(1)
|
||||
response = ser.read(response_size)
|
||||
etx = ser.read(1)
|
||||
bcc = ser.read(1)
|
||||
|
||||
if stx == chr(0x02) and etx == chr(0x03) and bcc and self._lrc(response) == ord(bcc):
|
||||
got_response = True
|
||||
ser.write(chr(0x06))
|
||||
else:
|
||||
_logger.warning("received ACK but not a valid response, sending NACK...")
|
||||
sent_nacks += 1
|
||||
ser.write(chr(0x15))
|
||||
|
||||
if not got_response:
|
||||
_logger.error("sent " + str(MAX_NACKS) + " NACKS without receiving response, giving up.")
|
||||
return ""
|
||||
|
||||
ser.close()
|
||||
return response
|
||||
else:
|
||||
ser.close()
|
||||
return ""
|
||||
|
||||
blackbox_thread = Blackbox()
|
||||
hw_proxy.drivers['fiscal_data_module'] = blackbox_thread
|
||||
|
||||
class BlackboxDriver(hw_proxy.Proxy):
|
||||
@http.route('/hw_proxy/request_blackbox/', type='json', auth='none', cors='*')
|
||||
def request_blackbox(self, high_level_message, response_size):
|
||||
to_send = blackbox_thread._wrap_low_level_message_around(high_level_message)
|
||||
|
||||
with blackbox_thread.blackbox_lock:
|
||||
response = blackbox_thread._send_to_blackbox(to_send, response_size, blackbox_thread.device_path)
|
||||
|
||||
return response
|
||||
@@ -198,7 +198,7 @@ class account_invoice(osv.osv):
|
||||
required=True, readonly=True),
|
||||
}
|
||||
_constraints = [
|
||||
(_check_communication, 'Invalid BBA Structured Communication !', ['Communication']),
|
||||
(_check_communication, 'Invalid BBA Structured Communication !', ['reference', 'reference_type']),
|
||||
]
|
||||
|
||||
account_invoice()
|
||||
|
||||
@@ -154,7 +154,7 @@ class MailMail(models.Model):
|
||||
if partner:
|
||||
email_to = [formataddr((partner.name, partner.email))]
|
||||
else:
|
||||
email_to = tools.email_split(self.email_to)
|
||||
email_to = tools.email_split_and_format(self.email_to)
|
||||
return email_to
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -38,7 +38,7 @@ def format_tz(pool, cr, uid, dt, tz=False, format=False, context=None):
|
||||
format_date = lang_params.get("date_format", '%B-%d-%Y')
|
||||
format_time = lang_params.get("time_format", '%I-%M %p')
|
||||
|
||||
fdate = ts.strftime(format_date)
|
||||
fdate = ts.strftime(format_date).decode('utf-8')
|
||||
ftime = ts.strftime(format_time)
|
||||
return "%s %s%s" % (fdate, ftime, (' (%s)' % tz) if tz else '')
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ class TestMail(common.SavepointCase):
|
||||
cls.env['ir.mail_server']._patch_method('build_email', build_email)
|
||||
cls.env['ir.mail_server']._patch_method('send_email', send_email)
|
||||
|
||||
|
||||
# User groups
|
||||
user_group_employee = cls.env.ref('base.group_user')
|
||||
user_group_portal = cls.env.ref('base.group_portal')
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from email.utils import formataddr
|
||||
|
||||
from .common import TestMail
|
||||
from openerp.tools import mute_logger
|
||||
import socket
|
||||
|
||||
@@ -1714,3 +1714,9 @@ msgstr ""
|
||||
msgid "© 2014 All Rights Reserved"
|
||||
msgstr ""
|
||||
|
||||
#. module: mass_mailing
|
||||
#: code:addons/mass_mailing/models/mail_mail.py:72
|
||||
#, python-format
|
||||
msgid "Click to unsubscribe"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import re
|
||||
import urlparse
|
||||
import re
|
||||
import werkzeug.urls
|
||||
import re
|
||||
|
||||
from openerp import tools
|
||||
from openerp import SUPERUSER_ID
|
||||
@@ -86,6 +87,14 @@ class MailMail(osv.Model):
|
||||
body = re.sub('(<img(?=\s)[^>]*\ssrc=")(/[^/][^"]+)', _sub_relative2absolute, body)
|
||||
body = re.sub(r'(<[^>]+\bstyle="[^"]+\burl\(\'?)(/[^/\'][^\'")]+)', _sub_relative2absolute, body)
|
||||
|
||||
# resolve relative image url to absolute for outlook.com
|
||||
def _sub_relative2absolute(match):
|
||||
return match.group(1) + urlparse.urljoin(domain, match.group(2))
|
||||
# Regex: https://regex101.com/r/aE8uG5/3
|
||||
body = re.sub('(<img(?=\s)[^>]*\ssrc=["\'])(/[^/][^"\']+)', _sub_relative2absolute, body)
|
||||
# Regex: https://regex101.com/r/kT3lD5/2
|
||||
body = re.sub(r'(<[^>]+\bstyle=["\'][^"\']+\burl\([\'"]?)(/[^/\'"][^\'")]+)', _sub_relative2absolute, body)
|
||||
|
||||
# generate tracking URL
|
||||
if mail.statistics_ids:
|
||||
tracking_url = self._get_tracking_url(cr, uid, mail, partner, context=context)
|
||||
|
||||
@@ -685,6 +685,8 @@ class MassMailing(osv.Model):
|
||||
value['mailing_domain'] = "[('list_id', 'in', %s), ('opt_out', '=', False)]" % list(mailing_list_ids)
|
||||
else:
|
||||
value['mailing_domain'] = "[('list_id', '=', False)]"
|
||||
elif 'opt_out' in self.pool[mailing_model]._fields:
|
||||
value['mailing_domain'] = "[('opt_out', '=', False)]"
|
||||
else:
|
||||
value['mailing_domain'] = []
|
||||
value['body_html'] = "on_change_model_and_list"
|
||||
|
||||
@@ -384,7 +384,8 @@
|
||||
<form string="Mass Mailing">
|
||||
<header style="min-height:31px;">
|
||||
<button name="action_test_mailing" type="object" class="oe_highlight" string="Test Mailing"/>
|
||||
<button name="put_in_queue" type="object" attrs="{'invisible': ['|', ('state', 'in', ('in_queue', 'done'))]}" class="oe_highlight" string="Send to All"/>
|
||||
<button name="put_in_queue" type="object" attrs="{'invisible': ['|', ('state', 'in', ('in_queue', 'done'))]}" class="oe_highlight" string="Send to All"
|
||||
confirm="This will send the email to all recipients. Do you still want to proceed ?"/>
|
||||
<button name="cancel_mass_mailing" type="object" attrs="{'invisible': [('state', '!=', 'in_queue')]}" class="oe_highlight" string="Cancel"/>
|
||||
<button name="retry_failed_mail" type="object" attrs="{'invisible': ['|', ('state', '!=', 'done'), ('failed', '=', 0)]}" class="oe_highlight" string="Retry"/>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-'8' "-*-"
|
||||
from hashlib import sha1
|
||||
import logging
|
||||
import urllib
|
||||
import urlparse
|
||||
|
||||
from openerp.addons.payment.models.payment_acquirer import ValidationError
|
||||
@@ -12,6 +13,16 @@ from openerp.tools.translate import _
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def normalize_keys_upper(data):
|
||||
"""Set all keys of a dictionnary to uppercase
|
||||
|
||||
Buckaroo parameters names are case insensitive
|
||||
convert everything to upper case to be able to easily detected the presence
|
||||
of a parameter by checking the uppercase key only
|
||||
"""
|
||||
return dict((key.upper(), val) for key, val in data.items())
|
||||
|
||||
|
||||
class AcquirerBuckaroo(osv.Model):
|
||||
_inherit = 'payment.acquirer'
|
||||
|
||||
@@ -61,17 +72,22 @@ class AcquirerBuckaroo(osv.Model):
|
||||
values = dict(values or {})
|
||||
|
||||
if inout == 'out':
|
||||
if 'BRQ_SIGNATURE' in values:
|
||||
del values['BRQ_SIGNATURE']
|
||||
items = sorted((k.upper(), v) for k, v in values.items())
|
||||
sign = ''.join('%s=%s' % (k, v) for k, v in items)
|
||||
for key in values.keys():
|
||||
# case insensitive keys
|
||||
if key.upper() == 'BRQ_SIGNATURE':
|
||||
del values[key]
|
||||
break
|
||||
|
||||
items = sorted(values.items(), key=lambda (x, y): x.lower())
|
||||
sign = ''.join('%s=%s' % (k, urllib.unquote_plus(v)) for k, v in items)
|
||||
else:
|
||||
sign = ''.join('%s=%s' % (k,get_value(k)) for k in keys)
|
||||
#Add the pre-shared secret key at the end of the signature
|
||||
sign = sign + acquirer.brq_secretkey
|
||||
if isinstance(sign, str):
|
||||
# TODO: remove me? should not be used
|
||||
sign = urlparse.parse_qsl(sign)
|
||||
shasign = sha1(sign).hexdigest()
|
||||
shasign = sha1(sign.encode('utf-8')).hexdigest()
|
||||
return shasign
|
||||
|
||||
|
||||
@@ -117,6 +133,8 @@ class TxBuckaroo(osv.Model):
|
||||
def _buckaroo_form_get_tx_from_data(self, cr, uid, data, context=None):
|
||||
""" Given a data dict coming from buckaroo, verify it and find the related
|
||||
transaction record. """
|
||||
origin_data = dict(data)
|
||||
data = normalize_keys_upper(data)
|
||||
reference, pay_id, shasign = data.get('BRQ_INVOICENUMBER'), data.get('BRQ_PAYMENT'), data.get('BRQ_SIGNATURE')
|
||||
if not reference or not pay_id or not shasign:
|
||||
error_msg = _('Buckaroo: received data with missing reference (%s) or pay_id (%s) or shasign (%s)') % (reference, pay_id, shasign)
|
||||
@@ -135,7 +153,7 @@ class TxBuckaroo(osv.Model):
|
||||
tx = self.pool['payment.transaction'].browse(cr, uid, tx_ids[0], context=context)
|
||||
|
||||
#verify shasign
|
||||
shasign_check = self.pool['payment.acquirer']._buckaroo_generate_digital_sign(tx.acquirer_id, 'out' ,data)
|
||||
shasign_check = self.pool['payment.acquirer']._buckaroo_generate_digital_sign(tx.acquirer_id, 'out', origin_data)
|
||||
if shasign_check.upper() != shasign.upper():
|
||||
error_msg = _('Buckaroo: invalid shasign, received %s, computed %s, for data %s') % (shasign, shasign_check, data)
|
||||
_logger.info(error_msg)
|
||||
@@ -145,7 +163,7 @@ class TxBuckaroo(osv.Model):
|
||||
|
||||
def _buckaroo_form_get_invalid_parameters(self, cr, uid, tx, data, context=None):
|
||||
invalid_parameters = []
|
||||
|
||||
data = normalize_keys_upper(data)
|
||||
if tx.acquirer_reference and data.get('BRQ_TRANSACTIONS') != tx.acquirer_reference:
|
||||
invalid_parameters.append(('Transaction Id', data.get('BRQ_TRANSACTIONS'), tx.acquirer_reference))
|
||||
# check what is buyed
|
||||
@@ -157,6 +175,7 @@ class TxBuckaroo(osv.Model):
|
||||
return invalid_parameters
|
||||
|
||||
def _buckaroo_form_validate(self, cr, uid, tx, data, context=None):
|
||||
data = normalize_keys_upper(data)
|
||||
status_code = int(data.get('BRQ_STATUSCODE','0'))
|
||||
if status_code in self._buckaroo_valid_tx_status:
|
||||
tx.write({
|
||||
|
||||
@@ -122,7 +122,7 @@ class BuckarooForm(BuckarooCommon):
|
||||
'BRQ_CURRENCY': u'EUR',
|
||||
'BRQ_CUSTOMER_NAME': u'Jan de Tester',
|
||||
'BRQ_INVOICENUMBER': u'SO004',
|
||||
'BRQ_PAYMENT': u'573311D081B04069BD6336001611DBD4',
|
||||
'brq_payment': u'573311D081B04069BD6336001611DBD4',
|
||||
'BRQ_PAYMENT_METHOD': u'paypal',
|
||||
'BRQ_SERVICE_PAYPAL_PAYERCOUNTRY': u'NL',
|
||||
'BRQ_SERVICE_PAYPAL_PAYEREMAIL': u'fhe@openerp.com',
|
||||
@@ -130,7 +130,7 @@ class BuckarooForm(BuckarooCommon):
|
||||
'BRQ_SERVICE_PAYPAL_PAYERLASTNAME': u'Tester',
|
||||
'BRQ_SERVICE_PAYPAL_PAYERMIDDLENAME': u'de',
|
||||
'BRQ_SERVICE_PAYPAL_PAYERSTATUS': u'verified',
|
||||
'BRQ_SIGNATURE': u'175d82dd53a02bad393fee32cb1eafa3b6fbbd91',
|
||||
'Brq_signature': u'175d82dd53a02bad393fee32cb1eafa3b6fbbd91',
|
||||
'BRQ_STATUSCODE': u'190',
|
||||
'BRQ_STATUSCODE_DETAIL': u'S001',
|
||||
'BRQ_STATUSMESSAGE': u'Transaction successfully processed',
|
||||
|
||||
@@ -411,14 +411,16 @@ var Gui = core.Class.extend({
|
||||
numpad_input: function(buffer, input, options) {
|
||||
var newbuf = buffer.slice(0);
|
||||
options = options || {};
|
||||
var newbuf_float = instance.web.parse_value(newbuf, {type: "float"}, 0);
|
||||
var decimal_point = instance.web._t.database.parameters.decimal_point;
|
||||
|
||||
if (input === '.') {
|
||||
if (input === decimal_point) {
|
||||
if (options.firstinput) {
|
||||
newbuf = "0.";
|
||||
}else if (!newbuf.length || newbuf === '-') {
|
||||
newbuf += "0.";
|
||||
} else if (newbuf.indexOf('.') < 0){
|
||||
newbuf = newbuf + '.';
|
||||
} else if (newbuf.indexOf(decimal_point) < 0){
|
||||
newbuf = newbuf + decimal_point;
|
||||
}
|
||||
} else if (input === 'CLEAR') {
|
||||
newbuf = "";
|
||||
@@ -435,7 +437,7 @@ var Gui = core.Class.extend({
|
||||
newbuf = '-' + newbuf;
|
||||
}
|
||||
} else if (input[0] === '+' && !isNaN(parseFloat(input))) {
|
||||
newbuf = '' + ((parseFloat(newbuf) || 0) + parseFloat(input));
|
||||
newbuf = this.chrome.format_currency_no_symbol(newbuf_float + parseFloat(input));
|
||||
} else if (!isNaN(parseInt(input))) {
|
||||
if (options.firstinput) {
|
||||
newbuf = '' + input;
|
||||
|
||||
@@ -1092,10 +1092,11 @@ exports.Orderline = Backbone.Model.extend({
|
||||
clone: function(){
|
||||
var orderline = new exports.Orderline({},{
|
||||
pos: this.pos,
|
||||
order: null,
|
||||
order: this.order,
|
||||
product: this.product,
|
||||
price: this.price,
|
||||
});
|
||||
orderline.order = null;
|
||||
orderline.quantity = this.quantity;
|
||||
orderline.quantityStr = this.quantityStr;
|
||||
orderline.discount = this.discount;
|
||||
|
||||
@@ -205,6 +205,7 @@ var NumberPopupWidget = PopupWidget.extend({
|
||||
this._super(options);
|
||||
|
||||
this.inputbuffer = '' + (options.value || '');
|
||||
this.decimal_separator = instance.web._t.database.parameters.decimal_point;
|
||||
this.renderElement();
|
||||
this.firstinput = true;
|
||||
},
|
||||
|
||||
@@ -1300,7 +1300,7 @@ var ClientListScreenWidget = ScreenWidget.extend({
|
||||
contents.append($(QWeb.render('ClientDetailsEdit',{widget:this,partner:partner})));
|
||||
this.toggle_save_button();
|
||||
|
||||
contents.find('.image-uploader').on('change',function(){
|
||||
contents.find('.image-uploader').on('change',function(event){
|
||||
self.load_image_file(event.target.files[0],function(res){
|
||||
if (res) {
|
||||
contents.find('.client-picture img, .client-picture .fa').remove();
|
||||
@@ -1519,7 +1519,7 @@ var PaymentScreenWidget = ScreenWidget.extend({
|
||||
event.keyCode === 110 || // Decimal point (numpad)
|
||||
event.keyCode === 188 || // Comma
|
||||
event.keyCode === 46 ) { // Numpad dot
|
||||
key = '.';
|
||||
key = self.decimal_point;
|
||||
} else if (event.keyCode >= 48 && event.keyCode <= 57) { // Numbers
|
||||
key = '' + (event.keyCode - 48);
|
||||
} else if (event.keyCode === 45) { // Minus
|
||||
@@ -1578,7 +1578,7 @@ var PaymentScreenWidget = ScreenWidget.extend({
|
||||
order.selected_paymentline.set_amount(amount);
|
||||
this.order_changes();
|
||||
this.render_paymentlines();
|
||||
this.$('.paymentline.selected .edit').text(this.inputbuffer);
|
||||
this.$('.paymentline.selected .edit').text(this.format_currency_no_symbol(amount));
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1704,9 +1704,9 @@ var PaymentScreenWidget = ScreenWidget.extend({
|
||||
|
||||
this.gui.show_popup('number',{
|
||||
'title': tip ? _t('Change Tip') : _t('Add Tip'),
|
||||
'value': value,
|
||||
'value': self.format_currency_no_symbol(value),
|
||||
'confirm': function(value) {
|
||||
order.set_tip(Number(value));
|
||||
order.set_tip(instance.web.parse_value(value, {type: "float"}, 0));
|
||||
self.order_changes();
|
||||
self.render_paymentlines();
|
||||
}
|
||||
|
||||
@@ -559,7 +559,7 @@
|
||||
<br />
|
||||
<button class="input-button numpad-char" data-action='CLEAR' >C</button>
|
||||
<button class="input-button number-char" data-action='0'>0</button>
|
||||
<button class="input-button number-char" data-action='.'>.</button>
|
||||
<button class="input-button number-char" t-att-data-action='widget.decimal_point'><t t-esc='widget.decimal_point'/></button>
|
||||
<button class="input-button numpad-backspace" data-action='BACKSPACE' >
|
||||
<img src="/point_of_sale/static/src/img/backspace.png" width="24" height="21" />
|
||||
</button>
|
||||
@@ -702,9 +702,6 @@
|
||||
<div>Served by <t t-esc='receipt.cashier' /></div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if='!receipt.customer_count'>
|
||||
<div>Guests: <t t-esc='receipt.customer_count' /></div>
|
||||
</t>
|
||||
</div>
|
||||
<br /><br />
|
||||
|
||||
@@ -1006,7 +1003,7 @@
|
||||
<br />
|
||||
<button class="input-button numpad-char" data-action='CLEAR' >C</button>
|
||||
<button class="input-button number-char" data-action='0'>0</button>
|
||||
<button class="input-button number-char dot" data-action='.'>.</button>
|
||||
<button class="input-button number-char dot" t-att-data-action='widget.decimal_separator'><t t-esc='widget.decimal_separator'/></button>
|
||||
<button class="input-button numpad-backspace" data-action='BACKSPACE' >
|
||||
<img style="pointer-events: none;" src="/point_of_sale/static/src/img/backspace.png" width="24" height="21" />
|
||||
</button>
|
||||
@@ -1298,7 +1295,7 @@
|
||||
<tr>
|
||||
<td>Subtotal:</td>
|
||||
<td class="pos-right-align">
|
||||
<t t-esc="widget.format_currency(order.get_subtotal())"/>
|
||||
<t t-esc="widget.format_currency(order.get_total_without_tax())"/>
|
||||
</td>
|
||||
</tr>
|
||||
<t t-foreach="order.get_tax_details()" t-as="taxdetail">
|
||||
|
||||
@@ -3,4 +3,3 @@ data_dir = /var/run/odoo
|
||||
log_level = error
|
||||
logfile = /var/log/odoo/odoo-server.log
|
||||
pidfile = /var/run/odoo/odoo.pid
|
||||
server_wide_modules = web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_scale,hw_scanner,hw_escpos
|
||||
@@ -26,7 +26,7 @@ test -x $DAEMON || exit 0
|
||||
set -e
|
||||
|
||||
function _start() {
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER:$USER --background --make-pidfile --exec $DAEMON -- --config $CONFIG --logfile $LOGFILE --load=web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_scale,hw_scanner,hw_escpos
|
||||
start-stop-daemon --start --quiet --pidfile $PIDFILE --chuid $USER:$USER --background --make-pidfile --exec $DAEMON -- --config $CONFIG --logfile $LOGFILE --load=web,hw_proxy,hw_posbox_homepage,hw_posbox_upgrade,hw_scale,hw_scanner,hw_escpos,hw_blackbox_be
|
||||
}
|
||||
|
||||
function _stop() {
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<t t-if='receipt.table'>
|
||||
at table <t t-esc='receipt.table' />
|
||||
</t>
|
||||
<t t-if='receipt.customer_count'>
|
||||
<div>Guests: <t t-esc='receipt.customer_count' /></div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
@@ -14,6 +17,9 @@
|
||||
<t t-if='receipt.table'>
|
||||
at table <t t-esc='receipt.table' />
|
||||
</t>
|
||||
<t t-if='receipt.customer_count'>
|
||||
<div>Guests: <t t-esc='receipt.customer_count' /></div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -567,7 +567,7 @@ class product_template(osv.osv):
|
||||
if ptype != 'standard_price':
|
||||
res[product.id] = product[ptype] or 0.0
|
||||
else:
|
||||
company_id = product.env.user.company_id.id
|
||||
company_id = context.get('force_company') or product.env.user.company_id.id
|
||||
product = product.with_context(force_company=company_id)
|
||||
res[product.id] = res[product.id] = product.sudo()[ptype]
|
||||
if ptype == 'list_price':
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from openerp import api, fields, models, _
|
||||
from openerp import api, fields, models, _, SUPERUSER_ID
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools.float_utils import float_is_zero, float_compare
|
||||
@@ -581,7 +581,11 @@ class PurchaseOrderLine(models.Model):
|
||||
self.name += '\n' + product_lang.description_purchase
|
||||
|
||||
fpos = self.order_id.fiscal_position_id
|
||||
self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id)
|
||||
if self.env.uid == SUPERUSER_ID:
|
||||
company_id = self.env.user.company_id.id
|
||||
self.taxes_id = self.product_id.supplier_taxes_id.filtered(lambda r: r.company_id.id == company_id)
|
||||
else:
|
||||
self.taxes_id = fpos.map_tax(self.product_id.supplier_taxes_id)
|
||||
|
||||
self._suggest_quantity()
|
||||
self._onchange_quantity()
|
||||
|
||||
+11
-1
@@ -741,7 +741,8 @@ class SaleOrderLine(models.Model):
|
||||
quantity=self.product_uom_qty,
|
||||
date_order=self.order_id.date_order,
|
||||
pricelist=self.order_id.pricelist_id.id,
|
||||
uom=self.product_uom.id
|
||||
uom=self.product_uom.id,
|
||||
fiscal_position=self.env.context.get('fiscal_position')
|
||||
)
|
||||
self.price_unit = self.env['account.tax']._fix_tax_included_price(product.price, product.taxes_id, self.tax_id)
|
||||
|
||||
@@ -751,6 +752,15 @@ class SaleOrderLine(models.Model):
|
||||
raise UserError(_('You can not remove a sale order line.\nDiscard changes and try setting the quantity to 0.'))
|
||||
return super(SaleOrderLine, self).unlink()
|
||||
|
||||
def onchange_product_uom(self, cursor, user, ids, pricelist, product, qty=0,
|
||||
uom=False, qty_uos=0, uos=False, name='', partner_id=False,
|
||||
lang=False, update_tax=True, date_order=False, fiscal_position=False, context=None):
|
||||
ctx = dict(context or {}, fiscal_position=fiscal_position)
|
||||
return self.product_uom_change(cursor, user, ids, pricelist, product,
|
||||
qty=qty, uom=uom, qty_uos=qty_uos, uos=uos, name=name,
|
||||
partner_id=partner_id, lang=lang, update_tax=update_tax,
|
||||
date_order=date_order, context=ctx)
|
||||
|
||||
@api.multi
|
||||
def _get_delivered_qty(self):
|
||||
'''
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from datetime import datetime, timedelta
|
||||
from openerp import api, fields, models, _
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_compare
|
||||
from openerp.exceptions import UserError
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
@@ -203,7 +204,7 @@ class SaleOrderLine(models.Model):
|
||||
mto_route_id = False
|
||||
try:
|
||||
mto_route_id = self.env['stock.warehouse']._get_mto_route()
|
||||
except models.except_orm:
|
||||
except UserError:
|
||||
# if route MTO not found in ir_model_data, we treat the product as in MTS
|
||||
pass
|
||||
if mto_route_id and mto_route_id in product_routes.ids:
|
||||
|
||||
@@ -1556,7 +1556,6 @@ class stock_picking(models.Model):
|
||||
for pack in pick.pack_operation_ids:
|
||||
if pack.product_id and pack.product_id.tracking != 'none':
|
||||
raise UserError(_('Some products require lots, so you need to specify those first!'))
|
||||
|
||||
view = data_obj.xmlid_to_res_id(cr, uid, 'stock.view_immediate_transfer')
|
||||
wiz_id = self.pool['stock.immediate.transfer'].create(cr, uid, {'pick_id': pick.id}, context=context)
|
||||
return {
|
||||
@@ -4206,7 +4205,7 @@ class stock_package(osv.osv):
|
||||
quant_obj = self.pool.get('stock.quant')
|
||||
for package in self.browse(cr, uid, ids, context=context):
|
||||
quant_ids = [quant.id for quant in package.quant_ids]
|
||||
quant_obj.write(cr, uid, quant_ids, {'package_id': package.parent_id.id or False}, context=context)
|
||||
quant_obj.write(cr, SUPERUSER_ID, quant_ids, {'package_id': package.parent_id.id or False}, context=context)
|
||||
children_package_ids = [child_package.id for child_package in package.children_ids]
|
||||
self.write(cr, uid, children_package_ids, {'parent_id': package.parent_id.id or False}, context=context)
|
||||
#delete current package since it contains nothing anymore
|
||||
|
||||
@@ -368,7 +368,7 @@ class stock_move(osv.osv):
|
||||
def _store_average_cost_price(self, cr, uid, move, context=None):
|
||||
''' move is a browe record '''
|
||||
product_obj = self.pool.get('product.product')
|
||||
if any([q.qty <= 0 for q in move.quant_ids]):
|
||||
if any([q.qty <= 0 for q in move.quant_ids]) or move.product_qty == 0:
|
||||
#if there is a negative quant, the standard price shouldn't be updated
|
||||
return
|
||||
#Note: here we can't store a quant.cost directly as we may have moved out 2 units (1 unit to 5€ and 1 unit to 7€) and in case of a product return of 1 unit, we can't know which of the 2 costs has to be used (5€ or 7€?). So at that time, thanks to the average valuation price we are storing we will valuate it at 6€
|
||||
|
||||
@@ -1478,7 +1478,7 @@ class Reports(http.Controller):
|
||||
if 'name' not in action:
|
||||
reports = request.session.model('ir.actions.report.xml')
|
||||
res_id = reports.search([('report_name', '=', action['report_name']),],
|
||||
0, False, False, context)
|
||||
context=context)
|
||||
if len(res_id) > 0:
|
||||
file_name = reports.read(res_id[0], ['name'], context)['name']
|
||||
else:
|
||||
|
||||
@@ -843,7 +843,7 @@ var CalendarView = View.extend({
|
||||
var index = this.dataset.get_id_index(id);
|
||||
if (index !== null) {
|
||||
event_id = this.dataset.ids[index];
|
||||
this.dataset.write(event_id, data, {}).done(function() {
|
||||
this.dataset.write(event_id, data, {}).always(function() {
|
||||
if (is_virtual_id(event_id)) {
|
||||
// this is a virtual ID and so this will create a new event
|
||||
// with an unknown id for us.
|
||||
|
||||
@@ -675,6 +675,7 @@ table.well tr td {
|
||||
}
|
||||
.oe_website_spinner input {
|
||||
text-align: center;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
div.carousel .container > .carousel-caption {
|
||||
|
||||
@@ -545,6 +545,7 @@ table.well tr
|
||||
width: 121px
|
||||
input
|
||||
text-align: center
|
||||
min-width: 50px
|
||||
|
||||
div.carousel
|
||||
.container
|
||||
|
||||
@@ -133,6 +133,32 @@ class sale_order_line(osv.Model):
|
||||
'discounted_price': fields.function(_fnct_get_discounted_price, string='Discounted price', type='float', digits_compute=dp.get_precision('Product Price')),
|
||||
}
|
||||
|
||||
class sale_order_line(osv.Model):
|
||||
_inherit = "sale.order.line"
|
||||
|
||||
def _fnct_get_discounted_price(self, cr, uid, ids, field_name, args, context=None):
|
||||
res = dict.fromkeys(ids, False)
|
||||
for line in self.browse(cr, uid, ids, context=context):
|
||||
res[line.id] = (line.price_unit * (1.0 - (line.discount or 0.0) / 100.0))
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'discounted_price': fields.function(_fnct_get_discounted_price, string='Discounted price', type='float', digits_compute=dp.get_precision('Product Price')),
|
||||
}
|
||||
|
||||
class sale_order_line(osv.Model):
|
||||
_inherit = "sale.order.line"
|
||||
|
||||
def _fnct_get_discounted_price(self, cr, uid, ids, field_name, args, context=None):
|
||||
res = dict.fromkeys(ids, False)
|
||||
for line in self.browse(cr, uid, ids, context=context):
|
||||
res[line.id] = (line.price_unit * (1.0 - (line.discount or 0.0) / 100.0))
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'discounted_price': fields.function(_fnct_get_discounted_price, string='Discounted price', type='float', digits_compute=dp.get_precision('Product Price')),
|
||||
}
|
||||
|
||||
|
||||
class website(orm.Model):
|
||||
_inherit = 'website'
|
||||
|
||||
@@ -101,7 +101,7 @@ $('.oe_website_sale').each(function () {
|
||||
|
||||
$(oe_website_sale).on("change", 'input[name="add_qty"]', function (event) {
|
||||
var product_ids = [];
|
||||
var product_dom = $(".js_add_cart_variants[data-attribute_value_ids]").last();
|
||||
var product_dom = $(".js_product .js_add_cart_variants[data-attribute_value_ids]").last();
|
||||
if (!product_dom.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1478,10 +1478,13 @@
|
||||
|
||||
<!-- Add a link to the product page in the portal orders followup page -->
|
||||
<template id="orders_followup_products_links" name="Orders Followup Products Links" inherit_id="website_portal_sale.orders_followup">
|
||||
<xpath expr="//span[@t-esc='ol.product_id.name']" position="replace">
|
||||
<a t-att-href="ol.product_id.website_url">
|
||||
<span t-esc="ol.product_id.name"/>
|
||||
<xpath expr="//div[@id='product_name']/*" position="replace">
|
||||
<a t-if="ol.product_id.website_published" t-att-href="ol.product_id.website_url">
|
||||
<span t-esc="ol.name"/>
|
||||
</a>
|
||||
<t t-if="not ol.product_id.website_published">
|
||||
<span t-esc="ol.name"/>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ Delivery Costs
|
||||
'data': [
|
||||
'views/website_sale_delivery.xml',
|
||||
'views/website_sale_delivery_view.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'demo/website_sale_delivery_data.xml'
|
||||
],
|
||||
'demo': [
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
|
||||
from openerp.osv import orm, fields
|
||||
from openerp import SUPERUSER_ID
|
||||
@@ -7,6 +8,9 @@ from openerp.exceptions import ValidationError
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class delivery_carrier(orm.Model):
|
||||
_name = 'delivery.carrier'
|
||||
_inherit = ['delivery.carrier', 'website.published.mixin']
|
||||
@@ -99,25 +103,32 @@ class SaleOrder(orm.Model):
|
||||
if carrier_id:
|
||||
order.delivery_set()
|
||||
else:
|
||||
order._delivery_unset()
|
||||
order._delivery_unset()
|
||||
|
||||
return bool(carrier_id)
|
||||
|
||||
def _get_delivery_methods(self, cr, uid, order, context=None):
|
||||
carrier_obj = self.pool.get('delivery.carrier')
|
||||
delivery_ids = carrier_obj.search(cr, uid, [('website_published','=',True)], context=context)
|
||||
carrier_ids = carrier_obj.search(cr, SUPERUSER_ID, [('website_published', '=', True)], context=context)
|
||||
available_carrier_ids = []
|
||||
# Following loop is done to avoid displaying delivery methods who are not available for this order
|
||||
# This can surely be done in a more efficient way, but at the moment, it mimics the way it's
|
||||
# done in delivery_set method of sale.py, from delivery module
|
||||
for delivery_id in carrier_obj.browse(cr, SUPERUSER_ID, delivery_ids, context=dict(context, order_id=order.id)):
|
||||
|
||||
new_context = dict(context, order_id=order.id)
|
||||
for carrier in carrier_ids:
|
||||
|
||||
try:
|
||||
if not delivery_id.available:
|
||||
delivery_ids.remove(delivery_id.id)
|
||||
except ValidationError:
|
||||
# RIM: hack to remove in master, because available field should not depend on a SOAP call to external shipping provider
|
||||
# The validation error is used in backend to display errors in fedex config, but should fail silently in frontend
|
||||
delivery_ids.remove(delivery_id.id)
|
||||
return delivery_ids
|
||||
_logger.debug("Checking availability of carrier #%s" % carrier)
|
||||
available = carrier_obj.read(cr, SUPERUSER_ID, [carrier], fields=['available'], context=new_context)[0]['available']
|
||||
if available:
|
||||
available_carrier_ids = available_carrier_ids + [carrier]
|
||||
except ValidationError as e:
|
||||
# RIM: hack to remove in master, because available field should not depend on a SOAP call to external shipping provider
|
||||
# The validation error is used in backend to display errors in fedex config, but should fail silently in frontend
|
||||
_logger.debug("Carrier #%s removed from e-commerce carrier list. %s" % (carrier, e))
|
||||
|
||||
return available_carrier_ids
|
||||
|
||||
def _get_errors(self, cr, uid, order, context=None):
|
||||
errors = super(SaleOrder, self)._get_errors(cr, uid, order, context=context)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_delivery_carrier_public,delivery.carrier.public,delivery.model_delivery_carrier,,1,0,0,0
|
||||
|
@@ -12,6 +12,11 @@
|
||||
@MAX-Z-INDEX : 2147483647;
|
||||
} .define-color-if-not-defined();
|
||||
|
||||
.top_covered_image {
|
||||
object-fit: cover;
|
||||
object-position: top;
|
||||
}
|
||||
|
||||
.oe_slides_panel_footer {
|
||||
background-color: @navbar-default-bg;
|
||||
}
|
||||
@@ -45,6 +50,7 @@ section.oe_slides_promote_box {
|
||||
}
|
||||
|
||||
img.oe_slides_channel_thumbnail {
|
||||
.top_covered_image();
|
||||
height: 250px;
|
||||
width: 100%
|
||||
}
|
||||
@@ -54,6 +60,7 @@ img.oe_slides_opacity {
|
||||
}
|
||||
|
||||
img.oe_slides_promote_image {
|
||||
.top_covered_image();
|
||||
max-height: 300px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
@@ -65,6 +72,7 @@ img.oe_slides_promote_image {
|
||||
}
|
||||
|
||||
.oe_slides_apart_small {
|
||||
.top_covered_image();
|
||||
height: 60px;
|
||||
width: 90px;
|
||||
}
|
||||
@@ -97,6 +105,7 @@ textarea.o_slides_comment {
|
||||
}
|
||||
|
||||
.oe_slides_grid_thumbnail {
|
||||
.top_covered_image();
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
Italy, 2015-11-05
|
||||
|
||||
Abstract Srl agrees to the terms of the Odoo Corporate Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Davide Corio davide.corio@abstract.it https://github.com/dcorio
|
||||
|
||||
|
||||
List of contributors:
|
||||
|
||||
Davide Corio davide.corio@abstract.it https://github.com/dcorio
|
||||
Simone Orsi simone.orsi@abstract.it https://github.com/simahawk
|
||||
Giacomo Spettoli giacomo.spettoli@abstract.it https://github.com/giacomos
|
||||
@@ -0,0 +1,40 @@
|
||||
Switzerland, 2015-11-09
|
||||
|
||||
brain-tec AG agrees to the terms of the Odoo Corporate Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Pascal Zenklusen pascal.zenklusen@braintec-group.com https://github.com/BT-pzenklusen
|
||||
|
||||
List of contributors:
|
||||
|
||||
Álvaro Estébanez López alvaro.estebanez@braintec-group.com https://github.com/BT-aestebanez
|
||||
Andreas Stauder andreas.stauder@braintec-group.com https://github.com/BT-astauder
|
||||
Carlos Millán de Silva carlos.millan@braintec-group.com https://github.com/BT-cmillan
|
||||
Cesar Andres Sanchez cesar-andres.sanchez@braintec-group.com https://github.com/BT-csanchez
|
||||
Carlos Serra Toro carlos.serra@braintec-group.com https://github.com/BT-cserra
|
||||
Dominik Schleich dominik.schleich@braintec-group.com https://github.com/BT-dschleich
|
||||
Frédéric Aebi frederic.aebi@braintec-group.com https://github.com/BT-faebi
|
||||
Fréderic Garbely frederic.garbely@braintec-group.com https://github.com/BT-fgarbely
|
||||
Florian Wonneberger florian.wonneberger@braintec-group.com https://github.com/BT-fwonneberger
|
||||
Jorge Asensio jorge.asensio@braintec-group.com https://github.com/BT-jasensio
|
||||
Jose J. Duran-Martin jose-javier.duran@braintec-group.com https://github.com/BT-jduran
|
||||
Federico Javier Mesa Hidalgo javier.mesa@braintec-group.com https://github.com/BT-jmesa
|
||||
Joël Michaud joel.michaud@braintec-group.com https://github.com/BT-jmichaud
|
||||
Jose Montero jose.montero@braintec-group.com https://github.com/BT-jmontero
|
||||
José Luis Vallejo Diaz jose.vallejo@braintec-group.com https://github.com/BT-jvallejo
|
||||
Kumar Aberer kumar.aberer@braintec-group.com https://github.com/BT-kaberer
|
||||
Matt Fasola matt.fasola@braintec-group.com https://github.com/BT-mfasola
|
||||
Miguel Tallon Benitez miguel.tallon@braintec-group.com https://github.com/BT-mtallon
|
||||
Nicolas Frei nicolas.frei@braintec-group.com https://github.com/BT-nfrei
|
||||
Nathanael Leutenegger nathanael.leutenegger@braintec-group.com https://github.com/BT-nleutenegger
|
||||
Olivier Jossen olivier.jossen@braintec-group.com https://github.com/BT-ojossen
|
||||
Philipp Fux philipp.fux@braintec-group.com https://github.com/BT-pfux
|
||||
Raúl Martín raul.martin@braintec-group.com https://github.com/BT-rmartin
|
||||
Silvan Wyden silvan.wyden@braintec-group.com https://github.com/BT-swyden
|
||||
Timka Piric Muratovic timka.piric@braintec-group.com https://github.com/BT-tpiric
|
||||
Pascal Zenklusen pascal.zenklusen@braintec-group.com https://github.com/BT-pzenklusen
|
||||
@@ -0,0 +1,30 @@
|
||||
China, 2015-12-10
|
||||
|
||||
Elico Corporation agrees to the terms of the Odoo Corporate Contributor License
|
||||
Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Eric Caudal eric.caudal@elico-corp.com https://github.com/elicoidal
|
||||
|
||||
List of contributors:
|
||||
Eric Caudal eric.caudal@elico-corp.com https://github.com/elicoidal
|
||||
Sébastien Maillard sebastien.maillard@elico-corp.com https://github.com/seb-elico
|
||||
Siyuan Gu siyuancn@hotmail.com https://github.com/siyuancn
|
||||
Wang Liping lynn.config@gmail.com https://github.com/lynndotconfig
|
||||
Luke Zheng cialuo@126.com https://github.com/cialuo
|
||||
Gary Wei gary.wei@elico-corp.com https://github.com/Garywei1314
|
||||
Yaxi Zheng zheng.yaxi@elico-corp.com https://github.com/Elico-Yaxi
|
||||
Faust Huang faust_huang@hotmail.com https://github.com/fausthuang
|
||||
Yu Lin yu_lin@me.com https://github.com/uynil
|
||||
Xie Xiaopeng hellomyjob@163.com https://github.com/xie8899
|
||||
Connie Xiao connie.xiao@elico-corp.com https://github.com/ConnieXiao
|
||||
Alex Duan alex.duan@elico-corp.com https://github.com/duanyp1991
|
||||
Liu Lixia liulixia19901988@163.com https://github.com/liulixia1990
|
||||
Liliane Li liliane.li@elico-corp.com http://github.com/lilianeandbrody
|
||||
Rona Lin ssauapw@qq.com https://github.com/Rona111
|
||||
Bob Luo luo.yumeng0831@gmail.com https://github.com/m6519578
|
||||
Waller Zhang waller.zhang@elico-corp.com https://github.com/wallerzhang
|
||||
@@ -0,0 +1,21 @@
|
||||
KMEE, Brazil, 2015-12-04
|
||||
|
||||
KMEE INFORMATICA LTDA agrees to the terms of the Odoo Corporate
|
||||
Contributor License Agreement v1.0.
|
||||
|
||||
I declare that I am authorized and able to make this agreement and sign this
|
||||
declaration.
|
||||
|
||||
Signed,
|
||||
|
||||
Luis Felipe Miléo mileo@kmee.com.br https://github.com/kmee
|
||||
|
||||
List of contributors:
|
||||
|
||||
Luis Felipe Miléo mileo@kmee.com.br https://github.com/mileo
|
||||
Ananias Pereira Batista Filho ananias@kmee.com.br https://github.com/ananiasfilho
|
||||
Michell Stuttigart michell.stuttigart@kmee.com.br https://github.com/mstuttgart
|
||||
Fernando Marcato Rodrigues fernando.marcato@kmee.com.br https://github.com/fernandomr
|
||||
Daniel Sadamo daniel.sadamo@kmee.com.br https://github.com/sadamo
|
||||
Bianca Tella bianca.tella@kmee.com.br https://github.com/biancatella
|
||||
Luiz Felipe do Divino luiz.divino@kmee.com.br https://github.com/lfdivino
|
||||
@@ -17,3 +17,4 @@ Suganthi Karunanithi ksuganthi@sodexis.com https://github.com/suganthikarunanith
|
||||
Xavier Dass xavier@sodexis.com https://github.com/xavier-dass
|
||||
Atchuthan atchuthan@sodexis.com https://github.com/atchuthan
|
||||
Dhinesh dhinesh@sodexis.com https://github.com/dvdhinesh
|
||||
SodexisTeam dev@sodexis.com https://github.com/SodexisTeam
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
Switzerland, 2015-12-11
|
||||
|
||||
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,
|
||||
|
||||
Florian Bruhin git@the-compiler.org https://github.com/The-Compiler
|
||||
Florian Bruhin me@the-compiler.org https://github.com/The-Compiler
|
||||
@@ -0,0 +1,11 @@
|
||||
Spain, 2015-12-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,
|
||||
|
||||
Juan Carlos Montoya juancarlos.montoya.chamba@gmail.com https://github.com/jcarlosmontoya
|
||||
@@ -1098,10 +1098,31 @@ class ir_model_data(osv.osv):
|
||||
},context=context)
|
||||
else:
|
||||
if mode=='init' or (mode=='update' and xml_id):
|
||||
inherit_xml_ids = []
|
||||
for table, field_name in model_obj._inherits.items():
|
||||
xml_ids = self.pool['ir.model.data'].search(cr, uid, [
|
||||
('module', '=', module),
|
||||
('name', '=', xml_id + '_' + table.replace('.', '_')),
|
||||
], context=context)
|
||||
# XML ID found in the database, try to recover an existing record
|
||||
if xml_ids:
|
||||
found_xml_id = self.pool['ir.model.data'].browse(cr, uid, xml_ids[0], context=context)
|
||||
record = self.pool[found_xml_id.model].browse(cr, uid, [found_xml_id.res_id], context=context)[0]
|
||||
# The record exists, store the id and don't recreate the XML ID
|
||||
if record.exists():
|
||||
inherit_xml_ids.append(found_xml_id.model)
|
||||
values[field_name] = found_xml_id.res_id
|
||||
# Orphan XML ID, delete it
|
||||
else:
|
||||
found_xml_id.unlink()
|
||||
|
||||
res_id = model_obj.create(cr, uid, values, context=context)
|
||||
if xml_id:
|
||||
if model_obj._inherits:
|
||||
for table in model_obj._inherits:
|
||||
if table in inherit_xml_ids:
|
||||
continue
|
||||
|
||||
inherit_id = model_obj.browse(cr, uid,
|
||||
res_id,context=context)[model_obj._inherits[table]]
|
||||
self.create(cr, SUPERUSER_ID, {
|
||||
|
||||
@@ -673,12 +673,13 @@ class ir_translation(osv.osv):
|
||||
return ir_translation_import_cursor(cr, uid, self, context=context)
|
||||
|
||||
def load_module_terms(self, cr, modules, langs, context=None):
|
||||
context = dict(context or {}) # local copy
|
||||
context_template = dict(context or {}) # local copy
|
||||
for module_name in modules:
|
||||
modpath = openerp.modules.get_module_path(module_name)
|
||||
if not modpath:
|
||||
continue
|
||||
for lang in langs:
|
||||
context = dict(context_template)
|
||||
lang_code = tools.get_iso_codes(lang)
|
||||
base_lang_code = None
|
||||
if '_' in lang_code:
|
||||
|
||||
@@ -619,10 +619,15 @@ class view(osv.osv):
|
||||
requested (similar to ``id``)
|
||||
"""
|
||||
if context is None: context = {}
|
||||
context = context.copy()
|
||||
|
||||
# if view_id is not a root view, climb back to the top.
|
||||
base = v = self.browse(cr, uid, view_id, context=context)
|
||||
check_view_ids = context.setdefault('check_view_ids', [])
|
||||
while v.mode != 'primary':
|
||||
# Add inherited views to the list of loading forced views
|
||||
# Otherwise, inherited views could not find elements created in their direct parents if that parent is defined in the same module
|
||||
check_view_ids.append(v.id)
|
||||
v = v.inherit_id
|
||||
root_id = v.id
|
||||
|
||||
|
||||
+2
-2
@@ -433,8 +433,8 @@ def route(route=None, **kw):
|
||||
|
||||
* ``user``: The user must be authenticated and the current request
|
||||
will perform using the rights of the user.
|
||||
* ``admin``: The user may not be authenticated and the current request
|
||||
will perform using the admin user.
|
||||
* ``public``: The user may or may not be authenticated. If she isn't,
|
||||
the current request will perform using the shared Public user.
|
||||
* ``none``: The method is always active, even if there is no
|
||||
database. Mainly used by the framework and authentication
|
||||
modules. There request code will not have any facilities to access
|
||||
|
||||
@@ -1375,6 +1375,7 @@ class BaseModel(object):
|
||||
from openerp.http import request
|
||||
Users = self.pool['res.users']
|
||||
for group_ext_id in groups.split(','):
|
||||
group_ext_id = group_ext_id.strip()
|
||||
if group_ext_id == 'base.group_no_one':
|
||||
# check: the group_no_one is effective in debug mode only
|
||||
if Users.has_group(cr, uid, group_ext_id) and request and request.debug:
|
||||
|
||||
+15
-3
@@ -11,7 +11,7 @@ import re
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
from email.utils import getaddresses
|
||||
from email.utils import getaddresses, formataddr
|
||||
|
||||
import openerp
|
||||
from openerp.loglevels import ustr
|
||||
@@ -632,10 +632,10 @@ def append_content_to_html(html, content, plaintext=True, preserve=False, contai
|
||||
#----------------------------------------------------------
|
||||
|
||||
# matches any email in a body of text
|
||||
email_re = re.compile(r"""([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6})""", re.VERBOSE)
|
||||
email_re = re.compile(r"""([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63})""", re.VERBOSE)
|
||||
|
||||
# matches a string containing only one email
|
||||
single_email_re = re.compile(r"""^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$""", re.VERBOSE)
|
||||
single_email_re = re.compile(r"""^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$""", re.VERBOSE)
|
||||
|
||||
res_re = re.compile(r"\[([0-9]+)\]", re.UNICODE)
|
||||
command_re = re.compile("^Set-([a-z]+) *: *(.+)$", re.I + re.UNICODE)
|
||||
@@ -709,3 +709,15 @@ def email_split(text):
|
||||
# is strictly required in RFC2822's `addr-spec`.
|
||||
if addr[1]
|
||||
if '@' in addr[1]]
|
||||
|
||||
def email_split_and_format(text):
|
||||
""" Return a list of email addresses found in ``text``, formatted using
|
||||
formataddr. """
|
||||
if not text:
|
||||
return []
|
||||
return [formataddr((addr[0], addr[1])) for addr in getaddresses([text])
|
||||
# getaddresses() returns '' when email parsing fails, and
|
||||
# sometimes returns emails without at least '@'. The '@'
|
||||
# is strictly required in RFC2822's `addr-spec`.
|
||||
if addr[1]
|
||||
if '@' in addr[1]]
|
||||
|
||||
Reference in New Issue
Block a user