[MERGE] forward port branch saas-11.3 up to b4555df336

This commit is contained in:
Christophe Simonis
2018-07-30 17:02:28 +02:00
64 changed files with 26976 additions and 23879 deletions
+2
View File
@@ -1370,6 +1370,8 @@ class AccountInvoice(models.Model):
values[name] = line[name]
elif name == 'invoice_line_tax_ids':
values[name] = [(6, 0, line[name].ids)]
elif name == 'analytic_tag_ids':
values[name] = [(6, 0, line[name].ids)]
result.append((0, 0, values))
return result
+9 -9
View File
@@ -1532,15 +1532,15 @@ class AccountPartialReconcile(models.Model):
'amount_currency': self.amount_currency and line.currency_id.round(-line.amount_currency * amount / line.balance) or 0.0,
'partner_id': line.partner_id.id,
})
if newly_created_move:
if move_date > (self.company_id.period_lock_date or '0000-00-00') and newly_created_move.date != move_date:
# The move date should be the maximum date between payment and invoice (in case
# of payment in advance). However, we should make sure the move date is not
# recorded before the period lock date as the tax statement for this period is
# probably already sent to the estate.
newly_created_move.write({'date': move_date})
# post move
newly_created_move.post()
if newly_created_move:
if move_date > (self.company_id.period_lock_date or '0000-00-00') and newly_created_move.date != move_date:
# The move date should be the maximum date between payment and invoice (in case
# of payment in advance). However, we should make sure the move date is not
# recorded before the period lock date as the tax statement for this period is
# probably already sent to the estate.
newly_created_move.write({'date': move_date})
# post move
newly_created_move.post()
def _create_tax_basis_move(self):
# Check if company_journal for cash basis is set if not, raise exception
@@ -76,6 +76,47 @@ class Partner(models.Model):
street_value += street_format[previous_pos:]
partner.street = street_value
def _split_street_with_params(self, street_raw, street_format):
street_fields = self.get_street_fields()
vals = {}
previous_pos = 0
field_name = None
# iter on fields in street_format, detected as '%(<field_name>)s'
for re_match in re.finditer(r'%\(\w+\)s', street_format):
field_pos = re_match.start()
if not field_name:
#first iteration: remove the heading chars
street_raw = street_raw[field_pos:]
# get the substring between 2 fields, to be used as separator
separator = street_format[previous_pos:field_pos]
field_value = None
if separator and field_name:
#maxsplit set to 1 to unpack only the first element and let the rest untouched
tmp = street_raw.split(separator, 1)
if len(tmp) == 2:
field_value, street_raw = tmp
vals[field_name] = field_value
if field_value or not field_name:
# select next field to find (first pass OR field found)
# [2:-2] is used to remove the extra chars '%(' and ')s'
field_name = re_match.group()[2:-2]
else:
# value not found: keep looking for the same field
pass
if field_name not in street_fields:
raise UserError(_("Unrecognized field %s in street format.") % field_name)
previous_pos = re_match.end()
# last field value is what remains in street_raw minus trailing chars in street_format
trailing_chars = street_format[previous_pos:]
if trailing_chars and street_raw.endswith(trailing_chars):
vals[field_name] = street_raw[:-len(trailing_chars)]
else:
vals[field_name] = street_raw
return vals
@api.multi
@api.depends('street')
def _split_street(self):
@@ -90,47 +131,18 @@ class Partner(models.Model):
street_format = (partner.country_id.street_format or
'%(street_number)s/%(street_number2)s %(street_name)s')
vals = {}
previous_pos = 0
street_raw = partner.street
field_name = None
# iter on fields in street_format, detected as '%(<field_name>)s'
for re_match in re.finditer(r'%\(\w+\)s', street_format):
field_pos = re_match.start()
if not field_name:
#first iteration: remove the heading chars
street_raw = street_raw[field_pos:]
# get the substring between 2 fields, to be used as separator
separator = street_format[previous_pos:field_pos]
field_value = None
if separator and field_name:
#maxsplit set to 1 to unpack only the first element and let the rest untouched
tmp = street_raw.split(separator, 1)
if len(tmp) == 2:
field_value, street_raw = tmp
vals[field_name] = field_value
if field_value or not field_name:
# select next field to find (first pass OR field found)
# [2:-2] is used to remove the extra chars '%(' and ')s'
field_name = re_match.group()[2:-2]
else:
# value not found: keep looking for the same field
pass
if field_name not in street_fields:
raise UserError(_("Unrecognized field %s in street format.") % field_name)
previous_pos = re_match.end()
# last field value is what remains in street_raw minus trailing chars in street_format
trailing_chars = street_format[previous_pos:]
if trailing_chars and street_raw.endswith(trailing_chars):
vals[field_name] = street_raw[:-len(trailing_chars)]
else:
vals[field_name] = street_raw
vals = self._split_street_with_params(street_raw, street_format)
# assign the values to the fields
for k, v in vals.items():
partner[k] = v
def write(self, vals):
res = super(Partner, self).write(vals)
if 'country_id' in vals:
self._set_street()
return res
class Company(models.Model):
_inherit = 'res.company'
@@ -51,5 +51,5 @@ class TestStreetFields(TransactionCase):
self.write_and_assert(p1, {'street': 'Chaussee de Namur'}, 'Chaussee de Namur', 'Chaussee de Namur', '', '')
self.write_and_assert(p1, {'street_name': 'Chee de Namur', 'street_number': '40'}, 'Chee de Namur, 40', 'Chee de Namur', '40', '')
self.write_and_assert(p1, {'street_number2': '4'}, 'Chee de Namur, 40/4', 'Chee de Namur', '40', '4')
#we don't recompute the street fields when we change the country
self.write_and_assert(p1, {'country_id': self.env.ref('base.us').id}, 'Chee de Namur, 40/4', 'Chee de Namur', '40', '4')
#we do recompute the street fields when we change the country
self.write_and_assert(p1, {'country_id': self.env.ref('base.us').id}, '40/4 Chee de Namur', 'Chee de Namur', '40', '4')
@@ -72,8 +72,8 @@ class ResPartner(models.Model):
if len(lines) == 1:
lines = [x.strip() for x in lines[0].split(' ') if x]
partner_address['street'] = lines.pop(0)
#_set_address_field(partner, 'street', lines.pop(0))
vals = self._split_street_with_params(', '.join(lines.pop(0).rsplit(' ', 1)), '%(street_name)s, %(street_number)s/%(street_number2)s')
partner_address.update(vals)
if len(lines) > 0:
res = _check_city(lines, result['countryCode'])
@@ -372,7 +372,6 @@
<td style="padding:25px 0px;">
<strong>Add this event to your calendar</strong>
<a href="https://www.google.com/calendar/render?action=TEMPLATE&amp;text=${object.event_id.name}&amp;dates=${date_begin}/${date_end}&amp;location=${location}" style="padding:3px 5px;border:1px solid #875A7B;color:#875A7B;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(135,90,123)/16" style="vertical-align:middle;" height="16"/> Google</a>
<a href="https://bay02.calendar.live.com/calendar/calendar.aspx?rru=addevent&amp;summary=${object.event_id.name}&amp;dtstart=${date_begin}&amp;dtend=${date_end}&amp;location=${location}" style="padding:3px 5px;border:1px solid #875A7B;color:#875A7B;text-decoration:none;border-radius:3px;" target="new"><img src="/web_editor/font_to_img/61525/rgb(135,90,123)/16" style="vertical-align:middle;" height="16"/> Outlook</a>
<a href="https://calendar.yahoo.com/?v=60&amp;view=d&amp;type=20&amp;title=${object.event_id.name}&amp;in_loc=${location}&amp;st=${format_tz(object.event_id.date_begin, tz='UTC', format='%Y%m%dT%H%M%S')}&amp;et=${format_tz(object.event_id.date_end, tz='UTC', format='%Y%m%dT%H%M%S')}" style="padding:3px 5px;border:1px solid #875A7B;color:#875A7B;text-decoration:none;border-radius:3px;" target="new">
<img src="/web_editor/font_to_img/61525/rgb(135,90,123)/16" style="vertical-align:middle;" height="16"/> Yahoo</a>
</td>
+11
View File
@@ -459,6 +459,17 @@ class EventRegistration(models.Model):
pass
return recipients
@api.multi
def message_get_default_recipients(self):
# Prioritize registration email over partner_id, which may be shared when a single
# partner booked multiple seats
return {
r.id: {'partner_ids': [],
'email_to': r.email,
'email_cc': False}
for r in self
}
def _message_post_after_hook(self, message, *args, **kwargs):
if self.email and not self.partner_id:
# we consider that posting a message with a specified recipient (not a follower, a specific one)
@@ -44,7 +44,7 @@
<t t-if="l.task_id"><span t-field="l.task_id.name"/></t>
</td>
<td class="text-right">
<span t-esc="'%s:%02.0f' % tuple(int(round(x)) for x in divmod(l.unit_amount*60,60))"/>
<span t-field="l.unit_amount" t-options="{'widget': 'float_time'}"/>
</td>
</tr>
</t>
@@ -53,7 +53,9 @@
<td />
<td t-if="show_task"/>
<td class="text-right"><strong>Total</strong></td>
<td class="text-right"><strong t-esc="'%s:%02.0f' % tuple(int(round(x)) for x in divmod(sum(docs.mapped('unit_amount'))*60,60))"/></td>
<t t-set="total_value" t-value="sum(docs.mapped('unit_amount'))" />
<t t-set="sign" t-value="'-' if total_value &lt; 0 else ''"/>
<td class="text-right"><strong t-esc="sign + '%s:%02.0f' % tuple(int(round(x)) for x in divmod(abs(total_value)*60,60))"/></td>
</tr>
</tbody>
</table>
@@ -205,6 +205,7 @@
<field name="res_model">mail.shortcode</field>
<field name="view_mode">tree</field>
<field name="view_id" ref="im_livechat_canned_response_view_tree"/>
<field name="domain">[]</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a new canned response
+155
View File
@@ -0,0 +1,155 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * web_planner
#
# Translators:
# Mpasua Msonobari <msonobari@gmail.com>, 2018
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~18\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-09-20 09:54+0000\n"
"PO-Revision-Date: 2017-09-20 09:54+0000\n"
"Last-Translator: Mpasua Msonobari <msonobari@gmail.com>, 2018\n"
"Language-Team: Swahili (https://www.transifex.com/odoo/teams/41243/sw/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: sw\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: web_planner
#: model:ir.model.fields,field_description:web_planner.field_web_planner_active
#: model:ir.ui.view,arch_db:web_planner.web_planner_view_search
msgid "Active"
msgstr "Amilifu"
#. module: web_planner
#. openerp-web
#: code:addons/web_planner/static/src/js/web_planner_common.js:379
#, python-format
msgid "Cancel"
msgstr "Katiza"
#. module: web_planner
#: model:ir.model.fields,field_description:web_planner.field_web_planner_create_uid
msgid "Created by"
msgstr ""
#. module: web_planner
#: model:ir.model.fields,field_description:web_planner.field_web_planner_create_date
msgid "Created on"
msgstr ""
#. module: web_planner
#: model:ir.model.fields,field_description:web_planner.field_web_planner_data
msgid "Data"
msgstr ""
#. module: web_planner
#: model:ir.model.fields,field_description:web_planner.field_web_planner_display_name
msgid "Display Name"
msgstr ""
#. module: web_planner
#: model:ir.model.fields,field_description:web_planner.field_web_planner_id
msgid "ID"
msgstr ""
#. module: web_planner
#: model:ir.model.fields,help:web_planner.field_web_planner_active
msgid ""
"If the active field is set to False, it will allow you to hide the planner. "
"This change requires a refresh of your page."
msgstr ""
#. module: web_planner
#: model:ir.ui.view,arch_db:web_planner.web_planner_view_search
msgid "Inactive"
msgstr ""
#. module: web_planner
#: model:ir.model.fields,field_description:web_planner.field_web_planner___last_update
msgid "Last Modified on"
msgstr ""
#. module: web_planner
#: model:ir.model.fields,field_description:web_planner.field_web_planner_write_uid
msgid "Last Updated by"
msgstr ""
#. module: web_planner
#: model:ir.model.fields,field_description:web_planner.field_web_planner_write_date
msgid "Last Updated on"
msgstr ""
#. module: web_planner
#. openerp-web
#: code:addons/web_planner/static/src/js/web_planner_common.js:133
#, python-format
msgid "Mark As Done"
msgstr ""
#. module: web_planner
#: model:ir.model.fields,field_description:web_planner.field_web_planner_menu_id
msgid "Menu"
msgstr ""
#. module: web_planner
#: model:ir.model.fields,field_description:web_planner.field_web_planner_name
msgid "Name"
msgstr ""
#. module: web_planner
#. openerp-web
#: code:addons/web_planner/static/src/js/web_planner_common.js:141
#, python-format
msgid "Next Step"
msgstr ""
#. module: web_planner
#. openerp-web
#: code:addons/web_planner/static/src/js/web_planner_common.js:388
#, python-format
msgid "Odoo Enterprise"
msgstr ""
#. module: web_planner
#: model:ir.model,name:web_planner.model_web_planner
#: model:ir.ui.view,arch_db:web_planner.web_planner_view_search
#: model:ir.ui.view,arch_db:web_planner.web_planner_view_tree
msgid "Planner"
msgstr ""
#. module: web_planner
#: model:ir.model.fields,field_description:web_planner.field_web_planner_planner_application
msgid "Planner Application"
msgstr ""
#. module: web_planner
#: model:ir.model.fields,field_description:web_planner.field_web_planner_tooltip_planner
msgid "Planner Tooltips"
msgstr ""
#. module: web_planner
#: model:ir.actions.act_window,name:web_planner.web_planner_action
#: model:ir.ui.menu,name:web_planner.web_planner_menu
msgid "Planners"
msgstr ""
#. module: web_planner
#: model:ir.model.fields,field_description:web_planner.field_web_planner_progress
msgid "Progress Percentage"
msgstr ""
#. module: web_planner
#: model:ir.model.fields,field_description:web_planner.field_web_planner_view_id
msgid "Template"
msgstr ""
#. module: web_planner
#. openerp-web
#: code:addons/web_planner/static/src/js/web_planner_common.js:365
#, python-format
msgid "Upgrade now"
msgstr ""
+119 -119
View File
@@ -1,185 +1,185 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<odoo>
<!-- Account Tax Tags -->
<record id="tag_de_41" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 41</field>
<field name="name">Ust-VA KZ 41</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_44" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 44</field>
<field name="name">Ust-VA KZ 44</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_49" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 49</field>
<field name="name">Ust-VA KZ 49</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_43" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 43</field>
<field name="name">Ust-VA KZ 43</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_48" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 48</field>
<field name="name">Ust-VA KZ 48</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_81" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 81</field>
<field name="name">Ust-VA KZ 81</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_86" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 86</field>
<field name="name">Ust-VA KZ 86</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_35" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 35</field>
<field name="name">Ust-VA KZ 35</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_77" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 77</field>
<field name="name">Ust-VA KZ 77</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_76" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 76</field>
<field name="name">Ust-VA KZ 76</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_91" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 91</field>
<field name="name">Ust-VA KZ 91</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_89" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 89</field>
<field name="name">Ust-VA KZ 89</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_93" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 93</field>
<field name="name">Ust-VA KZ 93</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_95" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 95</field>
<field name="name">Ust-VA KZ 95</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_94" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 94</field>
<field name="name">Ust-VA KZ 94</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_42" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 42</field>
<field name="name">Ust-VA KZ 42</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_68" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 68</field>
<field name="name">Ust-VA KZ 68</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_60" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 60</field>
<field name="name">Ust-VA KZ 60</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_21" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 21</field>
<field name="name">Ust-VA KZ 21</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_45" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 45</field>
<field name="name">Ust-VA KZ 45</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_46" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 46</field>
<field name="name">Ust-VA KZ 46</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_52" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 52</field>
<field name="name">Ust-VA KZ 52</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_73" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 73</field>
<field name="name">Ust-VA KZ 73</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_78" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 78</field>
<field name="name">Ust-VA KZ 78</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_84" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 84</field>
<field name="name">Ust-VA KZ 84</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_36" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 36</field>
<field name="name">Ust-VA KZ 36</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_80" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 80</field>
<field name="name">Ust-VA KZ 80</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_98" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 98</field>
<field name="name">Ust-VA KZ 98</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_96" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 96</field>
<field name="name">Ust-VA KZ 96</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_47" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 47</field>
<field name="name">Ust-VA KZ 47</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_53" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 53</field>
<field name="name">Ust-VA KZ 53</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_74" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 74</field>
<field name="name">Ust-VA KZ 74</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_79" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 79</field>
<field name="name">Ust-VA KZ 79</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_85" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 85</field>
<field name="name">Ust-VA KZ 85</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_65" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 65</field>
<field name="name">Ust-VA KZ 65</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_66" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 66</field>
<field name="name">Ust-VA KZ 66</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_61" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 61</field>
<field name="name">Ust-VA KZ 61</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_62" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 62</field>
<field name="name">Ust-VA KZ 62</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_67" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 67</field>
<field name="name">Ust-VA KZ 67</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_63" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 63</field>
<field name="name">Ust-VA KZ 63</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_64" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 64</field>
<field name="name">Ust-VA KZ 64</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_59" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 59</field>
<field name="name">Ust-VA KZ 59</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_69" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 69</field>
<field name="name">Ust-VA KZ 69</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_39" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 39</field>
<field name="name">Ust-VA KZ 39</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_83" model="account.account.tag">
<field name="name">Umsatzsteuer-Voranmeldung Code 83</field>
<field name="name">Ust-VA KZ 83</field>
<field name="applicability">taxes</field>
</record>
@@ -188,300 +188,300 @@
<field name="applicability">taxes</field>
</record>
<record id="tag_de_intracom_community_supplies" model="account.account.tag">
<field name="name">Sonstige leistungen</field>
<field name="name">Sonstige Leistungen</field>
<field name="applicability">taxes</field>
</record>
<record id="tag_de_intracom_ABC" model="account.account.tag">
<field name="name">Dreiecksgecshäfte</field>
<field name="name">Dreiecksgeschäfte</field>
<field name="applicability">taxes</field>
</record>
<!-- Profit and loss tags -->
<record id="tag_de_pl_01" model="account.account.tag">
<field name="name">P&amp;L: 1-Umsatzerlöse</field>
<field name="name">G&amp;V: 1-Umsatzerlöse</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_02" model="account.account.tag">
<field name="name">P&amp;L: 2-Erhöhung oder Verminderung des Bestands an fertigen und unfertigen Erzeugnissen</field>
<field name="name">G&amp;V: 2-Erhöhung oder Verminderung des Bestands an fertigen und unfertigen Erzeugnissen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_03" model="account.account.tag">
<field name="name">P&amp;L: 3-Andere aktivierte eigenleistungen</field>
<field name="name">G&amp;V: 3-Andere aktivierte Eigenleistungen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_04" model="account.account.tag">
<field name="name">P&amp;L: 4-Sonstige betrieliche erträge</field>
<field name="name">G&amp;V: 4-Sonstige betrieliche Erträge</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_05" model="account.account.tag">
<field name="name">P&amp;L: 5-Materialaufwand</field>
<field name="name">G&amp;V: 5-Materialaufwand</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_06" model="account.account.tag">
<field name="name">P&amp;L: 6-Personalaufwand</field>
<field name="name">G&amp;V: 6-Personalaufwand</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_07" model="account.account.tag">
<field name="name">P&amp;L: 7-Abschreibungen</field>
<field name="name">G&amp;V: 7-Abschreibungen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_08_1" model="account.account.tag">
<field name="name">P&amp;L: 8.1-Raumkosten</field>
<field name="name">G&amp;V: 8.1-Raumkosten</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_08_2" model="account.account.tag">
<field name="name">P&amp;L: 8.2-Versicherungen, Beiträge und Abgaben</field>
<field name="name">G&amp;V: 8.2-Versicherungen, Beiträge und Abgaben</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_08_3" model="account.account.tag">
<field name="name">P&amp;L: 8.3-eparaturen und Instandhaltungen</field>
<field name="name">G&amp;V: 8.3-Reparaturen und Instandhaltungen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_08_4" model="account.account.tag">
<field name="name">P&amp;L: 8.4-Fahrzeugkosten</field>
<field name="name">G&amp;V: 8.4-Fahrzeugkosten</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_08_5" model="account.account.tag">
<field name="name">P&amp;L: 8.5-Werbe- und Reisekosten</field>
<field name="name">G&amp;V: 8.5-Werbe- und Reisekosten</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_08_6" model="account.account.tag">
<field name="name">P&amp;L: 8.6-Kosten der Warenabgabe</field>
<field name="name">G&amp;V: 8.6-Kosten der Warenabgabe</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_08_7" model="account.account.tag">
<field name="name">P&amp;L: 8.7-verschiedene betriebliche Kosten</field>
<field name="name">G&amp;V: 8.7-verschiedene betriebliche Kosten</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_09" model="account.account.tag">
<field name="name">P&amp;L: 9-Erträge aus beteiligungen</field>
<field name="name">G&amp;V: 9-Erträge aus Beteiligungen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_10" model="account.account.tag">
<field name="name">P&amp;L: 10-Erträge aus anderen wertpapieren und ausleihungen des finanzanlagevermögens</field>
<field name="name">G&amp;V: 10-Erträge aus anderen Wertpapieren und Ausleihungen des Finanzanlagevermögens</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_11" model="account.account.tag">
<field name="name">P&amp;L: 11-Sonstige zinsen und ähnliche erträge</field>
<field name="name">G&amp;V: 11-Sonstige Zinsen und ähnliche Erträge</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_12" model="account.account.tag">
<field name="name">P&amp;L: 12-Abschreibungen auf Finanzanlagen und auf Wertpapiere des Umlaufvermögens</field>
<field name="name">G&amp;V: 12-Abschreibungen auf Finanzanlagen und auf Wertpapiere des Umlaufvermögens</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_13" model="account.account.tag">
<field name="name">P&amp;L: 13-Zinsen und ähnliche Aufwendungen</field>
<field name="name">G&amp;V: 13-Zinsen und ähnliche Aufwendungen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_14" model="account.account.tag">
<field name="name">P&amp;L: 14-Steuern vom einkommen und ertrag</field>
<field name="name">G&amp;V: 14-Steuern vom Einkommen und Ertrag</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_pl_15" model="account.account.tag">
<field name="name">P&amp;L: 15-Sonstige steuern</field>
<field name="name">G&amp;V: 15-Sonstige Steuern</field>
<field name="applicability">accounts</field>
</record>
<!-- Balance sheet tags -->
<record id="tag_de_asset_bs_A_I_1" model="account.account.tag">
<field name="name">BS-Aktivseite: A I 1-Selbst geschaffene gewerbliche Schutzrechte und ähnliche Rechte und Werte</field>
<field name="name">Bilanz-Aktiva: A I 1-Selbst geschaffene gewerbliche Schutzrechte und ähnliche Rechte und Werte</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_A_I_2" model="account.account.tag">
<field name="name">BS-Aktivseite: A I 2-Konzessionen, Lizenzen und ähnliche Rechte und Werte</field>
<field name="name">Bilanz-Aktiva: A I 2-Konzessionen, Lizenzen und ähnliche Rechte und Werte</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_A_I_3" model="account.account.tag">
<field name="name">BS-Aktivseite: A I 3-Geschäfts- oder Firmenwert</field>
<field name="name">Bilanz-Aktiva: A I 3-Geschäfts- oder Firmenwert</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_A_I_4" model="account.account.tag">
<field name="name">BS-Aktivseite: A I 4-geleistete Anzahlungen</field>
<field name="name">Bilanz-Aktiva: A I 4-geleistete Anzahlungen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_A_II_1" model="account.account.tag">
<field name="name">BS-Aktivseite: A II 1-Grundstücke. grundstücksgleiche Rechte und Bauten</field>
<field name="name">Bilanz-Aktiva: A II 1-Grundstücke. grundstücksgleiche Rechte und Bauten</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_A_II_2" model="account.account.tag">
<field name="name">BS-Aktivseite: A II 2-Technische Anlagen und Maschinen</field>
<field name="name">Bilanz-Aktiva: A II 2-Technische Anlagen und Maschinen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_A_II_3" model="account.account.tag">
<field name="name">BS-Aktivseite: A II 3-Andere Anlagen. Betriebs- und Geschäftsausstattung</field>
<field name="name">Bilanz-Aktiva: A II 3-Andere Anlagen. Betriebs- und Geschäftsausstattung</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_A_II_4" model="account.account.tag">
<field name="name">BS-Aktivseite: A II 4-Geleistete Anzahlungen und Anlagen im Bau</field>
<field name="name">Bilanz-Aktiva: A II 4-Geleistete Anzahlungen und Anlagen im Bau</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_A_III_1" model="account.account.tag">
<field name="name">BS-Aktivseite: A III 1-Anteile an verbundenen Unternehmen</field>
<field name="name">Bilanz-Aktiva: A III 1-Anteile an verbundenen Unternehmen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_A_III_2" model="account.account.tag">
<field name="name">BS-Aktivseite: A III 2-Ausleihungen an verbundene Unternehmen</field>
<field name="name">Bilanz-Aktiva: A III 2-Ausleihungen an verbundene Unternehmen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_A_III_3" model="account.account.tag">
<field name="name">BS-Aktivseite: A III 3-Beteiligungen</field>
<field name="name">Bilanz-Aktiva: A III 3-Beteiligungen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_A_III_4" model="account.account.tag">
<field name="name">BS-Aktivseite: A III 4-Ausleihungen an Unternehmen, mit denen ein Beteiligungsverhältnis besteht</field>
<field name="name">Bilanz-Aktiva: A III 4-Ausleihungen an Unternehmen, mit denen ein Beteiligungsverhältnis besteht</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_A_III_5" model="account.account.tag">
<field name="name">BS-Aktivseite: A III 5-Wertpapiere des Anlagevermögens</field>
<field name="name">Bilanz-Aktiva: A III 5-Wertpapiere des Anlagevermögens</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_A_III_6" model="account.account.tag">
<field name="name">BS-Aktivseite: A III 6-sonstige Ausleihungen</field>
<field name="name">Bilanz-Aktiva: A III 6-sonstige Ausleihungen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_B_I_1" model="account.account.tag">
<field name="name">BS-Aktivseite: B I 1-Roh-, Hilfs- und Betriebsstoffe</field>
<field name="name">Bilanz-Aktiva: B I 1-Roh-, Hilfs- und Betriebsstoffe</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_B_I_2" model="account.account.tag">
<field name="name">BS-Aktivseite: B I 2-Unfertige Erzeugnisse, unfertige Leistungen</field>
<field name="name">Bilanz-Aktiva: B I 2-Unfertige Erzeugnisse, unfertige Leistungen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_B_I_3" model="account.account.tag">
<field name="name">BS-Aktivseite: B I 3-Fertige Erzeugnisse und Waren</field>
<field name="name">Bilanz-Aktiva: B I 3-Fertige Erzeugnisse und Waren</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_B_I_4" model="account.account.tag">
<field name="name">BS-Aktivseite: B I 4-Geleistete Anzahlungen</field>
<field name="name">Bilanz-Aktiva: B I 4-Geleistete Anzahlungen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_B_II_1" model="account.account.tag">
<field name="name">BS-Aktivseite: B II 1-Forderungen aus Lieferungen und Leistungen</field>
<field name="name">Bilanz-Aktiva: B II 1-Forderungen aus Lieferungen und Leistungen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_B_II_2" model="account.account.tag">
<field name="name">BS-Aktivseite: B II 2-Forderungen gegen verbundene Unternehmen</field>
<field name="name">Bilanz-Aktiva: B II 2-Forderungen gegen verbundene Unternehmen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_B_II_3" model="account.account.tag">
<field name="name">BS-Aktivseite: B II 3-Forderungen gegen Unternehmen, mit denen ein Beteiligungsverhältnis besteht</field>
<field name="name">Bilanz-Aktiva: B II 3-Forderungen gegen Unternehmen, mit denen ein Beteiligungsverhältnis besteht</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_B_II_4" model="account.account.tag">
<field name="name">BS-Aktivseite: B II 4-Sonstige Vermögensgegenstände</field>
<field name="name">Bilanz-Aktiva: B II 4-Sonstige Vermögensgegenstände</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_B_III_1" model="account.account.tag">
<field name="name">BS-Aktivseite: B III 1-Anteile an verbundenen Unternehmen</field>
<field name="name">Bilanz-Aktiva: B III 1-Anteile an verbundenen Unternehmen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_B_III_2" model="account.account.tag">
<field name="name">BS-Aktivseite: B III 2-sonstige Wertpapiere</field>
<field name="name">Bilanz-Aktiva: B III 2-sonstige Wertpapiere</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_B_IV" model="account.account.tag">
<field name="name">BS-Aktivseite: B IV-Kassenbestand, Bundesbankguthaben, Guthaben bei Kreditinstituten und Schecks</field>
<field name="name">Bilanz-Aktiva: B IV-Kassenbestand, Bundesbankguthaben, Guthaben bei Kreditinstituten und Schecks</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_C" model="account.account.tag">
<field name="name">BS-Aktivseite: C-Rechnungsabgrenzungsposten</field>
<field name="name">Bilanz-Aktiva: C-Rechnungsabgrenzungsposten</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_D" model="account.account.tag">
<field name="name">BS-Aktivseite: D-Aktive latente Steuern</field>
<field name="name">Bilanz-Aktiva: D-Aktive latente Steuern</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_asset_bs_E" model="account.account.tag">
<field name="name">BS-Aktivseite: E-Aktiver Unterschiedsbetrag aus der Vermögensverrechnung</field>
<field name="name">Bilanz-Aktiva: E-Aktiver Unterschiedsbetrag aus der Vermögensverrechnung</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_A_I" model="account.account.tag">
<field name="name">BS-Passivseite: A I-Gezeichnetes Kapital</field>
<field name="name">Bilanz-Passiva: A I-Gezeichnetes Kapital</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_A_II" model="account.account.tag">
<field name="name">BS-Passivseite: A II-Kapitalrücklage</field>
<field name="name">Bilanz-Passiva: A II-Kapitalrücklage</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_A_III_1" model="account.account.tag">
<field name="name">BS-Passivseite: A III 1-Gesetzliche Rücklage</field>
<field name="name">Bilanz-Passiva: A III 1-Gesetzliche Rücklage</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_A_III_2" model="account.account.tag">
<field name="name">BS-Passivseite: A III 2-Rücklage für Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen</field>
<field name="name">Bilanz-Passiva: A III 2-Rücklage für Anteile an einem herrschenden oder mehrheitlich beteiligten Unternehmen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_A_III_3" model="account.account.tag">
<field name="name">BS-Passivseite: A III 3-Satzungsmäßige Rücklagen</field>
<field name="name">Bilanz-Passiva: A III 3-Satzungsmäßige Rücklagen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_A_III_4" model="account.account.tag">
<field name="name">BS-Passivseite: A III 4-Andere Gewinnrücklagen</field>
<field name="name">Bilanz-Passiva: A III 4-Andere Gewinnrücklagen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_A_IV" model="account.account.tag">
<field name="name">BS-Passivseite: A IV-Gewinnvortrag/Verlustvortrag</field>
<field name="name">Bilanz-Passiva: A IV-Gewinnvortrag/Verlustvortrag</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_A_V" model="account.account.tag">
<field name="name">BS-Passivseite: A V-Jahresüberschuß/Jahresfehlbetrag</field>
<field name="name">Bilanz-Passiva: A V-Jahresüberschuß/Jahresfehlbetrag</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_B" model="account.account.tag">
<field name="name">BS-Passivseite: B Sonderposten mit Rücklageanteil</field>
<field name="name">Bilanz-Passiva: B Sonderposten mit Rücklageanteil</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_C_1" model="account.account.tag">
<field name="name">BS-Passivseite: C 1-Rückstellungen für Pensionen und ähnliche Verpflichtungen</field>
<field name="name">Bilanz-Passiva: C 1-Rückstellungen für Pensionen und ähnliche Verpflichtungen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_C_2" model="account.account.tag">
<field name="name">BS-Passivseite: C 2-Steuerrückstellungen</field>
<field name="name">Bilanz-Passiva: C 2-Steuerrückstellungen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_C_3" model="account.account.tag">
<field name="name">BS-Passivseite: C 3-Sonstige Rückstellungen</field>
<field name="name">Bilanz-Passiva: C 3-Sonstige Rückstellungen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_D_1" model="account.account.tag">
<field name="name">BS-Passivseite: D 1-Anleihen, davon konvertibeln</field>
<field name="name">Bilanz-Passiva: D 1-Anleihen, davon konvertibeln</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_D_2" model="account.account.tag">
<field name="name">BS-Passivseite: D 2-Verbindlichkeiten gegenüber Kreditinstituten</field>
<field name="name">Bilanz-Passiva: D 2-Verbindlichkeiten gegenüber Kreditinstituten</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_D_3" model="account.account.tag">
<field name="name">BS-Passivseite: D 3-Erhaltene Anzahlungen auf Bestellungen</field>
<field name="name">Bilanz-Passiva: D 3-Erhaltene Anzahlungen auf Bestellungen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_D_4" model="account.account.tag">
<field name="name">BS-Passivseite: D 4-Verbindlichkeiten aus Lieferungen und Leistungen</field>
<field name="name">Bilanz-Passiva: D 4-Verbindlichkeiten aus Lieferungen und Leistungen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_D_5" model="account.account.tag">
<field name="name">BS-Passivseite: D 5-Verbindlichkeiten aus der Annahme gezogener Wechsel und der Ausstellung eigener Wechsel</field>
<field name="name">Bilanz-Passiva: D 5-Verbindlichkeiten aus der Annahme gezogener Wechsel und der Ausstellung eigener Wechsel</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_D_6" model="account.account.tag">
<field name="name">BS-Passivseite: D 6-Verbindlichkeiten gegenüber verbundenen Unternehmen</field>
<field name="name">Bilanz-Passiva: D 6-Verbindlichkeiten gegenüber verbundenen Unternehmen</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_D_7" model="account.account.tag">
<field name="name">BS-Passivseite: D 7-Verbindlichkeiten gegenüber Unternehmen, mit denen ein Beteiligungsverhältnis besteht</field>
<field name="name">Bilanz-Passiva: D 7-Verbindlichkeiten gegenüber Unternehmen, mit denen ein Beteiligungsverhältnis besteht</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_D_8" model="account.account.tag">
<field name="name">BS-Passivseite: D 8-Sonstige Verbindlichkeiten, davon aus Steuern, davon im Rahmen der sozialen Sicherheit</field>
<field name="name">Bilanz-Passiva: D 8-Sonstige Verbindlichkeiten, davon aus Steuern, davon im Rahmen der sozialen Sicherheit</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_E" model="account.account.tag">
<field name="name">BS-Passivseite: E-Rechnungsabgrenzungsposten</field>
<field name="name">Bilanz-Passiva: E-Rechnungsabgrenzungsposten</field>
<field name="applicability">accounts</field>
</record>
<record id="tag_de_liabilities_bs_F" model="account.account.tag">
<field name="name">BS-Passivseite: F-Passive latente Steuern</field>
<field name="name">Bilanz-Passiva: F-Passive latente Steuern</field>
<field name="applicability">accounts</field>
</record>
+18 -1
View File
@@ -1,4 +1,6 @@
from odoo import fields, models
from odoo import api, fields, models
from odoo.exceptions import UserError
from odoo.tools.translate import _
class AccountTaxTemplate(models.Model):
_inherit = 'account.tax.template'
@@ -14,3 +16,18 @@ class AccountTax(models.Model):
_inherit = "account.tax"
l10n_de_datev_code = fields.Char(size=2, help="2 digits code use by Datev")
class AccountInvoice(models.Model):
_inherit = "account.invoice"
def invoice_validate(self):
for invoice in self:
for line in invoice.invoice_line_ids:
account_tax = line.account_id.tax_ids.ids
if account_tax and invoice.company_id.country_id.code == 'DE':
account_name = line.account_id.name
for tax in line.invoice_line_tax_ids:
if tax.id not in account_tax:
raise UserError(_('Account %s does not authorize to have tax %s specified on the line. \
Change the tax used in this invoice or remove all taxes from the account') % (account_name, tax.name))
return super(AccountInvoice, self).invoice_validate()
+18 -37
View File
@@ -5,22 +5,24 @@
# =====
# Dieses Modul bietet Ihnen einen deutschen Kontenplan basierend auf dem SKR03.
# Gemäss der aktuellen Einstellungen ist die Firma nicht Umsatzsteuerpflichtig.
# Diese Grundeinstellung ist sehr einfach zu ändern und bedarf in der Regel
# grundsätzlich eine initiale Zuweisung von Steuerkonten zu Produkten und / oder
# Sachkonten oder zu Partnern.
# Die Umsatzsteuern (voller Steuersatz, reduzierte Steuer und steuerfrei)
# sollten bei den Produktstammdaten hinterlegt werden (in Abhängigkeit der
# Steuervorschriften). Die Zuordnung erfolgt auf dem Aktenreiter Finanzbuchhaltung
# (Kategorie: Umsatzsteuer).
# Die Vorsteuern (voller Steuersatz, reduzierte Steuer und steuerfrei)
# sollten ebenso bei den Produktstammdaten hinterlegt werden (in Abhängigkeit
# der Steuervorschriften). Die Zuordnung erfolgt auf dem Aktenreiter
# Finanzbuchhaltung (Kategorie: Vorsteuer).
# Gemäss der aktuellen Einstellungen ist ein neues Unternehmen in Odoo
# Umsatzsteuerpflichtig. Zahlreiche Erlös- und Aufwandskonten enthalten
# bereits eine zugeordnete Steuer. Hierdurch wird für diese Konten bereits
# die richtige Vorsteuer (Eingangsrechnungen) bzw. Umsatzsteuer
# (Ausgangsrechnungen) automatisch ausgewählt.
#
# Die Zuordnung von Steuerkonten zu Produkten und / oder Sachkonten kann
# für den jeweiligen betrieblichen Anwendungszweck überarbeitet oder
# auch erweitert werden.
# Die mit diesem Kontenrahmen installierten Steuerschlüssel (z.B. 19%, 7%,
# steuerfrei) können hierzu bei den Produktstammdaten hinterlegt werden
# (in Abhängigkeit der Steuervorschriften). Die Zuordnung erfolgt auf
# dem Aktenreiter Finanzbuchhaltung (Kategorie: Umsatzsteuer / Vorsteuer).
# Die Zuordnung der Steuern für Ein- und Ausfuhren aus EU Ländern, sowie auch
# für den Ein- und Verkauf aus und in Drittländer sollten beim Partner
# (Lieferant/Kunde)hinterlegt werden (in Anhängigkeit vom Herkunftsland
# des Lieferanten/Kunden). Die Zuordnung beim Kunden ist 'höherwertig' als
# (Lieferant / Kunde) hinterlegt werden (in Anhängigkeit vom Herkunftsland
# des Lieferanten/Kunden). Diese Zuordnung ist 'höherwertig' als
# die Zuordnung bei Produkten und überschreibt diese im Einzelfall.
#
# Zur Vereinfachung der Steuerausweise und Buchung bei Auslandsgeschäften
@@ -28,32 +30,10 @@
# (z.B. Zuordnung 'Umsatzsteuer 19%' zu 'steuerfreie Einfuhren aus der EU')
# zwecks Zuordnung dieses Mappings zum ausländischen Partner (Kunde/Lieferant).
# Die Rechnungsbuchung beim Einkauf bewirkt folgendes:
# Die Steuerbemessungsgrundlage (exklusive Steuer) wird ausgewiesen bei den
# jeweiligen Kategorien für den Vorsteuer Steuermessbetrag (z.B. Vorsteuer
# Steuermessbetrag Voller Steuersatz 19%).
# Der Steuerbetrag erscheint unter der Kategorie 'Vorsteuern' (z.B. Vorsteuer
# 19%). Durch multidimensionale Hierachien können verschiedene Positionen
# zusammengefasst werden und dann in Form eines Reports ausgegeben werden.
#
# Die Rechnungsbuchung beim Verkauf bewirkt folgendes:
# Die Steuerbemessungsgrundlage (exklusive Steuer) wird ausgewiesen bei den
# jeweiligen Kategorien für den Umsatzsteuer Steuermessbetrag
# (z.B. Umsatzsteuer Steuermessbetrag Voller Steuersatz 19%).
# Der Steuerbetrag erscheint unter der Kategorie 'Umsatzsteuer'
# (z.B. Umsatzsteuer 19%). Durch multidimensionale Hierachien können
# verschiedene Positionen zusammengefasst werden.
# Die zugewiesenen Steuerausweise können auf Ebene der einzelnen
# Rechnung (Eingangs- und Ausgangsrechnung) nachvollzogen werden,
# und dort gegebenenfalls angepasst werden.
# Rechnungsgutschriften führen zu einer Korrektur (Gegenposition)
# der Steuerbuchung, in Form einer spiegelbildlichen Buchung.
{
'name': 'Deutschland SKR03 - Accounting',
'version': '2.0',
'version': '3.0',
'author': 'openbig.org',
'website': 'http://www.openbig.org',
'category': 'Localization',
@@ -68,6 +48,7 @@ German accounting chart and localization.
'data/l10n_de_skr03_chart_data.xml',
'data/account_data.xml',
'data/account_tax_fiscal_position_data.xml',
'data/account_reconcile_model_template.xml',
'data/account_chart_template_data.xml',
],
'auto_install': True
+12 -3
View File
@@ -4,13 +4,22 @@
<!-- Account Tax Group -->
<record id="tax_group_0" model="account.tax.group">
<field name="name">Tax 0%</field>
<field name="name">USt 0%</field>
</record>
<record id="tax_group_7" model="account.tax.group">
<field name="name">Tax 7%</field>
<field name="name">USt 7%</field>
</record>
<record id="tax_group_55" model="account.tax.group">
<field name="name">USt 5,5%</field>
</record>
<record id="tax_group_107" model="account.tax.group">
<field name="name">USt 10,7%</field>
</record>
<record id="tax_group_x" model="account.tax.group">
<field name="name">USt x%</field>
</record>
<record id="tax_group_19" model="account.tax.group">
<field name="name">Tax 19%</field>
<field name="name">USt 19%</field>
</record>
</data>
</odoo>
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="reconcile_3731" model="account.reconcile.model.template">
<field name="name">Skonto-EK-7%</field>
<field name="account_id" ref="account_3731"/>
<field name="amount_type">percentage</field>
<field name="tax_id" ref="tax_vst_7_taxinclusive_skr03"/>
<field name="amount">100</field>
<field name="label">Skonto-EK-7%</field>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="reconcile_3736" model="account.reconcile.model.template">
<field name="name">Skonto-EK-19%</field>
<field name="account_id" ref="account_3736"/>
<field name="amount_type">percentage</field>
<field name="tax_id" ref="tax_vst_19_taxinclusive_skr03"/>
<field name="amount">100</field>
<field name="label">Skonto-EK-19%</field>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="reconcile_8731" model="account.reconcile.model.template">
<field name="name">Skonto-VK-7%</field>
<field name="account_id" ref="account_8731"/>
<field name="amount_type">percentage</field>
<field name="tax_id" ref="tax_ust_7_taxinclusive_skr03"/>
<field name="amount">100</field>
<field name="label">Skonto-VK-7%</field>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="reconcile_8736" model="account.reconcile.model.template">
<field name="name">Skonto-VK-19%</field>
<field name="account_id" ref="account_8736"/>
<field name="amount_type">percentage</field>
<field name="tax_id" ref="tax_ust_19_taxinclusive_skr03"/>
<field name="amount">100</field>
<field name="label">Skonto-VK-19%</field>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="reconcile_2401" model="account.reconcile.model.template">
<field name="name">Forderungsverlust-7%</field>
<field name="account_id" ref="account_2401"/>
<field name="amount_type">percentage</field>
<field name="tax_id" ref="tax_ust_7_taxinclusive_skr03"/>
<field name="amount">100</field>
<field name="label">Forderungsverlust-7%</field>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
<record id="reconcile_2406" model="account.reconcile.model.template">
<field name="name">Forderungsverlust-19%</field>
<field name="account_id" ref="account_2406"/>
<field name="amount_type">percentage</field>
<field name="tax_id" ref="tax_ust_19_taxinclusive_skr03"/>
<field name="amount">100</field>
<field name="label">Forderungsverlust-19%</field>
<field name="chart_template_id" ref="l10n_de_chart_template"/>
</record>
</data>
</odoo>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+18 -40
View File
@@ -3,26 +3,25 @@
# SKR04
# =====
# Dieses Modul bietet Ihnen einen deutschen Kontenplan basierend auf dem SKR03.
# Gemäss der aktuellen Einstellungen ist ein neues Unternehmen in Odoo
# Umsatzsteuerpflichtig. Zahlreiche Erlös- und Aufwandskonten enthalten
# bereits eine zugeordnete Steuer. Hierdurch wird für diese Konten bereits
# die richtige Vorsteuer (Eingangsrechnungen) bzw. Umsatzsteuer
# (Ausgangsrechnungen) automatisch ausgewählt.
#
# Die Zuordnung von Steuerkonten zu Produkten und / oder Sachkonten kann
# für den jeweiligen betrieblichen Anwendungszweck überarbeitet oder
# auch erweitert werden.
# Die mit diesem Kontenrahmen installierten Steuerschlüssel (z.B. 19%, 7%,
# steuerfrei) können hierzu bei den Produktstammdaten hinterlegt werden
# (in Abhängigkeit der Steuervorschriften). Die Zuordnung erfolgt auf
# dem Aktenreiter Finanzbuchhaltung (Kategorie: Umsatzsteuer / Vorsteuer).
# Dieses Modul bietet Ihnen einen deutschen Kontenplan basierend auf dem SKR04.
# Gemäss der aktuellen Einstellungen ist die Firma nicht Umsatzsteuerpflichtig,
# d.h. im Standard existiert keine Zuordnung von Produkten und Sachkonten zu
# Steuerschlüsseln.
# Diese Grundeinstellung ist sehr einfach zu ändern und bedarf in der Regel
# grundsätzlich eine initiale Zuweisung von Steuerschlüsseln zu Produkten und / oder
# Sachkonten oder zu Partnern.
# Die Umsatzsteuern (voller Steuersatz, reduzierte Steuer und steuerfrei)
# sollten bei den Produktstammdaten hinterlegt werden (in Abhängigkeit der
# Steuervorschriften). Die Zuordnung erfolgt auf dem Aktenreiter Finanzbuchhaltung
# (Kategorie: Umsatzsteuer).
# Die Vorsteuern (voller Steuersatz, reduzierte Steuer und steuerfrei)
# sollten ebenso bei den Produktstammdaten hinterlegt werden (in Abhängigkeit
# der Steuervorschriften). Die Zuordnung erfolgt auf dem Aktenreiter
# Finanzbuchhaltung (Kategorie: Vorsteuer).
# Die Zuordnung der Steuern für Ein- und Ausfuhren aus EU Ländern, sowie auch
# für den Ein- und Verkauf aus und in Drittländer sollten beim Partner
# (Lieferant/Kunde) hinterlegt werden (in Anhängigkeit vom Herkunftsland
# des Lieferanten/Kunden). Die Zuordnung beim Kunden ist 'höherwertig' als
# (Lieferant / Kunde) hinterlegt werden (in Anhängigkeit vom Herkunftsland
# des Lieferanten/Kunden). Diese Zuordnung ist 'höherwertig' als
# die Zuordnung bei Produkten und überschreibt diese im Einzelfall.
#
# Zur Vereinfachung der Steuerausweise und Buchung bei Auslandsgeschäften
@@ -30,32 +29,10 @@
# (z.B. Zuordnung 'Umsatzsteuer 19%' zu 'steuerfreie Einfuhren aus der EU')
# zwecks Zuordnung dieses Mappings zum ausländischen Partner (Kunde/Lieferant).
# Die Rechnungsbuchung beim Einkauf bewirkt folgendes:
# Die Steuerbemessungsgrundlage (exklusive Steuer) wird ausgewiesen bei den
# jeweiligen Kategorien für den Vorsteuer Steuermessbetrag (z.B. Vorsteuer
# Steuermessbetrag Voller Steuersatz 19%).
# Der Steuerbetrag erscheint unter der Kategorie 'Vorsteuern' (z.B. Vorsteuer
# 19%). Durch multidimensionale Hierachien können verschiedene Positionen
# zusammengefasst werden und dann in Form eines Reports ausgegeben werden.
#
# Die Rechnungsbuchung beim Verkauf bewirkt folgendes:
# Die Steuerbemessungsgrundlage (exklusive Steuer) wird ausgewiesen bei den
# jeweiligen Kategorien für den Umsatzsteuer Steuermessbetrag
# (z.B. Umsatzsteuer Steuermessbetrag Voller Steuersatz 19%).
# Der Steuerbetrag erscheint unter der Kategorie 'Umsatzsteuer'
# (z.B. Umsatzsteuer 19%). Durch multidimensionale Hierachien können
# verschiedene Positionen zusammengefasst werden.
# Die zugewiesenen Steuerausweise können auf Ebene der einzelnen
# Rechnung (Eingangs- und Ausgangsrechnung) nachvollzogen werden,
# und dort gegebenenfalls angepasst werden.
# Rechnungsgutschriften führen zu einer Korrektur (Gegenposition)
# der Steuerbuchung, in Form einer spiegelbildlichen Buchung.
{
'name': 'Deutschland SKR04 - Accounting',
'version': '2.0',
'version': '3.0',
'author': 'openbig.org',
'website': 'http://www.openbig.org',
'category': 'Localization',
@@ -70,6 +47,7 @@ German accounting chart and localization.
'data/l10n_de_skr04_chart_data.xml',
'data/account_data.xml',
'data/account_tax_fiscal_position_data.xml',
'data/account_reconcile_model_template.xml',
'data/account_chart_template_data.xml',
],
}
+12 -3
View File
@@ -4,13 +4,22 @@
<!-- Account Tax Group -->
<record id="tax_group_0" model="account.tax.group">
<field name="name">Tax 0%</field>
<field name="name">USt 0%</field>
</record>
<record id="tax_group_7" model="account.tax.group">
<field name="name">Tax 7%</field>
<field name="name">USt 7%</field>
</record>
<record id="tax_group_55" model="account.tax.group">
<field name="name">USt 5,5%</field>
</record>
<record id="tax_group_107" model="account.tax.group">
<field name="name">USt 10,7%</field>
</record>
<record id="tax_group_x" model="account.tax.group">
<field name="name">USt x%</field>
</record>
<record id="tax_group_19" model="account.tax.group">
<field name="name">Tax 19%</field>
<field name="name">USt 19%</field>
</record>
</data>
</odoo>
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="reconcile_5731" model="account.reconcile.model.template">
<field name="name">Skonto-EK-7%</field>
<field name="account_id" ref="chart_skr04_5731"/>
<field name="amount_type">percentage</field>
<field name="tax_id" ref="tax_vst_7_taxinclusive_skr04"/>
<field name="amount">100</field>
<field name="label">Skonto-EK-7%</field>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="reconcile_5736" model="account.reconcile.model.template">
<field name="name">Skonto-EK-19%</field>
<field name="account_id" ref="chart_skr04_5736"/>
<field name="amount_type">percentage</field>
<field name="tax_id" ref="tax_vst_19_taxinclusive_skr04"/>
<field name="amount">100</field>
<field name="label">Skonto-EK-19%</field>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="reconcile_4731" model="account.reconcile.model.template">
<field name="name">Skonto-VK-7%</field>
<field name="account_id" ref="chart_skr04_4731"/>
<field name="amount_type">percentage</field>
<field name="tax_id" ref="tax_ust_7_taxinclusive_skr04"/>
<field name="amount">100</field>
<field name="label">Skonto-VK-7%</field>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="reconcile_4736" model="account.reconcile.model.template">
<field name="name">Skonto-VK-19%</field>
<field name="account_id" ref="chart_skr04_4736"/>
<field name="amount_type">percentage</field>
<field name="tax_id" ref="tax_ust_19_taxinclusive_skr04"/>
<field name="amount">100</field>
<field name="label">Skonto-VK-19%</field>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="reconcile_6931" model="account.reconcile.model.template">
<field name="name">Forderungsverlust-7%</field>
<field name="account_id" ref="chart_skr04_6931"/>
<field name="amount_type">percentage</field>
<field name="tax_id" ref="tax_ust_7_taxinclusive_skr04"/>
<field name="amount">100</field>
<field name="label">Forderungsverlust-7%</field>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
<record id="reconcile_6936" model="account.reconcile.model.template">
<field name="name">Forderungsverlust-19%</field>
<field name="account_id" ref="chart_skr04_6936"/>
<field name="amount_type">percentage</field>
<field name="tax_id" ref="tax_ust_19_taxinclusive_skr04"/>
<field name="amount">100</field>
<field name="label">Forderungsverlust-19%</field>
<field name="chart_template_id" ref="l10n_chart_de_skr04"/>
</record>
</data>
</odoo>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -63,7 +63,7 @@ def format_amount(env, amount, currency):
lang = env['res.lang']._lang_get(env.context.get('lang') or 'en_US')
formatted_amount = lang.format(fmt, currency.round(amount), grouping=True, monetary=True)\
.replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'\u2011')
.replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}')
pre = post = u''
if currency.position == 'before':
@@ -249,6 +249,11 @@ class PosConfig(models.Model):
if any(self.journal_ids.mapped(lambda journal: journal.currency_id and journal.currency_id != self.currency_id)):
raise ValidationError(_("All payment methods must be in the same currency as the Sales Journal or the company currency if that is not set."))
@api.constrains('company_id', 'available_pricelist_ids')
def _check_companies(self):
if any(self.available_pricelist_ids.mapped(lambda pl: pl.company_id.id not in (False, self.company_id.id))):
raise ValidationError(_("The selected pricelists must belong to no company or the company of the point of sale."))
@api.onchange('iface_print_via_proxy')
def _onchange_iface_print_via_proxy(self):
self.iface_print_auto = self.iface_print_via_proxy
+1 -1
View File
@@ -228,7 +228,7 @@ class ProductProduct(models.Model):
def _compute_partner_ref(self):
for supplier_info in self.seller_ids:
if supplier_info.name.id == self._context.get('partner_id'):
product_name = supplier_info.product_name or self.default_code
product_name = supplier_info.product_name or self.default_code or self.name
self.partner_ref = '%s%s' % (self.code and '[%s] ' % self.code or '', product_name)
break
else:
+3
View File
@@ -180,6 +180,9 @@ class CustomerPortal(CustomerPortal):
@http.route(['/my/task/<int:task_id>'], type='http', auth="user", website=True)
def portal_my_task(self, task_id=None, **kw):
task = request.env['project.task'].browse(task_id)
task.check_access_rights('read')
task.check_access_rule('read')
vals = {
'task': task,
'user': request.env.user
+2 -2
View File
@@ -154,8 +154,8 @@
</button>
</div>
<div class="oe_title">
<label string="Request for Quotation " attrs="{'invisible': [('state','not in',('draft','sent','bid'))]}"/>
<label string="Purchase Order " attrs="{'invisible': [('state','in',('draft','sent','bid'))]}"/>
<label string="Request for Quotation " attrs="{'invisible': [('state','not in',('draft','sent'))]}"/>
<label string="Purchase Order " attrs="{'invisible': [('state','in',('draft','sent'))]}"/>
<h1>
<field name="name" readonly="1"/>
</h1>
+9
View File
@@ -209,3 +209,12 @@ class Orderpoint(models.Model):
result['domain'] = "[('id','in',%s)]" % (purchase_ids.ids)
return result
class PushedFlow(models.Model):
_inherit = "stock.location.path"
def _prepare_move_copy_values(self, move_to_copy, new_date):
res = super()._prepare_move_copy_values(move_to_copy, new_date)
res['purchase_line_id'] = None
return res
+2 -1
View File
@@ -236,12 +236,13 @@
</div>
<t t-foreach="order.order_line" t-as="ol">
<div class="row orders_vertical_align">
<div class="visible-lg col-lg-1 text-center">
<div t-if="not request.env.user._is_public()" class="visible-lg col-lg-1 text-center">
<span t-field="ol.product_image" t-options="{'widget': 'image', 'class': 'img-rounded', 'style':'width: 48px;height:48px'}" />
</div>
<div id='product_name' t-att-class="'col-lg-3 col-sm-5 col-xs-4' if display_discount else 'col-lg-4 col-sm-6 col-xs-3'">
<span t-esc="ol.product_id.name"/>
</div>
<div t-if="request.env.user._is_public()" class="visible-lg col-lg-1 text-center" /> <!-- Just for alignment -->
<div class="col-sm-3 hidden-xs text-right">
<span t-field="ol.price_unit" t-options='{"widget": "monetary", "display_currency": order.pricelist_id.currency_id}'/>
</div>
@@ -6,7 +6,7 @@
<record id="account_analytic_line_rule_billing_user" model="ir.rule">
<field name="name">account.analytic.line.billing.user</field>
<field name="model_id" ref="analytic.model_account_analytic_line"/>
<field name="domain_force">[('project_id', '=', False)]</field>
<field name="domain_force">[(1, '=', 1)]</field>
<field name="groups" eval="[(4, ref('account.group_account_invoice'))]"/>
<field name="perm_create" eval="1"/>
<field name="perm_write" eval="1"/>
+5 -5
View File
@@ -28,11 +28,11 @@ class Company(models.Model):
location.sudo().write({'company_id': company.id})
company.write({'internal_transit_location_id': location.id})
warehouses = self.env['stock.warehouse'].search([('partner_id', '=', self.partner_id.id)])
warehouses.mapped('partner_id').with_context(force_company=self.id).write({
'property_stock_customer': location.id,
'property_stock_supplier': location.id,
})
warehouses = self.env['stock.warehouse'].search([('partner_id', '=', company.partner_id.id)])
warehouses.mapped('partner_id').with_context(force_company=company.id).write({
'property_stock_customer': location.id,
'property_stock_supplier': location.id,
})
@api.model
def create(self, vals):
+2 -2
View File
@@ -262,12 +262,12 @@ class StockQuant(models.Model):
# if we want to reserve
available_quantity = self._get_available_quantity(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict)
if float_compare(quantity, available_quantity, precision_rounding=rounding) > 0:
raise UserError(_('It is not possible to reserve more products of %s than you have in stock.') % (', '.join(quants.mapped('product_id').mapped('display_name'))))
raise UserError(_('It is not possible to reserve more products of %s than you have in stock.') % product_id.display_name)
elif float_compare(quantity, 0, precision_rounding=rounding) < 0:
# if we want to unreserve
available_quantity = sum(quants.mapped('reserved_quantity'))
if float_compare(abs(quantity), available_quantity, precision_rounding=rounding) > 0:
raise UserError(_('It is not possible to unreserve more products of %s than you have in stock.') % (', '.join(quants.mapped('product_id').mapped('display_name'))))
raise UserError(_('It is not possible to unreserve more products of %s than you have in stock.') % product_id.display_name)
else:
return reserved_quants
+25 -5
View File
@@ -695,11 +695,31 @@ class Warehouse(models.Model):
def _get_sequence_values(self):
return {
'in_type_id': {'name': self.name + ' ' + _('Sequence in'), 'prefix': self.code + '/IN/', 'padding': 5},
'out_type_id': {'name': self.name + ' ' + _('Sequence out'), 'prefix': self.code + '/OUT/', 'padding': 5},
'pack_type_id': {'name': self.name + ' ' + _('Sequence packing'), 'prefix': self.code + '/PACK/', 'padding': 5},
'pick_type_id': {'name': self.name + ' ' + _('Sequence picking'), 'prefix': self.code + '/PICK/', 'padding': 5},
'int_type_id': {'name': self.name + ' ' + _('Sequence internal'), 'prefix': self.code + '/INT/', 'padding': 5},
'in_type_id': {
'name': self.name + ' ' + _('Sequence in'),
'prefix': self.code + '/IN/', 'padding': 5,
'company_id': self.company_id.id,
},
'out_type_id': {
'name': self.name + ' ' + _('Sequence out'),
'prefix': self.code + '/OUT/', 'padding': 5,
'company_id': self.company_id.id,
},
'pack_type_id': {
'name': self.name + ' ' + _('Sequence packing'),
'prefix': self.code + '/PACK/', 'padding': 5,
'company_id': self.company_id.id,
},
'pick_type_id': {
'name': self.name + ' ' + _('Sequence picking'),
'prefix': self.code + '/PICK/', 'padding': 5,
'company_id': self.company_id.id,
},
'int_type_id': {
'name': self.name + ' ' + _('Sequence internal'),
'prefix': self.code + '/INT/', 'padding': 5,
'company_id': self.company_id.id,
},
}
def _format_rulename(self, from_loc, dest_loc, suffix):
@@ -107,7 +107,7 @@ class LandedCost(models.Model):
'landed_cost_value': new_landed_cost_value,
'value': line.move_id.value + cost_to_add,
'remaining_value': line.move_id.remaining_value + cost_to_add,
'price_unit': (line.move_id.value + new_landed_cost_value) / line.move_id.product_qty,
'price_unit': (line.move_id.value + cost_to_add) / line.move_id.product_qty,
})
# `remaining_qty` is negative if the move is out and delivered proudcts that were not
# in stock.
+1 -1
View File
@@ -25,7 +25,7 @@ class Http(models.AbstractModel):
return {
"session_id": request.session.sid,
"uid": request.session.uid,
"is_system": user._is_system(),
"is_system": user._is_system() if request.session.uid else False,
"is_superuser": user._is_superuser() if request.session.uid else False,
"user_context": request.session.get_context() if request.session.uid else {},
"db": request.session.db,
+1
View File
@@ -48,6 +48,7 @@ ol {
ul {
list-style: disc;
padding-left: 40px;
}
ul ul {
@@ -704,7 +704,7 @@ ActionManager.include({
var env = ev.data.env;
var context = new Context(env.context, actionData.context || {});
var recordID = env.currentID || null; // pyeval handles null value, not undefined
var def;
var def = $.Deferred();
// determine the action to execute according to the actionData
if (actionData.special) {
@@ -1843,7 +1843,7 @@ var StatInfo = AbstractField.extend({
*/
_render: function () {
var options = {
value: this.value || 0,
value: this._formatValue(this.value || 0),
};
if (! this.attrs.nolabel) {
if (this.nodeOptions.label_field && this.recordData[this.nodeOptions.label_field]) {
@@ -141,6 +141,7 @@ var KanbanColumnProgressBar = Widget.extend({
}
// Display and animate the progress bars
var barNumber = 0;
_.each(self.colors, function (val, key) {
var $bar = self.$bars[val];
var count = self.subgroupCounts && self.subgroupCounts[key] || 0;
@@ -165,7 +166,14 @@ var KanbanColumnProgressBar = Widget.extend({
window.getComputedStyle($bar[0]).getPropertyValue('width'); // Force reflow so that animations work
if (count > 0) {
$bar.addClass('o_bar_has_records');
// Make sure every bar that has records has some space
// and that everything adds up to 100%
var minWidth = 6*barNumber;
var maxWidth = 100 - minWidth;
self.$('.progress-bar.o_bar_has_records').css('max-width', maxWidth + '%');
self.$('.progress-bar.o_bar_has_records').css('min-width', minWidth + '%');
$bar.css('width', (count * 100 / self.groupCount) + '%');
barNumber++;
} else {
$bar.css('width', '');
}
@@ -72,18 +72,16 @@
max-width: 70%;
line-height: 1.2;
.o_stat_value {
> .o_stat_value, > .o_stat_text {
@include o-text-overflow(block);
}
.o_stat_value {
font-weight: 700;
color: $o-brand-primary;
}
.o_stat_text {
@include o-text-overflow(block);
font-size: small;
.o_stat_value {
display: inline;
}
}
}
> span {
@@ -72,11 +72,6 @@
margin-bottom: 0;
box-shadow: none;
cursor: pointer;
// Be sure that a bar with data has a minimun width
&.o_bar_has_records {
min-width: 10px;
}
}
.o_kanban_counter_label {
@@ -4074,6 +4074,40 @@ QUnit.module('basic_fields', {
QUnit.module('StatInfo');
QUnit.test('statinfo widget formats decimal precision', function (assert) {
// sometimes the round method can return numbers such as 14.000001
// when asked to round a number to 2 decimals, as such is the behaviour of floats.
// we check that even in that eventuality, only two decimals are displayed
assert.expect(2);
this.data.partner.fields.monetary = {string: "Monetary", type: 'monetary'};
this.data.partner.records[0].monetary = 9.999999;
this.data.partner.records[0].currency_id = 1;
var form = createView({
View: FormView,
model: 'partner',
data: this.data,
arch: '<form string="Partners">' +
'<button class="oe_stat_button" name="items" icon="fa-gear">' +
'<field name="qux" widget="statinfo"/>' +
'</button>' +
'<button class="oe_stat_button" name="money" icon="fa-money">' +
'<field name="monetary" widget="statinfo"/>' +
'</button>' +
'</form>',
res_id: 1,
});
// formatFloat renders according to this.field.digits
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').eq(0).text(),
'0.4', "Default precision should be [16,1]");
assert.strictEqual(form.$('.oe_stat_button .o_field_widget.o_stat_info .o_stat_value').eq(1).text(),
'10.00', "Currency decimal precision should be 2");
form.destroy();
});
QUnit.test('statinfo widget in form view', function (assert) {
assert.expect(9);
@@ -8,6 +8,7 @@ var Widget = require('web.Widget');
var weContext = require('web_editor.context');
var rte = require('web_editor.rte');
var weWidgets = require('web_editor.widget');
var Dialog = require('web.Dialog');
var _t = core._t;
@@ -26,6 +27,7 @@ var RTETranslatorWidget = rte.Class.extend({
* @override
*/
_saveElement: function ($el, context, withLang) {
var self = this;
if ($el.data('oe-translation-id')) {
return this._rpc({
model: 'ir.translation',
@@ -35,7 +37,9 @@ var RTETranslatorWidget = rte.Class.extend({
this._getEscapedElement($el).html(),
context || weContext.get()
],
});
}).fail(function (error) {
Dialog.alert(null, error.data.message);
});
}
return this._super($el, context, withLang === undefined ? true : withLang);
},
@@ -1528,7 +1528,7 @@ var LinkDialog = Dialog.extend({
+ ((size && size.length) ? (' btn-' + size) : '');
var isNewWindow = this.$('input[name="is_new_window"]').prop('checked');
if (url.indexOf('@') >= 0 && url.indexOf('mailto:') < 0) {
if (url.indexOf('@') >= 0 && url.indexOf('mailto:') < 0 && !url.match(/^http[s]?/i)) {
url = ('mailto:' + url);
}
return {
@@ -72,7 +72,8 @@ class WebsiteHrRecruitment(http.Controller):
@http.route('/jobs/add', type='http', auth="user", website=True)
def jobs_add(self, **kwargs):
job = request.env['hr.job'].create({
# avoid branding of website_description by setting rendering_bundle in context
job = request.env['hr.job'].with_context(rendering_bundle=True).create({
'name': _('Job Title'),
})
return request.redirect("/jobs/detail/%s?enable_editor=1" % slug(job))
@@ -76,4 +76,4 @@ class WebsiteSaleDelivery(WebsiteSale):
lang = request.env['res.lang']._lang_get(request.env.context.get('lang') or 'en_US')
return lang.format(fmt, currency.round(amount), grouping=True, monetary=True)\
.replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'\u2011')
.replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}')
+11
View File
@@ -0,0 +1,11 @@
France, 2018-07-20
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,
tducasse 11507599+tducasse@users.noreply.github.com https://github.com/tducasse
+11
View File
@@ -113,6 +113,10 @@ Running the server
runs tests after installing modules
.. option:: --test-tags 'tag_1,tag_2,...,-tag_n'
select the tests to run by using tags.
.. option:: --dev <feature,feature,...,feature>
* ``all``: all the features below are activated
@@ -305,6 +309,13 @@ customize the amount of logging output
enables `log rotation <https://docs.python.org/2/library/logging.handlers.html#timedrotatingfilehandler>`_
daily, keeping 30 backups. Log rotation frequency and number of backups is
not configurable.
.. danger::
Built-in log rotation is not reliable in multi-workers scenarios
and may incur significant data loss. It is *strongly recommended* to
use an external log rotation utility or use system loggers (--syslog)
instead.
.. option:: --syslog
-2
View File
@@ -71,8 +71,6 @@ This means the first *group rule* restricts access, but any further
.. warning:: record rules do not apply to the Administrator user
:class: aphorism
although access rules do
.. _reference/security/fields:
Field Access
+144 -1
View File
@@ -64,6 +64,8 @@ related to testing Odoo content (modules, mainly):
.. autoclass:: odoo.tests.common.HttpCase
:members: browse_ref, ref, url_open, phantom_js
.. autofunction:: odoo.tests.common.tagged
By default, tests are run once right after the corresponding module has been
installed. Test cases can also be configured to run after all modules have
been installed, and not run right after the module installation:
@@ -111,6 +113,147 @@ supported.
.. _unittest documentation: https://docs.python.org/2/library/unittest.html
Test selection
--------------
In Odoo, Python tests can be tagged to facilitate the test selection when
running tests.
Subclasses of :class:`odoo.tests.common.BaseCase` (usually through
:class:`~odoo.tests.common.TransactionCase`,
:class:`~odoo.tests.common.SavepointCase` or
:class:`~odoo.tests.common.HttpCase`) are automatically tagged with
``standard``, ``at_install`` and their source module's name by default.
Invocation
^^^^^^^^^^
:option:`--test-tags <odoo-bin --test-tags>` can be used to select/filter tests
to run on the command-line.
This option defaults to ``+standard`` meaning tests tagged ``standard``
(explicitly or implicitly) will be run by default when starting Odoo
with :option:`--test-enable <odoo-bin --test-enable>`.
When writing tests, the :func:`~odoo.tests.common.tagged` decorator can be
used on **test classes** to add or remove tags.
The decorator's arguments are tag names, as strings.
.. danger:: :func:`~odoo.tests.common.tagged` is a class decorator, it has no
effect on functions or methods
Tags can be prefixed with the minus (``-``) sign, to *remove* them instead of
add or select them e.g. if you don't want your test to be executed by
default you can remove the ``standard`` tag:
.. code-block:: python
from odoo.tests import TransactionCase, tagged
@tagged('-standard', 'nice')
class NiceTest(TransactionCase):
...
This test will not be selected by default, to run it the relevant tag will
have to be selected explicitely:
.. code-block:: console
$ odoo-bin --test-enable --test-tags nice
Note that only the tests tagged ``nice`` are going to be executed. To run
*both* ``nice`` and ``standard`` tests, provide multiple values to
:option:`--test-tags <odoo-bin --test-tags>`: on the command-line, values
are *additive* (you're selecting all tests with *any* of the specified tags)
.. code-block:: console
$ odoo-bin --test-enable --test-tags nice,standard
The config switch parameter also accepts the ``+`` and ``-`` prefixes. The
``+`` prefix is implied and therefore, totaly optional. The ``-`` (minus)
prefix is made to deselect tests tagged with the prefixed tags, even if they
are selected by other specified tags e.g. if there are ``standard`` tests which
are also tagged as ``slow`` you can run all standard tests *except* the slow
ones:
.. code-block:: console
$ odoo-bin --test-enable --test-tags 'standard,-slow'
When you write a test that does not inherit from the
:class:`~odoo.tests.common.BaseCase`, this test will not have the default tags,
you have to add them explicitely to have the test included in the default test
suite. This is a common issue when using a simple ``unittest.TestCase`` as
they're not going to get run:
.. code-block:: python
import unittest
from odoo.tests import tagged
@tagged('standard', 'at_install')
class SmallTest(unittest.TestCase):
...
Special tags
^^^^^^^^^^^^
- ``standard``: All Odoo tests that inherit from
:class:`~odoo.tests.common.BaseCase` are implicitely tagged standard.
:option:`--test-tags <odoo-bin --test-tags>` also defaults to ``standard``.
That means untagged test will be executed by default when tests are enabled.
- ``at_install``: Means that the test will be executed right after the module
installation and before other modules are installed. This is a default
implicit tag.
- ``post_install``: Means that the test will be executed after all the modules
are installed. This is what you want for HttpCase tests most of the time.
Note that this is *not exclusive* with ``at_install``, however since you
will generally not want both ``post_install`` is usually paired with
``-at_install`` when tagging a test class.
- *module_name*: Odoo tests classes extending
:class:`~odoo.tests.common.BaseCase` are implicitely tagged with the
technical name of their module. This allows easily selecting or excluding
specific modules when testing e.g. if you want to only run tests from
``stock_account``:
.. code-block:: console
$ odoo-bin --test-enable --test-tags stock_account
Examples
^^^^^^^^
.. important::
Tests will be executed only in the installed or updated modules. So
modules have to be selected with the :option:`-u <odoo-bin -u>` or
:option:`-i <odoo-bin -i>` switches. For simplicity, those switches are
not specified in the examples below.
Run only the tests from the sale module:
.. code-block:: console
$ odoo-bin --test-enable --test-tags sale
Run the tests from the sale module but not the ones tagged as slow:
.. code-block:: console
$ odoo-bin --test-enable --test-tags 'sale,-slow'
Run only the tests from stock or tagged as slow:
.. code-block:: console
$ odoo-bin --test-enable --test-tags '-standard, slow, stock'
.. note:: ``-standard`` is implicit (not required), and present for clarity
Testing JS code
===============
@@ -153,4 +296,4 @@ that the web client and the server work together. In order to do that, we can
write another kind of test: tours. A tour is a mini scenario of some interesting
business flow. It explains a sequence of steps that should be followed. The
test runner will then create a phantom_js browser, point it to the proper url
and simulate the click and inputs, according to the scenario.
and simulate the click and inputs, according to the scenario.
+19 -13
View File
@@ -564,23 +564,29 @@ class IrModelFields(models.Model):
This method prevents the modification/deletion of many2one fields
that have an inverse one2many, for instance.
"""
failed_dependencies = []
for rec in self:
model = self.env[rec.model]
field = model._fields[rec.name]
for dependant, path in model._field_triggers.get(field, ()):
if dependant.manual:
failed_dependencies.append((field, dependant))
for inverse in model._field_inverses.get(field, ()):
if inverse.manual and inverse.type == 'one2many':
failed_dependencies.append((field, inverse))
if not self._context.get(MODULE_UNINSTALL_FLAG) and failed_dependencies:
msg = _("The field '%s' cannot be removed because the field '%s' depends on it.")
raise UserError(msg % failed_dependencies[0])
elif failed_dependencies:
dependants = {rel[1] for rel in failed_dependencies}
to_unlink = [self._get(field.model_name, field.name) for field in dependants]
self.browse().union(*to_unlink).unlink()
self = self.filtered(lambda record: record.state == 'manual')
if not self:
return
for record in self:
model = self.env[record.model]
field = model._fields[record.name]
if field.type == 'many2one' and model._field_inverses.get(field):
if self._context.get(MODULE_UNINSTALL_FLAG):
# automatically unlink the corresponding one2many field(s)
inverses = self.search([('relation', '=', field.model_name),
('relation_field', '=', field.name)])
inverses.unlink()
continue
msg = _("The field '%s' cannot be removed because the field '%s' depends on it.")
raise UserError(msg % (field, model._field_inverses[field][0]))
# remove fields from registry, and check that views are not broken
fields = [self.env[record.model]._pop_field(record.name) for record in self]
domain = expression.OR([('arch_db', 'like', record.name)] for record in self)
+6 -5
View File
@@ -7,6 +7,7 @@ from odoo import api, fields, models, _
from PIL import Image
import babel
from lxml import etree
import math
from odoo.tools import html_escape as escape, posix_to_ldml, safe_eval, float_utils, format_date, pycompat
@@ -125,7 +126,7 @@ class IntegerConverter(models.AbstractModel):
@api.model
def value_to_html(self, value, options):
return pycompat.to_text(self.user_lang().format('%d', value, grouping=True).replace(r'-', u'\u2011'))
return pycompat.to_text(self.user_lang().format('%d', value, grouping=True).replace(r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}'))
class FloatConverter(models.AbstractModel):
@@ -145,7 +146,7 @@ class FloatConverter(models.AbstractModel):
value = float_utils.float_round(value, precision_digits=precision)
fmt = '%.{precision}f'.format(precision=precision)
formatted = self.user_lang().format(fmt, value, grouping=True).replace(r'-', u'\u2011')
formatted = self.user_lang().format(fmt, value, grouping=True).replace(r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}')
# %f does not strip trailing zeroes. %g does but its precision causes
# it to switch to scientific notation starting at a million *and* to
@@ -327,7 +328,7 @@ class MonetaryConverter(models.AbstractModel):
lang = self.user_lang()
formatted_amount = lang.format(fmt, display_currency.round(value),
grouping=True, monetary=True).replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'\u2011')
grouping=True, monetary=True).replace(r' ', u'\N{NO-BREAK SPACE}').replace(r'-', u'-\N{ZERO WIDTH NO-BREAK SPACE}')
pre = post = u''
if display_currency.position == 'before':
@@ -373,9 +374,9 @@ class FloatTimeConverter(models.AbstractModel):
@api.model
def value_to_html(self, value, options):
factor = -1 if value < 0 else 1
sign = math.copysign(1.0, value)
hours, minutes = divmod(abs(value) * 60, 60)
return '%02d:%02d' % (hours * factor, minutes)
return '%02d:%02d' % (sign * hours, minutes)
class DurationConverter(models.AbstractModel):
@@ -616,6 +616,14 @@ class IrTranslation(models.Model):
SELECT 1 FROM ir_translation
WHERE lang=l.code AND type='model' AND name=%(name)s AND res_id=%(res_id)s
);
DELETE FROM ir_translation dup
WHERE type='model' AND name=%(name)s AND res_id=%(res_id)s
AND dup.id NOT IN (SELECT MAX(t.id)
FROM ir_translation t
WHERE t.lang=dup.lang AND type='model' AND name=%(name)s AND res_id=%(res_id)s
);
UPDATE ir_translation SET src=%(src)s
WHERE type='model' AND name=%(name)s AND res_id=%(res_id)s;
"""
+3
View File
@@ -136,6 +136,9 @@ class Partner(models.Model):
def _default_company(self):
return self.env['res.company']._company_default_get('res.partner')
def _split_street_with_params(self, street_raw, street_format):
return {'street': street_raw}
name = fields.Char(index=True)
display_name = fields.Char(compute='_compute_display_name', store=True, index=True)
date = fields.Date(index=True)
+53 -45
View File
@@ -246,9 +246,6 @@ class TestCustomFields(common.TransactionCase):
self.registry.enter_test_mode(self.cr)
self.addCleanup(self.registry.leave_test_mode)
# do not reload the registry after removing a field
self.env = self.env(context={'_force_unlink': True})
def create_field(self, name):
""" create a custom field and return it """
model = self.env['ir.model'].search([('model', '=', self.MODEL)])
@@ -274,48 +271,6 @@ class TestCustomFields(common.TransactionCase):
with self.assertRaises(ValidationError):
self.create_field('foo')
def test_create_custom_o2m(self):
""" try creating a custom o2m and then deleting its m2o inverse """
model = self.env['ir.model'].search([('model', '=', self.MODEL)])
comodel = self.env['ir.model'].search([('model', '=', self.COMODEL)])
m2o_field = self.env['ir.model.fields'].create({
'model_id': comodel.id,
'name': 'x_my_m2o',
'field_description': 'my_m2o',
'ttype': 'many2one',
'relation': self.MODEL,
})
o2m_field = self.env['ir.model.fields'].create({
'model_id': model.id,
'name': 'x_my_o2m',
'field_description': 'my_o2m',
'ttype': 'one2many',
'relation': self.COMODEL,
'relation_field': m2o_field.name,
})
m2o_field.unlink()
self.assertFalse(o2m_field.exists())
def test_create_custom_related(self):
""" try creating a custom related then deleting its inverse """
comodel = self.env['ir.model'].search([('model', '=', self.COMODEL)])
field = self.create_field('x_my_char')
related_field = self.env['ir.model.fields'].create({
'model_id': comodel.id,
'name': 'x_oh_boy',
'field_description': 'x_oh_boy',
'ttype': 'char',
'related': 'partner_id.x_my_char',
})
field.unlink()
self.assertFalse(related_field.exists())
def test_rename_custom(self):
""" custom field names must be start with 'x_' """
field = self.create_field('x_foo')
@@ -375,3 +330,56 @@ class TestCustomFields(common.TransactionCase):
with self.assertRaises(UserError):
field.name = 'x_bar'
self.assertIn('x_foo', self.env[self.MODEL]._fields)
def test_unlink_with_inverse(self):
""" create a custom o2m and then delete its m2o inverse """
model = self.env['ir.model']._get(self.MODEL)
comodel = self.env['ir.model']._get(self.COMODEL)
m2o_field = self.env['ir.model.fields'].create({
'model_id': comodel.id,
'name': 'x_my_m2o',
'field_description': 'my_m2o',
'ttype': 'many2one',
'relation': self.MODEL,
})
o2m_field = self.env['ir.model.fields'].create({
'model_id': model.id,
'name': 'x_my_o2m',
'field_description': 'my_o2m',
'ttype': 'one2many',
'relation': self.COMODEL,
'relation_field': m2o_field.name,
})
# normal mode: you cannot break dependencies
with self.assertRaises(UserError):
m2o_field.unlink()
# uninstall mode: unlink dependant fields
m2o_field.with_context(_force_unlink=True).unlink()
self.assertFalse(o2m_field.exists())
def test_unlink_with_dependant(self):
""" create a computed field, then delete its dependency """
# Also applies to compute fields
comodel = self.env['ir.model'].search([('model', '=', self.COMODEL)])
field = self.create_field('x_my_char')
dependant = self.env['ir.model.fields'].create({
'model_id': comodel.id,
'name': 'x_oh_boy',
'field_description': 'x_oh_boy',
'ttype': 'char',
'related': 'partner_id.x_my_char',
})
# normal mode: you cannot break dependencies
with self.assertRaises(UserError):
field.unlink()
# uninstall mode: unlink dependant fields
field.with_context(_force_unlink=True).unlink()
self.assertFalse(dependant.exists())
+31
View File
@@ -275,6 +275,37 @@ class TestTranslation(TransactionCase):
self.assertEqual(categories.ids, [padawans.id, self.customers.id],
"Search ordered by translated name should return Padawans (Apprentis) before Customers (Clients)")
def test_105_duplicated_translation(self):
""" Test synchronizing translations with duplicated source """
# create a category with a French translation
padawans = self.env['res.partner.category'].create({'name': 'Padawan'})
self.env['ir.translation'].create({
'type': 'model',
'name': 'res.partner.category,name',
'module':'base',
'lang': 'fr_FR',
'res_id': padawans.id,
'value': 'Apprenti',
'state': 'translated',
})
# change name and insert a duplicate manually
padawans.write({'name': 'Padawans'})
self.env['ir.translation'].create({
'type': 'model',
'name': 'res.partner.category,name',
'module':'base',
'lang': 'fr_FR',
'res_id': padawans.id,
'value': 'Apprentis',
'state': 'translated',
})
self.env['ir.translation'].translate_fields('res.partner.category', padawans.id, 'name')
translations = self.env['ir.translation'].search([
('res_id', '=', padawans.id), ('name', '=', 'res.partner.category,name')
])
self.assertEqual(len(translations), 1, "Translations were not merged after `translate_fields` call")
self.assertEqual(translations.value, "Apprentis", "The most recent translation must stay")
class TestXMLTranslation(TransactionCase):
def setUp(self):
@@ -73,7 +73,7 @@ class TestFloatExport(TestBasicExport):
converter = self.get_converter('float')
value = converter(-42.0)
self.assertEqual(value, u"\u201142.0")
self.assertEqual(value, u"-\N{ZERO WIDTH NO-BREAK SPACE}42.0")
value = converter(42.0100)
self.assertEqual(value, "42.01")
@@ -120,7 +120,7 @@ class TestCurrencyExport(TestExport):
converted = self.convert(obj, dest=currency)
self.assertEqual(
converted, u'<span class="oe_currency_value">\u20110.12</span>'
converted, u'<span class="oe_currency_value">-\N{ZERO WIDTH NO-BREAK SPACE}0.12</span>'
u'\N{NO-BREAK SPACE}{symbol}'.format(
obj=obj,
symbol=currency.symbol
+37 -38
View File
@@ -139,14 +139,10 @@ class TestOnChange(common.TransactionCase):
self.env.cache.invalidate()
result = self.Discussion.onchange(values, 'name', field_onchange)
self.assertIn('messages', result['value'])
self.assertItemsEqual(result['value']['messages'], [
self.assertEqual(result['value']['messages'], [
(5,),
(1, message.id, {
'name': "[%s] %s" % ("Foo", USER.name),
'body': message.body,
'author': message.author.name_get()[0],
'size': message.size,
'important': message.important,
}),
(0, 0, {
'name': "[%s] %s" % ("Foo", USER.name),
@@ -157,6 +153,17 @@ class TestOnChange(common.TransactionCase):
}),
])
# ensure onchange changing one2many without subfield works
one_level_fields = {k: v for k, v in field_onchange.items() if k.count('.') < 1}
values = dict(values, name='{generate_dummy_message}')
result = self.Discussion.with_context(generate_dummy_message=True).onchange(values, 'name', one_level_fields)
self.assertEqual(result['value']['messages'], [
(5,),
(4, message.id),
(0, 0, {}),
(0, 0, {}),
])
def test_onchange_one2many_reference(self):
""" test the effect of onchange() on one2many fields with line references """
BODY = "What a beautiful day!"
@@ -245,8 +252,7 @@ class TestOnChange(common.TransactionCase):
'lines': [
(5,),
(1, line1.id, {'name': partner2.name,
'partner': (partner2.id, partner2.name),
'tags': [(5,)]}),
'partner': (partner2.id, partner2.name)}),
(0, 0, {'name': partner2.name,
'partner': (partner2.id, partner2.name),
'tags': [(5,)]}),
@@ -254,6 +260,17 @@ class TestOnChange(common.TransactionCase):
})
# do it again, but this time with a new tag on the second line
expected_new_tag_result = {
'name': partner2.name,
'lines': [
(5,),
(1, line1.id, {'name': partner2.name,
'partner': (partner2.id, partner2.name)}),
(0, 0, {'name': partner2.name,
'partner': (partner2.id, partner2.name),
'tags': [(5,), (0, 0, {'name': 'Tag'})]}),
],
}
values = {
'name': partner1.name,
'partner': partner2.id, # this one just changed
@@ -265,18 +282,13 @@ class TestOnChange(common.TransactionCase):
self.env.cache.invalidate()
result = multi.onchange(values, 'partner', field_onchange)
self.assertEqual(result['value'], {
'name': partner2.name,
'lines': [
(5,),
(1, line1.id, {'name': partner2.name,
'partner': (partner2.id, partner2.name),
'tags': [(5,)]}),
(0, 0, {'name': partner2.name,
'partner': (partner2.id, partner2.name),
'tags': [(5,), (0, 0, {'name': 'Tag'})]}),
],
})
self.assertEqual(result['value'], expected_new_tag_result)
# ensure ID is not returned when asked and a many2many record is set to be created
self.env.cache.invalidate()
result = multi.onchange(values, 'partner', dict(field_onchange, **{'lines.tags.id': None}))
self.assertEqual(result['value'], expected_new_tag_result)
def test_onchange_specific(self):
""" test the effect of field-specific onchange method """
@@ -308,8 +320,7 @@ class TestOnChange(common.TransactionCase):
self.assertIn('participants', result['value'])
self.assertItemsEqual(
result['value']['participants'],
[(5,)] + [(1, user.id, {'display_name': user.display_name})
for user in discussion.participants + demo],
[(5,)] + [(4, user.id) for user in discussion.participants + demo],
)
def test_onchange_default(self):
@@ -345,6 +356,8 @@ class TestOnChange(common.TransactionCase):
self.assertEqual(len(discussion.messages), 3)
messages = [(4, msg.id) for msg in discussion.messages]
messages[0] = (1, messages[0][1], {'body': 'test onchange'})
lines = ["%s:%s" % (m.name, m.body) for m in discussion.messages]
lines[0] = "%s:%s" % (discussion.messages[0].name, 'test onchange')
values = {
'name': discussion.name,
'moderator': demo.id,
@@ -355,8 +368,7 @@ class TestOnChange(common.TransactionCase):
}
result = discussion.onchange(values, 'messages', field_onchange)
self.assertIn('message_concat', result['value'])
self.assertEqual(result['value']['message_concat'],
"\n".join(["%s:%s" % (m.name, m.body) for m in discussion.messages]))
self.assertEqual(result['value']['message_concat'], "\n".join(lines))
def test_onchange_one2many_with_domain_on_related_field(self):
""" test the value of the one2many field when defined with a domain on a related field"""
@@ -402,28 +414,15 @@ class TestOnChange(common.TransactionCase):
'categories': [(4, cat.id) for cat in discussion.categories],
'messages': [(4, msg.id) for msg in discussion.messages],
'participants': [(4, usr.id) for usr in discussion.participants],
'message_changes': 0,
'important_messages': [(4, msg.id) for msg in discussion.important_messages],
'important_emails': [(4, eml.id) for eml in discussion.important_emails],
}
self.env.cache.invalidate()
result = discussion.onchange(values, 'name', field_onchange)
# When one2many domain contains non-computed field, things are ok
self.assertEqual(result['value']['important_messages'],
[(5,)] + [(4, msg.id) for msg in discussion.important_messages])
# But here with commit 5676d81, we get value of: [(2, email.id)]
self.assertEqual(
result['value']['important_emails'],
[(5,),
(1, email.id, {
'name': u'[Foo Bar] %s' % USER.name,
'body': email.body,
'author': USER.name_get()[0],
'important': True,
'email_to': demo.email,
'size': email.size,
})]
[(5,), (1, email.id, {'name': u'[Foo Bar] %s' % USER.name})],
)
def test_onchange_related(self):
+106 -29
View File
@@ -4982,28 +4982,114 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
if not all(name in self._fields for name in names):
return {}
# filter out keys in field_onchange that do not refer to actual fields
dotnames = []
for dotname in field_onchange:
try:
model = self.browse()
for name in dotname.split('.'):
model = model[name]
dotnames.append(dotname)
except Exception:
pass
class PrefixTree(OrderedDict):
""" A prefix tree for sequences of field names. The tree is a
dictionary that associates each given field name to its
corresponding subtree (in fields order)::
# tree corresponding to dotnames
# ['name', 'line_ids.product_id', 'line_ids.tags_ids.name']
{
'name': {},
'line_ids': {
'product_id': {},
'tags_ids': {
'name': {},
},
},
}
"""
def __init__(self, model, dotnames):
super(PrefixTree, self).__init__()
if not dotnames:
return
# group dotnames by prefix
suffixes = defaultdict(list)
for dotname in dotnames:
names = dotname.split('.', 1)
name_suffixes = suffixes[names[0]]
if len(names) > 1:
name_suffixes.append(names[1])
# fill in self in fields order
for name in model._fields:
if name in suffixes:
self[name] = PrefixTree(model[name], suffixes[name])
def dotnames(self):
""" Iterate over the sequences of field names. """
for name, subnames in self.items():
yield name
for dotname in subnames.dotnames():
yield "%s.%s" % (name, dotname)
nametree = PrefixTree(self.browse(), field_onchange)
dotnames = list(nametree.dotnames())
def snapshot(record, tree=nametree):
""" Return a dict with the values of record, following nametree. """
vals = {}
for name, subnames in tree.items():
if subnames:
# x2many fields as {line: snapshot(line), ...}
vals[name] = OrderedDict(
(line, snapshot(line, subnames))
for line in record[name]
)
else:
vals[name] = record[name]
return vals
def diff(record, old, new, tree=nametree):
""" Return the values that differ between snapshots.
The snapshot ``old`` may be empty (for new records).
"""
result = {}
for name, subnames in tree.items():
if name == 'id':
continue
if old and old[name] == new[name]:
continue
field = record._fields[name]
if not subnames:
result[name] = field.convert_to_onchange(new[name], record, {})
continue
# x2many fields: serialize value as commands
result[name] = commands = [(5,)]
old_val = old.get(name) or {}
for line, vals in new[name].items():
vals0 = (old_val.get(line) or snapshot(line, subnames)) if line.id else {}
line_diff = diff(line, vals0, vals, subnames)
if not line.id:
commands.append((0, line.id.ref or 0, line_diff))
elif line_diff:
commands.append((1, line.id, line_diff))
else:
commands.append((4, line.id))
return result
# prefetch x2many lines without data (for the initial snapshot)
for name, subnames in nametree.items():
if subnames and values.get(name):
# retrieve all ids in commands, and read the expected fields
line_ids = []
for cmd in values[name]:
if cmd[0] in (1, 4):
line_ids.append(cmd[1])
elif cmd[0] == 6:
line_ids.extend(cmd[2])
lines = self.browse()[name].browse(line_ids)
lines.read(list(subnames), load='_classic_write')
# create a new record with values, and attach ``self`` to it
with env.do_in_onchange():
record = self.new(values)
values = {name: record[name] for name in record._cache}
values = {name: record[name] for name in nametree}
# attach ``self`` with a different context (for cache consistency)
record._origin = self.with_context(__onchange=True)
# load fields on secondary records, to avoid false changes
# make a snapshot based on the initial values of record
with env.do_in_onchange():
for dotname in dotnames:
record.mapped(dotname)
before = snapshot(record)
# determine which field(s) should be triggered an onchange
todo = list(names) or list(values)
@@ -5022,7 +5108,6 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
record[name] = value
result = {}
dirty = set()
# process names in order (or the keys of values if no name given)
while todo:
@@ -5048,22 +5133,14 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
field.type in ('one2many', 'many2many') and newval._is_dirty()
):
todo.append(name)
dirty.add(name)
# determine subfields for field.convert_to_onchange() below
Tree = lambda: defaultdict(Tree)
subnames = Tree()
for dotname in dotnames:
subtree = subnames
for name in dotname.split('.'):
subtree = subtree[name]
# collect values from dirty fields
# make a snapshot based on the final values of record
with env.do_in_onchange():
result['value'] = {
name: self._fields[name].convert_to_onchange(record[name], record, subnames[name])
for name in dirty
}
after = snapshot(record)
# determine values that have changed by comparing snapshots
self.invalidate_cache()
result['value'] = diff(record, before, after)
return result
+1 -1
View File
@@ -390,7 +390,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
for (model, name) in cr.fetchall():
if model in registry and not registry[model]._abstract and not registry[model]._transient:
_logger.warning('The model %s has no access rules, consider adding one. E.g. access_%s,access_%s,model_%s,,1,0,0,0',
_logger.warning('The model %s has no access rules, consider adding one. E.g. access_%s,access_%s,model_%s,base.group_user,1,0,0,0',
model, model.replace('.', '_'), model.replace('.', '_'), model.replace('.', '_'))
# Temporary warning while we remove access rights on osv_memory objects, as they have
+1 -1
View File
@@ -131,7 +131,7 @@ class FSWatcher(object):
if isinstance(event, (FileCreatedEvent, FileModifiedEvent, FileMovedEvent)):
if not event.is_directory:
path = getattr(event, 'dest_path', event.src_path)
if path.endswith('.py'):
if path.endswith('.py') and not os.path.basename(path).startswith('.~'):
try:
source = open(path, 'rb').read() + b'\n'
compile(source, path, 'exec')
+10
View File
@@ -80,6 +80,11 @@ def at_install(flag):
By default, tests are run right after installing the module, before
starting the installation of the next module.
.. deprecated:: 12.0
``at_install`` is now a flag, you can use :func:`tagged` to
add/remove it, although ``tagged`` only works on test classes
"""
def decorator(obj):
obj.at_install = flag
@@ -93,6 +98,11 @@ def post_install(flag):
By default, tests are *not* run after installation of all modules in the
current installation set.
.. deprecated:: 12.0
``post_install`` is now a flag, you can use :func:`tagged` to
add/remove it, although ``tagged`` only works on test classes
"""
def decorator(obj):
obj.post_install = flag
+2 -2
View File
@@ -107,8 +107,8 @@ _SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [
'JUMP_FORWARD', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE', 'JUMP_ABSOLUTE',
# New in Python 2.7 - http://bugs.python.org/issue4715 :
'JUMP_IF_FALSE_OR_POP', 'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE',
'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'END_FINALLY', 'RAISE_VARARGS',
'LOAD_NAME', 'STORE_NAME', 'DELETE_NAME', 'LOAD_ATTR',
'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'SETUP_FINALLY', 'END_FINALLY',
'RAISE_VARARGS', 'LOAD_NAME', 'STORE_NAME', 'DELETE_NAME', 'LOAD_ATTR',
'LOAD_FAST', 'STORE_FAST', 'DELETE_FAST', 'UNPACK_SEQUENCE',
'LOAD_GLOBAL', # Only allows access to restricted globals
] if x in opmap))