[MERGE] forward port of branch 8.0 up to 222b2d3

This commit is contained in:
Christophe Simonis
2015-02-17 18:54:52 +01:00
47 changed files with 1184 additions and 318 deletions
+10 -3
View File
@@ -430,7 +430,11 @@ class account_bank_statement(osv.osv):
for statement in self.browse(cr, uid, ids, context=context):
for st_line in statement.line_ids:
if st_line.bank_account_id and st_line.partner_id and st_line.bank_account_id.partner_id.id != st_line.partner_id.id:
self.pool.get('res.partner.bank').write(cr, uid, [st_line.bank_account_id.id], {'partner_id': st_line.partner_id.id}, context=context)
# Update the partner informations of the bank account, possibly overriding existing ones
bank_obj = self.pool.get('res.partner.bank')
bank_vals = bank_obj.onchange_partner_id(cr, uid, [st_line.bank_account_id.id], st_line.partner_id.id, context=context)['value']
bank_vals.update({'partner_id': st_line.partner_id.id})
bank_obj.write(cr, uid, [st_line.bank_account_id.id], bank_vals, context=context)
class account_bank_statement_line(osv.osv):
@@ -659,7 +663,8 @@ class account_bank_statement_line(osv.osv):
mv_line_pool = self.pool.get('account.move.line')
domain = self._domain_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids=excluded_ids, str=str, additional_domain=additional_domain, context=context)
# Get move lines ; in case of a partial reconciliation, only consider one line
# Get move lines ; in case of a partial reconciliation, only keep one line (the first whose amount is greater than
# the residual amount because it is presumably the invoice, which is the relevant item in this situation)
filtered_lines = []
reconcile_partial_ids = []
actual_offset = offset
@@ -668,7 +673,9 @@ class account_bank_statement_line(osv.osv):
lines = mv_line_pool.browse(cr, uid, line_ids, context=context)
make_one_more_loop = False
for line in lines:
if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids:
if line.reconcile_partial_id and \
(line.reconcile_partial_id.id in reconcile_partial_ids or \
abs(line.debit - line.credit) < abs(line.amount_residual)):
#if we filtered a line because it is partially reconciled with an already selected line, we must do one more loop
#in order to get the right number of items in the pager
make_one_more_loop = True
+1 -1
View File
@@ -643,7 +643,7 @@ class account_invoice(models.Model):
partner = invoice.partner_id
if partner.lang:
ctx['lang'] = partner.lang
for taxe in account_invoice_tax.compute(invoice).values():
for taxe in account_invoice_tax.compute(invoice.with_context(ctx)).values():
account_invoice_tax.create(taxe)
# dummy write on self to trigger recomputations
return self.with_context(ctx).write({'invoice_line': []})
+5 -2
View File
@@ -783,7 +783,7 @@ class account_move_line(osv.osv):
ret_line = {
'id': line.id,
'name': line.name != '/' and line.move_id.name + ': ' + line.name or line.move_id.name,
'ref': line.move_id.ref,
'ref': line.move_id.ref or '',
# For reconciliation between statement transactions and already registered payments (eg. checks)
'already_paid': line.account_id.type == 'liquidity',
'account_code': line.account_id.code,
@@ -1378,6 +1378,8 @@ class account_move_line(osv.osv):
}
self.create(cr, uid, data, context)
#create the Tax movement
if not tax['amount']:
continue
data = {
'move_id': vals['move_id'],
'name': tools.ustr(vals['name'] or '') + ' ' + tools.ustr(tax['name'] or ''),
@@ -1395,7 +1397,8 @@ class account_move_line(osv.osv):
self.create(cr, uid, data, context)
del vals['account_tax_id']
if check and not context.get('novalidate') and (context.get('recompute', True) or journal.entry_posted):
recompute = journal.env.recompute and context.get('recompute', True)
if check and not context.get('novalidate') and (recompute or journal.entry_posted):
tmp = move_obj.validate(cr, uid, [vals['move_id']], context)
if journal.entry_posted and tmp:
move_obj.button_validate(cr,uid, [vals['move_id']], context)
+2 -1
View File
@@ -615,7 +615,8 @@
<field name="amount" attrs="{'readonly' : [('journal_entry_ids', '!=', [])] }"/>
<field name="amount_currency" groups="base.group_multi_currency" attrs="{'readonly' : [('journal_entry_ids', '!=', [])] }"/>
<field name="currency_id" groups="base.group_multi_currency" attrs="{'readonly' : [('journal_entry_ids', '!=', [])] }"/>
<field name="bank_account_id" groups="base.group_no_one"/>
<field name="bank_account_id" groups="base.group_no_one" attrs="{'readonly' : [('journal_entry_id', '!=', False)] }"
domain="['|', ('partner_id', '=', partner_id), ('partner_id', '=', False)]"/>
</tree>
</field>
</page>
+24 -5
View File
@@ -7,6 +7,7 @@ import openerp
import openerp.service.report
import uuid
import collections
import babel.dates
from werkzeug.exceptions import BadRequest
from datetime import datetime, timedelta
from dateutil import parser
@@ -1441,7 +1442,16 @@ class calendar_event(osv.Model):
return invitation
def get_interval(self, cr, uid, ids, date, interval, tz=None, context=None):
#Function used only in calendar_event_data.xml for email template
''' Format and localize some dates to be used in email templates
:param string date: date/time to be formatted
:param string interval: Among 'day', 'month', 'dayname' and 'time' indicating the desired formatting
:param string tz: Timezone indicator (optional)
:return unicode: Formatted date or time (as unicode string, to prevent jinja2 crash)
(Function used only in calendar_event_data.xml) '''
date = openerp.fields.Datetime.from_string(date)
if tz:
@@ -1449,14 +1459,23 @@ class calendar_event(osv.Model):
date = date.replace(tzinfo=pytz.timezone('UTC')).astimezone(timezone)
if interval == 'day':
res = str(date.day)
# Day number (1-31)
res = unicode(date.day)
elif interval == 'month':
res = date.strftime('%B') + " " + str(date.year)
# Localized month name and year
res = babel.dates.format_date(date=date, format='MMMM y', locale=context.get('lang', 'en_US'))
elif interval == 'dayname':
res = date.strftime('%A')
# Localized day name
res = babel.dates.format_date(date=date, format='EEEE', locale=context.get('lang', 'en_US'))
elif interval == 'time':
# Localized time
dummy, format_time = self.get_date_formats(cr, uid, context=context)
res = date.strftime(format_time + " %Z")
res = tools.ustr(date.strftime(format_time + " %Z"))
return res
def search(self, cr, uid, args, offset=0, limit=0, order=None, context=None, count=False):
+54 -22
View File
@@ -212,13 +212,20 @@ class google_calendar(osv.AbstractModel):
type = 'dateTime'
vstype = 'date'
attendee_list = []
for attendee in event.attendee_ids:
attendee_list.append({
'email': attendee.email or 'NoEmail@mail.com',
'displayName': attendee.partner_id.name,
'responseStatus': attendee.state or 'needsAction',
})
reminders = []
for alarm in event.alarm_ids:
reminders.append({
"method": "email" if alarm.type == "email" else "popup",
"minutes": alarm.duration_minutes
})
data = {
"summary": event.name or '',
"description": event.description or '',
@@ -233,6 +240,10 @@ class google_calendar(osv.AbstractModel):
'timeZone': 'UTC'
},
"attendees": attendee_list,
"reminders": {
"overrides": reminders,
"useDefault": "false"
},
"location": event.location or '',
"visibility": event['class'] or 'public',
}
@@ -244,7 +255,6 @@ class google_calendar(osv.AbstractModel):
if not self.get_need_synchro_attendee(cr, uid, context=context):
data.pop("attendees")
return data
def create_an_event(self, cr, uid, event, context=None):
@@ -286,7 +296,7 @@ class google_calendar(osv.AbstractModel):
registry = openerp.modules.registry.RegistryManager.get(request.session.db)
with registry.cursor() as cur:
self.pool['res.users'].write(cur, uid, [uid], {'google_calendar_token': False, 'google_calendar_token_validity': False}, context=context)
self.pool['res.users'].write(cur, SUPERUSER_ID, [uid], {'google_calendar_token': False, 'google_calendar_token_validity': False}, context=context)
raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context)
raise
@@ -400,14 +410,16 @@ class google_calendar(osv.AbstractModel):
calendar_event = self.pool['calendar.event']
res_partner_obj = self.pool['res.partner']
calendar_attendee_obj = self.pool['calendar.attendee']
calendar_alarm_obj = self.pool['calendar.alarm']
user_obj = self.pool['res.users']
myPartnerID = user_obj.browse(cr, uid, uid, context).partner_id.id
attendee_record = []
alarm_record = []
partner_record = [(4, myPartnerID)]
result = {}
if single_event_dict.get('attendees', False):
for google_attendee in single_event_dict['attendees']:
if self.get_need_synchro_attendee(cr, uid, context=context):
for google_attendee in single_event_dict.get('attendees', []):
if type == "write":
for oe_attendee in event['attendee_ids']:
if oe_attendee.email == google_attendee['email']:
@@ -415,22 +427,41 @@ class google_calendar(osv.AbstractModel):
google_attendee['found'] = True
continue
if google_attendee.get('found', False):
if google_attendee.get('found'):
continue
if self.get_need_synchro_attendee(cr, uid, context=context):
attendee_id = res_partner_obj.search(cr, uid, [('email', '=', google_attendee['email'])], context=context)
if not attendee_id:
data = {
'email': google_attendee['email'],
'customer': False,
'name': google_attendee.get("displayName", False) or google_attendee['email']
}
attendee_id = [res_partner_obj.create(cr, uid, data, context=context)]
attendee = res_partner_obj.read(cr, uid, attendee_id[0], ['email'], context=context)
partner_record.append((4, attendee.get('id')))
attendee['partner_id'] = attendee.pop('id')
attendee['state'] = google_attendee['responseStatus']
attendee_record.append((0, 0, attendee))
attendee_id = res_partner_obj.search(cr, uid, [('email', '=', google_attendee['email'])], context=context)
if not attendee_id:
data = {
'email': google_attendee['email'],
'customer': False,
'name': google_attendee.get("displayName", False) or google_attendee['email']
}
attendee_id = [res_partner_obj.create(cr, uid, data, context=context)]
attendee = res_partner_obj.read(cr, uid, attendee_id[0], ['email'], context=context)
partner_record.append((4, attendee.get('id')))
attendee['partner_id'] = attendee.pop('id')
attendee['state'] = google_attendee['responseStatus']
attendee_record.append((0, 0, attendee))
for google_alarm in single_event_dict.get('reminders', {}).get('overrides', []):
alarm_id = calendar_alarm_obj.search(
cr,
uid,
[
('type', '=', google_alarm['method'] if google_alarm['method'] == 'email' else 'notification'),
('duration_minutes', '=', google_alarm['minutes'])
],
context=context
)
if not alarm_id:
data = {
'type': google_alarm['method'] if google_alarm['method'] == 'email' else 'notification',
'duration': google_alarm['minutes'],
'interval': 'minutes',
'name': "%s minutes - %s" % (google_alarm['minutes'], google_alarm['method'])
}
alarm_id = [calendar_alarm_obj.create(cr, uid, data, context=context)]
alarm_record.append(alarm_id[0])
UTC = pytz.timezone('UTC')
if single_event_dict.get('start') and single_event_dict.get('end'): # If not cancelled
@@ -458,6 +489,7 @@ class google_calendar(osv.AbstractModel):
result.update({
'attendee_ids': attendee_record,
'partner_ids': list(set(partner_record)),
'alarm_ids': [(6, 0, alarm_record)],
'name': single_event_dict.get('summary', 'Event'),
'description': single_event_dict.get('description', False),
@@ -666,7 +698,7 @@ class google_calendar(osv.AbstractModel):
cr.rollback()
registry = openerp.modules.registry.RegistryManager.get(request.session.db)
with registry.cursor() as cur:
self.pool['res.users'].write(cur, uid, [uid], {'google_calendar_last_sync_date': False}, context=context)
self.pool['res.users'].write(cur, SUPERUSER_ID, [uid], {'google_calendar_last_sync_date': False}, context=context)
error_key = simplejson.loads(str(e))
error_key = error_key.get('error', {}).get('message', 'nc')
error_msg = "Google is lost... the next synchro will be a full synchro. \n\n %s" % error_key
@@ -943,7 +975,7 @@ class calendar_event(osv.Model):
_inherit = "calendar.event"
def get_fields_need_update_google(self, cr, uid, context=None):
return ['name', 'description', 'allday', 'date', 'date_end', 'stop', 'attendee_ids', 'location', 'class', 'active']
return ['name', 'description', 'allday', 'date', 'date_end', 'stop', 'attendee_ids', 'alarm_ids', 'location', 'class', 'active']
def write(self, cr, uid, ids, vals, context=None):
if context is None:
+5 -3
View File
@@ -847,18 +847,20 @@ result = rules.NET > categories.NET * 0.10''',
rule = self.browse(cr, uid, rule_id, context=context)
if rule.amount_select == 'fix':
try:
return rule.amount_fix, eval(rule.quantity, localdict), 100.0
return rule.amount_fix, float(eval(rule.quantity, localdict)), 100.0
except:
raise UserError(_('Wrong quantity defined for salary rule %s (%s).') % (rule.name, rule.code))
elif rule.amount_select == 'percentage':
try:
return eval(rule.amount_percentage_base, localdict), eval(rule.quantity, localdict), rule.amount_percentage
return (float(eval(rule.amount_percentage_base, localdict)),
float(eval(rule.quantity, localdict)),
rule.amount_percentage)
except:
raise UserError(_('Wrong percentage base or quantity defined for salary rule %s (%s).') % (rule.name, rule.code))
else:
try:
eval(rule.amount_python_compute, localdict, mode='exec', nocopy=True)
return localdict['result'], 'result_qty' in localdict and localdict['result_qty'] or 1.0, 'result_rate' in localdict and localdict['result_rate'] or 100.0
return float(localdict['result']), 'result_qty' in localdict and localdict['result_qty'] or 1.0, 'result_rate' in localdict and localdict['result_rate'] or 100.0
except:
raise UserError(_('Wrong python code defined for salary rule %s (%s).') % (rule.name, rule.code))
@@ -36,7 +36,7 @@
<tr class="oe_timesheet_total">
<td>
<div class="oe_timesheet_weekly_adding_tot">
<div class="oe_timesheet_weekly_adding"><button class="oe_timesheet_button_add">Add a Line</button></div>
<div t-if="!widget.get('effective_readonly')" class="oe_timesheet_weekly_adding"><button class="oe_timesheet_button_add">Add a Line</button></div>
<div class="oe_timesheet_weekly_tottot"><span>Total</span></div>
</div>
</td>
+1 -1
View File
@@ -84,7 +84,7 @@
<!-- Template rendering all the scripts required to execute the Livechat from a page containing Odoo -->
<template id="internal_loader" name="All the scripts to launch the LiveSupport from an internal Web Page">
<!-- css style -->
<link t-att-href="'%s/web/css/im_livechat.internal_lib' % (url)" rel="stylesheet"/>
<link property='stylesheet' t-att-href="'%s/web/css/im_livechat.internal_lib' % (url)" rel="stylesheet"/>
<!-- js of all the required lib (internal and external) -->
<script t-att-src="'%s/web/js/im_livechat.internal_lib' % (url)" type="text/javascript" />
<!-- the loader -->
+2
View File
@@ -0,0 +1,2 @@
import wizard
import models
+85
View File
@@ -0,0 +1,85 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Business Applications
# Copyright (C) 2015 Odoo S.A. <http://www.odoo.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'EU Mini One Stop Shop (MOSS)',
'version': '1.0',
'author': 'Odoo SA',
'website': 'http://www.odoo.com',
'category': '',
'description': """
EU Mini One Stop Shop (MOSS) VAT for telecommunications, broadcasting and electronic services
=============================================================================================
As of January 1rst, 2015, telecommunications, broadcasting
and electronic services sold within the European Union
have to be always taxed in the country where the customer
belongs. In order to simplify the application of this EU
directive, the Mini One Stop Shop (MOSS) registration scheme
allows businesses to make a unique tax declaration.
This module makes it possible by helping with the creation
of the required EU fiscal positions and taxes in order to
automatically apply and record the required taxes.
This module installs a wizard to help setup fiscal positions
and taxes for selling electronic services inside EU.
The wizard lets you select:
- the EU countries to which you are selling these
services
- your national VAT tax for services, to be mapped
to the target country's tax
- optionally: a template fiscal position, in order
to copy the account mapping. Should be your
existing B2C Intra-EU fiscal position. (defaults
to no account mapping)
- optionally: an account to use for collecting the
tax amounts (defaults to the account used by your
national VAT tax for services)
It creates the corresponding fiscal positions and taxes,
automatically applicable for EU sales with a customer
in the selected countries.
The wizard can be run again for adding more countries.
The wizard creates a separate Chart of Taxes for collecting the
VAT amounts of the MOSS declaration, so extracting the MOSS
data should be easy.
Look for a Chart of Taxes named "EU MOSS VAT Chart" in the
Taxes Report menu (Generic Accounting Report).
References
++++++++++
- Directive 2008/8/EC
- Council Implementing Regulation (EU) No 1042/2013
""",
'depends': ['account_accountant'],
'data': [
'security/ir.model.access.csv',
'wizard/wizard.xml',
'wizard/l10n_eu_service.service_tax_rate.csv'
],
'test': [],
'demo': [],
'auto_install': False,
'installable': True,
}
@@ -0,0 +1,184 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * l10n_eu_service
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-17 14:34+0000\n"
"PO-Revision-Date: 2015-02-17 14:34+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: l10n_eu_service
#: field:l10n_eu_service.wizard,done_country_ids:0
msgid "Already Supported"
msgstr ""
#. module: l10n_eu_service
#: code:addons/l10n_eu_service/wizard/wizard.py:128
#, python-format
msgid "Base - VAT for EU Services to %(country_name)s"
msgstr ""
#. module: l10n_eu_service
#: view:l10n_eu_service.wizard:l10n_eu_service.eu_service_view
msgid "Based on the options selected below, this wizard will create one\n"
" fiscal position mapping for each EU country to which you are selling\n"
" services.\n"
" Each fiscal position will automatically map your national VAT tax for\n"
" services to the corresponding VAT tax in the country your customer\n"
" belongs to."
msgstr ""
#. module: l10n_eu_service
#: view:l10n_eu_service.wizard:l10n_eu_service.eu_service_view
msgid "Cancel"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.wizard,chart_id:0
msgid "Chart of Accounts"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.service_tax_rate,country_id:0
msgid "Country"
msgstr ""
#. module: l10n_eu_service
#: view:l10n_eu_service.wizard:l10n_eu_service.eu_service_view
msgid "Create Fiscal Positions and Taxes"
msgstr ""
#. module: l10n_eu_service
#: model:ir.model,name:l10n_eu_service.model_l10n_eu_service_wizard
msgid "Create fiscal positions for EU Service VAT"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.service_tax_rate,create_uid:0
#: field:l10n_eu_service.wizard,create_uid:0
msgid "Created by"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.service_tax_rate,create_date:0
#: field:l10n_eu_service.wizard,create_date:0
msgid "Created on"
msgstr ""
#. module: l10n_eu_service
#: view:l10n_eu_service.wizard:l10n_eu_service.eu_service_view
msgid "Current EU B2C Fiscal Position, if any"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.wizard,todo_country_ids:0
msgid "EU Customers From"
msgstr ""
#. module: l10n_eu_service
#: code:addons/l10n_eu_service/wizard/wizard.py:110
#, python-format
msgid "EU MOSS VAT Chart - %(company)s"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.wizard,fiscal_position_id:0
msgid "Fiscal Position"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.service_tax_rate,id:0
#: field:l10n_eu_service.wizard,id:0
msgid "ID"
msgstr ""
#. module: l10n_eu_service
#: code:addons/l10n_eu_service/wizard/wizard.py:161
#, python-format
msgid "Intra-EU B2C in %(country_name)s"
msgstr ""
#. module: l10n_eu_service
#: view:l10n_eu_service.wizard:l10n_eu_service.eu_service_view
msgid "Keep empty to use current Service VAT account"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.service_tax_rate,write_uid:0
#: field:l10n_eu_service.wizard,write_uid:0
msgid "Last Updated by"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.service_tax_rate,write_date:0
#: field:l10n_eu_service.wizard,write_date:0
msgid "Last Updated on"
msgstr ""
#. module: l10n_eu_service
#: help:l10n_eu_service.wizard,account_collected_id:0
msgid "Optional account to use for collecting tax amounts when selling services in each EU country selected below. If not set, the current collecting account of your Service VAT will be used."
msgstr ""
#. module: l10n_eu_service
#: help:l10n_eu_service.wizard,fiscal_position_id:0
msgid "Optional fiscal position to use as template for general account mapping. Should usually be your current Intra-EU B2B fiscal position. If not set, no general account mapping will be configured for EU fiscal positions."
msgstr ""
#. module: l10n_eu_service
#: help:l10n_eu_service.wizard,tax_id:0
msgid "Select your current VAT tax for services. This is the tax that will be mapped to the corresponding VAT tax in each EU country selected below."
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.wizard,tax_id:0
msgid "Service VAT"
msgstr ""
#. module: l10n_eu_service
#: view:account.config.settings:l10n_eu_service.view_account_config_settings_inherit
#: model:ir.actions.act_window,name:l10n_eu_service.action_eu_service
#: view:l10n_eu_service.wizard:l10n_eu_service.eu_service_view
msgid "Setup EU MOSS Taxes"
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.wizard,account_collected_id:0
msgid "Tax Collection Account"
msgstr ""
#. module: l10n_eu_service
#: code:addons/l10n_eu_service/wizard/wizard.py:36
#, python-format
msgid "The Europe country group cannot be found. Please update the base module."
msgstr ""
#. module: l10n_eu_service
#: field:l10n_eu_service.service_tax_rate,rate:0
msgid "VAT Rate"
msgstr ""
#. module: l10n_eu_service
#: code:addons/l10n_eu_service/wizard/wizard.py:132
#, python-format
msgid "VAT for EU Services to %(country_name)s"
msgstr ""
#. module: l10n_eu_service
#: view:l10n_eu_service.wizard:l10n_eu_service.eu_service_view
msgid "You can use the wizard again later to add more countries."
msgstr ""
#. module: l10n_eu_service
#: view:l10n_eu_service.wizard:l10n_eu_service.eu_service_view
msgid "or"
msgstr ""
@@ -0,0 +1 @@
import eu_service_tax_rate
@@ -0,0 +1,7 @@
from openerp import fields, models, api
class service_tax_rate(models.Model):
_name = "l10n_eu_service.service_tax_rate"
country_id = fields.Many2one('res.country', string='Country')
rate = fields.Float(string="VAT Rate")
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_l10n_eu_service_service_tax_rate,access_l10n_eu_service_service_tax_rate,model_l10n_eu_service_service_tax_rate,,1,0,0,0
access_l10n_eu_service_service_tax_rate,access_l10n_eu_service_service_tax_rate,model_l10n_eu_service_service_tax_rate,account.group_account_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_l10n_eu_service_service_tax_rate access_l10n_eu_service_service_tax_rate model_l10n_eu_service_service_tax_rate 1 0 0 0
3 access_l10n_eu_service_service_tax_rate access_l10n_eu_service_service_tax_rate model_l10n_eu_service_service_tax_rate account.group_account_manager 1 1 1 1
@@ -0,0 +1 @@
import wizard
@@ -0,0 +1,30 @@
id,country_id:id,rate
eu_rate_at,base.at,0.21
eu_rate_be,base.be,0.21
eu_rate_bg,base.bg,0.20
eu_rate_hr,base.hr,0.25
eu_rate_cy,base.cy,0.21
eu_rate_cz,base.cz,0.21
eu_rate_dk,base.dk,0.25
eu_rate_ee,base.ee,0.20
eu_rate_fi,base.fi,0.24
eu_rate_fr,base.fr,0.20
eu_rate_de,base.de,0.19
eu_rate_gr,base.gr,0.23
eu_rate_hu,base.hu,0.27
eu_rate_ie,base.ie,0.23
eu_rate_it,base.it,0.22
eu_rate_lv,base.lv,0.21
eu_rate_lt,base.lt,0.21
eu_rate_lu,base.lu,0.17
eu_rate_mt,base.mt,0.18
eu_rate_nl,base.nl,0.21
eu_rate_pl,base.pl,0.23
eu_rate_pt,base.pt,0.23
eu_rate_ro,base.ro,0.24
eu_rate_pt,base.pt,0.23
eu_rate_sk,base.sk,0.20
eu_rate_si,base.si,0.22
eu_rate_es,base.es,0.21
eu_rate_se,base.se,0.24
eu_rate_uk,base.uk,0.20
1 id country_id:id rate
2 eu_rate_at base.at 0.21
3 eu_rate_be base.be 0.21
4 eu_rate_bg base.bg 0.20
5 eu_rate_hr base.hr 0.25
6 eu_rate_cy base.cy 0.21
7 eu_rate_cz base.cz 0.21
8 eu_rate_dk base.dk 0.25
9 eu_rate_ee base.ee 0.20
10 eu_rate_fi base.fi 0.24
11 eu_rate_fr base.fr 0.20
12 eu_rate_de base.de 0.19
13 eu_rate_gr base.gr 0.23
14 eu_rate_hu base.hu 0.27
15 eu_rate_ie base.ie 0.23
16 eu_rate_it base.it 0.22
17 eu_rate_lv base.lv 0.21
18 eu_rate_lt base.lt 0.21
19 eu_rate_lu base.lu 0.17
20 eu_rate_mt base.mt 0.18
21 eu_rate_nl base.nl 0.21
22 eu_rate_pl base.pl 0.23
23 eu_rate_pt base.pt 0.23
24 eu_rate_ro base.ro 0.24
25 eu_rate_pt base.pt 0.23
26 eu_rate_sk base.sk 0.20
27 eu_rate_si base.si 0.22
28 eu_rate_es base.es 0.21
29 eu_rate_se base.se 0.24
30 eu_rate_uk base.uk 0.20
+177
View File
@@ -0,0 +1,177 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Business Applications
# Copyright (C) 2015 Odoo S.A. <http://www.odoo.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.exceptions import Warning
from openerp import fields, models, api
from openerp.tools.translate import _
class l10n_eu_service(models.TransientModel):
"""Create fiscal positions for EU Service VAT"""
_name = "l10n_eu_service.wizard"
_description = __doc__
def _get_eu_res_country_group(self):
eu_group = self.env.ref("base.europe", raise_if_not_found=False)
if not eu_group:
raise Warning(_('The Europe country group cannot be found. '
'Please update the base module.'))
return eu_group
def _default_chart_id(self):
user = self.env.user
return self.env['account.account'].search(
[('company_id', '=', user.company_id.id), ('parent_id', '=', False)], limit=1)
def _default_fiscal_position_id(self):
user = self.env.user
eu_id = self._get_eu_res_country_group()
return self.env['account.fiscal.position'].search(
[('company_id', '=', user.company_id.id), ('vat_required', '=', True),
('country_group_id.id', '=', eu_id.id)], limit=1)
def _default_tax_id(self):
user = self.env.user
return self.env['account.tax'].search(
[('company_id', '=', user.company_id.id), ('type_tax_use', '=', 'sale'),
('type', '=', 'percent'), ('account_collected_id', '!=', False),
('tax_code_id', '!=', False)], limit=1, order='amount desc')
def _default_done_country_ids(self):
user = self.env.user
eu_country_group = self._get_eu_res_country_group()
return eu_country_group.country_ids - self._default_todo_country_ids() - user.company_id.country_id
def _default_todo_country_ids(self):
user = self.env.user
eu_country_group = self._get_eu_res_country_group()
eu_fiscal = self.env['account.fiscal.position'].search(
[('country_id', 'in', eu_country_group.country_ids.ids),
('vat_required', '=', False), ('auto_apply', '=', True),
('company_id', '=', user.company_id.id)])
return eu_country_group.country_ids - eu_fiscal.mapped('country_id') - user.company_id.country_id
chart_id = fields.Many2one(
"account.account", string="Chart of Accounts", required=True, default=_default_chart_id)
company_id = fields.Many2one(
'res.company', string='Company', required=True,
related='chart_id.company_id', readonly=True)
fiscal_position_id = fields.Many2one(
'account.fiscal.position', string='Fiscal Position', default=_default_fiscal_position_id,
help="Optional fiscal position to use as template for general account mapping. "
"Should usually be your current Intra-EU B2B fiscal position. "
"If not set, no general account mapping will be configured for EU fiscal positions.")
tax_id = fields.Many2one(
'account.tax', string='Service VAT', required=True, default=_default_tax_id,
help="Select your current VAT tax for services. This is the tax that will be mapped "
"to the corresponding VAT tax in each EU country selected below.")
account_collected_id = fields.Many2one(
"account.account", string="Tax Collection Account",
help="Optional account to use for collecting tax amounts when selling services in "
"each EU country selected below. If not set, the current collecting account of "
"your Service VAT will be used.")
done_country_ids = fields.Many2many(
'res.country', 'l10n_eu_service_country_rel_done', default=_default_done_country_ids,
string='Already Supported')
todo_country_ids = fields.Many2many(
'res.country', 'l10n_eu_service_country_rel_todo', default=_default_todo_country_ids,
string='EU Customers From', required=True)
@api.multi
def generate_eu_service(self):
imd = self.env['ir.model.data']
tax_code = self.env['account.tax.code']
tax_rate = self.env["l10n_eu_service.service_tax_rate"]
account_tax = self.env['account.tax']
fpos = self.env['account.fiscal.position']
chart_xid = 'l10n_eu_service.tax_chart_service_eu_company_%s' % self.company_id.name
chart = self.env.ref(chart_xid, raise_if_not_found=False)
if not chart:
vals = {
'name': _("EU MOSS VAT Chart - %(company)s") % {'company': self.company_id.name},
'company_id': self.company_id.id,
'parent_id': False
}
chart_id = tax_code.create(vals).id
vals_data = {
'name': 'tax_chart_service_eu_company_%s'%(self.company_id.name),
'model': 'account.tax.code',
'module': 'l10n_eu_service',
'res_id': chart_id,
'noupdate': True, # Don't drop it when module is updated
}
imd.create(vals_data)
else:
chart_id = chart.id
for country in self.todo_country_ids:
format_params = {'country_name': country.name}
tx_base_code_data = {
'name': _("Base - VAT for EU Services to %(country_name)s") % format_params,
'code': "BASE-EU-VAT-%s" % country.code,
'parent_id': chart_id,
}
tax_name = _("VAT for EU Services to %(country_name)s") % format_params
tx_code_data = {
'name': tax_name,
'code': "EU-VAT-%s" % country.code,
'parent_id': chart_id,
}
tx_base_code = tax_code.create(tx_base_code_data)
tx_code = tax_code.create(tx_code_data)
#create a new tax based on the selected service tax
data_tax = {
'name': tax_name,
'amount': tax_rate.search([('country_id', '=', country.id)]).rate,
'base_code_id': self.tax_id.base_code_id.id,
'account_collected_id': self.account_collected_id.id or self.tax_id.account_collected_id.id,
'account_paid_id': self.account_collected_id.id or self.tax_id.account_collected_id.id,
'type_tax_use': 'sale',
'base_code_id': tx_base_code.id,
'ref_base_code_id': tx_base_code.id,
'tax_code_id': tx_code.id,
'ref_tax_code_id': tx_code.id,
'ref_base_sign': -1,
'ref_tax_sign': -1,
'description': "EU-VAT-%s-S" % country.code
}
tax = account_tax.create(data_tax)
if self.fiscal_position_id:
account_ids = [(6, 0, self.fiscal_position_id.account_ids.ids)]
else:
account_ids = False
#create a fiscal position for the country
fiscal_pos_name = _("Intra-EU B2C in %(country_name)s") % {'country_name': country.name}
fiscal_pos_name += " (EU-VAT-%s)" % country.code
data_fiscal = {
'name': fiscal_pos_name,
'company_id': self.chart_id.company_id.id,
'vat_required': False,
'auto_apply': True,
'country_id': country.id,
'account_ids': account_ids,
'tax_ids': [(0, 0, {'tax_src_id': self.tax_id.id, 'tax_dest_id': tax.id})],
}
fpos.create(data_fiscal)
return {'type': 'ir.actions.act_window_close'}
+78
View File
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="eu_service_view" model="ir.ui.view">
<field name="name">l10n_eu_service.wizard.form</field>
<field name="model">l10n_eu_service.wizard</field>
<field name="arch" type="xml">
<form string='Setup EU MOSS Taxes'>
<p>
Based on the options selected below, this wizard will create one
fiscal position mapping for each EU country to which you are selling
services.
Each fiscal position will automatically map your national VAT tax for
services to the corresponding VAT tax in the country your customer
belongs to.
<br/>
You can use the wizard again later to add more countries.
</p>
<group>
<field name="chart_id" domain="[('parent_id','=', False)]"
context="{'company_id': company_id}" options="{'no_create': True}"/>
<field name="company_id" invisible="1"/>
<field name="fiscal_position_id" domain="[('company_id','=', company_id)]"
options="{'no_create': True}"
placeholder="Current EU B2C Fiscal Position, if any"/>
<field name="tax_id"
domain="[('type_tax_use', '=', 'sale'),
('type', '=', 'percent'),
('account_collected_id', '!=', False),
('tax_code_id', '!=', False),
('company_id','=', company_id)]"
options="{'no_create': True}"/>
<field name="account_collected_id"
placeholder="Keep empty to use current Service VAT account"/>
<field name="done_country_ids" widget="many2many_tags" readonly="1"
attrs="{'invisible': [('done_country_ids', '=', [(6, False, [])])]}"/>
<field name="todo_country_ids" widget="many2many_tags"
domain="[('country_group_ids', 'in', %(base.europe)d)]"/>
</group>
<footer>
<button string="Create Fiscal Positions and Taxes"
name="generate_eu_service" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_eu_service" model="ir.actions.act_window">
<field name="name">Setup EU MOSS Taxes</field>
<field name="res_model">l10n_eu_service.wizard</field>
<field name="view_mode">form</field>
<field name="view_id" ref="eu_service_view"/>
<field name="target">new</field>
</record>
<record id="open_l10n_eu_service" model="ir.actions.todo">
<field name="action_id" ref="action_eu_service"/>
<field name="type">automatic</field>
<field name="state">open</field>
</record>
<record id="view_account_config_settings_inherit" model="ir.ui.view">
<field name="name">account settings</field>
<field name="model">account.config.settings</field>
<field name="inherit_id" ref="account.view_account_config_settings"/>
<field name="arch" type="xml">
<field name="default_sale_tax" position="after">
<button type="action" name="%(action_eu_service)d"
string="Setup EU MOSS Taxes" class="oe_link"/>
</field>
</field>
</record>
</data>
</openerp>
+18 -18
View File
@@ -1332,7 +1332,7 @@ Cuentas del plan
<record id="cuenta1121001000" model="account.account.template">
<field name="code">1121001000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta1121"/>
<field name="type">receivable</field>
<field name="user_type" ref="account_type_receivable"/>
@@ -1341,7 +1341,7 @@ Cuentas del plan
<record id="cuenta1122001000" model="account.account.template">
<field name="code">1122001000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta1122"/>
<field name="type">receivable</field>
<field name="user_type" ref="account_type_receivable"/>
@@ -1359,7 +1359,7 @@ Cuentas del plan
<record id="cuenta1122004000" model="account.account.template">
<field name="code">1122004000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta1122"/>
<field name="type">receivable</field>
<field name="user_type" ref="account_type_receivable"/>
@@ -1368,7 +1368,7 @@ Cuentas del plan
<record id="cuenta1122005000" model="account.account.template">
<field name="code">1122005000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta1122"/>
<field name="type">receivable</field>
<field name="user_type" ref="account_type_receivable"/>
@@ -1395,7 +1395,7 @@ Cuentas del plan
<record id="cuenta1125001000" model="account.account.template">
<field name="code">1125001000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta1125"/>
<field name="type">receivable</field>
<field name="user_type" ref="account_type_receivable"/>
@@ -1413,7 +1413,7 @@ Cuentas del plan
<record id="cuenta1125003000" model="account.account.template">
<field name="code">1125003000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta1125"/>
<field name="type">receivable</field>
<field name="user_type" ref="account_type_receivable"/>
@@ -1467,7 +1467,7 @@ Cuentas del plan
<record id="cuenta1128001000" model="account.account.template">
<field name="code">1128001000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta1128"/>
<field name="type">receivable</field>
<field name="user_type" ref="account_type_receivable"/>
@@ -1530,7 +1530,7 @@ Cuentas del plan
<record id="cuenta1129009000" model="account.account.template">
<field name="code">1129009000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta1129"/>
<field name="type">receivable</field>
<field name="user_type" ref="account_type_receivable"/>
@@ -1881,7 +1881,7 @@ Cuentas del plan
<record id="cuenta1271005000" model="account.account.template">
<field name="code">1271005000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta1271"/>
<field name="type">receivable</field>
<field name="user_type" ref="account_type_receivable"/>
@@ -1944,7 +1944,7 @@ Cuentas del plan
<record id="cuenta2122001000" model="account.account.template">
<field name="code">2122001000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta2122"/>
<field name="type">payable</field>
<field name="user_type" ref="account_type_payable"/>
@@ -1971,7 +1971,7 @@ Cuentas del plan
<record id="cuenta2122005000" model="account.account.template">
<field name="code">2122005000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta2122"/>
<field name="type">payable</field>
<field name="user_type" ref="account_type_payable"/>
@@ -1980,7 +1980,7 @@ Cuentas del plan
<record id="cuenta2124001000" model="account.account.template">
<field name="code">2124001000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta2124"/>
<field name="type">payable</field>
<field name="user_type" ref="account_type_payable"/>
@@ -1989,7 +1989,7 @@ Cuentas del plan
<record id="cuenta2151001000" model="account.account.template">
<field name="code">2151001000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta2151"/>
<field name="type">payable</field>
<field name="user_type" ref="account_type_payable"/>
@@ -1998,7 +1998,7 @@ Cuentas del plan
<record id="cuenta2151002000" model="account.account.template">
<field name="code">2151002000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta2151"/>
<field name="type">receivable</field>
<field name="user_type" ref="account_type_receivable"/>
@@ -2151,7 +2151,7 @@ Cuentas del plan
<record id="cuenta2175008000" model="account.account.template">
<field name="code">2175008000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta2175"/>
<field name="type">payable</field>
<field name="user_type" ref="account_type_payable"/>
@@ -2205,7 +2205,7 @@ Cuentas del plan
<record id="cuenta2175014000" model="account.account.template">
<field name="code">2175014000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta2175"/>
<field name="type">payable</field>
<field name="user_type" ref="account_type_payable"/>
@@ -2223,7 +2223,7 @@ Cuentas del plan
<record id="cuenta2175017000" model="account.account.template">
<field name="code">2175017000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta2175"/>
<field name="type">payable</field>
<field name="user_type" ref="account_type_payable"/>
@@ -2232,7 +2232,7 @@ Cuentas del plan
<record id="cuenta2175018000" model="account.account.template">
<field name="code">2175018000</field>
<field name="reconcile" eval="False"/>
<field name="reconcile" eval="True"/>
<field name="parent_id" ref="cuenta2175"/>
<field name="type">payable</field>
<field name="user_type" ref="account_type_payable"/>
+9 -2
View File
@@ -379,8 +379,15 @@ class PaymentTransaction(osv.Model):
help='Reference of the customer in the acquirer database'),
}
_sql_constraints = [
('reference_uniq', 'UNIQUE(reference)', 'The payment transaction reference must be unique!'),
def _check_reference(self, cr, uid, ids, context=None):
transaction = self.browse(cr, uid, ids[0], context=context)
if transaction.state not in ['cancel', 'error']:
if self.search(cr, uid, [('reference', '=', transaction.reference), ('id', '!=', transaction.id)], context=context, count=True):
return False
return True
_constraints = [
(_check_reference, 'The payment transaction reference must be unique!', ['reference', 'state']),
]
_defaults = {
+5 -1
View File
@@ -663,7 +663,8 @@ class product_template(osv.osv):
for variant in all_variants:
for value_id in variant_id.value_ids:
temp_variants.append(variant + [int(value_id)])
all_variants = temp_variants
if temp_variants:
all_variants = temp_variants
# adding an attribute with only one value should not recreate product
# write this attribute on every product to make sure we don't lose them
@@ -1107,6 +1108,9 @@ class product_product(osv.osv):
if context is None:
context={}
if default is None:
default = {}
product = self.browse(cr, uid, id, context)
if context.get('variant'):
# if we copy a variant or create one, we keep the same template
+5
View File
@@ -1297,6 +1297,11 @@ class procurement_order(osv.osv):
def _get_product_supplier(self, cr, uid, procurement, context=None):
''' returns the main supplier of the procurement's product given as argument'''
supplierinfo = self.pool['product.supplierinfo']
company_supplier = supplierinfo.search(cr, uid,
[('product_tmpl_id', '=', procurement.product_id.product_tmpl_id.id), ('company_id', '=', procurement.company_id.id)], limit=1, context=context)
if company_supplier:
return supplierinfo.browse(cr, uid, company_supplier[0], context=context).name
return procurement.product_id.seller_id
def _get_po_line_values_from_procs(self, cr, uid, procurements, partner, schedule_date, context=None):
+1 -1
View File
@@ -1384,7 +1384,7 @@ instance.web.WebClient = instance.web.Client.extend({
},
update_logo: function() {
var company = this.session.company_id;
var img = this.session.url('/web/binary/company_logo' + (company ? '?company=' + company : ''));
var img = this.session.url('/web/binary/company_logo' + '?db=' + this.session.db + (company ? '&company=' + company : ''));
this.$('.oe_logo img').attr('src', '').attr('src', img);
this.$('.oe_logo_edit').toggleClass('oe_logo_edit_admin', this.session.uid === 1);
},
@@ -226,6 +226,7 @@
var item = false;
if (record) {
item = record.attributes;
this.dataset.select_id(record.get('id'));
} else {
record = this.make_empty_record(false);
this.records.add(record, {
@@ -869,6 +869,10 @@ div.media_iframe_video .css_editable_mode_display {
z-index: 2;
}
html[data-browser^="msie"] div.media_iframe_video iframe {
margin-left: 0;
}
/* Mobile view */
@media (max-width: 768px) {
img:not(.cke_iframe), .media_iframe_video, span.fa, i.fa {
@@ -685,6 +685,9 @@ div.media_iframe_video
display: none
z-index: 2
html[data-browser^="msie"] div.media_iframe_video iframe
margin-left: 0
/* Mobile view */
@media (max-width: 768px)
+12
View File
@@ -1,6 +1,18 @@
(function() {
"use strict";
/* --- Set the browser into the dom for css selectors --- */
var browser;
if ($.browser.webkit) browser = "webkit";
else if ($.browser.safari) browser = "safari";
else if ($.browser.opera) browser = "opera";
else if ($.browser.msie || ($.browser.mozilla && +$.browser.version.replace(/^([0-9]+\.[0-9]+).*/, '\$1') < 20)) browser = "msie";
else if ($.browser.mozilla) browser = "mozilla";
browser += ","+$.browser.version;
if (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase())) browser += ",mobile";
document.documentElement.setAttribute('data-browser', browser);
/* ---------------------------------------------------- */
var website = {};
openerp.website = website;
+13 -13
View File
@@ -47,7 +47,7 @@
</p>
</div>
<div class="col-md-6 mt16">
<img class="img img-responsive shadow mb16" src="/website/static/src/img/text_image.png"/>
<img class="img img-responsive shadow mb16" src="/website/static/src/img/text_image.png" alt="Odoo text and image block"/>
</div>
</div>
</div>
@@ -59,7 +59,7 @@
<div class="container">
<div class="row">
<div class="col-md-6 mt16">
<img class="img img-responsive shadow mb16" src="/website/static/src/img/image_text.jpg"/>
<img class="img img-responsive shadow mb16" src="/website/static/src/img/image_text.jpg" alt="Odoo image and text block"/>
</div>
<div class="col-md-6 mt16">
<h3>A Section Subtitle</h3>
@@ -84,7 +84,7 @@
<div class="col-md-12 mb16 mt16">
<div class="o_image_floating o_margin_l pull-right">
<div class="o_container">
<img class="img img-rounded img-responsive" src="/website/static/src/img/odoo.jpg"/>
<img class="img img-rounded img-responsive" src="/website/static/src/img/odoo.jpg" alt="Odoo Members"/>
<mark class="text-center"><a href="#"><strong>Click Here</strong></a></mark>
</div>
<div class="o_footer">
@@ -214,7 +214,7 @@
<h2>A Punchy Headline</h2>
</div>
<div class="col-md-12">
<img class="img img-responsive" src="/website/static/src/img/big_picture.png" style="margin: 0 auto;"/>
<img class="img img-responsive" src="/website/static/src/img/big_picture.png" style="margin: 0 auto;" alt="Odoo CMS - a big picture"/>
</div>
<div class="col-md-6 col-md-offset-3 mb16 mt16">
<p class="text-center">
@@ -239,7 +239,7 @@
<div class="container">
<div class="row">
<div class="col-md-4">
<img class="img img-rounded img-responsive" src="/website/static/src/img/desert_thumb.jpg"/>
<img class="img img-rounded img-responsive" src="/website/static/src/img/desert_thumb.jpg" alt="Odoo - Sample 1 for three columns"/>
<h4 class="mt16">Feature One</h4>
<p>
Adapt these three columns to fit you design need.
@@ -248,7 +248,7 @@
</p>
</div>
<div class="col-md-4">
<img class="img img-rounded img-responsive" src="/website/static/src/img/deers_thumb.jpg"/>
<img class="img img-rounded img-responsive" src="/website/static/src/img/deers_thumb.jpg" alt="Odoo - Sample 2 for three columns"/>
<h4 class="mt16">Feature Two</h4>
<p>
To add a fourth column, reduce the size of these
@@ -258,7 +258,7 @@
</p>
</div>
<div class="col-md-4">
<img class="img img-rounded img-responsive" src="/website/static/src/img/china_thumb.jpg"/>
<img class="img img-rounded img-responsive" src="/website/static/src/img/china_thumb.jpg" alt="Odoo - Sample 3 for three columns"/>
<h4 class="mt16">Feature Three</h4>
<p>
Delete the above image or replace it with a picture
@@ -307,7 +307,7 @@
<template id="s_image_floating" name="Image Floating">
<div class="o_image_floating o_margin_l pull-right s_image_floating">
<div class="o_container">
<img class="img img-rounded img-responsive" src="/website/static/src/img/library/ipad.png"/>
<img class="img img-rounded img-responsive" src="/website/static/src/img/library/ipad.png" alt="Odoo CMS- Sample image floating"/>
<mark class="text-center"><a href="#"><strong>Click Here</strong></a></mark>
</div>
<div class="o_footer">
@@ -538,19 +538,19 @@
</blockquote>
</div>
<div class="col-md-2 col-md-offset-1">
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail" alt="OpenERP logo"/>
</div>
<div class="col-md-2">
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail" alt="OpenERP logo"/>
</div>
<div class="col-md-2">
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail" alt="OpenERP logo"/>
</div>
<div class="col-md-2">
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail" alt="OpenERP logo"/>
</div>
<div class="col-md-2">
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail" alt="OpenERP logo"/>
</div>
</div>
</div>
+3 -3
View File
@@ -167,8 +167,8 @@
Create a <a href="http://www.odoo.com/page/website-builder">free website</a> with
<a class="label label-danger" href="http://www.odoo.com/page/website-builder">Odoo</a>
</div>
<div class="pull-left text-muted">
Copyright &amp;copy; <span t-field="res_company.name">Company name</span>
<div class="pull-left text-muted" itemscope="itemscope" itemtype="http://schema.org/Organization">
Copyright &amp;copy; <span t-field="res_company.name" itemprop="name">Company name</span>
</div>
</div>
</xpath>
@@ -363,7 +363,7 @@
<a t-att-href="website.social_twitter" t-if="website.social_twitter"><i class="fa fa-twitter"/></a>
<a t-att-href="website.social_linkedin" t-if="website.social_linkedin"><i class="fa fa-linkedin"/></a>
<a t-att-href="website.social_youtube" t-if="website.social_youtube"><i class="fa fa-youtube-play"/></a>
<a t-att-href="website.social_googleplus" t-if="website.social_googleplus"><i class="fa fa-google-plus-square"/></a>
<a t-att-href="website.social_googleplus" t-if="website.social_googleplus" rel="publisher"><i class="fa fa-google-plus-square"/></a>
<a t-att-href="website.social_github" t-if="website.social_github"><i class="fa fa-github"/></a>
</h2>
</div>
@@ -42,7 +42,7 @@ class sale_order(osv.Model):
values['event_id'] = ticket.event_id.id
values['event_ticket_id'] = ticket.id
values['price_unit'] = ticket.price
values['name'] = "%s: %s" % (ticket.event_id.name, ticket.name)
values['name'] = "%s\n%s" % (ticket.event_id.display_name, ticket.name)
return values
+2
View File
@@ -142,6 +142,8 @@ class sale_quote(http.Controller):
order = request.registry.get('sale.order').browse(request.cr, SUPERUSER_ID, order_id)
if token != order.access_token:
return request.website.render('website.404')
if order.state not in ['draft', 'sent']:
return request.website.render('website.http_error', {'status_code': 'Forbidden', 'status_message': _('You cannot add options to a confirmed order.')})
option_obj = request.registry.get('sale.order.option')
option = option_obj.browse(request.cr, SUPERUSER_ID, option_id)
@@ -457,7 +457,7 @@
</div>
</strong>
</td>
<td class="pull-right">
<td class="pull-right" t-if="quotation.state in ['draft', 'sent']">
<a t-attf-href="/quote/add_line/#{ option.id }/#{ quotation.id }/#{ quotation.access_token }" class="mb8 hidden-print">
<span class="fa fa-shopping-cart"/>
</a>
+1 -1
View File
@@ -63,7 +63,7 @@ class sale_order(osv.Model):
values['name'] = line.name
else:
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
values['name'] = product.description_sale or product.name
values['name'] = "%s\n%s" % (product.display_name, product.description_sale)
values['product_id'] = product_id
values['order_id'] = order_id
@@ -193,6 +193,79 @@
height: 570px; }
.col-md-9 .oe-height-8 {
height: 760px; } }
/* ---- Cart ---- */
.oe_cart a span.visible-xs-inline, #modal_optional_products a span.visible-xs-inline {
display: none !important;
}
.oe_cart a span.hidden-xs, #modal_optional_products a span.hidden-xs {
display: inline !important;
}
.oe_cart table td:first-child {
min-width: 76px;
}
@media (max-width: 992px) {
#modal_optional_products table:last-child td:last-child {
overflow: hidden;
}
#modal_optional_products table:last-child td:nth-child(2) {
width: 50%;
}
#modal_optional_products table:last-child td:nth-child(3) {
width: 20%;
}
#modal_optional_products table:last-child td:last-child, #modal_optional_products table:last-child td:nth-last-child(2) {
width: 15%;
}
}
@media (max-width: 500px) {
.oe_website_sale .oe_website_spinner {
margin: auto;
}
.oe_cart table, #modal_optional_products table {
display: inline-block;
}
.oe_cart table thead, #modal_optional_products table thead {
display: none;
}
.oe_cart table tbody, .oe_cart table tr, .oe_cart table td, #modal_optional_products table tbody, #modal_optional_products table tr, #modal_optional_products table td {
float: left;
width: 100% !important;
display: inline-block;
}
.oe_cart table td:first-child, #modal_optional_products table td:first-child {
display: none;
}
.oe_cart table td:nth-child(2), #modal_optional_products table td:nth-child(2) {
border-top-style: double;
border-top-width: 6px;
}
.oe_cart table td:last-child, #modal_optional_products table td:last-child {
margin-bottom: 16px;
}
.oe_cart table:last-child tr:last-child td:last-child, #modal_optional_products table:last-child tr:last-child td:last-child {
margin-bottom: 0;
}
.oe_cart table td:last-child, .oe_cart table td:nth-last-child(2), #modal_optional_products table td:last-child, #modal_optional_products table td:nth-last-child(2) {
width: 50% !important;
height: 46px;
text-align: center;
}
#modal_optional_products table:last-child td:nth-child(3) > div {
float: none !important;
}
.oe_cart a span.hidden-xs, #modal_optional_products a span.hidden-xs {
display: none !important;
}
.oe_cart a span.visible-xs-inline, #modal_optional_products a span.visible-xs-inline {
display: inline !important;
}
}
/* ---- Product list style ---- */
@media (min-width: 400px) {
.oe_product.oe_list {
@@ -218,6 +218,58 @@
.oe-height-8
height: 760px
/* ---- Cart ---- */
.oe_cart, #modal_optional_products
a span.visible-xs-inline
display: none !important
.oe_cart, #modal_optional_products
a span.hidden-xs
display: inline !important
.oe_cart table td:first-child
min-width: 76px
@media (max-width: 992px)
#modal_optional_products table:last-child
td:last-child
overflow: hidden
td:nth-child(2)
width: 50%
td:nth-child(3)
width: 20%
td:last-child, td:nth-last-child(2)
width: 15%
@media (max-width: 500px)
.oe_website_sale .oe_website_spinner
margin: auto
.oe_cart table, #modal_optional_products table
display: inline-block
thead
display: none
tbody, tr, td
float: left
width: 100% !important
display: inline-block
td:first-child
display: none
td:nth-child(2)
border-top-style: double
border-top-width: 6px
td:last-child
margin-bottom: 16px
&:last-child tr:last-child td:last-child
margin-bottom: 0
td:last-child, td:nth-last-child(2)
width: 50% !important
height: 46px
text-align: center
#modal_optional_products table:last-child td:nth-child(3) > div
float: none !important
.oe_cart a, #modal_optional_products a
span.hidden-xs
display: none !important
span.visible-xs-inline
display: inline !important
/* ---- Product list style ---- */
@media (min-width: 400px)
+4 -4
View File
@@ -70,7 +70,7 @@
<!-- Product item used by /shop and /shop/cart -->
<template id="products_item" name="Product item">
<form action="/shop/cart/update" method="post" style="display: inline-block;">
<form action="/shop/cart/update" method="post">
<div itemscope="itemscope" itemtype="http://schema.org/Product">
<div class="ribbon-wrapper">
<div class="ribbon btn btn-danger">Sale</div>
@@ -616,7 +616,7 @@
<strong t-esc="line.product_id.with_context(display_default_code=False).display_name"/>
</a>
</div>
<div class="text-muted" t-field="line.name"/>
<div class="text-muted" t-esc="'\n'.join(line.name.splitlines()[1:])"/>
</td>
<td class="text-center" name="price">
<t t-if="(compute_currency(line.product_id.lst_price) - line.price_unit) &gt; 1">
@@ -653,7 +653,7 @@
<t t-call="website_sale.total"/>
<div class="clearfix"/>
<a t-if="not optional_products and website_sale_order and website_sale_order.website_order_line" class="btn btn-primary pull-right mb32" href="/shop/checkout">Process Checkout <span class="fa fa-long-arrow-right"/></a>
<a t-if="not optional_products and website_sale_order and website_sale_order.website_order_line" class="btn btn-primary pull-right mb32" href="/shop/checkout"><span class="hidden-xs">Process Checkout</span><span class="visible-xs-inline">Checkout</span> <span class="fa fa-long-arrow-right"/></a>
<div class="oe_structure"/>
</div>
@@ -736,7 +736,7 @@
<template id="continue_shopping" inherit_id="website_sale.cart" customize_show="True" name="Continue Shopping Button">
<xpath expr="//a[@href='/shop/checkout']" position="before">
<a href="/shop" class="btn btn-default mb32"><span class="fa fa-long-arrow-left"/> Continue Shopping</a>
<a href="/shop" class="btn btn-default mb32"><span class="fa fa-long-arrow-left"/> <span class="hidden-xs">Continue Shopping</span><span class="visible-xs-inline">Continue</span></a>
</xpath>
</template>
@@ -61,7 +61,7 @@
"widget": "monetary",
"from_currency": "website.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/><br/>
}'/>
<span class="oe_price" style="white-space: nowrap;"
t-field="product.price"
t-field-options='{
@@ -134,8 +134,8 @@
</table>
</div>
<div class="modal-footer">
<a class="btn btn-default a-submit js_goto_shop"><i class="fa fa-chevron-left"></i> Continue shopping</a>
<a class="btn btn-primary pull-right a-submit"><i class="fa fa-shopping-cart fa-fw"></i> Proceed to checkout</a>
<a class="btn btn-default a-submit js_goto_shop"><i class="fa fa-chevron-left"></i> <span class="hidden-xs">Continue Shopping</span><span class="visible-xs-inline">Continue</span></a>
<a class="btn btn-primary pull-right a-submit"><i class="fa fa-shopping-cart fa-fw"></i> <span class="hidden-xs">Proceed to checkout</span><span class="visible-xs-inline">Checkout</span></a>
</div>
</div>
</div>
+7 -2
View File
@@ -68,8 +68,13 @@ def visit_table(self, node):
self._table_row_index = 0
self.context.append(self.compact_p)
self.compact_p = True
classes = ' '.join({'table', self.settings.table_style}).strip()
self.body.append(self.starttag(node, 'table', CLASS=classes))
classes = {self.settings.table_style}
node_classes = node.get('classes', [])
if 'no-table' in node_classes: node_classes.remove('no-table')
else: classes.add('table')
self.body.append(self.starttag(node, 'table', CLASS=' '.join(classes).strip()))
def starttag_data(self, node, tagname, suffix='\n', empty=False, **attributes):
attributes.update(
+177 -168
View File
@@ -6811,49 +6811,58 @@ td.field-body > ul {
margin-bottom: 2em;
}
@media (min-width: 992px) {
.stripe .section > *,
.stripe .section > .force-left {
.stripe .section:not(.force-right) > *,
.stripe .section:not(.force-right) > .force-left {
width: 49%;
float: left;
clear: left;
}
.stripe .section > .force-right {
.stripe .section:not(.force-right) > .force-right {
padding-left: 1em;
padding-right: 1em;
}
.stripe .section > .force-right,
.stripe .section > [class*=highlight] {
.stripe .section:not(.force-right) > .force-right,
.stripe .section:not(.force-right) > [class*=highlight] {
float: none;
clear: none;
margin-left: 50%;
width: 50%;
color: #eeeeee;
}
.stripe .section > .force-right a,
.stripe .section > [class*=highlight] a {
.stripe .section:not(.force-right) > .force-right legend,
.stripe .section:not(.force-right) > [class*=highlight] legend {
color: inherit;
}
.stripe .section:not(.force-right) > .force-right input,
.stripe .section:not(.force-right) > [class*=highlight] input {
color: inherit;
background-color: #555555;
}
.stripe .section:not(.force-right) > .force-right a,
.stripe .section:not(.force-right) > [class*=highlight] a {
color: #d9a8cc;
}
.stripe .section > .force-right code,
.stripe .section > [class*=highlight] code,
.stripe .section > .force-right .literal,
.stripe .section > [class*=highlight] .literal {
.stripe .section:not(.force-right) > .force-right code,
.stripe .section:not(.force-right) > [class*=highlight] code,
.stripe .section:not(.force-right) > .force-right .literal,
.stripe .section:not(.force-right) > [class*=highlight] .literal {
color: #f9f2f4;
background-color: #555555;
}
.stripe .section > .force-right:not(.highlight-json) .highlight,
.stripe .section > [class*=highlight]:not(.highlight-json) .highlight {
.stripe .section:not(.force-right) > .force-right:not(.highlight-json) .highlight,
.stripe .section:not(.force-right) > [class*=highlight]:not(.highlight-json) .highlight {
border-bottom-color: #777777;
}
.stripe .section > .force-right.admonition,
.stripe .section > [class*=highlight].admonition {
.stripe .section:not(.force-right) > .force-right.admonition,
.stripe .section:not(.force-right) > [class*=highlight].admonition {
margin-left: 51%;
width: 49%;
border-top-color: #777777;
border-bottom-color: #777777;
border-right-color: #777777;
}
.stripe .section > .force-right .highlight,
.stripe .section > [class*=highlight] .highlight {
.stripe .section:not(.force-right) > .force-right .highlight,
.stripe .section:not(.force-right) > [class*=highlight] .highlight {
border-color: #555555;
border-style: solid;
border-width: 1px 0;
@@ -6930,302 +6939,302 @@ td.field-body > ul {
/* Name.Variable.Instance */
/* Literal.Number.Integer.Long */
}
.stripe .section > .force-right .highlight .lineno,
.stripe .section > [class*=highlight] .highlight .lineno {
.stripe .section:not(.force-right) > .force-right .highlight .lineno,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .lineno {
color: #586e75;
}
.stripe .section > .force-right .highlight .c,
.stripe .section > [class*=highlight] .highlight .c {
.stripe .section:not(.force-right) > .force-right .highlight .c,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .c {
color: #586e75;
}
.stripe .section > .force-right .highlight .err,
.stripe .section > [class*=highlight] .highlight .err {
.stripe .section:not(.force-right) > .force-right .highlight .err,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .err {
color: #cccccc;
}
.stripe .section > .force-right .highlight .g,
.stripe .section > [class*=highlight] .highlight .g {
.stripe .section:not(.force-right) > .force-right .highlight .g,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .g {
color: #cccccc;
}
.stripe .section > .force-right .highlight .k,
.stripe .section > [class*=highlight] .highlight .k {
.stripe .section:not(.force-right) > .force-right .highlight .k,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .k {
color: #859900;
}
.stripe .section > .force-right .highlight .l,
.stripe .section > [class*=highlight] .highlight .l {
.stripe .section:not(.force-right) > .force-right .highlight .l,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .l {
color: #cccccc;
}
.stripe .section > .force-right .highlight .n,
.stripe .section > [class*=highlight] .highlight .n {
.stripe .section:not(.force-right) > .force-right .highlight .n,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .n {
color: #cccccc;
}
.stripe .section > .force-right .highlight .o,
.stripe .section > [class*=highlight] .highlight .o {
.stripe .section:not(.force-right) > .force-right .highlight .o,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .o {
color: #859900;
}
.stripe .section > .force-right .highlight .x,
.stripe .section > [class*=highlight] .highlight .x {
.stripe .section:not(.force-right) > .force-right .highlight .x,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .x {
color: #cb4b16;
}
.stripe .section > .force-right .highlight .p,
.stripe .section > [class*=highlight] .highlight .p {
.stripe .section:not(.force-right) > .force-right .highlight .p,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .p {
color: #cccccc;
}
.stripe .section > .force-right .highlight .cm,
.stripe .section > [class*=highlight] .highlight .cm {
.stripe .section:not(.force-right) > .force-right .highlight .cm,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .cm {
color: #586e75;
}
.stripe .section > .force-right .highlight .cp,
.stripe .section > [class*=highlight] .highlight .cp {
.stripe .section:not(.force-right) > .force-right .highlight .cp,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .cp {
color: #859900;
}
.stripe .section > .force-right .highlight .c1,
.stripe .section > [class*=highlight] .highlight .c1 {
.stripe .section:not(.force-right) > .force-right .highlight .c1,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .c1 {
color: #586e75;
}
.stripe .section > .force-right .highlight .cs,
.stripe .section > [class*=highlight] .highlight .cs {
.stripe .section:not(.force-right) > .force-right .highlight .cs,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .cs {
color: #859900;
}
.stripe .section > .force-right .highlight .gd,
.stripe .section > [class*=highlight] .highlight .gd {
.stripe .section:not(.force-right) > .force-right .highlight .gd,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .gd {
color: #2aa198;
}
.stripe .section > .force-right .highlight .ge,
.stripe .section > [class*=highlight] .highlight .ge {
.stripe .section:not(.force-right) > .force-right .highlight .ge,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .ge {
color: #cccccc;
font-style: italic;
}
.stripe .section > .force-right .highlight .gr,
.stripe .section > [class*=highlight] .highlight .gr {
.stripe .section:not(.force-right) > .force-right .highlight .gr,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .gr {
color: #dc322f;
}
.stripe .section > .force-right .highlight .gh,
.stripe .section > [class*=highlight] .highlight .gh {
.stripe .section:not(.force-right) > .force-right .highlight .gh,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .gh {
color: #cb4b16;
}
.stripe .section > .force-right .highlight .gi,
.stripe .section > [class*=highlight] .highlight .gi {
.stripe .section:not(.force-right) > .force-right .highlight .gi,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .gi {
color: #859900;
}
.stripe .section > .force-right .highlight .go,
.stripe .section > [class*=highlight] .highlight .go {
.stripe .section:not(.force-right) > .force-right .highlight .go,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .go {
color: #cccccc;
}
.stripe .section > .force-right .highlight .gp,
.stripe .section > [class*=highlight] .highlight .gp {
.stripe .section:not(.force-right) > .force-right .highlight .gp,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .gp {
color: #cccccc;
}
.stripe .section > .force-right .highlight .gs,
.stripe .section > [class*=highlight] .highlight .gs {
.stripe .section:not(.force-right) > .force-right .highlight .gs,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .gs {
color: #cccccc;
font-weight: bold;
}
.stripe .section > .force-right .highlight .gu,
.stripe .section > [class*=highlight] .highlight .gu {
.stripe .section:not(.force-right) > .force-right .highlight .gu,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .gu {
color: #cb4b16;
}
.stripe .section > .force-right .highlight .gt,
.stripe .section > [class*=highlight] .highlight .gt {
.stripe .section:not(.force-right) > .force-right .highlight .gt,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .gt {
color: #cccccc;
}
.stripe .section > .force-right .highlight .kc,
.stripe .section > [class*=highlight] .highlight .kc {
.stripe .section:not(.force-right) > .force-right .highlight .kc,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .kc {
color: #cb4b16;
}
.stripe .section > .force-right .highlight .kd,
.stripe .section > [class*=highlight] .highlight .kd {
.stripe .section:not(.force-right) > .force-right .highlight .kd,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .kd {
color: #268bd2;
}
.stripe .section > .force-right .highlight .kn,
.stripe .section > [class*=highlight] .highlight .kn {
.stripe .section:not(.force-right) > .force-right .highlight .kn,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .kn {
color: #859900;
}
.stripe .section > .force-right .highlight .kp,
.stripe .section > [class*=highlight] .highlight .kp {
.stripe .section:not(.force-right) > .force-right .highlight .kp,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .kp {
color: #859900;
}
.stripe .section > .force-right .highlight .kr,
.stripe .section > [class*=highlight] .highlight .kr {
.stripe .section:not(.force-right) > .force-right .highlight .kr,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .kr {
color: #268bd2;
}
.stripe .section > .force-right .highlight .kt,
.stripe .section > [class*=highlight] .highlight .kt {
.stripe .section:not(.force-right) > .force-right .highlight .kt,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .kt {
color: #dc322f;
}
.stripe .section > .force-right .highlight .ld,
.stripe .section > [class*=highlight] .highlight .ld {
.stripe .section:not(.force-right) > .force-right .highlight .ld,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .ld {
color: #cccccc;
}
.stripe .section > .force-right .highlight .m,
.stripe .section > [class*=highlight] .highlight .m {
.stripe .section:not(.force-right) > .force-right .highlight .m,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .m {
color: #2aa198;
}
.stripe .section > .force-right .highlight .s,
.stripe .section > [class*=highlight] .highlight .s {
.stripe .section:not(.force-right) > .force-right .highlight .s,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .s {
color: #2aa198;
}
.stripe .section > .force-right .highlight .na,
.stripe .section > [class*=highlight] .highlight .na {
.stripe .section:not(.force-right) > .force-right .highlight .na,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .na {
color: #cccccc;
}
.stripe .section > .force-right .highlight .nb,
.stripe .section > [class*=highlight] .highlight .nb {
.stripe .section:not(.force-right) > .force-right .highlight .nb,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .nb {
color: #b58900;
}
.stripe .section > .force-right .highlight .nc,
.stripe .section > [class*=highlight] .highlight .nc {
.stripe .section:not(.force-right) > .force-right .highlight .nc,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .nc {
color: #268bd2;
}
.stripe .section > .force-right .highlight .no,
.stripe .section > [class*=highlight] .highlight .no {
.stripe .section:not(.force-right) > .force-right .highlight .no,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .no {
color: #cb4b16;
}
.stripe .section > .force-right .highlight .nd,
.stripe .section > [class*=highlight] .highlight .nd {
.stripe .section:not(.force-right) > .force-right .highlight .nd,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .nd {
color: #268bd2;
}
.stripe .section > .force-right .highlight .ni,
.stripe .section > [class*=highlight] .highlight .ni {
.stripe .section:not(.force-right) > .force-right .highlight .ni,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .ni {
color: #cb4b16;
}
.stripe .section > .force-right .highlight .ne,
.stripe .section > [class*=highlight] .highlight .ne {
.stripe .section:not(.force-right) > .force-right .highlight .ne,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .ne {
color: #cb4b16;
}
.stripe .section > .force-right .highlight .nf,
.stripe .section > [class*=highlight] .highlight .nf {
.stripe .section:not(.force-right) > .force-right .highlight .nf,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .nf {
color: #268bd2;
}
.stripe .section > .force-right .highlight .nl,
.stripe .section > [class*=highlight] .highlight .nl {
.stripe .section:not(.force-right) > .force-right .highlight .nl,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .nl {
color: #cccccc;
}
.stripe .section > .force-right .highlight .nn,
.stripe .section > [class*=highlight] .highlight .nn {
.stripe .section:not(.force-right) > .force-right .highlight .nn,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .nn {
color: #cccccc;
}
.stripe .section > .force-right .highlight .nx,
.stripe .section > [class*=highlight] .highlight .nx {
.stripe .section:not(.force-right) > .force-right .highlight .nx,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .nx {
color: #cccccc;
}
.stripe .section > .force-right .highlight .py,
.stripe .section > [class*=highlight] .highlight .py {
.stripe .section:not(.force-right) > .force-right .highlight .py,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .py {
color: #cccccc;
}
.stripe .section > .force-right .highlight .nt,
.stripe .section > [class*=highlight] .highlight .nt {
.stripe .section:not(.force-right) > .force-right .highlight .nt,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .nt {
color: #268bd2;
}
.stripe .section > .force-right .highlight .nv,
.stripe .section > [class*=highlight] .highlight .nv {
.stripe .section:not(.force-right) > .force-right .highlight .nv,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .nv {
color: #268bd2;
}
.stripe .section > .force-right .highlight .ow,
.stripe .section > [class*=highlight] .highlight .ow {
.stripe .section:not(.force-right) > .force-right .highlight .ow,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .ow {
color: #859900;
}
.stripe .section > .force-right .highlight .w,
.stripe .section > [class*=highlight] .highlight .w {
.stripe .section:not(.force-right) > .force-right .highlight .w,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .w {
color: #cccccc;
}
.stripe .section > .force-right .highlight .mf,
.stripe .section > [class*=highlight] .highlight .mf {
.stripe .section:not(.force-right) > .force-right .highlight .mf,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .mf {
color: #2aa198;
}
.stripe .section > .force-right .highlight .mh,
.stripe .section > [class*=highlight] .highlight .mh {
.stripe .section:not(.force-right) > .force-right .highlight .mh,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .mh {
color: #2aa198;
}
.stripe .section > .force-right .highlight .mi,
.stripe .section > [class*=highlight] .highlight .mi {
.stripe .section:not(.force-right) > .force-right .highlight .mi,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .mi {
color: #2aa198;
}
.stripe .section > .force-right .highlight .mo,
.stripe .section > [class*=highlight] .highlight .mo {
.stripe .section:not(.force-right) > .force-right .highlight .mo,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .mo {
color: #2aa198;
}
.stripe .section > .force-right .highlight .sb,
.stripe .section > [class*=highlight] .highlight .sb {
.stripe .section:not(.force-right) > .force-right .highlight .sb,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .sb {
color: #586e75;
}
.stripe .section > .force-right .highlight .sc,
.stripe .section > [class*=highlight] .highlight .sc {
.stripe .section:not(.force-right) > .force-right .highlight .sc,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .sc {
color: #2aa198;
}
.stripe .section > .force-right .highlight .sd,
.stripe .section > [class*=highlight] .highlight .sd {
.stripe .section:not(.force-right) > .force-right .highlight .sd,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .sd {
color: #cccccc;
}
.stripe .section > .force-right .highlight .s2,
.stripe .section > [class*=highlight] .highlight .s2 {
.stripe .section:not(.force-right) > .force-right .highlight .s2,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .s2 {
color: #2aa198;
}
.stripe .section > .force-right .highlight .se,
.stripe .section > [class*=highlight] .highlight .se {
.stripe .section:not(.force-right) > .force-right .highlight .se,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .se {
color: #cb4b16;
}
.stripe .section > .force-right .highlight .sh,
.stripe .section > [class*=highlight] .highlight .sh {
.stripe .section:not(.force-right) > .force-right .highlight .sh,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .sh {
color: #cccccc;
}
.stripe .section > .force-right .highlight .si,
.stripe .section > [class*=highlight] .highlight .si {
.stripe .section:not(.force-right) > .force-right .highlight .si,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .si {
color: #2aa198;
}
.stripe .section > .force-right .highlight .sx,
.stripe .section > [class*=highlight] .highlight .sx {
.stripe .section:not(.force-right) > .force-right .highlight .sx,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .sx {
color: #2aa198;
}
.stripe .section > .force-right .highlight .sr,
.stripe .section > [class*=highlight] .highlight .sr {
.stripe .section:not(.force-right) > .force-right .highlight .sr,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .sr {
color: #dc322f;
}
.stripe .section > .force-right .highlight .s1,
.stripe .section > [class*=highlight] .highlight .s1 {
.stripe .section:not(.force-right) > .force-right .highlight .s1,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .s1 {
color: #2aa198;
}
.stripe .section > .force-right .highlight .ss,
.stripe .section > [class*=highlight] .highlight .ss {
.stripe .section:not(.force-right) > .force-right .highlight .ss,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .ss {
color: #2aa198;
}
.stripe .section > .force-right .highlight .bp,
.stripe .section > [class*=highlight] .highlight .bp {
.stripe .section:not(.force-right) > .force-right .highlight .bp,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .bp {
color: #268bd2;
}
.stripe .section > .force-right .highlight .vc,
.stripe .section > [class*=highlight] .highlight .vc {
.stripe .section:not(.force-right) > .force-right .highlight .vc,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .vc {
color: #268bd2;
}
.stripe .section > .force-right .highlight .vg,
.stripe .section > [class*=highlight] .highlight .vg {
.stripe .section:not(.force-right) > .force-right .highlight .vg,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .vg {
color: #268bd2;
}
.stripe .section > .force-right .highlight .vi,
.stripe .section > [class*=highlight] .highlight .vi {
.stripe .section:not(.force-right) > .force-right .highlight .vi,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .vi {
color: #268bd2;
}
.stripe .section > .force-right .highlight .il,
.stripe .section > [class*=highlight] .highlight .il {
.stripe .section:not(.force-right) > .force-right .highlight .il,
.stripe .section:not(.force-right) > [class*=highlight] .highlight .il {
color: #2aa198;
}
.stripe .body > .section > .section {
border-top: 1px solid #eeeeee;
}
.stripe .section > h1,
.stripe .section > h2,
.stripe .section > h3,
.stripe .section > h4,
.stripe .section > h5,
.stripe .section > h6 {
.stripe .section:not(.force-right) > h1,
.stripe .section:not(.force-right) > h2,
.stripe .section:not(.force-right) > h3,
.stripe .section:not(.force-right) > h4,
.stripe .section:not(.force-right) > h5,
.stripe .section:not(.force-right) > h6 {
max-width: 50%;
}
.stripe .section > h1,
.stripe .section > h2,
.stripe .section > h3,
.stripe .section > h4,
.stripe .section > h5,
.stripe .section > h6,
.stripe .section > .section {
.stripe .section:not(.force-right) > h1,
.stripe .section:not(.force-right) > h2,
.stripe .section:not(.force-right) > h3,
.stripe .section:not(.force-right) > h4,
.stripe .section:not(.force-right) > h5,
.stripe .section:not(.force-right) > h6,
.stripe .section > .section:not(.force-right) {
position: relative;
width: auto;
float: none;
+19 -9
View File
@@ -610,24 +610,31 @@ td.field-body {
// === columning only on medium+ ===
@media (min-width: @screen-md-min) {
// column 1
.section > *,
.section > .force-left {
.section:not(.force-right) > *,
.section:not(.force-right) > .force-left {
width: 49%;
float: left;
clear: left;
}
// column 2
.section > .force-right {
.section:not(.force-right) > .force-right {
padding-left: 1em;
padding-right: 1em;
}
.section > .force-right,
.section > [class*=highlight] {
.section:not(.force-right) > .force-right,
.section:not(.force-right) > [class*=highlight] {
float: none;
clear: none;
margin-left: 50%;
width: 50%;
color: @color-right;
legend {
color: inherit;
}
input {
color: inherit;
background-color: @gray;
}
a {
color: lighten(@link-color, 30%);
}
@@ -728,13 +735,16 @@ td.field-body {
.body > .section > .section {
border-top: 1px solid @color-right;
}
.section > h1, .section > h2, .section > h3, .section > h4, .section > h5,
.section > h6 {
.section:not(.force-right) > h1, .section:not(.force-right) > h2,
.section:not(.force-right) > h3, .section:not(.force-right) > h4,
.section:not(.force-right) > h5, .section:not(.force-right) > h6 {
max-width: 50%;
}
.section > h1, .section > h2, .section > h3, .section > h4, .section > h5,
.section > h6, .section > .section {
.section:not(.force-right) > h1, .section:not(.force-right) > h2,
.section:not(.force-right) > h3, .section:not(.force-right) > h4,
.section:not(.force-right) > h5, .section:not(.force-right) > h6,
.section > .section:not(.force-right) {
position: relative;
width: auto;
float: none;
+16
View File
@@ -0,0 +1,16 @@
Belgium, 2015-02-13
Apertoso 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,
Jos De Graeve Jos.DeGraeve@apertoso.be https://github.com/apertoso
List of contributors:
Jos De Graeve Jos.DeGraeve@apertoso.be https://github.com/JosDeGraeve
Maarten De Wispelaere Maarten.DeWispelaere https://github.com/bitprocessor
+11
View File
@@ -0,0 +1,11 @@
Lisbon, 2015-02-16
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,
Daniel Reis dgreis@sapo.pt https://github.com/dreispt
+4
View File
@@ -68,6 +68,10 @@ class format_address(object):
doc = etree.fromstring(arch)
for node in doc.xpath("//div[@class='address_format']"):
tree = etree.fromstring(v % {'city': _('City'), 'zip': _('ZIP'), 'state': _('State')})
for child in node.xpath("//field"):
if child.attrib.get('modifiers'):
for field in tree.xpath("//field[@name='%s']" % child.attrib.get('name')):
field.attrib['modifiers'] = child.attrib.get('modifiers')
node.getparent().replace(node, tree)
arch = etree.tostring(doc)
break
+14
View File
@@ -890,6 +890,19 @@ class Environment(object):
if invalids:
raise UserError('Invalid cache for fields\n' + pformat(invalids))
@property
def recompute(self):
return self.all.recompute
@contextmanager
def norecompute(self):
tmp = self.all.recompute
self.all.recompute = False
try:
yield
finally:
self.all.recompute = tmp
class Environments(object):
""" A common object for all environments in a request. """
@@ -897,6 +910,7 @@ class Environments(object):
self.envs = WeakSet() # weak set of environments
self.todo = {} # recomputations {field: [records]}
self.mode = False # flag for draft/onchange
self.recompute = True
def add(self, env):
""" Add the environment `env`. """
+6 -5
View File
@@ -3979,7 +3979,7 @@ class BaseModel(object):
self.pool[model_name]._store_set_values(cr, user, todo, fields_to_recompute, context)
# recompute new-style fields
if context.get('recompute', True):
if recs.env.recompute and context.get('recompute', True):
recs.recompute()
self.step_workflow(cr, user, ids, context=context)
@@ -4226,7 +4226,7 @@ class BaseModel(object):
# check Python constraints
recs._validate_fields(vals)
if context.get('recompute', True):
if recs.env.recompute and context.get('recompute', True):
result += self._store_get_values(cr, user, [id_new],
list(set(vals.keys() + self._inherits.values())),
context)
@@ -4239,7 +4239,7 @@ class BaseModel(object):
# recompute new-style fields
recs.recompute()
if self._log_create and context.get('recompute', True):
if self._log_create and recs.env.recompute and context.get('recompute', True):
message = self._description + \
" '" + \
self.name_get(cr, user, [id_new], context=context)[0][1] + \
@@ -5635,12 +5635,13 @@ class BaseModel(object):
field, recs = self.env.get_todo()
# evaluate the fields to recompute, and save them to database
names = [f.name for f in field.computed_fields if f.store]
for rec, rec1 in zip(recs, recs.with_context(recompute=False)):
for rec in recs:
try:
values = rec._convert_to_write({
name: rec[name] for name in names
})
rec1._write(values)
with rec.env.norecompute():
rec._write(values)
except MissingError:
pass
# mark the computed fields as done
+48 -47
View File
@@ -736,57 +736,58 @@ class one2many(_column):
result = []
context = dict(context or {})
context.update(self._context)
context['recompute'] = False # recomputation is done by outer create/write
if not values:
return
obj = obj.pool[self._obj]
_table = obj._table
for act in values:
if act[0] == 0:
act[2][self._fields_id] = id
id_new = obj.create(cr, user, act[2], context=context)
result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
elif act[0] == 1:
obj.write(cr, user, [act[1]], act[2], context=context)
elif act[0] == 2:
obj.unlink(cr, user, [act[1]], context=context)
elif act[0] == 3:
inverse_field = obj._fields.get(self._fields_id)
assert inverse_field, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
# if the model has on delete cascade, just delete the row
if inverse_field.ondelete == "cascade":
rec = obj.browse(cr, user, [], context=context)
with rec.env.norecompute():
_table = obj._table
for act in values:
if act[0] == 0:
act[2][self._fields_id] = id
id_new = obj.create(cr, user, act[2], context=context)
result += obj._store_get_values(cr, user, [id_new], act[2].keys(), context)
elif act[0] == 1:
obj.write(cr, user, [act[1]], act[2], context=context)
elif act[0] == 2:
obj.unlink(cr, user, [act[1]], context=context)
else:
cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
elif act[0] == 4:
# table of the field (parent_model in case of inherit)
field = obj.pool[self._obj]._fields[self._fields_id]
field_model = field.base_field.model_name
field_table = obj.pool[field_model]._table
cr.execute("select 1 from {0} where id=%s and {1}=%s".format(field_table, self._fields_id), (act[1], id))
if not cr.fetchone():
# Must use write() to recompute parent_store structure if needed and check access rules
obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
elif act[0] == 5:
inverse_field = obj._fields.get(self._fields_id)
assert inverse_field, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
# if the o2m has a static domain we must respect it when unlinking
domain = self._domain(obj) if callable(self._domain) else self._domain
extra_domain = domain or []
ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context)
# If the model has cascade deletion, we delete the rows because it is the intended behavior,
# otherwise we only nullify the reverse foreign key column.
if inverse_field.ondelete == "cascade":
obj.unlink(cr, user, ids_to_unlink, context=context)
else:
obj.write(cr, user, ids_to_unlink, {self._fields_id: False}, context=context)
elif act[0] == 6:
# Must use write() to recompute parent_store structure if needed
obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
ids2 = act[2] or [0]
cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
ids3 = map(lambda x:x[0], cr.fetchall())
obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
elif act[0] == 3:
inverse_field = obj._fields.get(self._fields_id)
assert inverse_field, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
# if the model has on delete cascade, just delete the row
if inverse_field.ondelete == "cascade":
obj.unlink(cr, user, [act[1]], context=context)
else:
cr.execute('update '+_table+' set '+self._fields_id+'=null where id=%s', (act[1],))
elif act[0] == 4:
# table of the field (parent_model in case of inherit)
field = obj.pool[self._obj]._fields[self._fields_id]
field_model = field.base_field.model_name
field_table = obj.pool[field_model]._table
cr.execute("select 1 from {0} where id=%s and {1}=%s".format(field_table, self._fields_id), (act[1], id))
if not cr.fetchone():
# Must use write() to recompute parent_store structure if needed and check access rules
obj.write(cr, user, [act[1]], {self._fields_id:id}, context=context or {})
elif act[0] == 5:
inverse_field = obj._fields.get(self._fields_id)
assert inverse_field, 'Trying to unlink the content of a o2m but the pointed model does not have a m2o'
# if the o2m has a static domain we must respect it when unlinking
domain = self._domain(obj) if callable(self._domain) else self._domain
extra_domain = domain or []
ids_to_unlink = obj.search(cr, user, [(self._fields_id,'=',id)] + extra_domain, context=context)
# If the model has cascade deletion, we delete the rows because it is the intended behavior,
# otherwise we only nullify the reverse foreign key column.
if inverse_field.ondelete == "cascade":
obj.unlink(cr, user, ids_to_unlink, context=context)
else:
obj.write(cr, user, ids_to_unlink, {self._fields_id: False}, context=context)
elif act[0] == 6:
# Must use write() to recompute parent_store structure if needed
obj.write(cr, user, act[2], {self._fields_id:id}, context=context or {})
ids2 = act[2] or [0]
cr.execute('select id from '+_table+' where '+self._fields_id+'=%s and id <> ALL (%s)', (id,ids2))
ids3 = map(lambda x:x[0], cr.fetchall())
obj.write(cr, user, ids3, {self._fields_id:False}, context=context or {})
return result
def search(self, cr, obj, args, name, value, offset=0, limit=None, uid=None, operator='like', context=None):