summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2015-05-26 17:34:04 +0200
committerChristophe Simonis <chs@odoo.com>2015-05-28 14:27:38 +0200
commitfe2b5f35a40ca8df662130608b0f89427a196362 (patch)
tree0c7773f0599f3b1e6dbb869104fad0b04869b269 /addons
parentd4d8a2eae31e7a6a45029b84603e055300e3be36 (diff)
parentbf1e99964cdbc374f8a0c4cec1b338bf7c7c2930 (diff)
[MERGE] forward port of branch saas-6 up to bf1e999
Diffstat (limited to 'addons')
-rw-r--r--addons/account/account.py1
-rw-r--r--addons/account/account_bank_statement.py2
-rw-r--r--addons/account/account_invoice.py2
-rw-r--r--addons/account/report/account_invoice_report.py21
-rw-r--r--addons/account/res_config.py8
-rw-r--r--addons/account/static/src/css/account_reconciliation.scss9
-rw-r--r--addons/account/static/src/js/account_reconciliation_widgets.js9
-rw-r--r--addons/account/tests/test_search.py15
-rw-r--r--addons/account/views/account_invoice_view.xml2
-rw-r--r--addons/account/wizard/account_invoice_refund.py1
-rw-r--r--addons/account_analytic_analysis/account_analytic_analysis.py18
-rw-r--r--addons/account_asset/__init__.py21
-rw-r--r--addons/account_asset/__openerp__.py60
-rw-r--r--addons/account_asset/account_asset.py696
-rw-r--r--addons/account_asset/account_asset_demo.yml14
-rw-r--r--addons/account_asset/account_asset_invoice.py145
-rw-r--r--addons/account_asset/account_asset_invoice_view.xml29
-rw-r--r--addons/account_asset/report/__init__.py19
-rw-r--r--addons/account_asset/report/account_asset_report.py86
-rw-r--r--addons/account_asset/report/account_asset_report_view.xml115
-rw-r--r--addons/account_asset/security/account_asset_security.xml4
-rw-r--r--addons/account_asset/static/src/js/account_asset.js39
-rw-r--r--addons/account_asset/static/src/xml/account_asset_template.xml8
-rw-r--r--addons/account_asset/test/account_asset.yml35
-rw-r--r--addons/account_asset/test/account_asset_demo.yml9
-rw-r--r--addons/account_asset/test/account_asset_demo_test.xml108
-rw-r--r--addons/account_asset/test/account_asset_wizard.yml27
-rw-r--r--addons/account_asset/tests/__init__.py5
-rw-r--r--addons/account_asset/tests/test_account_asset.py128
-rw-r--r--addons/account_asset/views/account_asset.xml10
-rw-r--r--addons/account_asset/views/account_asset_invoice_view.xml39
-rw-r--r--addons/account_asset/views/account_asset_view.xml (renamed from addons/account_asset/account_asset_view.xml)227
-rw-r--r--addons/account_asset/wizard/__init__.py21
-rw-r--r--addons/account_asset/wizard/account_asset_change_duration.py138
-rw-r--r--addons/account_asset/wizard/account_asset_change_duration_view.xml82
-rw-r--r--addons/account_asset/wizard/wizard_asset_compute.py55
-rw-r--r--addons/account_asset/wizard/wizard_asset_compute_view.xml86
-rw-r--r--addons/account_cancel/account_cancel_view.xml3
-rw-r--r--addons/account_test/report/account_test_report.py3
-rw-r--r--addons/analytic_contract_hr_expense/analytic_contract_hr_expense.py4
-rw-r--r--addons/anonymization/anonymization.py1
-rw-r--r--addons/auth_ldap/users_ldap.py4
-rw-r--r--addons/barcodes/static/src/js/barcode_parser.js9
-rw-r--r--addons/base_action_rule/base_action_rule.py1
-rw-r--r--addons/base_import/models.py11
-rw-r--r--addons/calendar/calendar.py13
-rw-r--r--addons/crm/calendar_event_menu.xml11
-rw-r--r--addons/crm/crm_lead.py29
-rw-r--r--addons/crm/crm_lead_view.xml4
-rw-r--r--addons/crm/crm_phonecall.py1
-rw-r--r--addons/crm/security/ir.model.access.csv2
-rw-r--r--addons/crm_claim/crm_claim.py8
-rw-r--r--addons/crm_claim/test/process/claim.yml2
-rw-r--r--addons/crm_helpdesk/crm_helpdesk.py1
-rw-r--r--addons/crm_partner_assign/crm_lead.py1
-rw-r--r--addons/delivery/__openerp__.py3
-rw-r--r--addons/delivery/delivery.py3
-rw-r--r--addons/delivery/sale.py7
-rw-r--r--addons/delivery/test/stock_move_values_with_invoice_before_delivery.yml79
-rw-r--r--addons/document/document.py7
-rw-r--r--addons/event/event.py3
-rw-r--r--addons/event/event_view.xml2
-rw-r--r--addons/event/tests/test_mail_schedule.py2
-rw-r--r--addons/google_calendar/google_calendar.py10
-rw-r--r--addons/google_drive/google_drive.py1
-rw-r--r--addons/hr_timesheet/hr_timesheet.py4
-rw-r--r--addons/hr_timesheet_invoice/hr_timesheet_invoice.py4
-rw-r--r--addons/hw_proxy/controllers/main.py4
-rw-r--r--addons/hw_scale/__openerp__.py1
-rw-r--r--addons/hw_scale/controllers/main.py20
-rw-r--r--addons/hw_scanner/__openerp__.py1
-rw-r--r--addons/hw_scanner/controllers/main.py13
-rw-r--r--addons/im_chat/static/src/xml/im_chat.xml2
-rw-r--r--addons/im_livechat/models/im_chat_session.py5
-rw-r--r--addons/im_livechat/models/im_livechat_channel.py2
-rw-r--r--addons/im_livechat/static/src/js/im_livechat.js3
-rw-r--r--addons/im_odoo_support/static/src/js/im_odoo_support.js2
-rw-r--r--addons/l10n_ca/account_chart_en.xml430
-rw-r--r--addons/l10n_ca/account_chart_fr.xml455
-rw-r--r--addons/l10n_ca/account_tax_en.xml64
-rw-r--r--addons/l10n_ca/account_tax_fr.xml64
-rw-r--r--addons/l10n_fr/report/base_report.py1
-rw-r--r--addons/l10n_jp/__init__.py17
-rw-r--r--addons/l10n_jp/__openerp__.py53
-rw-r--r--addons/l10n_jp/data/account.account.template.csv157
-rw-r--r--addons/l10n_jp/data/account.chart.template.csv2
-rw-r--r--addons/l10n_jp/data/account.fiscal.position.template.csv7
-rw-r--r--addons/l10n_jp/data/account.tax.code.template.csv20
-rw-r--r--addons/l10n_jp/data/account.tax.template.csv9
-rw-r--r--addons/l10n_jp/static/description/icon.pngbin0 -> 8422 bytes
-rw-r--r--addons/mail/models/mail_alias.py1
-rw-r--r--addons/mail/models/mail_followers.py1
-rw-r--r--addons/mail/models/mail_message.py4
-rw-r--r--addons/mail/static/src/js/mail.js3
-rw-r--r--addons/mail/wizard/mail_compose_message.py4
-rw-r--r--addons/mass_mailing/models/mass_mailing_stats.py40
-rw-r--r--addons/mass_mailing/views/mass_mailing.xml2
-rw-r--r--addons/membership/membership_view.xml2
-rw-r--r--addons/mrp/mrp.py32
-rw-r--r--addons/mrp/mrp_demo.xml50
-rw-r--r--addons/mrp/mrp_view.xml1
-rw-r--r--addons/mrp/procurement.py4
-rw-r--r--addons/mrp/stock.py24
-rw-r--r--addons/mrp_operations/mrp_operations.py2
-rw-r--r--addons/mrp_repair/mrp_repair.py24
-rw-r--r--addons/note/note.py2
-rw-r--r--addons/payment/models/res_config.py2
-rw-r--r--addons/payment/views/res_config_view.xml4
-rw-r--r--addons/payment_adyen/controllers/main.py7
-rw-r--r--addons/payment_adyen/models/adyen.py4
-rw-r--r--addons/point_of_sale/point_of_sale_view.xml2
-rw-r--r--addons/point_of_sale/report/pos_details.py8
-rw-r--r--addons/point_of_sale/static/src/js/db.js2
-rw-r--r--addons/point_of_sale/static/src/js/models.js29
-rw-r--r--addons/point_of_sale/static/src/js/screens.js10
-rw-r--r--addons/point_of_sale/static/src/js/widget_base.js1
-rw-r--r--addons/point_of_sale/static/src/xml/pos.xml7
-rw-r--r--addons/point_of_sale/wizard/pos_details.py4
-rw-r--r--addons/portal/wizard/share_wizard.py30
-rw-r--r--addons/pos_restaurant/static/src/js/splitbill.js2
-rw-r--r--addons/product/partner_view.xml1
-rw-r--r--addons/product/pricelist.py9
-rw-r--r--addons/product/product.py6
-rw-r--r--addons/product/product_view.xml2
-rw-r--r--addons/product_visible_discount/product_visible_discount.py18
-rw-r--r--addons/project/project.py11
-rw-r--r--addons/project/project_view.xml2
-rw-r--r--addons/project/static/src/js/web_planner_project.js2
-rw-r--r--addons/project_issue/project_issue.py1
-rw-r--r--addons/purchase/partner_view.xml3
-rw-r--r--addons/purchase/purchase.py76
-rw-r--r--addons/purchase/report/purchase_report.py5
-rw-r--r--addons/purchase/stock.py4
-rw-r--r--addons/report/models/report.py3
-rw-r--r--addons/report/static/src/css/reset.min.css108
-rw-r--r--addons/report/views/layouts.xml20
-rw-r--r--addons/sale/__openerp__.py1
-rw-r--r--addons/sale/report/sale_report.py4
-rw-r--r--addons/sale/sale.py73
-rw-r--r--addons/sale/test/canceled_lines_order.yml60
-rw-r--r--addons/sale/test/manual_order_policy.yml2
-rw-r--r--addons/sale/wizard/sale_line_invoice.py2
-rw-r--r--addons/sale_layout/security/ir.model.access.csv2
-rw-r--r--addons/sale_margin/sale_margin.py14
-rw-r--r--addons/sale_mrp/sale_mrp.py10
-rw-r--r--addons/sale_mrp/tests/test_move_explode.py34
-rw-r--r--addons/sale_order_dates/sale_order_dates.py2
-rw-r--r--addons/sale_stock/__openerp__.py1
-rw-r--r--addons/sale_stock/sale_stock.py66
-rw-r--r--addons/sale_stock/test/sale_order_canceled_line.yml45
-rw-r--r--addons/sales_team/sales_team.py22
-rw-r--r--addons/share/wizard/share_wizard.py8
-rw-r--r--addons/stock/procurement.py2
-rw-r--r--addons/stock/product.py4
-rw-r--r--addons/stock/stock.py68
-rw-r--r--addons/stock/stock_view.xml5
-rw-r--r--addons/stock/test/inventory.yml2
-rw-r--r--addons/stock/wizard/stock_change_product_qty.py15
-rw-r--r--addons/stock/wizard/stock_change_product_qty_view.xml4
-rw-r--r--addons/stock/wizard/stock_transfer_details.py8
-rw-r--r--addons/stock_account/product.py13
-rw-r--r--addons/stock_account/product_view.xml4
-rw-r--r--addons/stock_account/stock.py11
-rw-r--r--addons/stock_account/stock_account.py10
-rw-r--r--addons/stock_account/wizard/stock_valuation_history.py4
-rw-r--r--addons/stock_dropshipping/__init__.py1
-rw-r--r--addons/stock_dropshipping/stock_dropshipping.py22
-rw-r--r--addons/stock_dropshipping/test/crossdock.yml86
-rw-r--r--addons/web/controllers/main.py11
-rw-r--r--addons/web/static/lib/datejs/globalization/bs-BA.js0
-rw-r--r--addons/web/static/lib/datejs/globalization/kab_DZ.js194
-rw-r--r--addons/web/static/lib/datejs/globalization/lo-LA.js0
-rw-r--r--addons/web/static/src/css/base.css20
-rw-r--r--addons/web/static/src/css/base.sass13
-rw-r--r--addons/web/static/src/js/action_manager.js3
-rw-r--r--addons/web/static/src/js/framework/crash_manager.js15
-rw-r--r--addons/web/static/src/js/tour.js8
-rw-r--r--addons/web/static/src/js/views/form_view.js2
-rw-r--r--addons/web/static/src/js/views/form_widgets.js10
-rw-r--r--addons/web/static/src/js/views/graph_widget.js1
-rw-r--r--addons/web/static/src/js/views/pivot_view.js13
-rw-r--r--addons/web/static/src/js/views/search_filters.js4
-rw-r--r--addons/web/static/src/js/views/search_inputs.js14
-rw-r--r--addons/web/static/src/js/views/search_view.js9
-rw-r--r--addons/web/static/src/js/widgets/sidebar.js1
-rw-r--r--addons/web/static/src/js/widgets/user_menu.js2
-rw-r--r--addons/web/static/src/xml/base.xml10
-rw-r--r--addons/web/static/test/search.js6
-rw-r--r--addons/web_diagram/controllers/main.py1
-rw-r--r--addons/web_kanban/static/src/js/kanban_view.js1
-rw-r--r--addons/website/controllers/main.py2
-rw-r--r--addons/website/models/ir_http.py145
-rw-r--r--addons/website/models/ir_ui_view.py4
-rw-r--r--addons/website/static/src/css/editor.css3
-rw-r--r--addons/website/static/src/css/editor.sass2
-rw-r--r--addons/website/static/src/css/website.css10
-rw-r--r--addons/website/static/src/css/website.sass8
-rw-r--r--addons/website/static/src/img/deers.jpgbin134084 -> 336308 bytes
-rw-r--r--addons/website/static/src/img/deers_thumb.jpgbin34440 -> 53388 bytes
-rw-r--r--addons/website/static/src/js/website.editor.js17
-rw-r--r--addons/website/static/src/js/website.js21
-rw-r--r--addons/website/static/src/js/website.seo.js2
-rw-r--r--addons/website/static/src/js/website.snippets.animation.js3
-rw-r--r--addons/website/static/src/js/website.translator.js19
-rw-r--r--addons/website/views/website_templates.xml6
-rw-r--r--addons/website_crm_score/controllers/main.py9
-rw-r--r--addons/website_crm_score/static/src/js/display_filters.js15
-rw-r--r--addons/website_crm_score/static/src/xml/track_page.xml12
-rw-r--r--addons/website_event_sale/controllers/main.py4
-rw-r--r--addons/website_forum/controllers/main.py9
-rw-r--r--addons/website_forum/static/src/css/website_forum.css102
-rw-r--r--addons/website_forum/static/src/css/website_forum.sass3
-rw-r--r--addons/website_forum/static/src/js/website_forum.share.js4
-rw-r--r--addons/website_forum/views/website_forum.xml8
-rw-r--r--addons/website_hr_recruitment/controllers/main.py16
-rw-r--r--addons/website_links/models/website_links.py2
-rw-r--r--addons/website_membership/__init__.py1
-rw-r--r--addons/website_membership/controllers/main.py51
-rw-r--r--addons/website_membership/models/__init__.py1
-rw-r--r--addons/website_membership/models/membership.py35
-rw-r--r--addons/website_quote/controllers/main.py2
-rw-r--r--addons/website_quote/models/order.py4
-rw-r--r--addons/website_quote/static/src/css/website_quotation.css5
-rw-r--r--addons/website_quote/views/website_quotation.xml10
-rw-r--r--addons/website_sale/controllers/main.py11
-rw-r--r--addons/website_sale/models/sale_order.py5
-rw-r--r--addons/website_sale/security/ir.model.access.csv4
-rw-r--r--addons/website_sale/static/src/js/website_sale.js7
-rw-r--r--addons/website_sale/views/templates.xml8
-rw-r--r--addons/website_sale_delivery/views/website_sale_delivery.xml1
-rw-r--r--addons/website_sale_options/static/src/js/website_sale.js2
-rw-r--r--addons/website_sale_options/views/templates.xml5
-rw-r--r--addons/website_slides/static/src/js/slides_upload.js2
233 files changed, 4074 insertions, 1887 deletions
diff --git a/addons/account/account.py b/addons/account/account.py
index ee0da138daf..04bf1a02fe9 100644
--- a/addons/account/account.py
+++ b/addons/account/account.py
@@ -5,6 +5,7 @@ import math
from openerp.osv import expression
from openerp.tools.float_utils import float_round as round
+from openerp.tools.safe_eval import safe_eval as eval
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.exceptions import UserError
from openerp import api, fields, models, _
diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py
index 866ad49c01d..d71735a12b7 100644
--- a/addons/account/account_bank_statement.py
+++ b/addons/account/account_bank_statement.py
@@ -2,11 +2,13 @@
from openerp import api, fields, models, _
from openerp.osv import expression
+from openerp.tools import float_is_zero
from openerp.tools import float_compare, float_round
from openerp.tools.misc import formatLang
from openerp.exceptions import UserError, ValidationError
import time
+import math
class AccountCashboxLine(models.Model):
""" Cash Box Details """
diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py
index e7ec5649f2f..a6e6ad03257 100644
--- a/addons/account/account_invoice.py
+++ b/addons/account/account_invoice.py
@@ -95,7 +95,7 @@ class AccountInvoice(models.Model):
if line.account_id.internal_type in ('receivable', 'payable'):
residual_signed += line.amount_residual * sign
if line.currency_id == self.currency_id:
- residual += line.currency_id and line.amount_residual_currency or line.amount_residual
+ residual += line.amount_residual_currency if line.currency_id else line.amount_residual
else:
from_currency = (line.currency_id and line.currency_id.with_context(date=line.date)) or line.company_id.currency_id.with_context(date=line.date)
residual += from_currency.compute(line.amount_residual, self.currency_id)
diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py
index 4a6f923979c..b324d06aded 100644
--- a/addons/account/report/account_invoice_report.py
+++ b/addons/account/report/account_invoice_report.py
@@ -158,18 +158,23 @@ class AccountInvoiceReport(models.Model):
# self._table = account_invoice_report
tools.drop_view_if_exists(cr, self._table)
cr.execute("""CREATE or REPLACE VIEW %s as (
+ WITH currency_rate (currency_id, rate, date_start, date_end) AS (
+ SELECT r.currency_id, r.rate, r.name AS date_start,
+ (SELECT name FROM res_currency_rate r2
+ WHERE r2.name > r.name AND
+ r2.currency_id = r.currency_id
+ ORDER BY r2.name ASC
+ LIMIT 1) AS date_end
+ FROM res_currency_rate r
+ )
%s
FROM (
%s %s %s
) AS sub
- JOIN res_currency_rate cr ON (cr.currency_id = sub.currency_id)
- WHERE
- cr.id IN (SELECT id
- FROM res_currency_rate cr2
- WHERE (cr2.currency_id = sub.currency_id)
- AND ((sub.date IS NOT NULL AND cr2.name <= sub.date)
- OR (sub.date IS NULL AND cr2.name <= NOW()))
- ORDER BY name DESC LIMIT 1)
+ JOIN currency_rate cr ON
+ (cr.currency_id = sub.currency_id AND
+ cr.date_start <= COALESCE(sub.date, NOW()) AND
+ (cr.date_end IS NULL OR cr.date_end > COALESCE(sub.date, NOW())))
)""" % (
self._table,
self._select(), self._sub_select(), self._from(), self._group_by()))
diff --git a/addons/account/res_config.py b/addons/account/res_config.py
index 9a6443854e5..b29e0175f1a 100644
--- a/addons/account/res_config.py
+++ b/addons/account/res_config.py
@@ -83,9 +83,11 @@ class AccountConfigSettings(models.TransientModel):
module_account_accountant = fields.Boolean(string='Full accounting features: journals, legal statements, chart of accounts, etc.',
help="""If you do not check this box, you will be able to do invoicing & payments,
but not accounting (Journal Items, Chart of Accounts, ...)""")
- module_account_asset = fields.Boolean(string='Assets management',
- help='This allows you to manage the assets owned by a company or a person.\n'
- 'It keeps track of the depreciation occurred on those assets, and creates account move for those depreciation lines.\n'
+ module_account_asset = fields.Boolean(string='Assets management & Revenue recognition',
+ help='Asset management: This allows you to manage the assets owned by a company or a person.'
+ 'It keeps track of the depreciation occurred on those assets, and creates account move for those depreciation lines.\n\n'
+ 'Revenue recognition: This allows you to manage the Revenue recognition on selling product.'
+ 'It keeps track of the installment occurred on those revenue recognition, and creates account move for those installment lines.\n'
'-This installs the module account_asset. If you do not check this box, you will be able to do invoicing & payments, '
'but not accounting (Journal Items, Chart of Accounts, ...)')
module_account_budget = fields.Boolean(string='Budget management',
diff --git a/addons/account/static/src/css/account_reconciliation.scss b/addons/account/static/src/css/account_reconciliation.scss
index 1e1c3e5ed23..c2d4973d259 100644
--- a/addons/account/static/src/css/account_reconciliation.scss
+++ b/addons/account/static/src/css/account_reconciliation.scss
@@ -560,11 +560,10 @@ $aestetic_animation_speed: 300ms;
}
&.add_line_container {
- &:nth-child(2n+1) {
- width: 98%;
- float: none;
- margin: auto;
- }
+ width: 98%;
+ float: none;
+ clear: both;
+ margin: auto;
td {
text-align: center;
diff --git a/addons/account/static/src/js/account_reconciliation_widgets.js b/addons/account/static/src/js/account_reconciliation_widgets.js
index 86b486de48b..c650c15d291 100644
--- a/addons/account/static/src/js/account_reconciliation_widgets.js
+++ b/addons/account/static/src/js/account_reconciliation_widgets.js
@@ -1853,10 +1853,11 @@ var bankStatementReconciliationLine = abstractReconciliationLine.extend({
changePartnerClickHandler: function() {
var self = this;
- self.$(".change_partner_container").find("input").attr("placeholder", self.st_line.partner_name);
- self.$(".change_partner_container").show();
- self.$(".partner_name").hide();
- self.change_partner_field.$drop_down.trigger("click");
+ $.when(self.changePartner(false)).then(function(){
+ self.$(".change_partner_container").show();
+ self.$(".partner_name").hide();
+ self.change_partner_field.$drop_down.trigger("click");
+ })
},
diff --git a/addons/account/tests/test_search.py b/addons/account/tests/test_search.py
index 7a8ceaef362..f79e368d9c2 100644
--- a/addons/account/tests/test_search.py
+++ b/addons/account/tests/test_search.py
@@ -54,3 +54,18 @@ class TestSearch(AccountTestUsers):
asale_ids = self.account_model.name_search(name='XX200', operator='ilike', args=[('id', 'in', self.all_ids)])
self.assertEqual(set([self.asale[0]]), set([a[0] for a in asale_ids]), "name_search 'ilike XX200' should have returned Product Sales account only")
+
+ def test_property_unset_search(self):
+ cr, uid = self.cr, self.uid
+ res_partner_model = self.registry('res.partner')
+ account_payment_term_model = self.registry('account.payment.term')
+
+ a_partner = res_partner_model.create(cr, uid, {'name': 'test partner'})
+ a_payment_term = account_payment_term_model.create(cr, uid, {'name': 'test payment term'})
+
+ partner_ids = res_partner_model.search(cr, uid, [('property_payment_term', '=', False), ('id', '=', a_partner)])
+ self.assertTrue(partner_ids, "unset property field 'propety_payment_term' should have been found")
+
+ res_partner_model.write(cr, uid, [a_partner], {'property_payment_term': a_payment_term})
+ partner_ids = res_partner_model.search(cr, uid, [('property_payment_term', '=', False), ('id', '=', a_partner)])
+ self.assertFalse(partner_ids, "set property field 'propety_payment_term' should not have been found")
diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml
index 9bacb760f79..b6d801e27f9 100644
--- a/addons/account/views/account_invoice_view.xml
+++ b/addons/account/views/account_invoice_view.xml
@@ -545,7 +545,7 @@
<field name="view_mode">tree,form,calendar,graph</field>
<field name="view_id" ref="invoice_tree"/>
<field name="domain">[('type','in', ['out_invoice', 'out_refund']), ('state', 'not in', ['draft', 'cancel'])]</field>
- <field name="context">{'type':False}</field>
+ <field name="context">{'default_type':'out_invoice', 'type':'out_invoice', 'journal_type': 'sale'}</field>
<field name="search_view_id" ref="view_account_invoice_filter"/>
</record>
diff --git a/addons/account/wizard/account_invoice_refund.py b/addons/account/wizard/account_invoice_refund.py
index 4aaf8b8ca16..cb6645739a4 100644
--- a/addons/account/wizard/account_invoice_refund.py
+++ b/addons/account/wizard/account_invoice_refund.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from openerp import models, fields, api, _
+from openerp.tools.safe_eval import safe_eval as eval
from openerp.exceptions import UserError
diff --git a/addons/account_analytic_analysis/account_analytic_analysis.py b/addons/account_analytic_analysis/account_analytic_analysis.py
index 605e4379cee..17a05e1da90 100644
--- a/addons/account_analytic_analysis/account_analytic_analysis.py
+++ b/addons/account_analytic_analysis/account_analytic_analysis.py
@@ -669,34 +669,36 @@ class account_analytic_account(osv.osv):
context = context or {}
journal_obj = self.pool.get('account.journal')
+ fpos_obj = self.pool['account.fiscal.position']
+ partner = contract.partner_id
- if not contract.partner_id:
+ if not partner:
raise UserError(_("You must first select a Customer for Contract %s!") % contract.name )
- fpos = contract.partner_id.property_account_position or False
+ fpos_id = fpos_obj.get_fiscal_position(cr, uid, partner.company_id.id, partner.id, context=context)
journal_ids = journal_obj.search(cr, uid, [('type', '=','sale'),('company_id', '=', contract.company_id.id or False)], limit=1)
if not journal_ids:
raise UserError(_('Please define a sale journal for the company "%s".') % (contract.company_id.name or '', ))
- partner_payment_term = contract.partner_id.property_payment_term and contract.partner_id.property_payment_term.id or False
+ partner_payment_term = partner.property_payment_term and partner.property_payment_term.id or False
currency_id = False
if contract.pricelist_id:
currency_id = contract.pricelist_id.currency_id.id
- elif contract.partner_id.property_product_pricelist:
- currency_id = contract.partner_id.property_product_pricelist.currency_id.id
+ elif partner.property_product_pricelist:
+ currency_id = partner.property_product_pricelist.currency_id.id
elif contract.company_id:
currency_id = contract.company_id.currency_id.id
invoice = {
- 'account_id': contract.partner_id.property_account_receivable.id,
+ 'account_id': partner.property_account_receivable.id,
'type': 'out_invoice',
- 'partner_id': contract.partner_id.id,
+ 'partner_id': partner.id,
'currency_id': currency_id,
'journal_id': len(journal_ids) and journal_ids[0] or False,
'date_invoice': contract.recurring_next_date,
'origin': contract.code,
- 'fiscal_position_id': fpos and fpos.id,
+ 'fiscal_position_id': fpos_id,
'payment_term_id': partner_payment_term,
'company_id': contract.company_id.id or False,
}
diff --git a/addons/account_asset/__init__.py b/addons/account_asset/__init__.py
index 8c1b6b60c16..f826640892b 100644
--- a/addons/account_asset/__init__.py
+++ b/addons/account_asset/__init__.py
@@ -1,23 +1,4 @@
-# -*- encoding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-##############################################################################
+# -*- coding: utf-8 -*-
import account_asset
import account_asset_invoice
diff --git a/addons/account_asset/__openerp__.py b/addons/account_asset/__openerp__.py
index 010fc167bfc..7a1f36b6f8c 100644
--- a/addons/account_asset/__openerp__.py
+++ b/addons/account_asset/__openerp__.py
@@ -1,36 +1,20 @@
-# -*- encoding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-##############################################################################
+# -*- coding: utf-8 -*-
{
- 'name': 'Assets Management',
+ 'name': 'Assets & Revenue Recognitions Management',
'version': '1.0',
- 'depends': ['account'],
- 'author': 'OpenERP S.A.',
+ 'depends': ['account_accountant'],
+ 'author': 'Odoo S.A.',
'description': """
-Financial and accounting asset management.
-==========================================
+Assets management
+=================
+Manage assets owned by a company or a person.
+Keeps track of depreciations, and creates corresponding journal entries.
-This Module manages the assets owned by a company or an individual. It will keep
-track of depreciation's occurred on those assets. And it allows to create Move's
-of the depreciation lines.
+Revenue recognition
+===================
+Manage revenue recognition on product sales.
+Keeps track of the revenue recognition installments, and creates corresponding journal entries.
""",
'website': 'https://www.odoo.com/page/accounting',
@@ -39,23 +23,23 @@ of the depreciation lines.
'demo': [
'account_asset_demo.yml',
],
- 'test': [
- '../account/test/account_minimal_test.xml',
- 'test/account_asset_demo_test.xml',
- 'test/account_asset_demo.yml',
- 'test/account_asset.yml',
- 'test/account_asset_wizard.yml',
- ],
+ # 'test': [
+ # '../account/test/account_minimal_test.xml',
+ # 'test/account_asset_demo_test.xml',
+ # ],
'data': [
'security/account_asset_security.xml',
'security/ir.model.access.csv',
'wizard/account_asset_change_duration_view.xml',
'wizard/wizard_asset_compute_view.xml',
- 'account_asset_view.xml',
- 'account_asset_invoice_view.xml',
+ 'views/account_asset_view.xml',
+ 'views/account_asset_invoice_view.xml',
'report/account_asset_report_view.xml',
+ 'views/account_asset.xml',
+ ],
+ 'qweb': [
+ "static/src/xml/account_asset_template.xml",
],
- 'auto_install': False,
'installable': True,
'application': False,
}
diff --git a/addons/account_asset/account_asset.py b/addons/account_asset/account_asset.py
index 7a326a8df7a..ebe22bc408c 100644
--- a/addons/account_asset/account_asset.py
+++ b/addons/account_asset/account_asset.py
@@ -1,335 +1,313 @@
-# -*- encoding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-##############################################################################
-
-import time
-from datetime import datetime
+# -*- coding: utf-8 -*-
+
+from datetime import date, datetime
from dateutil.relativedelta import relativedelta
-from openerp.osv import fields, osv
import openerp.addons.decimal_precision as dp
-from openerp.tools.translate import _
-from openerp.exceptions import UserError
+from openerp import api, fields, models, _
+from openerp.exceptions import UserError, ValidationError
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
+
-class account_asset_category(osv.osv):
+class AccountAssetCategory(models.Model):
_name = 'account.asset.category'
_description = 'Asset category'
- _columns = {
- 'name': fields.char('Name', required=True, select=1),
- 'note': fields.text('Note'),
- 'account_analytic_id': fields.many2one('account.analytic.account', 'Analytic account'),
- 'account_asset_id': fields.many2one('account.account', 'Asset Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)]),
- 'account_depreciation_id': fields.many2one('account.account', 'Depreciation Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)]),
- 'account_expense_depreciation_id': fields.many2one('account.account', 'Depr. Expense Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)]),
- 'journal_id': fields.many2one('account.journal', 'Journal', required=True),
- 'company_id': fields.many2one('res.company', 'Company', required=True),
- 'method': fields.selection([('linear','Linear: Computed on basis of Gross Value / Number of Depreciations'),('degressive','Degressive: Computed on basis of Residual Value * Degressive Factor')], 'Computation Method', required=True, help="Choose the method to use to compute the amount of depreciation lines.\n"\
- " * Linear: Calculated on basis of: Gross Value / Number of Depreciations\n" \
- " * Degressive: Calculated on basis of: Residual Value * Degressive Factor"),
- 'method_number': fields.integer('Number of Depreciations', help="The number of depreciations needed to depreciate your asset"),
- 'method_period': fields.integer('Period Length', help="State here the time between 2 depreciations, in months", required=True),
- 'method_progress_factor': fields.float('Degressive Factor'),
- 'method_time': fields.selection([('number','Number of Depreciations'),('end','Ending Date')], 'Time Method', required=True,
- help="Choose the method to use to compute the dates and number of depreciation lines.\n"\
- " * Number of Depreciations: Fix the number of depreciation lines and the time between 2 depreciations.\n" \
- " * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond."),
- 'method_end': fields.date('Ending date'),
- 'prorata':fields.boolean('Prorata Temporis', help='Indicates that the first depreciation entry for this asset have to be done from the purchase date instead of the first January'),
- 'open_asset': fields.boolean('Skip Draft State', help="Check this if you want to automatically confirm the assets of this category when created by invoices."),
- }
-
- _defaults = {
- 'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'account.asset.category', context=context),
- 'method': 'linear',
- 'method_number': 5,
- 'method_time': 'number',
- 'method_period': 12,
- 'method_progress_factor': 0.3,
- }
-
- def onchange_account_asset(self, cr, uid, ids, account_asset_id, context=None):
- res = {'value':{}}
- if account_asset_id:
- res['value'] = {'account_depreciation_id': account_asset_id}
- return res
+ active = fields.Boolean(default=True)
+ name = fields.Char(required=True, index=True)
+ note = fields.Text()
+ account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account')
+ account_asset_id = fields.Many2one('account.account', string='Asset Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)])
+ account_income_recognition_id = fields.Many2one('account.account', string='Recognition Income Account', domain=[('internal_type','=','other'), ('deprecated', '=', False)], oldname='account_expense_depreciation_id')
+ account_depreciation_id = fields.Many2one('account.account', string='Depreciation Account', required=True, domain=[('internal_type','=','other'), ('deprecated', '=', False)])
+ journal_id = fields.Many2one('account.journal', string='Journal', required=True)
+ company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env['res.company']._company_default_get('account.asset.category'))
+ method = fields.Selection([('linear', 'Linear'), ('degressive', 'Degressive')], string='Computation Method', required=True, default='linear',
+ help="Choose the method to use to compute the amount of depreciation lines.\n"
+ " * Linear: Calculated on basis of: Gross Value / Number of Depreciations\n"
+ " * Degressive: Calculated on basis of: Residual Value * Degressive Factor")
+ method_number = fields.Integer(string='Number of Depreciations', default=5, help="The number of depreciations needed to depreciate your asset")
+ method_period = fields.Integer(string='Period Length', default=1, help="State here the time between 2 depreciations, in months", required=True)
+ method_progress_factor = fields.Float('Degressive Factor', default=0.3)
+ method_time = fields.Selection([('number', 'Number of Depreciations'), ('end', 'Ending Date')], string='Time Method', required=True, default='number',
+ help="Choose the method to use to compute the dates and number of depreciation lines.\n"
+ " * Number of Depreciations: Fix the number of depreciation lines and the time between 2 depreciations.\n"
+ " * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond.")
+ method_end = fields.Date('Ending date')
+ prorata = fields.Boolean(string='Prorata Temporis', help='Indicates that the first depreciation entry for this asset have to be done from the purchase date instead of the first of January')
+ open_asset = fields.Boolean(string='Skip Draft State', help="Check this if you want to automatically confirm the assets of this category when created by invoices.")
+ type = fields.Selection([('sale', 'Sale: Revenue Recognition'), ('purchase', 'Purchase: Asset')], required=True, index=True, default='purchase')
+
+ @api.onchange('account_asset_id')
+ def onchange_account_asset(self):
+ self.account_depreciation_id = self.account_asset_id
+
+ @api.onchange('type')
+ def onchange_type(self):
+ if self.type == 'sale':
+ self.prorata = True
+ self.method_period = 1
+ else:
+ self.method_period = 12
-class account_asset_asset(osv.osv):
+class AccountAssetAsset(models.Model):
_name = 'account.asset.asset'
- _description = 'Asset'
-
- def unlink(self, cr, uid, ids, context=None):
- for asset in self.browse(cr, uid, ids, context=context):
- if asset.account_move_line_ids:
- raise UserError(_('You cannot delete an asset that contains posted depreciation lines.'))
- return super(account_asset_asset, self).unlink(cr, uid, ids, context=context)
-
- def _get_last_depreciation_date(self, cr, uid, ids, context=None):
+ _description = 'Asset/Revenue Recognition'
+ _inherit = ['mail.thread', 'ir.needaction_mixin']
+
+ account_move_line_ids = fields.One2many('account.move.line', 'asset_id', string='Entries', readonly=True, states={'draft': [('readonly', False)]})
+ entry_count = fields.Integer(compute='_entry_count', string='# Asset Entries')
+ name = fields.Char(string='Asset/Deferred Revenue Name', required=True, readonly=True, states={'draft': [('readonly', False)]})
+ code = fields.Char(string='Reference', size=32, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env['ir.sequence'].next_by_code('account.asset.code'))
+ value = fields.Float(string='Gross Value', required=True, readonly=True, digits=0, states={'draft': [('readonly', False)]}, oldname='purchase_value')
+ currency_id = fields.Many2one('res.currency', string='Currency', required=True, readonly=True, states={'draft': [('readonly', False)]},
+ default=lambda self: self.env.user.company_id.currency_id.id)
+ company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, states={'draft': [('readonly', False)]},
+ default=lambda self: self.env['res.company']._company_default_get('account.asset.asset'))
+ note = fields.Text()
+ category_id = fields.Many2one('account.asset.category', string='Category', required=True, change_default=True, readonly=True, states={'draft': [('readonly', False)]})
+ date = fields.Date(string='Date', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=fields.Date.context_today, oldname="purchase_date")
+ state = fields.Selection([('draft', 'Draft'), ('open', 'Running'), ('close', 'Close')], 'Status', required=True, copy=False, default='draft',
+ help="When an asset is created, the status is 'Draft'.\n"
+ "If the asset is confirmed, the status goes in 'Running' and the depreciation lines can be posted in the accounting.\n"
+ "You can manually close an asset when the depreciation is over. If the last line of depreciation is posted, the asset automatically goes in that status.")
+ active = fields.Boolean(default=True)
+ partner_id = fields.Many2one('res.partner', string='Partner', readonly=True, states={'draft': [('readonly', False)]})
+ method = fields.Selection([('linear', 'Linear'), ('degressive', 'Degressive')], string='Computation Method', required=True, readonly=True, states={'draft': [('readonly', False)]}, default='linear',
+ help="Choose the method to use to compute the amount of depreciation lines.\n * Linear: Calculated on basis of: Gross Value / Number of Depreciations\n"
+ " * Degressive: Calculated on basis of: Residual Value * Degressive Factor")
+ method_number = fields.Integer(string='Number of Depreciations', readonly=True, states={'draft': [('readonly', False)]}, default=5, help="The number of depreciations needed to depreciate your asset")
+ method_period = fields.Integer(string='Number of Months in a Period', required=True, readonly=True, default=12, states={'draft': [('readonly', False)]},
+ help="The amount of time between two depreciations, in months")
+ method_end = fields.Date(string='Ending Date', readonly=True, states={'draft': [('readonly', False)]})
+ method_progress_factor = fields.Float(string='Degressive Factor', readonly=True, default=0.3, states={'draft': [('readonly', False)]})
+ value_residual = fields.Float(compute='_amount_residual', method=True, digits=0, string='Residual Value')
+ method_time = fields.Selection([('number', 'Number of Depreciations'), ('end', 'Ending Date')], string='Time Method', required=True, readonly=True, default='number', states={'draft': [('readonly', False)]},
+ help="Choose the method to use to compute the dates and number of depreciation lines.\n"
+ " * Number of Depreciations: Fix the number of depreciation lines and the time between 2 depreciations.\n"
+ " * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond.")
+ prorata = fields.Boolean(string='Prorata Temporis', readonly=True, states={'draft': [('readonly', False)]},
+ help='Indicates that the first depreciation entry for this asset have to be done from the purchase date instead of the first January / Start date of fiscal year')
+ history_ids = fields.One2many('account.asset.history', 'asset_id', string='History', readonly=True)
+ depreciation_line_ids = fields.One2many('account.asset.depreciation.line', 'asset_id', string='Depreciation Lines', readonly=True, states={'draft': [('readonly', False)], 'open': [('readonly', False)]})
+ salvage_value = fields.Float(string='Salvage Value', digits=0, readonly=True, states={'draft': [('readonly', False)]},
+ help="It is the amount you plan to have that you cannot depreciate.")
+ invoice_id = fields.Many2one('account.invoice', string='Invoice', states={'draft': [('readonly', False)]}, copy=False)
+ type = fields.Selection(related="category_id.type", string='Type', required=True, default='purchase')
+
+ @api.multi
+ def unlink(self):
+ for asset in self:
+ if asset.state in ['open', 'close']:
+ raise UserError(_('You cannot delete a document is in %s state.') % (asset.state,))
+ if asset.account_move_line_ids:
+ raise UserError(_('You cannot delete a document that contains posted lines.'))
+ return super(AccountAssetAsset, self).unlink()
+ @api.multi
+ def _get_last_depreciation_date(self):
"""
@param id: ids of a account.asset.asset objects
@return: Returns a dictionary of the effective dates of the last depreciation entry made for given asset ids. If there isn't any, return the purchase date of this asset
"""
- cr.execute("""
- SELECT a.id as id, COALESCE(MAX(l.date),a.purchase_date) AS date
+ self.env.cr.execute("""
+ SELECT a.id as id, COALESCE(MAX(l.date),a.date) AS date
FROM account_asset_asset a
LEFT JOIN account_move_line l ON (l.asset_id = a.id)
WHERE a.id IN %s
- GROUP BY a.id, a.purchase_date """, (tuple(ids),))
- return dict(cr.fetchall())
+ GROUP BY a.id, a.date """, (tuple(self.ids),))
+ result = dict(self.env.cr.fetchall())
+ return result
- def _compute_board_amount(self, cr, uid, asset, i, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date, context=None):
- #by default amount = 0
+ def _compute_board_amount(self, sequence, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date):
amount = 0
- if i == undone_dotation_number:
+ if sequence == undone_dotation_number:
amount = residual_amount
else:
- if asset.method == 'linear':
+ if self.method == 'linear':
amount = amount_to_depr / (undone_dotation_number - len(posted_depreciation_line_ids))
- if asset.prorata:
- amount = amount_to_depr / asset.method_number
+ if self.prorata and self.category_id.type == 'purchase':
+ amount = amount_to_depr / self.method_number
days = total_days - float(depreciation_date.strftime('%j'))
- if i == 1:
- amount = (amount_to_depr / asset.method_number) / total_days * days
- elif i == undone_dotation_number:
- amount = (amount_to_depr / asset.method_number) / total_days * (total_days - days)
- elif asset.method == 'degressive':
- amount = residual_amount * asset.method_progress_factor
- if asset.prorata:
+ if sequence == 1:
+ amount = (amount_to_depr / self.method_number) / total_days * days
+ elif sequence == undone_dotation_number:
+ amount = (amount_to_depr / self.method_number) / total_days * (total_days - days)
+ elif self.method == 'degressive':
+ amount = residual_amount * self.method_progress_factor
+ if self.prorata:
days = total_days - float(depreciation_date.strftime('%j'))
- if i == 1:
- amount = (residual_amount * asset.method_progress_factor) / total_days * days
- elif i == undone_dotation_number:
- amount = (residual_amount * asset.method_progress_factor) / total_days * (total_days - days)
+ if sequence == 1:
+ amount = (residual_amount * self.method_progress_factor) / total_days * days
+ elif sequence == undone_dotation_number:
+ amount = (residual_amount * self.method_progress_factor) / total_days * (total_days - days)
return amount
- def _compute_board_undone_dotation_nb(self, cr, uid, asset, depreciation_date, total_days, context=None):
- undone_dotation_number = asset.method_number
- if asset.method_time == 'end':
- end_date = datetime.strptime(asset.method_end, '%Y-%m-%d')
+ def _compute_board_undone_dotation_nb(self, depreciation_date, total_days):
+ undone_dotation_number = self.method_number
+ if self.method_time == 'end':
+ end_date = datetime.strptime(self.method_end, DF).date()
undone_dotation_number = 0
while depreciation_date <= end_date:
- depreciation_date = (datetime(depreciation_date.year, depreciation_date.month, depreciation_date.day) + relativedelta(months=+asset.method_period))
+ depreciation_date = date(depreciation_date.year, depreciation_date.month, depreciation_date.day) + relativedelta(months=+self.method_period)
undone_dotation_number += 1
- if asset.prorata:
+ if self.prorata and self.category_id.type == 'purchase':
undone_dotation_number += 1
return undone_dotation_number
- def compute_depreciation_board(self, cr, uid, ids, context=None):
- depreciation_lin_obj = self.pool.get('account.asset.depreciation.line')
- currency_obj = self.pool.get('res.currency')
- for asset in self.browse(cr, uid, ids, context=context):
- if asset.value_residual == 0.0:
- continue
- posted_depreciation_line_ids = depreciation_lin_obj.search(cr, uid, [('asset_id', '=', asset.id), ('move_check', '=', True)],order='depreciation_date desc')
- old_depreciation_line_ids = depreciation_lin_obj.search(cr, uid, [('asset_id', '=', asset.id), ('move_id', '=', False)])
- if old_depreciation_line_ids:
- depreciation_lin_obj.unlink(cr, uid, old_depreciation_line_ids, context=context)
-
- amount_to_depr = residual_amount = asset.value_residual
- if asset.prorata:
- depreciation_date = datetime.strptime(self._get_last_depreciation_date(cr, uid, [asset.id], context)[asset.id], '%Y-%m-%d')
+ @api.multi
+ def compute_depreciation_board(self):
+ self.ensure_one()
+
+ posted_depreciation_line_ids = self.depreciation_line_ids.filtered(lambda x: x.move_check)
+ unposted_depreciation_line_ids = self.depreciation_line_ids.filtered(lambda x: not x.move_check)
+
+ # Remove old unposted depreciation lines. We cannot use unlink() with One2many field
+ commands = [(2, line_id.id, False) for line_id in unposted_depreciation_line_ids]
+
+ if self.value != 0.0:
+ amount_to_depr = residual_amount = self.value_residual
+ if self.prorata:
+ depreciation_date = datetime.strptime(self._get_last_depreciation_date()[self.id], DF).date()
else:
- # depreciation_date = 1st January of purchase year
- purchase_date = datetime.strptime(asset.purchase_date, '%Y-%m-%d')
- #if we already have some previous validated entries, starting date isn't 1st January but last entry + method period
- if (len(posted_depreciation_line_ids)>0):
- last_depreciation_date = datetime.strptime(depreciation_lin_obj.browse(cr,uid,posted_depreciation_line_ids[0],context=context).depreciation_date, '%Y-%m-%d')
- depreciation_date = (last_depreciation_date+relativedelta(months=+asset.method_period))
+ # depreciation_date = 1st of January of purchase year
+ asset_date = datetime.strptime(self.date, DF).date()
+ # if we already have some previous validated entries, starting date isn't 1st January but last entry + method period
+ if posted_depreciation_line_ids and posted_depreciation_line_ids[0].depreciation_date:
+ last_depreciation_date = datetime.strptime(posted_depreciation_line_ids[0].depreciation_date, DF).date()
+ depreciation_date = last_depreciation_date + relativedelta(months=+self.method_period)
else:
- depreciation_date = datetime(purchase_date.year, 1, 1)
+ depreciation_date = asset_date
day = depreciation_date.day
month = depreciation_date.month
year = depreciation_date.year
total_days = (year % 4) and 365 or 366
- undone_dotation_number = self._compute_board_undone_dotation_nb(cr, uid, asset, depreciation_date, total_days, context=context)
+ undone_dotation_number = self._compute_board_undone_dotation_nb(depreciation_date, total_days)
for x in range(len(posted_depreciation_line_ids), undone_dotation_number):
- i = x + 1
- amount = self._compute_board_amount(cr, uid, asset, i, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date, context=context)
+ sequence = x + 1
+ amount = self._compute_board_amount(sequence, residual_amount, amount_to_depr, undone_dotation_number, posted_depreciation_line_ids, total_days, depreciation_date)
+ amount = self.currency_id.round(amount)
residual_amount -= amount
vals = {
- 'amount': amount,
- 'asset_id': asset.id,
- 'sequence': i,
- 'name': str(asset.id) +'/' + str(i),
- 'remaining_value': residual_amount,
- 'depreciated_value': (asset.purchase_value - asset.salvage_value) - (residual_amount + amount),
- 'depreciation_date': depreciation_date.strftime('%Y-%m-%d'),
+ 'amount': amount,
+ 'asset_id': self.id,
+ 'sequence': sequence,
+ 'name': (self.code or str(self.id)) + '/' + str(sequence),
+ 'remaining_value': residual_amount,
+ 'depreciated_value': (self.value - self.salvage_value) - (residual_amount + amount),
+ 'depreciation_date': depreciation_date.strftime(DF),
}
- depreciation_lin_obj.create(cr, uid, vals, context=context)
+ commands.append((0, False, vals))
# Considering Depr. Period as months
- depreciation_date = (datetime(year, month, day) + relativedelta(months=+asset.method_period))
+ depreciation_date = date(year, month, day) + relativedelta(months=+self.method_period)
day = depreciation_date.day
month = depreciation_date.month
year = depreciation_date.year
- return True
- def validate(self, cr, uid, ids, context=None):
- if context is None:
- context = {}
- return self.write(cr, uid, ids, {
- 'state':'open'
- }, context)
-
- def set_to_close(self, cr, uid, ids, context=None):
- return self.write(cr, uid, ids, {'state': 'close'}, context=context)
-
- def set_to_draft(self, cr, uid, ids, context=None):
- return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
-
- def _amount_residual(self, cr, uid, ids, name, args, context=None):
- cr.execute("""SELECT
- l.asset_id as id, SUM(abs(l.debit-l.credit)) AS amount
- FROM
- account_move_line l
- WHERE
- l.asset_id IN %s GROUP BY l.asset_id """, (tuple(ids),))
- res=dict(cr.fetchall())
- for asset in self.browse(cr, uid, ids, context):
- company_currency = asset.company_id.currency_id.id
- current_currency = asset.currency_id.id
- amount = self.pool['res.currency'].compute(cr, uid, company_currency, current_currency, res.get(asset.id, 0.0), context=context)
- res[asset.id] = asset.purchase_value - amount - asset.salvage_value
- for id in ids:
- res.setdefault(id, 0.0)
- return res
+ self.write({'depreciation_line_ids': commands})
- def onchange_company_id(self, cr, uid, ids, company_id=False, context=None):
- val = {}
- if company_id:
- company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
- val['currency_id'] = company.currency_id.id
- return {'value': val}
-
- def onchange_purchase_salvage_value(self, cr, uid, ids, purchase_value, salvage_value, context=None):
- val = {}
- for asset in self.browse(cr, uid, ids, context=context):
- if purchase_value:
- val['value_residual'] = purchase_value - salvage_value
- if salvage_value:
- val['value_residual'] = purchase_value - salvage_value
- return {'value': val}
- def _entry_count(self, cr, uid, ids, field_name, arg, context=None):
- MoveLine = self.pool('account.move.line')
- return {
- asset_id: MoveLine.search_count(cr, uid, [('asset_id', '=', asset_id)], context=context)
- for asset_id in ids
- }
- _columns = {
- 'account_move_line_ids': fields.one2many('account.move.line', 'asset_id', 'Entries', readonly=True, states={'draft':[('readonly',False)]}),
- 'entry_count': fields.function(_entry_count, string='# Asset Entries', type='integer'),
- 'name': fields.char('Asset Name', required=True, readonly=True, states={'draft':[('readonly',False)]}),
- 'code': fields.char('Reference', size=32, readonly=True, states={'draft':[('readonly',False)]}),
- 'purchase_value': fields.float('Gross Value', required=True, readonly=True, states={'draft':[('readonly',False)]}),
- 'currency_id': fields.many2one('res.currency','Currency',required=True, readonly=True, states={'draft':[('readonly',False)]}),
- 'company_id': fields.many2one('res.company', 'Company', required=True, readonly=True, states={'draft':[('readonly',False)]}),
- 'note': fields.text('Note'),
- 'category_id': fields.many2one('account.asset.category', 'Asset Category', required=True, change_default=True, readonly=True, states={'draft':[('readonly',False)]}),
- 'purchase_date': fields.date('Purchase Date', required=True, readonly=True, states={'draft':[('readonly',False)]}),
- 'state': fields.selection([('draft','Draft'),('open','Running'),('close','Close')], 'Status', required=True, copy=False,
- help="When an asset is created, the status is 'Draft'.\n" \
- "If the asset is confirmed, the status goes in 'Running' and the depreciation lines can be posted in the accounting.\n" \
- "You can manually close an asset when the depreciation is over. If the last line of depreciation is posted, the asset automatically goes in that status."),
- 'active': fields.boolean('Active'),
- 'partner_id': fields.many2one('res.partner', 'Supplier', readonly=True, states={'draft':[('readonly',False)]}),
- 'method': fields.selection([('linear','Linear'),('degressive','Degressive')], 'Computation Method', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="Choose the method to use to compute the amount of depreciation lines.\n"\
- " * Linear: Calculated on basis of: Gross Value / Number of Depreciations\n" \
- " * Degressive: Calculated on basis of: Residual Value * Degressive Factor"),
- 'method_number': fields.integer('Number of Depreciations', readonly=True, states={'draft':[('readonly',False)]}, help="The number of depreciations needed to depreciate your asset"),
- 'method_period': fields.integer('Number of Months in a Period', required=True, readonly=True, states={'draft':[('readonly',False)]}, help="The amount of time between two depreciations, in months"),
- 'method_end': fields.date('Ending Date', readonly=True, states={'draft':[('readonly',False)]}),
- 'method_progress_factor': fields.float('Degressive Factor', readonly=True, states={'draft':[('readonly',False)]}),
- 'value_residual': fields.function(_amount_residual, method=True, digits=0, string='Residual Value'),
- 'method_time': fields.selection([('number','Number of Depreciations'),('end','Ending Date')], 'Time Method', required=True, readonly=True, states={'draft':[('readonly',False)]},
- help="Choose the method to use to compute the dates and number of depreciation lines.\n"\
- " * Number of Depreciations: Fix the number of depreciation lines and the time between 2 depreciations.\n" \
- " * Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond."),
- 'prorata':fields.boolean('Prorata Temporis', readonly=True, states={'draft':[('readonly',False)]}, help='Indicates that the first depreciation entry for this asset have to be done from the purchase date instead of the first January'),
- 'history_ids': fields.one2many('account.asset.history', 'asset_id', 'History', readonly=True),
- 'depreciation_line_ids': fields.one2many('account.asset.depreciation.line', 'asset_id', 'Depreciation Lines', readonly=True, states={'draft':[('readonly',False)],'open':[('readonly',False)]}),
- 'salvage_value': fields.float('Salvage Value', digits=0, help="It is the amount you plan to have that you cannot depreciate.", readonly=True, states={'draft':[('readonly',False)]}),
- }
- _defaults = {
- 'code': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').next_by_code(cr, uid, 'account.asset.code'),
- 'purchase_date': lambda obj, cr, uid, context: time.strftime('%Y-%m-%d'),
- 'active': True,
- 'state': 'draft',
- 'method': 'linear',
- 'method_number': 5,
- 'method_time': 'number',
- 'method_period': 12,
- 'method_progress_factor': 0.3,
- 'currency_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
- 'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'account.asset.asset',context=context),
- }
-
- def _check_recursion(self, cr, uid, ids, context=None, parent=None):
- return super(account_asset_asset, self)._check_recursion(cr, uid, ids, context=context, parent=parent)
-
- def _check_prorata(self, cr, uid, ids, context=None):
- for asset in self.browse(cr, uid, ids, context=context):
- if asset.prorata and asset.method_time != 'number':
- return False
return True
- _constraints = [
- (_check_prorata, 'Prorata temporis can be applied only for time method "number of depreciations".', ['prorata']),
- ]
-
- def onchange_category_id(self, cr, uid, ids, category_id, context=None):
- res = {'value':{}}
- asset_categ_obj = self.pool.get('account.asset.category')
+ @api.multi
+ def validate(self):
+ self.write({'state': 'open'})
+
+ @api.multi
+ def set_to_close(self):
+ unposted_dep_line = self.env['account.asset.depreciation.line'].search_count(
+ [('asset_id', 'in', self.ids), ('move_check', '=', False)])
+ if unposted_dep_line:
+ raise UserError(_('You cannot close a document which has unposted lines.'))
+ self.message_post(body=_("Document closed."))
+ self.write({'state': 'close'})
+
+ @api.multi
+ def set_to_draft(self):
+ self.write({'state': 'draft'})
+
+ @api.one
+ @api.depends('value', 'salvage_value', 'depreciation_line_ids')
+ def _amount_residual(self):
+ total_amount = 0.0
+ for line in self.depreciation_line_ids:
+ if line.move_check:
+ total_amount += line.amount
+ self.value_residual = self.value - total_amount - self.salvage_value
+
+ @api.onchange('company_id')
+ def onchange_company_id(self):
+ self.currency_id = self.company_id.currency_id.id
+
+ @api.multi
+ @api.depends('account_move_line_ids')
+ def _entry_count(self):
+ for asset in self:
+ asset.entry_count = self.env['account.move.line'].search_count([('asset_id', '=', asset.id)])
+
+ @api.one
+ @api.constrains('prorata', 'method_time')
+ def _check_prorata(self):
+ if self.prorata and self.method_time != 'number':
+ raise ValidationError(_('Prorata temporis can be applied only for time method "number of depreciations".'))
+
+ @api.onchange('category_id')
+ def onchange_category_id(self):
+ vals = self.onchange_category_id_values(self.category_id.id)
+ # We cannot use 'write' on an object that doesn't exist yet
+ if vals:
+ for k, v in vals['value'].iteritems():
+ setattr(self, k, v)
+
+ def onchange_category_id_values(self, category_id):
if category_id:
- category_obj = asset_categ_obj.browse(cr, uid, category_id, context=context)
- res['value'] = {
- 'method': category_obj.method,
- 'method_number': category_obj.method_number,
- 'method_time': category_obj.method_time,
- 'method_period': category_obj.method_period,
- 'method_progress_factor': category_obj.method_progress_factor,
- 'method_end': category_obj.method_end,
- 'prorata': category_obj.prorata,
+ category = self.env['account.asset.category'].browse(category_id)
+ return {
+ 'value': {
+ 'method': category.method,
+ 'method_number': category.method_number,
+ 'method_time': category.method_time,
+ 'method_period': category.method_period,
+ 'method_progress_factor': category.method_progress_factor,
+ 'method_end': category.method_end,
+ 'prorata': category.prorata,
+ }
}
- return res
- def onchange_method_time(self, cr, uid, ids, method_time='number', context=None):
- res = {'value': {}}
- if method_time != 'number':
- res['value'] = {'prorata': False}
+ @api.onchange('method_time')
+ def onchange_method_time(self):
+ if self.method_time != 'number':
+ self.prorata = False
+
+ @api.multi
+ def copy_data(self, default=None):
+ if default is None:
+ default = {}
+ default['name'] = self.name + _(' (copy)')
+ return super(AccountAssetAsset, self).copy_data(default)[0]
+
+ @api.multi
+ def _compute_entries(self, date):
+ depreciation_ids = self.env['account.asset.depreciation.line'].search([
+ ('asset_id', 'in', self.ids), ('depreciation_date', '<=', date),
+ ('move_check', '=', False)])
+ return depreciation_ids.create_move()
+
+ @api.model
+ def create(self, vals):
+ asset = super(AccountAssetAsset, self.with_context(mail_create_nolog=True)).create(vals)
+ asset.compute_depreciation_board()
+ return asset
+
+ @api.multi
+ def write(self, vals):
+ res = super(AccountAssetAsset, self).write(vals)
+ if 'depreciation_line_ids' not in vals:
+ self.compute_depreciation_board()
return res
- def _compute_entries(self, cr, uid, ids, date, context=None):
- result = []
- depreciation_obj = self.pool.get('account.asset.depreciation.line')
- depreciation_ids = depreciation_obj.search(cr, uid, [('asset_id', 'in', ids), ('depreciation_date', '<=', date), ('move_check', '=', False)], context=context)
- context = dict(context or {}, depreciation_date=time.strftime('%Y-%m-%d'))
- return depreciation_obj.create_move(cr, uid, depreciation_ids, context=context)
-
- def create(self, cr, uid, vals, context=None):
- asset_id = super(account_asset_asset, self).create(cr, uid, vals, context=context)
- self.compute_depreciation_board(cr, uid, [asset_id], context=context)
- return asset_id
-
- def open_entries(self, cr, uid, ids, context=None):
- context = dict(context or {}, search_default_asset_id=ids, default_asset_id=ids)
+ @api.multi
+ def open_entries(self):
return {
'name': _('Journal Items'),
'view_type': 'form',
@@ -337,78 +315,74 @@ class account_asset_asset(osv.osv):
'res_model': 'account.move.line',
'view_id': False,
'type': 'ir.actions.act_window',
- 'context': context,
+ 'context': dict(self.env.context or {}, search_default_asset_id=self.id, default_asset_id=self.id),
}
-class account_asset_depreciation_line(osv.osv):
+class AccountAssetDepreciationLine(models.Model):
_name = 'account.asset.depreciation.line'
_description = 'Asset depreciation line'
- def _get_move_check(self, cr, uid, ids, name, args, context=None):
- res = {}
- for line in self.browse(cr, uid, ids, context=context):
- res[line.id] = bool(line.move_id)
- return res
-
- _columns = {
- 'name': fields.char('Depreciation Name', required=True, select=1),
- 'sequence': fields.integer('Sequence', required=True),
- 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True, ondelete='cascade'),
- 'parent_state': fields.related('asset_id', 'state', type='char', string='State of Asset'),
- 'amount': fields.float('Current Depreciation', digits=0, required=True),
- 'remaining_value': fields.float('Next Period Depreciation', digits=0, required=True),
- 'depreciated_value': fields.float('Amount Already Depreciated', required=True),
- 'depreciation_date': fields.date('Depreciation Date', select=1),
- 'move_id': fields.many2one('account.move', 'Depreciation Entry'),
- 'move_check': fields.function(_get_move_check, method=True, type='boolean', string='Posted', store=True)
- }
-
- def create_move(self, cr, uid, ids, context=None):
- context = dict(context or {})
- can_close = False
- asset_obj = self.pool.get('account.asset.asset')
- move_obj = self.pool.get('account.move')
- move_line_obj = self.pool.get('account.move.line')
- currency_obj = self.pool.get('res.currency')
+ name = fields.Char(string='Depreciation Name', required=True, index=True)
+ sequence = fields.Integer(required=True)
+ asset_id = fields.Many2one('account.asset.asset', string='Asset', required=True, ondelete='cascade')
+ parent_state = fields.Selection(related='asset_id.state', string='State of Asset')
+ amount = fields.Float(string='Current Depreciation', digits=0, required=True)
+ remaining_value = fields.Float(string='Next Period Depreciation', digits=0, required=True)
+ depreciated_value = fields.Float(string='Amount Already Depreciated', required=True)
+ depreciation_date = fields.Date('Depreciation Date', index=True)
+ move_id = fields.Many2one('account.move', string='Depreciation Entry')
+ move_check = fields.Boolean(compute='_get_move_check', string='Posted', track_visibility='always', store=True)
+
+ @api.one
+ @api.depends('move_id')
+ def _get_move_check(self):
+ self.move_check = bool(self.move_id)
+
+ @api.multi
+ def create_move(self):
created_move_ids = []
asset_ids = []
- for line in self.browse(cr, uid, ids, context=context):
- depreciation_date = context.get('depreciation_date') or line.depreciation_date or time.strftime('%Y-%m-%d')
- company_currency = line.asset_id.company_id.currency_id.id
- current_currency = line.asset_id.currency_id.id
- context.update({'date': depreciation_date})
- amount = currency_obj.compute(cr, uid, current_currency, company_currency, line.amount, context=context)
- sign = (line.asset_id.category_id.journal_id.type == 'purchase' and 1) or -1
+ for line in self:
+ depreciation_date = self.env.context.get('depreciation_date') or line.depreciation_date or fields.Date.context_today(self)
+ company_currency = line.asset_id.company_id.currency_id
+ current_currency = line.asset_id.currency_id
+ amount = company_currency.compute(line.amount, current_currency)
+ sign = (line.asset_id.category_id.journal_id.type == 'purchase' or line.asset_id.category_id.journal_id.type == 'sale' and 1) or -1
asset_name = line.asset_id.name
reference = line.name
journal_id = line.asset_id.category_id.journal_id.id
partner_id = line.asset_id.partner_id.id
+ categ_type = line.asset_id.category_id.type
+ debit_account = line.asset_id.category_id.account_asset_id.id
+ credit_account = line.asset_id.category_id.account_depreciation_id.id
move_line_1 = {
- 'name': asset_name,
+ 'name': asset_name or reference,
'ref': reference,
- 'account_id': line.asset_id.category_id.account_depreciation_id.id,
+ 'account_id': credit_account,
'debit': 0.0,
'credit': amount,
'journal_id': journal_id,
'partner_id': partner_id,
- 'currency_id': company_currency != current_currency and current_currency or False,
+ 'currency_id': company_currency != current_currency and current_currency or False,
'amount_currency': company_currency != current_currency and - sign * line.amount or 0.0,
+ 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id if categ_type == 'sale' else False,
'date': depreciation_date,
+ 'asset_id': line.asset_id.id if categ_type == 'sale' else False,
}
move_line_2 = {
- 'name': asset_name,
+ 'name': asset_name or reference,
'ref': reference,
- 'account_id': line.asset_id.category_id.account_expense_depreciation_id.id,
+ 'account_id': debit_account,
'credit': 0.0,
'debit': amount,
'journal_id': journal_id,
'partner_id': partner_id,
- 'currency_id': company_currency != current_currency and current_currency or False,
+ 'currency_id': company_currency != current_currency and current_currency or False,
'amount_currency': company_currency != current_currency and sign * line.amount or 0.0,
- 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id,
+ 'analytic_account_id': line.asset_id.category_id.account_analytic_id.id if categ_type == 'purchase' else False,
'date': depreciation_date,
- 'asset_id': line.asset_id.id
+ 'asset_id': line.asset_id.id if categ_type == 'purchase' else False
}
move_vals = {
'name': asset_name,
@@ -417,43 +391,67 @@ class account_asset_depreciation_line(osv.osv):
'journal_id': line.asset_id.category_id.journal_id.id,
'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)],
}
- move_id = move_obj.create(cr, uid, move_vals, context=context)
- self.write(cr, uid, line.id, {'move_id': move_id}, context=context)
- created_move_ids.append(move_id)
- move_obj.post(cr, uid, [move_id], context=context)
- asset_ids.append(line.asset_id.id)
+ move = self.env['account.move'].create(move_vals)
+ line.write({'move_id': move.id, 'move_check': True})
+ created_move_ids.append(move.id)
+ move.post()
+ asset_ids.append(line.asset_id)
+ partner_name = line.asset_id.partner_id.name
+ currency_name = line.asset_id.currency_id.name
+
+ def _format_message(message_description, tracked_values):
+ message = ''
+ if message_description:
+ message = '<span>%s</span>' % message_description
+ for name, values in tracked_values.iteritems():
+ message += '<div> &nbsp; &nbsp; &bull; <b>%s</b>: ' % name
+ message += '%s</div>' % values
+ return message
+
+ msg_values = {_('Currency'): currency_name, _('Amount'): line.amount}
+ if partner_name:
+ msg_values[_('Partner')] = partner_name
+ msg = _format_message(_('Depreciation line posted.'), msg_values)
+ line.asset_id.message_post(body=msg)
# we re-evaluate the assets to determine whether we can close them
- for asset in asset_obj.browse(cr, uid, list(set(asset_ids)), context=context):
- if currency_obj.is_zero(cr, uid, asset.currency_id, asset.value_residual):
+ for asset in asset_ids:
+ if asset.currency_id.is_zero(asset.value_residual):
+ asset.message_post(body=_("Document closed."))
asset.write({'state': 'close'})
+ asset.compute_depreciation_board()
return created_move_ids
+ @api.multi
+ def unlink(self):
+ for record in self:
+ if record.move_check:
+ if record.asset_id.category_id.type == 'purchase':
+ msg = _("You cannot delete posted depreciation lines.")
+ else:
+ msg = _("You cannot delete posted installment lines.")
+ raise UserError(msg)
+ return super(AccountAssetDepreciationLine, self).unlink()
+
-class account_move_line(osv.osv):
+class AccountMoveLine(models.Model):
_inherit = 'account.move.line'
- _columns = {
- 'asset_id': fields.many2one('account.asset.asset', 'Asset', ondelete="restrict"),
- }
+ asset_id = fields.Many2one('account.asset.asset', string='Asset', ondelete="restrict")
+
-class account_asset_history(osv.osv):
+class AccountAssetHistory(models.Model):
_name = 'account.asset.history'
_description = 'Asset history'
- _columns = {
- 'name': fields.char('History name', select=1),
- 'user_id': fields.many2one('res.users', 'User', required=True),
- 'date': fields.date('Date', required=True),
- 'asset_id': fields.many2one('account.asset.asset', 'Asset', required=True),
- 'method_time': fields.selection([('number','Number of Depreciations'),('end','Ending Date')], 'Time Method', required=True,
- help="The method to use to compute the dates and number of depreciation lines.\n"\
- "Number of Depreciations: Fix the number of depreciation lines and the time between 2 depreciations.\n" \
- "Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond."),
- 'method_number': fields.integer('Number of Depreciations', help="The number of depreciations needed to depreciate your asset"),
- 'method_period': fields.integer('Period Length', help="Time in month between two depreciations"),
- 'method_end': fields.date('Ending date'),
- 'note': fields.text('Note'),
- }
_order = 'date desc'
- _defaults = {
- 'date': lambda *args: time.strftime('%Y-%m-%d'),
- 'user_id': lambda self, cr, uid, ctx: uid
- }
+
+ name = fields.Char(string='History name', index=True)
+ user_id = fields.Many2one('res.users', string='User', required=True, default=lambda self: self.env.user)
+ date = fields.Date(required=True, default=fields.Date.context_today)
+ asset_id = fields.Many2one('account.asset.asset', string='Asset', required=True)
+ method_time = fields.Selection([('number', 'Number of Depreciations'), ('end', 'Ending Date')], string='Time Method', required=True,
+ help="The method to use to compute the dates and number of depreciation lines.\n"
+ "Number of Depreciations: Fix the number of depreciation lines and the time between 2 depreciations.\n"
+ "Ending Date: Choose the time between 2 depreciations and the date the depreciations won't go beyond.")
+ method_number = fields.Integer(string='Number of Depreciations', help="The number of depreciations needed to depreciate your asset")
+ method_period = fields.Integer(string='Period Length', help="Time in months between two depreciations")
+ method_end = fields.Date(string='Ending date')
+ note = fields.Text()
diff --git a/addons/account_asset/account_asset_demo.yml b/addons/account_asset/account_asset_demo.yml
index cdfdfbbbd46..c1bcfd0257c 100644
--- a/addons/account_asset/account_asset_demo.yml
+++ b/addons/account_asset/account_asset_demo.yml
@@ -14,7 +14,7 @@
'method_number': 3,
'account_asset_id': xfa_account_id[0],
'account_depreciation_id': xfa_account_id[0],
- 'account_expense_depreciation_id': expense_account_id[0],
+ 'account_income_recognition_id': expense_account_id[0],
}
self._update(cr, uid, 'account.asset.category', 'account_asset', vals, 'account_asset_category_fixedassets0')
vals = {
@@ -23,7 +23,7 @@
'method_number': 5,
'account_asset_id': xfa_account_id[0],
'account_depreciation_id': xfa_account_id[0],
- 'account_expense_depreciation_id': expense_account_id[0],
+ 'account_income_recognition_id': expense_account_id[0],
}
self._update(cr, uid, 'account.asset.category', 'account_asset', vals, 'account_asset_category_fixedassets1')
vals = {
@@ -32,7 +32,7 @@
'method_period': 12,
'method_number': 5,
'name': "CEO's car",
- 'purchase_value': 12000.0,
+ 'value': 12000.0,
'category_id': ref('account_asset_category_fixedassets0'),
}
self._update(cr, uid, 'account.asset.asset', 'account_asset', vals, 'account_asset_asset_vehicles0')
@@ -41,7 +41,7 @@
'salvage_value': 0.0,
'method_time': 'end',
'name': 'V6 Engine and 10 inches tires',
- 'purchase_value': 2800.0,
+ 'value': 2800.0,
'category_id': ref('account_asset_category_fixedassets0'),
}
self._update(cr, uid, 'account.asset.asset', 'account_asset', vals, 'account_asset_asset_cab0')
@@ -51,9 +51,9 @@
'state': 'open',
'method_period': 12,
'method_number': 20,
- 'purchase_date': time.strftime('%Y-01-01'),
+ 'date': time.strftime('%Y-01-01'),
'name': 'Office',
- 'purchase_value': 500000.0,
+ 'value': 500000.0,
'category_id': ref('account_asset_category_fixedassets0'),
}
self._update(cr, uid, 'account.asset.asset', 'account_asset', vals, 'account_asset_asset_office0')
@@ -67,6 +67,6 @@
'method_number': 6,
'account_asset_id': xfa_account_id[0],
'account_depreciation_id': xfa_account_id[0],
- 'account_expense_depreciation_id': sale_account_id[0],
+ 'account_income_recognition_id': sale_account_id[0],
}
self._update(cr, uid, 'account.asset.category', 'account_asset', vals, 'account_asset_category_sale')
diff --git a/addons/account_asset/account_asset_invoice.py b/addons/account_asset/account_asset_invoice.py
index 4f49018cfa1..b9b58171745 100644
--- a/addons/account_asset/account_asset_invoice.py
+++ b/addons/account_asset/account_asset_invoice.py
@@ -1,66 +1,97 @@
-# -*- encoding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-##############################################################################
-
-from openerp.osv import fields, osv
-
-class account_invoice(osv.osv):
+# -*- coding: utf-8 -*-
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+from openerp import api, fields, models
+import openerp.addons.decimal_precision as dp
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
+
+
+class AccountInvoice(models.Model):
_inherit = 'account.invoice'
- def action_number(self, cr, uid, ids, *args):
- result = super(account_invoice, self).action_number(cr, uid, ids, *args)
- for inv in self.browse(cr, uid, ids):
- self.pool.get('account.invoice.line').asset_create(cr, uid, inv.invoice_line_ids)
+
+ @api.multi
+ def action_move_create(self):
+ result = super(AccountInvoice, self).action_move_create()
+ for inv in self:
+ inv.invoice_line_ids.asset_create()
return result
- def line_get_convert(self, cr, uid, x, part, context=None):
- res = super(account_invoice, self).line_get_convert(cr, uid, x, part, context=context)
- res['asset_id'] = x.get('asset_id', False)
+ @api.model
+ def line_get_convert(self, line, part):
+ res = super(AccountInvoice, self).line_get_convert(line, part)
+ res['asset_id'] = line.get('asset_id', False)
return res
-class account_invoice_line(osv.osv):
-
+class AccountInvoiceLine(models.Model):
_inherit = 'account.invoice.line'
- _columns = {
- 'asset_category_id': fields.many2one('account.asset.category', 'Asset Category'),
- }
- def asset_create(self, cr, uid, lines, context=None):
- context = context or {}
- asset_obj = self.pool.get('account.asset.asset')
- for line in lines:
- if line.asset_category_id:
- vals = {
- 'name': line.name,
- 'code': line.invoice_id.number or False,
- 'category_id': line.asset_category_id.id,
- 'purchase_value': line.price_subtotal,
- 'date': line.invoice_id.date,
- 'partner_id': line.invoice_id.partner_id.id,
- 'company_id': line.invoice_id.company_id.id,
- 'currency_id': line.invoice_id.currency_id.id,
- 'purchase_date' : line.invoice_id.date_invoice,
- }
- changed_vals = asset_obj.onchange_category_id(cr, uid, [], vals['category_id'], context=context)
- vals.update(changed_vals['value'])
- asset_id = asset_obj.create(cr, uid, vals, context=context)
- if line.asset_category_id.open_asset:
- asset_obj.validate(cr, uid, [asset_id], context=context)
+
+ asset_category_id = fields.Many2one('account.asset.category', string='Asset Category')
+ asset_start_date = fields.Date(string='Asset End Date', compute='_get_asset_date', readonly=True, store=True)
+ asset_end_date = fields.Date(string='Asset Start Date', compute='_get_asset_date', readonly=True, store=True)
+ asset_mrr = fields.Float(string='Monthly Recurring Revenue', compute='_get_asset_date', readonly=True, digits=dp.get_precision('Account'), store=True)
+
+ @api.one
+ @api.depends('asset_category_id', 'invoice_id.date_invoice')
+ def _get_asset_date(self):
+ self.asset_mrr = 0
+ self.asset_start_date = False
+ self.asset_end_date = False
+ cat = self.asset_category_id
+ if cat:
+ months = cat.method_number * cat.method_period
+ if self.invoice_id.type in ['out_invoice', 'out_refund']:
+ self.asset_mrr = self.price_subtotal_signed / months
+ if self.invoice_id.date_invoice:
+ start_date = datetime.strptime(self.invoice_id.date_invoice, DF).replace(day=1)
+ end_date = (start_date + relativedelta(months=months, days=-1))
+ self.asset_start_date = start_date.strftime(DF)
+ self.asset_end_date = end_date.strftime(DF)
+
+ @api.one
+ def asset_create(self):
+ if self.asset_category_id and self.asset_category_id.method_number > 1:
+ vals = {
+ 'name': self.name,
+ 'code': self.invoice_id.number or False,
+ 'category_id': self.asset_category_id.id,
+ 'value': self.price_subtotal,
+ 'partner_id': self.invoice_id.partner_id.id,
+ 'company_id': self.invoice_id.company_id.id,
+ 'currency_id': self.invoice_id.currency_id.id,
+ 'date': self.asset_start_date or self.invoice_id.date_invoice,
+ 'invoice_id': self.invoice_id.id,
+ }
+ changed_vals = self.env['account.asset.asset'].onchange_category_id_values(vals['category_id'])
+ vals.update(changed_vals['value'])
+ asset = self.env['account.asset.asset'].create(vals)
+ if self.asset_category_id.open_asset:
+ asset.validate()
return True
+
+
+ @api.multi
+ def product_id_change(self, product, uom_id, qty=0, name='', type='out_invoice',
+ partner_id=False, fposition_id=False, price_unit=False, currency_id=False,
+ company_id=None, date_invoice=None):
+ res = super(AccountInvoiceLine, self).product_id_change(
+ product, uom_id, qty=qty, name=name, type=type, partner_id=partner_id,
+ fposition_id=fposition_id, price_unit=price_unit, currency_id=currency_id,
+ company_id=company_id, date_invoice=date_invoice
+ )
+ if product and res['value']:
+ product_obj = self.env['product.product'].browse(product)
+ if type == 'out_invoice':
+ res['value']['asset_category_id'] = product_obj.product_tmpl_id.deferred_revenue_category_id
+ elif type == 'in_invoice':
+ res['value']['asset_category_id'] = product_obj.product_tmpl_id.asset_category_id
+
+ return res
+
+
+class ProductTemplate(models.Model):
+ _inherit = 'product.template'
+ asset_category_id = fields.Many2one('account.asset.category', string='Asset Type', ondelete="restrict")
+ deferred_revenue_category_id = fields.Many2one('account.asset.category', string='Deferred Revenue Type', ondelete="restrict")
+
diff --git a/addons/account_asset/account_asset_invoice_view.xml b/addons/account_asset/account_asset_invoice_view.xml
deleted file mode 100644
index c44d0743e70..00000000000
--- a/addons/account_asset/account_asset_invoice_view.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0"?>
-<openerp>
- <data>
-
- <!-- Fiscal Year -->
- <record model="ir.ui.view" id="view_account_invoice_asset_form">
- <field name="name">account.invoice.line.form</field>
- <field name="model">account.invoice.line</field>
- <field name="inherit_id" ref="account.view_invoice_line_form"/>
- <field name="arch" type="xml">
- <field name="account_id" position="after">
- <field name="asset_category_id"/>
- </field>
- </field>
- </record>
-
- <record model="ir.ui.view" id="view_invoice_asset_category">
- <field name="name">account.invoice.supplier.form</field>
- <field name="model">account.invoice</field>
- <field name="inherit_id" ref="account.invoice_supplier_form"/>
- <field name="arch" type="xml">
- <xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='quantity']" position="before">
- <field name="asset_category_id"/>
- </xpath>
- </field>
- </record>
-
- </data>
-</openerp>
diff --git a/addons/account_asset/report/__init__.py b/addons/account_asset/report/__init__.py
index 5d59693a7fd..c60e4c9e043 100644
--- a/addons/account_asset/report/__init__.py
+++ b/addons/account_asset/report/__init__.py
@@ -1,22 +1,3 @@
# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-##############################################################################
import account_asset_report
diff --git a/addons/account_asset/report/account_asset_report.py b/addons/account_asset/report/account_asset_report.py
index 20bce5b17d3..938da73efd5 100644
--- a/addons/account_asset/report/account_asset_report.py
+++ b/addons/account_asset/report/account_asset_report.py
@@ -1,64 +1,46 @@
-# -*- encoding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-##############################################################################
+# -*- coding: utf-8 -*-
+from openerp import fields, models, tools
-from openerp import tools
-from openerp.osv import fields, osv
-class asset_asset_report(osv.osv):
+class AssetAssetReport(models.Model):
_name = "asset.asset.report"
_description = "Assets Analysis"
_auto = False
- _columns = {
- 'name': fields.char('Year', required=False, readonly=True),
- 'purchase_date': fields.date('Purchase Date', readonly=True),
- 'depreciation_date': fields.date('Depreciation Date', readonly=True),
- 'asset_id': fields.many2one('account.asset.asset', string='Asset', readonly=True),
- 'asset_category_id': fields.many2one('account.asset.category',string='Asset category'),
- 'partner_id': fields.many2one('res.partner', 'Partner', readonly=True),
- 'state': fields.selection([('draft','Draft'),('open','Running'),('close','Close')], 'Status', readonly=True),
- 'depreciation_value': fields.float('Amount of Depreciation Lines', readonly=True),
- 'move_check': fields.boolean('Posted', readonly=True),
- 'nbr': fields.integer('# of Depreciation Lines', readonly=True),
- 'gross_value': fields.float('Gross Amount', readonly=True),
- 'posted_value': fields.float('Posted Amount', readonly=True),
- 'unposted_value': fields.float('Unposted Amount', readonly=True),
- 'company_id': fields.many2one('res.company', 'Company', readonly=True),
- }
-
+
+ name = fields.Char(string='Year', required=False, readonly=True)
+ date = fields.Date(readonly=True)
+ depreciation_date = fields.Date(string='Depreciation Date', readonly=True)
+ asset_id = fields.Many2one('account.asset.asset', string='Asset', readonly=True)
+ asset_category_id = fields.Many2one('account.asset.category', string='Asset category', readonly=True)
+ partner_id = fields.Many2one('res.partner', string='Partner', readonly=True)
+ state = fields.Selection([('draft', 'Draft'), ('open', 'Running'), ('close', 'Close')], string='Status', readonly=True)
+ depreciation_value = fields.Float(string='Amount of Depreciation Lines', readonly=True)
+ installment_value = fields.Float(string='Amount of Installment Lines', readonly=True)
+ move_check = fields.Boolean(string='Posted', readonly=True)
+ installment_nbr = fields.Integer(string='# of Installment Lines', readonly=True)
+ depreciation_nbr = fields.Integer(string='# of Depreciation Lines', readonly=True)
+ gross_value = fields.Float(string='Gross Amount', readonly=True)
+ posted_value = fields.Float(string='Posted Amount', readonly=True)
+ unposted_value = fields.Float(string='Unposted Amount', readonly=True)
+ company_id = fields.Many2one('res.company', string='Company', readonly=True)
+
def init(self, cr):
- tools.drop_view_if_exists(cr, 'asset_asset_report')
- cr.execute("""
- create or replace view asset_asset_report as (
- select
+ tools.drop_view_if_exists(cr, 'asset_asset_report')
+ cr.execute("""
+ create or replace view asset_asset_report as (
+ select
min(dl.id) as id,
dl.name as name,
dl.depreciation_date as depreciation_date,
- a.purchase_date as purchase_date,
+ a.date as date,
(CASE WHEN (select min(d.id) from account_asset_depreciation_line as d
left join account_asset_asset as ac ON (ac.id=d.asset_id)
where a.id=ac.id) = min(dl.id)
- THEN a.purchase_value
+ THEN a.value
ELSE 0
END) as gross_value,
- dl.amount as depreciation_value,
+ dl.amount as depreciation_value,
+ dl.amount as installment_value,
(CASE WHEN dl.move_check
THEN dl.amount
ELSE 0
@@ -72,13 +54,13 @@ class asset_asset_report(osv.osv):
a.category_id as asset_category_id,
a.partner_id as partner_id,
a.state as state,
- count(dl.*) as nbr,
+ count(dl.*) as installment_nbr,
+ count(dl.*) as depreciation_nbr,
a.company_id as company_id
from account_asset_depreciation_line dl
left join account_asset_asset a on (dl.asset_id=a.id)
- group by
+ group by
dl.amount,dl.asset_id,dl.depreciation_date,dl.name,
- a.purchase_date, dl.move_check, a.state, a.category_id, a.partner_id, a.company_id,
- a.purchase_value, a.id, a.salvage_value
+ a.date, dl.move_check, a.state, a.category_id, a.partner_id, a.company_id,
+ a.value, a.id, a.salvage_value
)""")
-
diff --git a/addons/account_asset/report/account_asset_report_view.xml b/addons/account_asset/report/account_asset_report_view.xml
index 5b4b6d2bf8d..19f95375620 100644
--- a/addons/account_asset/report/account_asset_report_view.xml
+++ b/addons/account_asset/report/account_asset_report_view.xml
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<openerp>
-<data>
+<odoo>
<record model="ir.ui.view" id="action_account_asset_report_pivot">
<field name="name">asset.asset.report.pivot</field>
@@ -8,7 +7,7 @@
<field name="arch" type="xml">
<pivot string="Assets Analysis" disable_linking="True">
<field name="asset_category_id" type="row"/>
- <field name="purchase_date" type="col"/>
+ <field name="date" type="col"/>
<field name="posted_value" type="measure"/>
<field name="gross_value" type="measure"/>
<field name="depreciation_value" type="measure"/>
@@ -21,18 +20,46 @@
<field name="arch" type="xml">
<graph string="Assets Analysis" type="pivot">
<field name="asset_category_id" type="row"/>
- <field name="purchase_date" type="col"/>
+ <field name="date" type="col"/>
<field name="gross_value" type="measure"/>
</graph>
</field>
</record>
-
+
+ <!--
+ Asset Revenue Recognition Graph
+ -->
+ <record model="ir.ui.view" id="action_account_revenue_report_pivot">
+ <field name="name">asset.asset.report.pivot</field>
+ <field name="model">asset.asset.report</field>
+ <field name="arch" type="xml">
+ <pivot string="Deferred Revenue Analysis" disable_linking="True">
+ <field name="asset_category_id" type="row"/>
+ <field name="date" type="col"/>
+ <field name="gross_value" type="measure"/>
+ </pivot>
+ </field>
+ </record>
+ <record model="ir.ui.view" id="action_account_revenue_report_graph">
+ <field name="name">asset.revenue.report.graph</field>
+ <field name="model">asset.asset.report</field>
+ <field name="arch" type="xml">
+ <graph string="Deferred Revenue Analysis" type="pivot">
+ <field name="asset_id" type="row"/>
+ <field name="installment_nbr" type="measure"/>
+ <field name="gross_value" type="measure"/>
+ <field name="installment_value" type="measure"/>
+ <field name="posted_value" type="measure"/>
+ </graph>
+ </field>
+ </record>
+
<record id="view_asset_asset_report_search" model="ir.ui.view">
<field name="name">asset.asset.report.search</field>
<field name="model">asset.asset.report</field>
<field name="arch" type="xml">
<search string="Assets Analysis">
- <field name="purchase_date"/>
+ <field name="date"/>
<field name="depreciation_date"/>
<filter string="Draft" domain="[('state','=','draft')]" help="Assets in draft state"/>
<filter string="Running" domain="[('state','=','open')]" help="Assets in running state"/>
@@ -50,13 +77,46 @@
<filter string="Company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<separator/>
<filter string="Purchase Month" help="Date of asset purchase"
- context="{'group_by':'purchase_date:month'}"/>
+ context="{'group_by':'date:month'}"/>
<filter string="Depreciation Month" help="Date of depreciation"
context="{'group_by':'depreciation_date:month'}"/>
</group>
</search>
</field>
</record>
+
+ <!--
+ Asset Revenue Recognition Search
+ -->
+
+ <record id="view_asset_revenue_report_search" model="ir.ui.view">
+ <field name="name">asset.revenue.report.search</field>
+ <field name="model">asset.asset.report</field>
+ <field name="arch" type="xml">
+ <search string="Deferred Revenue Analysis">
+ <field name="date"/>
+ <field name="depreciation_date"/>
+ <filter string="Draft" domain="[('state','=','draft')]" help="Recognition in draft state"/>
+ <filter string="Running" domain="[('state','=','open')]" help="Assets in running state"/>
+ <separator/>
+ <filter string="Posted" name="posted" domain="[('move_check','=',True)]" help="Posted depreciation lines" context="{'unposted_value_visible': 0}"/>
+ <field name="asset_id"/>
+ <field name="asset_category_id"/>
+ <group expand="0" string="Extended Filters...">
+ <field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
+ <field name="company_id" groups="base.group_multi_company"/>
+ </group>
+ <group expand="1" string="Group By...">
+ <filter string="Revenue Recognition" name="revenue" context="{'group_by':'asset_id'}"/>
+ <filter string="Category" name="category" context="{'group_by':'asset_category_id'}"/>
+ <filter string="Company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
+ <filter string="Sales Month" domain="[('date','=',time.strftime('%%Y-%%m-%%d'))]" context="{'group_by':'date'}" help="Date of Revenue Sales"/>
+ <filter string="Revenue Month" name='rev_month'
+ context="{'group_by':'depreciation_date'}" help="Revenue Month"/>
+ </group>
+ </search>
+ </field>
+ </record>
<record model="ir.actions.act_window" id="action_asset_asset_report">
<field name="name">Assets Analysis</field>
@@ -64,18 +124,45 @@
<field name="view_type">form</field>
<field name="view_mode">pivot,graph</field>
<field name="search_view_id" ref="view_asset_asset_report_search"/>
+ <field name="domain">[('asset_category_id.type', '=', 'purchase')]</field>
<field name="context">{}</field> <!-- force empty -->
<field name="help" type="html">
<p>
- From this report, you can have an overview on all depreciation. The
- tool search can also be used to personalise your Assets reports and
- so, match this analysis to your needs;
+ From this report, you can have an overview on all depreciations. The
+ search bar can also be used to personalize your assets depreciation reporting.
</p>
</field>
</record>
-
+
+ <!--
+ Asset Revenue Recognition Action
+ -->
+
+ <record model="ir.actions.act_window" id="action_asset_revenue_report">
+ <field name="name">Deferred Revenue Analysis</field>
+ <field name="res_model">asset.asset.report</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">pivot,graph</field>
+ <field name="search_view_id" ref="view_asset_revenue_report_search"/>
+ <field name="domain">[('asset_category_id.type', '=', 'sale')]</field>
+ <field name="context">{}</field>
+ <field name="help" type="html">
+ <p>
+ From this report, you can have an overview of your deferred revenue. The
+ search bar can also be used to personalize your revenue recognition reporting.
+ </p>
+ </field>
+ </record>
+
<menuitem action="action_asset_asset_report"
id="menu_action_asset_asset_report"
- parent="account.menu_finance_reports"/>
-</data>
-</openerp>
+ parent="account.menu_finance_reports" sequence="21"/>
+
+ <!--
+ Asset Revenue Recognition Menu
+ -->
+
+ <menuitem action="action_asset_revenue_report"
+ id="menu_action_asset_revenue_report"
+ parent="account.menu_finance_reports" sequence="20"/>
+</odoo>
diff --git a/addons/account_asset/security/account_asset_security.xml b/addons/account_asset/security/account_asset_security.xml
index 77f218d13b9..02320b922dd 100644
--- a/addons/account_asset/security/account_asset_security.xml
+++ b/addons/account_asset/security/account_asset_security.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<openerp>
+<odoo>
<data noupdate="1">
<record id="account_asset_category_multi_company_rule" model="ir.rule">
<field name="name">Account Asset Category multi-company</field>
@@ -16,4 +16,4 @@
</record>
</data>
-</openerp>
+</odoo>
diff --git a/addons/account_asset/static/src/js/account_asset.js b/addons/account_asset/static/src/js/account_asset.js
new file mode 100644
index 00000000000..accd57ed3f5
--- /dev/null
+++ b/addons/account_asset/static/src/js/account_asset.js
@@ -0,0 +1,39 @@
+/*
+Purpose : show toggle button on depreciation/installment lines for posted/unposted line.
+Details : called in list view with "<button name="create_move" type="object" widget="widgetonbutton"/>",
+ this will call the method create_move on the object account.asset.depreciation.line
+*/
+
+odoo.define('account_asset.widget', function(require) {
+"use strict";
+
+// openerp.account_asset = function (instance) {
+ // var _t = instance.web._t,
+ // _lt = instance.web._lt;
+ // var QWeb = instance.web.qweb;
+ // instance.web.account_asset = instance.web.account_asset || {};
+
+var core = require('web.core');
+var QWeb = core.qweb;
+var list_widget_registry = core.list_widget_registry;
+var Column = list_widget_registry.get('field');
+
+var WidgetOnButton = Column.extend({
+ format: function (row_data, options) {
+ this._super(row_data, options);
+ this.has_value = !!row_data.move_check.value;
+ this.parent_state = row_data.parent_state.value;
+ this.icon = this.has_value ? 'gtk-yes' : 'gtk-no'; // or STOCK_YES and STOCK_NO
+ this.string = this.has_value ? 'Posted' : 'Unposted';
+ var template = this.icon && 'ListView.row.buttonwidget';
+ return QWeb.render(template, {
+ widget: this,
+ prefix: instance.session.prefix,
+ disabled: this.has_value,
+ invisible : true ? this.parent_state !== 'open' : false
+ });
+ },
+});
+
+list_widget_registry.add("button.widgetonbutton", WidgetOnButton);
+});
diff --git a/addons/account_asset/static/src/xml/account_asset_template.xml b/addons/account_asset/static/src/xml/account_asset_template.xml
new file mode 100644
index 00000000000..ed33ee35f87
--- /dev/null
+++ b/addons/account_asset/static/src/xml/account_asset_template.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template">
+ <button t-name="ListView.row.buttonwidget" type="button"
+ t-att-title="widget.string" t-att-disabled="disabled || undefined"
+ t-att-class="disabled ? 'oe_list_button_disabled' : '',invisible ? 'oe_form_invisible' : ''"
+ ><img t-attf-src="#{prefix}/web/static/src/img/icons/#{widget.icon}.png"
+ t-att-alt="widget.string"/></button>
+</templates> \ No newline at end of file
diff --git a/addons/account_asset/test/account_asset.yml b/addons/account_asset/test/account_asset.yml
deleted file mode 100644
index 708f16786a8..00000000000
--- a/addons/account_asset/test/account_asset.yml
+++ /dev/null
@@ -1,35 +0,0 @@
--
- In order to test the process of Account Asset, I perform a action to confirm Account Asset.
--
- !python {model: account.asset.asset}: |
- self.validate(cr, uid, [ref("account_asset_asset_vehicles_test0")])
--
- I check Asset is now in Open state.
--
- !assert {model: account.asset.asset, id: account_asset_asset_vehicles_test0, severity: error, string: Asset should be in Open state}:
- - state == 'open'
--
- I compute depreciation lines for asset of CEO's Car .
--
- !python {model: account.asset.asset}: |
- self.compute_depreciation_board(cr, uid, [ref("account_asset_asset_vehicles_test0")])
- # pressing computation button can be remove if creation of depreciation lines while asset is created
- value = self.browse(cr, uid, [ref("account_asset_asset_vehicles_test0")])[0]
- assert value.method_number == len(value.depreciation_line_ids), 'Depreciation lines not created correctly'
--
- I create account move for all depreciation lines.
--
- !python {model: account.asset.depreciation.line}: |
- ids = self.search(cr, uid, [('asset_id','=',ref('account_asset_asset_vehicles_test0'))])
- self.create_move(cr, uid, ids)
--
- I check the move line is created.
--
- !python {model: account.asset.asset}: |
- asset = self.browse(cr, uid, [ref("account_asset_asset_vehicles_test0")])[0]
- assert len(asset.depreciation_line_ids) == len(asset.account_move_line_ids), 'Move lines not created correctly'
--
- I Check that After creating all the moves of depreciation lines the state "Close".
--
- !assert {model: account.asset.asset, id: account_asset_asset_vehicles_test0}:
- - state == 'close'
diff --git a/addons/account_asset/test/account_asset_demo.yml b/addons/account_asset/test/account_asset_demo.yml
deleted file mode 100644
index 8e8813574a5..00000000000
--- a/addons/account_asset/test/account_asset_demo.yml
+++ /dev/null
@@ -1,9 +0,0 @@
--
- !record {model: account.asset.category, id: account_asset_category_fixedassets_test0}:
- account_asset_id: xfa
--
- !record {model: account.asset.asset, id: account_asset_asset_vehicles_test0}:
- category_id: account_asset_category_sale_test0
--
- !record {model: account.asset.asset, id: account_asset_asset_vehicles_test0}:
- method_number: 5
diff --git a/addons/account_asset/test/account_asset_demo_test.xml b/addons/account_asset/test/account_asset_demo_test.xml
index 4fbfd8e4a44..47af3b0d0cf 100644
--- a/addons/account_asset/test/account_asset_demo_test.xml
+++ b/addons/account_asset/test/account_asset_demo_test.xml
@@ -1,36 +1,49 @@
<?xml version="1.0" ?>
-<openerp>
+<odoo>
<data noupdate="1">
<!--
Asset Category Demo
-->
<record id="account_asset_category_fixedassets_test0" model="account.asset.category">
- <field name="account_expense_depreciation_id" ref="a_expense"/>
+ <field name="account_depreciation_id" ref="a_expense"/>
<field name="account_asset_id" ref="xfa"/>
- <field name="account_depreciation_id" ref="xfa"/>
<field name="journal_id" ref="expenses_journal"/>
<field name="name">Hardware - 3 Years</field>
<field name="method_number">3</field>
+ <field name="method_period">12</field>
</record>
<record id="account_asset_category_fixedassets_test1" model="account.asset.category">
- <field name="account_expense_depreciation_id" ref="a_expense"/>
+ <field name="account_depreciation_id" ref="a_expense"/>
<field name="account_asset_id" ref="xfa"/>
- <field name="account_depreciation_id" ref="xfa"/>
<field name="journal_id" ref="expenses_journal"/>
<field name="name">Cars - 5 Years</field>
<field name="method_number">5</field>
+ <field name="method_period">12</field>
</record>
<record id="account_asset_category_sale_test0" model="account.asset.category">
- <field name="account_expense_depreciation_id" ref="a_sale"/>
+ <field name="account_depreciation_id" ref="a_sale"/>
<field name="account_asset_id" ref="xfa"/>
- <field name="account_depreciation_id" ref="xfa"/>
- <field name="journal_id" ref="expenses_journal"/>
- <field name="name">Revenue Recognition Maintenance Contract - 3 Years</field>
+ <field name="journal_id" ref="sales_journal"/>
+ <field name="name">Maintenance Contract - 3 Years</field>
<field name="method_number">3</field>
+ <field name="method_period">12</field>
+ <field name="prorata" eval="True"/>
+ <field name="type">sale</field>
+ </record>
+
+ <record id="account_asset_category_sale1" model="account.asset.category">
+ <field name="account_asset_id" ref="xfa"/>
+ <field name="account_depreciation_id" ref="a_sale"/>
+ <field name="journal_id" ref="sales_journal"/>
+ <field name="name">Maintenance Contract - 1 Year</field>
+ <field name="method_number">12</field>
+ <field name="method_period">1</field>
+ <field name="prorata" eval="True"/>
+ <field name="type">sale</field>
</record>
<!--
@@ -43,7 +56,7 @@
<field eval="12" name="method_period"/>
<field eval="5" name="method_number"/>
<field name="name">CEO's Car</field>
- <field eval="12000.0" name="purchase_value"/>
+ <field eval="12000.0" name="value"/>
<field name="category_id" ref="account_asset_category_fixedassets_test0"/>
</record>
@@ -52,7 +65,7 @@
<field eval="0.0" name="salvage_value"/>
<field name="method_time">end</field>
<field name="name">V6 Engine and 10 inches tires</field>
- <field eval="2800.0" name="purchase_value"/>
+ <field eval="2800.0" name="value"/>
<field name="category_id" ref="account_asset_category_fixedassets_test0"/>
</record>
@@ -61,12 +74,75 @@
<field eval="100000.0" name="salvage_value"/>
<field name="state">open</field>
<field eval="12" name="method_period"/>
- <field eval="20" name="method_number"/>
- <field name="purchase_date" eval="time.strftime('%Y-01-01')"/>
+ <field eval="3" name="method_number"/>
+ <field name="date" eval="time.strftime('%Y-01-01')"/>
<field name="name">Office</field>
- <field eval="500000.0" name="purchase_value"/>
+ <field eval="500000.0" name="value"/>
<field name="category_id" ref="account_asset_category_fixedassets_test0"/>
</record>
-
+
+ <record id="account_asset_asset_pc" model="account.asset.asset">
+ <field eval="1" name="prorata"/>
+ <field name="state">draft</field>
+ <field eval="12" name="method_period"/>
+ <field eval="3" name="method_number"/>
+ <field name="date" eval="time.strftime('%Y-01-01')"/>
+ <field name="name">Car Maintenance</field>
+ <field eval="30000.0" name="value"/>
+ <field name="category_id" ref="account_asset_category_sale_test0"/>
+ </record>
+
+ <record id="account_asset_asset_a/c" model="account.asset.asset">
+ <field eval="1" name="prorata"/>
+ <field name="state">open</field>
+ <field eval="1" name="method_period"/>
+ <field eval="12" name="method_number"/>
+ <field name="date" eval="time.strftime('%Y-01-01')"/>
+ <field name="name">Air Conditioner Maintenance Contract</field>
+ <field eval="1000.0" name="value"/>
+ <field name="category_id" ref="account_asset_category_sale1"/>
+ </record>
+
+
+ <!--
+ Assets Tests
+ -->
+ <!--
+ <record id="data_fiscalyear_plus1" model="account.fiscalyear">
+ <field ref="base.main_company" name="company_id"/>
+ <field eval="'%s-01-01' %(datetime.now().year+1)" name="date_start"/>
+ <field eval="'%s-12-31' %(datetime.now().year+1)" name="date_stop"/>
+ <field name="name" eval="'Fiscal Year X %s' %(datetime.now().year+1)"/>
+ <field name="code" eval="'FY%s' %(datetime.now().year+1)"/>
+ </record>
+ <record id="data_fiscalyear_plus2" model="account.fiscalyear">
+ <field ref="base.main_company" name="company_id"/>
+ <field eval="'%s-01-01' %(datetime.now().year+2)" name="date_start"/>
+ <field eval="'%s-12-31' %(datetime.now().year+2)" name="date_stop"/>
+ <field name="name" eval="'Fiscal Year X %s' %(datetime.now().year+2)"/>
+ <field name="code" eval="'FY%s' %(datetime.now().year+2)"/>
+ </record>
+ <record id="data_fiscalyear_plus3" model="account.fiscalyear">
+ <field ref="base.main_company" name="company_id"/>
+ <field eval="'%s-01-01' %(datetime.now().year+3)" name="date_start"/>
+ <field eval="'%s-12-31' %(datetime.now().year+3)" name="date_stop"/>
+ <field name="name" eval="'Fiscal Year X %s' %(datetime.now().year+3)"/>
+ <field name="code" eval="'FY%s' %(datetime.now().year+3)"/>
+ </record>
+ <record id="data_fiscalyear_plus4" model="account.fiscalyear">
+ <field ref="base.main_company" name="company_id"/>
+ <field eval="'%s-01-01' %(datetime.now().year+4)" name="date_start"/>
+ <field eval="'%s-12-31' %(datetime.now().year+4)" name="date_stop"/>
+ <field name="name" eval="'Fiscal Year X %s' %(datetime.now().year+4)"/>
+ <field name="code" eval="'FY%s' %(datetime.now().year+4)"/>
+ </record>
+ <record id="data_fiscalyear_plus5" model="account.fiscalyear">
+ <field ref="base.main_company" name="company_id"/>
+ <field eval="'%s-01-01' %(datetime.now().year+5)" name="date_start"/>
+ <field eval="'%s-12-31' %(datetime.now().year+5)" name="date_stop"/>
+ <field name="name" eval="'Fiscal Year X %s' %(datetime.now().year+5)"/>
+ <field name="code" eval="'FY%s' %(datetime.now().year+5)"/>
+ </record>
+ -->
</data>
-</openerp> \ No newline at end of file
+</odoo>
diff --git a/addons/account_asset/test/account_asset_wizard.yml b/addons/account_asset/test/account_asset_wizard.yml
deleted file mode 100644
index 91a85218406..00000000000
--- a/addons/account_asset/test/account_asset_wizard.yml
+++ /dev/null
@@ -1,27 +0,0 @@
--
- I create a record to change the duration of asset for calculating depreciation.
--
- !record {model: asset.modify, id: asset_modify_number_0, context: "{'active_id': ref('account_asset_asset_office_test0')}"}:
- method_number: 10.0
--
- I change the duration.
--
- !python {model: asset.modify}: |
- context = {"active_id":ref('account_asset_asset_office_test0')}
- self.modify(cr, uid, [ref("asset_modify_number_0")], context=context)
--
- I check the proper depreciation lines created.
--
- !assert {model: account.asset.asset, id: account_asset.account_asset_asset_office_test0}:
- - method_number == len(depreciation_line_ids) -1
--
- I create a period to compute a asset on period.
--
- !record {model: asset.depreciation.confirmation.wizard, id: asset_compute_period_0}:
- {}
--
- I compute a asset on period.
--
- !python {model: asset.depreciation.confirmation.wizard}: |
- context = {"active_ids": [ref("account_asset.account_asset_asset_office_test0")], "active_id":ref('account_asset.account_asset_asset_office_test0')}
- self.asset_compute(cr, uid, [ref("asset_compute_period_0")], context=context)
diff --git a/addons/account_asset/tests/__init__.py b/addons/account_asset/tests/__init__.py
new file mode 100644
index 00000000000..054eb7df4fe
--- /dev/null
+++ b/addons/account_asset/tests/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+#
+##############################################################################
+
+from . import test_account_asset
diff --git a/addons/account_asset/tests/test_account_asset.py b/addons/account_asset/tests/test_account_asset.py
new file mode 100644
index 00000000000..c3c582e9507
--- /dev/null
+++ b/addons/account_asset/tests/test_account_asset.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+
+from openerp import tools
+from openerp.tests import common
+from openerp.modules.module import get_module_resource
+
+
+class TestAccountAsset(common.TransactionCase):
+
+ def _load(self, module, *args):
+ tools.convert_file(self.cr, 'account_asset',
+ get_module_resource(module, *args),
+ {}, 'init', False, 'test', self.registry._assertion_report)
+
+ def test_00_account_asset_asset(self):
+ self._load('account', 'test', 'account_minimal_test.xml')
+ self._load('account_asset', 'test', 'account_asset_demo_test.xml')
+
+ # self.browse_ref("account_asset.data_fiscalyear_plus1").create_period()
+ # self.browse_ref("account_asset.data_fiscalyear_plus2").create_period()
+ # self.browse_ref("account_asset.data_fiscalyear_plus3").create_period()
+ # self.browse_ref("account_asset.data_fiscalyear_plus4").create_period()
+ # self.browse_ref("account_asset.data_fiscalyear_plus5").create_period()
+
+ # In order to test the process of Account Asset, I perform a action to confirm Account Asset.
+ self.browse_ref("account_asset.account_asset_asset_vehicles0").validate()
+
+ # I check Asset is now in Open state.
+ self.assertEqual(self.browse_ref("account_asset.account_asset_asset_vehicles0").state, 'open',
+ 'Asset should be in Open state')
+
+ # I compute depreciation lines for asset of CEOs Car.
+ self.browse_ref("account_asset.account_asset_asset_vehicles0").compute_depreciation_board()
+ value = self.browse_ref("account_asset.account_asset_asset_vehicles0")
+ self.assertEqual(value.method_number, len(value.depreciation_line_ids),
+ 'Depreciation lines not created correctly')
+
+ # I create account move for all depreciation lines.
+ ids = self.env['account.asset.depreciation.line'].search([('asset_id', '=', self.ref('account_asset.account_asset_asset_vehicles0'))])
+ for line in ids:
+ line.create_move()
+
+ # I check the move line is created.
+ asset = self.env['account.asset.asset'].browse([self.ref("account_asset.account_asset_asset_vehicles0")])[0]
+ self.assertEqual(len(asset.depreciation_line_ids), len(asset.account_move_line_ids),
+ 'Move lines not created correctly')
+
+ # I Check that After creating all the moves of depreciation lines the state "Close".
+ self.assertEqual(self.browse_ref("account_asset.account_asset_asset_vehicles0").state, 'close',
+ 'State of asset should be close')
+
+ invoice = self.env['account.invoice'].create({
+ 'partner_id': self.ref("base.res_partner_12"),
+ 'account_id': self.ref("account_asset.a_sale"),
+ })
+ self.env['account.invoice.line'].create({
+ 'invoice_id': invoice.id,
+ 'account_id': self.ref("account_asset.a_sale"),
+ 'name': 'Insurance claim',
+ 'price_unit': 450,
+ 'quantity': 1,
+ 'asset_category_id': self.ref("account_asset.account_asset_category_sale1"),
+ })
+ invoice.signal_workflow('invoice_open')
+
+ from datetime import datetime
+ from dateutil.relativedelta import relativedelta
+ line_obj = self.env['account.asset.depreciation.line']
+ recognition_ids = self.env['account.asset.asset'].search([('code', '=', invoice.number)])
+ self.assertTrue(recognition_ids,
+ 'Revenue recognition has been not created from invoice.')
+
+ # I confirm revenue recognition.
+ for asset in recognition_ids:
+ asset.validate()
+ recognition = recognition_ids[0]
+ first_invoice_line = invoice.invoice_line_ids[0]
+ self.assertTrue(recognition.state == 'open',
+ 'Recognition should be in Open state')
+ self.assertEqual(recognition.value, first_invoice_line.price_subtotal,
+ 'Recognition value is not same as invoice line.')
+
+ # I post installment lines.
+ line_ids = [rec for rec in recognition.depreciation_line_ids]
+ for line in line_ids:
+ line.create_move()
+
+ # I check that move line is created from posted installment lines.
+ self.assertEqual(len(recognition.depreciation_line_ids), len(recognition.account_move_line_ids),
+ 'Move lines not created correctly.')
+
+ # I check data in move line and installment line.
+ first_installment_line = recognition.depreciation_line_ids[0]
+ first_move_line = recognition.account_move_line_ids[0]
+ self.assertEqual(first_installment_line.amount, first_move_line.credit,
+ 'First installment line amount is incorrect.')
+ remaining_value = recognition.value - first_installment_line.amount
+ self.assertEqual(first_installment_line.remaining_value, recognition.value - first_installment_line.amount,
+ 'Remaining value is incorrect.')
+
+ # I check next installment date.
+ last_installment_date = datetime.strptime(first_installment_line.depreciation_date, '%Y-%m-%d')
+ installment_date = (last_installment_date+relativedelta(months=+recognition.method_period))
+ self.assertEqual(recognition.depreciation_line_ids[1].depreciation_date, str(installment_date.date()),
+ 'Installment date is incorrect.')
+
+ # WIZARD
+ # I create a record to change the duration of asset for calculating depreciation.
+
+ account_asset_asset_office0 = self.browse_ref('account_asset.account_asset_asset_office0')
+ asset_modify_number_0 = self.env['asset.modify'].create({
+ 'name': 'Test reason',
+ 'method_number': 10.0,
+ }).with_context({'active_id': account_asset_asset_office0.id})
+ # I change the duration.
+ asset_modify_number_0.with_context({'active_id': account_asset_asset_office0.id}).modify()
+
+ # I check the proper depreciation lines created.
+ self.assertEqual(account_asset_asset_office0.method_number, len(account_asset_asset_office0.depreciation_line_ids) - 1)
+ # I compute a asset on period.
+
+ context = {
+ "active_ids": [self.ref("account_asset.menu_asset_depreciation_confirmation_wizard")],
+ "active_id": self.ref('account_asset.menu_asset_depreciation_confirmation_wizard'),
+ 'type': 'sale'
+ }
+ asset_compute_period_0 = self.env['asset.depreciation.confirmation.wizard'].create({})
+ asset_compute_period_0.with_context(context).asset_compute()
diff --git a/addons/account_asset/views/account_asset.xml b/addons/account_asset/views/account_asset.xml
new file mode 100644
index 00000000000..198e95172d4
--- /dev/null
+++ b/addons/account_asset/views/account_asset.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <template id="assets_backend" name="account assets" inherit_id="web.assets_backend">
+ <xpath expr="." position="inside">
+ <script type="text/javascript" src="/account_asset/static/src/js/account_asset.js"></script>
+ </xpath>
+ </template>
+</odoo>
+
+
diff --git a/addons/account_asset/views/account_asset_invoice_view.xml b/addons/account_asset/views/account_asset_invoice_view.xml
new file mode 100644
index 00000000000..b4207df4dd6
--- /dev/null
+++ b/addons/account_asset/views/account_asset_invoice_view.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<odoo>
+
+ <record model="ir.ui.view" id="view_account_invoice_asset_form">
+ <field name="name">account.invoice.line.form</field>
+ <field name="model">account.invoice.line</field>
+ <field name="inherit_id" ref="account.view_invoice_line_form"/>
+ <field name="arch" type="xml">
+ <field name="account_id" position="after">
+ <field name="asset_category_id" domain="[('type','=','sale')]" string="Revenue Recognition Category"/>
+ </field>
+ </field>
+ </record>
+
+ <!-- Add "Asset Category" to supplier invoices -->
+ <record model="ir.ui.view" id="view_invoice_asset_category">
+ <field name="name">account.invoice.supplier.form</field>
+ <field name="model">account.invoice</field>
+ <field name="inherit_id" ref="account.invoice_supplier_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='quantity']" position="before">
+ <field string="Asset Category" name="asset_category_id" domain="[('type','=','purchase')]" context="{'default_type':'purchase'}"/>
+ </xpath>
+ </field>
+ </record>
+
+ <!-- Add "Asset Category" to customer invoices -->
+ <record model="ir.ui.view" id="view_invoice_revenue_recognition_category">
+ <field name="name">account.invoice.form</field>
+ <field name="model">account.invoice</field>
+ <field name="inherit_id" ref="account.invoice_form"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='quantity']" position="before">
+ <field string="Deferred Revenue Category" name="asset_category_id" domain="[('type','=','sale')]" context="{'default_type':'sale'}"/>
+ </xpath>
+ </field>
+ </record>
+
+</odoo>
diff --git a/addons/account_asset/account_asset_view.xml b/addons/account_asset/views/account_asset_view.xml
index d5a9f609329..854e77a38e6 100644
--- a/addons/account_asset/account_asset_view.xml
+++ b/addons/account_asset/views/account_asset_view.xml
@@ -1,5 +1,4 @@
-<openerp>
-<data>
+<odoo>
<!--
Asset Category
@@ -13,25 +12,39 @@
<group>
<group>
<field name="name"/>
+ <field name="type" attrs="{'invisible': 1}"/>
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
</group>
<group>
<field name="journal_id"/>
- <field name="account_asset_id" on_change="onchange_account_asset(account_asset_id)"/>
- <field name="account_depreciation_id"/>
- <field name="account_expense_depreciation_id"/>
+ <div>
+ <label for="account_asset_id" string="Asset Account" attrs="{'invisible': [('type','!=','purchase')]}"/>
+ <label for="account_asset_id" string="Deferred Revenue Account" attrs="{'invisible': [('type','!=','sale')]}"/>
+ </div>
+ <field name="account_asset_id" nolabel="1" attrs="{'invisible': [('type','=', False)]}"/>
+ <div>
+ <label for="account_depreciation_id" string="Depreciation Account" attrs="{'invisible': [('type','!=','purchase')]}"/>
+ <label for="account_depreciation_id" string="Income Account" attrs="{'invisible': [('type','!=','sale')]}"/>
+ </div>
+ <field name="account_depreciation_id" nolabel="1"/>
</group>
- <group string="Depreciation Dates">
- <field name="method_time"/>
- <field name="method_number" attrs="{'invisible':[('method_time','=','end')], 'required':[('method_time','=','number')]}"/>
- <field name="method_period"/>
- <field name="method_end" attrs="{'required': [('method_time','=','end')], 'invisible':[('method_time','=','number')]}"/>
+ <group>
+ <separator string="Periodicity" colspan="2"/>
+ <field name="method_time" string="Time Method Based On" widget="radio" attrs="{'invisible': [('type','!=','purchase')]}"/>
+ <field name="method_number" string="Number of Entries" attrs="{'invisible':['|',('method_time','!=','number'),'&amp;',('type','=', False)], 'required':[('method_time','=','number')]}"/>
+ <label for="method_period" string="Every"/>
+ <div>
+ <field name="method_period" nolabel="1" attrs="{'invisible': [('type','=', False)]}" class="oe_inline"/>
+ months
+ </div>
+ <field name="method_end" attrs="{'required': [('method_time','=','end')], 'invisible':[('method_time','!=','end')]}"/>
+ <field name="open_asset"/>
</group>
- <group string="Depreciation Method">
+ <group attrs="{'invisible': [('type','=','sale')]}">
+ <separator string="Depreciation Method" colspan="2"/>
<field name="method" widget="radio"/>
<field name="method_progress_factor" attrs="{'invisible':[('method','=','linear')], 'required':[('method','=','degressive')]}"/>
<field name="prorata"/>
- <field name="open_asset"/>
</group>
<group groups="analytic.group_analytic_accounting" string="Analytic Information">
<field name="account_analytic_id"/>
@@ -61,25 +74,28 @@
<field name="model">account.asset.category</field>
<field name="arch" type="xml">
<search string="Search Asset Category">
- <field name="name" string="Asset Category"/>
+ <filter string="Sales" domain="[('type','=', 'sale')]" help="Deferred Revenues"/>
+ <filter string="Purchase" domain="[('type','=', 'purchase')]" help="Assets"/>
+ <field name="name" string="Category"/>
<field name="journal_id"/>
+ <group expand="0" string="Group By...">
+ <filter string="Type" domain="[]" context="{'group_by':'type'}"/>
+ </group>
</search>
</field>
</record>
- <!--
- Asset
- -->
-
<record model="ir.ui.view" id="view_account_asset_asset_form">
<field name="name">account.asset.asset.form</field>
<field name="model">account.asset.asset</field>
<field name="arch" type="xml">
<form string="Asset">
<header>
- <button name="validate" states="draft" string="Confirm Asset" type="object" class="oe_highlight"/>
+ <button name="validate" states="draft" string="Confirm" type="object" class="oe_highlight"/>
+ <button type="object" name="compute_depreciation_board" string="Compute Depreciation" states="draft"/>
<button name="set_to_close" states="open" string="Set to Close" type="object" class="oe_highlight"/>
<button name="set_to_draft" states="open" string="Set to Draft" type="object" />
+ <button name="%(action_asset_modify)d" states="open" string="Modify Depreciation" type="action"/>
<field name="state" widget="statusbar" statusbar_visible="draft,open"/>
</header>
<sheet>
@@ -96,63 +112,46 @@
</div>
<group>
<group>
- <field name="category_id" on_change="onchange_category_id(category_id)"/>
+ <field name="category_id" domain="[('type', '=', default_category_id_type)]" context="{'default_type': default_category_id_type}" help="Category of asset"/>
<field name="code"/>
+ <field name="date" help="Date of asset"/>
+ <field name="type" invisible="1"/>
</group>
<group>
- <field name="purchase_date"/>
<field name="currency_id" groups="base.group_multi_currency"/>
- <field name="company_id" options="{'no_create': True}" groups="base.group_multi_company" on_change="onchange_company_id(company_id)"/>
+ <field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
+ <field name="value" widget="monetary" options="{'currency_field': 'currency_id'}" help="Gross value of asset"/>
+ <field name="salvage_value" widget="monetary" options="{'currency_field': 'currency_id'}"/>
+ <field name="value_residual" widget="monetary" options="{'currency_field': 'currency_id'}"/>
+ <field name="partner_id" string="Partner"/>
+ <field name="invoice_id" string="Invoice" domain="[('invoice_line_ids.asset_category_id.type','=','purchase')]"/>
</group>
</group>
<notebook colspan="4">
- <page string="General">
- <group>
- <group>
- <field name="purchase_value" widget="monetary" options="{'currency_field': 'currency_id'}" on_change="onchange_purchase_salvage_value(purchase_value, salvage_value)"/>
- <field name="salvage_value" widget="monetary" options="{'currency_field': 'currency_id'}" on_change="onchange_purchase_salvage_value(purchase_value, salvage_value)"/>
- <field name="value_residual" widget="monetary" options="{'currency_field': 'currency_id'}"/>
- <field name="partner_id"/>
- </group>
- <group>
- <field name="method" widget="radio"/>
- <field name="method_progress_factor" attrs="{'invisible':[('method','=','linear')], 'required':[('method','=','degressive')]}"/>
- <label for="method_time"/>
- <div>
- <field name="method_time" on_change="onchange_method_time(method_time)" class="oe_inline"/>
- <button name="%(action_asset_modify)d" states="open" string="Change Duration" type="action" icon="terp-stock_effects-object-colorize" class="oe_inline" colspan="1"/>
- </div>
- <field name="prorata" attrs="{'invisible': [('method_time','=','end')]}"/>
- <field name="method_number" attrs="{'invisible':[('method_time','=','end')], 'required':[('method_time','=','number')]}"/>
- <field name="method_period"/>
- <field name="method_end" attrs="{'required': [('method_time','=','end')], 'invisible':[('method_time','=','number')]}"/>
- </group>
- </group>
- </page>
<page string="Depreciation Board">
- <field name="depreciation_line_ids" mode="tree">
- <tree string="Depreciation Lines" colors="blue:(move_check == False);black:(move_check == True)">
+ <field name="depreciation_line_ids" mode="tree" options="{'reload_whole_on_button': true}">
+ <tree string="Depreciation Lines" colors="blue:(move_check == False);black:(move_check == True)" create="false">
+ <field name="sequence" string="Serial Number"/>
<field name="depreciation_date"/>
- <field name="sequence" invisible="1"/>
<field name="depreciated_value" readonly="1"/>
<field name="amount" widget="monetary"/>
<field name="remaining_value" readonly="1" widget="monetary"/>
- <field name="move_check"/>
+ <field name="move_check" invisible="1"/>
<field name="parent_state" invisible="1"/>
- <button name="create_move" attrs="{'invisible':['|',('move_check','!=',False),('parent_state','!=','open')]}" icon="gtk-execute" string="Create Move" type="object"/>
+ <button name="create_move" type="object" widget="widgetonbutton"/>
</tree>
<form string="Depreciation Lines">
<group>
<group>
<field name="parent_state" invisible="1"/>
<field name="name"/>
- <field name="amount" widget="monetary"/>
+ <field name="sequence"/>
<field name="move_id"/>
<field name="move_check"/>
<field name="parent_state" invisible="1"/>
</group>
<group>
- <field name="sequence"/>
+ <field name="amount" widget="monetary"/>
<field name="depreciation_date"/>
<field name="depreciated_value"/>
<field name="remaining_value"/>
@@ -160,17 +159,33 @@
</group>
</form>
</field>
- <button type="object" name="compute_depreciation_board" string="Compute" icon="terp-stock_format-scientific" colspan="2" attrs="{'invisible':[('state','=','close')]}"/>
</page>
<page string="History">
- <field name="account_move_line_ids" readonly="1"/>
<field name="history_ids" readonly="1"/>
</page>
- <page string="Notes">
- <field name="note" placeholder="Add an internal note here..."/>
- </page>
</notebook>
+ <separator string="Depreciation Information" colspan="4"/>
+ <group>
+ <group>
+ <field name="method" widget="radio" attrs="{'invisible': [('type','=','sale')]}"/>
+ <field name="method_progress_factor" attrs="{'invisible':[('method','=','linear')], 'required':[('method','=','degressive')]}"/>
+ <label for="method_time"/>
+ <div>
+ <field name="method_time"/>
+ </div>
+ <field name="prorata" attrs="{'invisible': [('method_time','=','end')]}"/>
+ </group>
+ <group>
+ <field name="method_number" attrs="{'invisible':[('method_time','=','end')], 'required':[('method_time','=','number')]}"/>
+ <field name="method_period"/>
+ <field name="method_end" attrs="{'required': [('method_time','=','end')], 'invisible':[('method_time','=','number')]}"/>
+ </group>
+ </group>
</sheet>
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
</form>
</field>
</record>
@@ -182,27 +197,9 @@
<tree string="Assets" colors="blue:(state == 'draft');grey:(state == 'close')">
<field name="name"/>
<field name="category_id"/>
- <field name="purchase_date"/>
+ <field name="date"/>
<field name="partner_id"/>
- <field name="purchase_value"/>
- <field name="value_residual" widget="monetary"/>
- <field name="currency_id" groups="base.group_multi_currency"/>
- <field name="company_id" groups="base.group_multi_company"/>
- <field name="state"/>
- </tree>
- </field>
- </record>
-
- <record model="ir.ui.view" id="view_account_asset_asset_hierarchy_tree">
- <field name="name">account.asset.asset.hierarchy</field>
- <field name="model">account.asset.asset</field>
- <field name="arch" type="xml">
- <tree string="Assets">
- <field name="name"/>
- <field name="code"/>
- <field name="category_id"/>
- <field name="purchase_date"/>
- <field name="purchase_value"/>
+ <field name="value"/>
<field name="value_residual" widget="monetary"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="company_id" groups="base.group_multi_company"/>
@@ -217,15 +214,34 @@
<field name="arch" type="xml">
<search string="Account Asset">
<field name="name" string="Asset"/>
- <field name="purchase_date"/>
- <filter icon="terp-check" string="Current" domain="[('state','in', ('draft','open'))]" help="Assets in draft and open states"/>
- <filter icon="terp-dialog-close" string="Closed" domain="[('state','=', 'close')]" help="Assets in closed state"/>
+ <field name="date"/>
+ <filter string="Current" domain="[('state','in', ('draft','open'))]" help="Assets in draft and open states"/>
+ <filter string="Closed" domain="[('state','=', 'close')]" help="Assets in closed state"/>
<field name="category_id"/>
<field name="partner_id" filter_domain="[('partner_id','child_of',self)]"/>
+ <group expand="0" string="Group By...">
+ <filter string="Month" domain="[]" context="{'group_by':'date'}"/>
+ <filter string="Category" domain="[]" context="{'group_by':'category_id'}"/>
+ </group>
</search>
</field>
</record>
+ <!-- Product Template -->
+ <record id="view_product_template_form_inherit" model="ir.ui.view">
+ <field name="name">Product Template (form)</field>
+ <field name="model">product.template</field>
+ <field name="inherit_id" ref="product.product_template_form_view"/>
+ <field name="arch" type="xml">
+ <xpath expr="//group[@name='properties']" position="before">
+ <group name="asset types">
+ <field name="asset_category_id" domain="[('type','=','purchase')]" context="{'default_type':'purchase'}"/>
+ <field name="deferred_revenue_category_id" domain="[('type','=','sale')]" context="{'default_type':'sale'}"/>
+ </group>
+ </xpath>
+ </field>
+ </record>
+
<!--
Asset History
-->
@@ -269,15 +285,7 @@
</tree>
</field>
</record>
-
- <record model="ir.actions.act_window" id="action_account_asset_asset_tree">
- <field name="name">Asset Hierarchy</field>
- <field name="res_model">account.asset.asset</field>
- <field name="view_type">tree</field>
- <field name="domain">[]</field>
- <field name="view_id" ref="view_account_asset_asset_hierarchy_tree"/>
- </record>
-
+
<record id="view_account_move_line_form_inherit" model="ir.ui.view">
<field name="name">Journal Items (form)</field>
<field name="model">account.move.line</field>
@@ -300,26 +308,51 @@
</field>
</record>
+ <menuitem id="menu_finance_assets" name="Assets and Revenues" parent="account.menu_finance_entries" sequence="9" groups="account.group_account_user"/>
<record model="ir.actions.act_window" id="action_account_asset_asset_form">
<field name="name">Assets</field>
<field name="res_model">account.asset.asset</field>
+ <field name="domain">[('category_id.type', '=', 'purchase')]</field>
+ <field name="context">{'default_type': 'purchase', 'default_category_id_type': 'purchase'}</field>
+ </record>
+
+ <!-- Deferred Revenues Action -->
+
+ <record model="ir.actions.act_window" id="action_account_revenue_form">
+ <field name="name">Deferred Revenues</field>
+ <field name="res_model">account.asset.asset</field>
<field name="view_type">form</field>
- <field name="view_id" ref="view_account_asset_asset_tree"/>
- <field name="search_view_id" ref="view_account_asset_search"/>
+ <field name="domain">[('category_id.type', '=', 'sale')]</field>
+ <field name="context">{'default_type': 'sale', 'default_category_id_type': 'sale'}</field>
</record>
- <menuitem parent="account.menu_finance_entries" id="menu_action_account_asset_asset_form" action="action_account_asset_asset_form" name="Assets" groups="account.group_account_user" sequence="50"/>
+ <menuitem parent="menu_finance_assets" id="menu_revenue_recognition" action="action_account_revenue_form" sequence="102"/>
- <act_window id="act_entries_open" name="Entries" res_model="account.move.line" src_model="account.asset.asset" context="{'search_default_asset_id': [active_id], 'default_asset_id': active_id}"/>
+ <menuitem parent="menu_finance_assets" id="menu_action_account_asset_asset_form" action="action_account_asset_asset_form" sequence="104"/>
- <record model="ir.actions.act_window" id="action_account_asset_asset_list_normal">
- <field name="name">Asset Categories</field>
+
+ <!-- Configuration -->
+
+ <menuitem id="menu_finance_config_assets" name="Assets and Revenues" parent="account.menu_finance_configuration" sequence="25"/>
+
+ <record model="ir.actions.act_window" id="action_account_asset_asset_list_normal_sale">
+ <field name="name">Asset Types</field>
+ <field name="res_model">account.asset.category</field>
+ <field name="domain">[('type', '=', 'purchase')]</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="context">{'default_type': 'purchase'}</field>
+ </record>
+ <record model="ir.actions.act_window" id="action_account_asset_asset_list_normal_purchase">
+ <field name="name">Deferred Revenues Types</field>
<field name="res_model">account.asset.category</field>
+ <field name="domain">[('type', '=', 'sale')]</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
+ <field name="context">{'default_type': 'sale'}</field>
</record>
- <menuitem parent="account.account_management_menu" id="menu_action_account_asset_asset_list_normal" action="action_account_asset_asset_list_normal" sequence="2"/>
+ <menuitem parent="account.account_management_menu" id="menu_action_account_asset_asset_list_normal_sale" action="action_account_asset_asset_list_normal_sale" sequence="1"/>
+ <menuitem parent="account.account_management_menu" id="menu_action_account_asset_asset_list_normal_purchase" action="action_account_asset_asset_list_normal_purchase" sequence="2"/>
-</data>
-</openerp>
+</odoo>
diff --git a/addons/account_asset/wizard/__init__.py b/addons/account_asset/wizard/__init__.py
index e1ee90f90c2..0425aadb5d8 100644
--- a/addons/account_asset/wizard/__init__.py
+++ b/addons/account_asset/wizard/__init__.py
@@ -1,23 +1,4 @@
-# -*- encoding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-##############################################################################
+# -*- coding: utf-8 -*-
import account_asset_change_duration
import wizard_asset_compute
diff --git a/addons/account_asset/wizard/account_asset_change_duration.py b/addons/account_asset/wizard/account_asset_change_duration.py
index 35160c026ff..55c1b3d3f7e 100644
--- a/addons/account_asset/wizard/account_asset_change_duration.py
+++ b/addons/account_asset/wizard/account_asset_change_duration.py
@@ -1,63 +1,51 @@
-# -*- encoding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-##############################################################################
-import time
+# -*- coding: utf-8 -*-
from lxml import etree
-from openerp.osv import fields, osv
+from openerp import api, fields, models
+from openerp.osv.orm import setup_modifiers
-class asset_modify(osv.osv_memory):
+
+class AssetModify(models.TransientModel):
_name = 'asset.modify'
_description = 'Modify Asset'
- def _get_asset_method_time(self, cr, uid, ids, field_name, arg, context=None):
- if ids and len(ids) == 1 and context.get('active_id'):
- asset = self.pool['account.asset.asset'].browse(cr, uid, context.get('active_id'), context=context)
- return {ids[0]: asset.method_time}
- else:
- return dict.fromkeys(ids, False)
+ name = fields.Char(string='Reason', required=True)
+ method_number = fields.Integer(string='Number of Depreciations', required=True)
+ method_period = fields.Integer(string='Period Length')
+ method_end = fields.Date(string='Ending date')
+ note = fields.Text(string='Notes')
+ asset_method_time = fields.Char(compute='_get_asset_method_time', string='Asset Method Time', readonly=True)
+
+ @api.one
+ def _get_asset_method_time(self):
+ if self.env.context.get('active_id'):
+ asset = self.env['account.asset.asset'].browse(self.env.context.get('active_id'))
+ self.asset_method_time = asset.method_time
- _columns = {
- 'name': fields.char('Reason', required=True),
- 'method_number': fields.integer('Number of Depreciations', required=True),
- 'method_period': fields.integer('Period Length'),
- 'method_end': fields.date('Ending date'),
- 'note': fields.text('Notes'),
- 'asset_method_time': fields.function(_get_asset_method_time, type='char', string='Asset Method Time', readonly=True),
- }
+ @api.model
+ def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
+ result = super(AssetModify, self).fields_view_get(view_id, view_type, toolbar=toolbar, submenu=submenu)
+ asset_id = self.env.context.get('active_id')
+ active_model = self.env.context.get('active_model')
+ if active_model == 'account.asset.asset' and asset_id:
+ asset = self.env['account.asset.asset'].browse(asset_id)
+ doc = etree.XML(result['arch'])
+ if asset.method_time == 'number' and doc.xpath("//field[@name='method_end']"):
+ node = doc.xpath("//field[@name='method_end']")[0]
+ node.set('invisible', '1')
+ setup_modifiers(node, result['fields']['method_end'])
+ elif asset.method_time == 'end' and doc.xpath("//field[@name='method_number']"):
+ node = doc.xpath("//field[@name='method_number']")[0]
+ node.set('invisible', '1')
+ setup_modifiers(node, result['fields']['method_number'])
+ result['arch'] = etree.tostring(doc)
+ return result
- def default_get(self, cr, uid, fields, context=None):
- """ To get default values for the object.
- @param self: The object pointer.
- @param cr: A database cursor
- @param uid: ID of the user currently logged in
- @param fields: List of fields for which we want default values
- @param context: A standard dictionary
- @return: A dictionary which of fields with values.
- """
- if not context:
- context = {}
- asset_obj = self.pool.get('account.asset.asset')
- res = super(asset_modify, self).default_get(cr, uid, fields, context=context)
- asset_id = context.get('active_id', False)
- asset = asset_obj.browse(cr, uid, asset_id, context=context)
+ @api.model
+ def default_get(self, fields):
+ res = super(AssetModify, self).default_get(fields)
+ asset_id = self.env.context.get('active_id')
+ asset = self.env['account.asset.asset'].browse(asset_id)
if 'name' in fields:
res.update({'name': asset.name})
if 'method_number' in fields and asset.method_time == 'number':
@@ -66,44 +54,34 @@ class asset_modify(osv.osv_memory):
res.update({'method_period': asset.method_period})
if 'method_end' in fields and asset.method_time == 'end':
res.update({'method_end': asset.method_end})
- if context.get('active_id'):
- res['asset_method_time'] = self._get_asset_method_time(cr, uid, [0], 'asset_method_time', [], context=context)[0]
+ if self.env.context.get('active_id'):
+ res['asset_method_time'] = self._get_asset_method_time()
return res
-
- def modify(self, cr, uid, ids, context=None):
+
+ @api.multi
+ def modify(self):
""" Modifies the duration of asset for calculating depreciation
and maintains the history of old values.
- @param self: The object pointer.
- @param cr: A database cursor
- @param uid: ID of the user currently logged in
- @param ids: List of Ids
- @param context: A standard dictionary
- @return: Close the wizard.
- """
- if not context:
- context = {}
- asset_obj = self.pool.get('account.asset.asset')
- history_obj = self.pool.get('account.asset.history')
- asset_id = context.get('active_id', False)
- asset = asset_obj.browse(cr, uid, asset_id, context=context)
- data = self.browse(cr, uid, ids[0], context=context)
+ """
+ asset_id = self.env.context.get('active_id', False)
+ asset = self.env['account.asset.asset'].browse(asset_id)
history_vals = {
'asset_id': asset_id,
- 'name': data.name,
+ 'name': self.name,
'method_time': asset.method_time,
'method_number': asset.method_number,
'method_period': asset.method_period,
'method_end': asset.method_end,
- 'user_id': uid,
- 'date': time.strftime('%Y-%m-%d'),
- 'note': data.note,
+ 'user_id': self.env.uid,
+ 'date': fields.Date.context_today(self),
+ 'note': self.note,
}
- history_obj.create(cr, uid, history_vals, context=context)
+ self.env['account.asset.history'].create(history_vals)
asset_vals = {
- 'method_number': data.method_number,
- 'method_period': data.method_period,
- 'method_end': data.method_end,
+ 'method_number': self.method_number,
+ 'method_period': self.method_period,
+ 'method_end': self.method_end,
}
- asset_obj.write(cr, uid, [asset_id], asset_vals, context=context)
- asset_obj.compute_depreciation_board(cr, uid, [asset_id], context=context)
+ asset.write(asset_vals)
+ asset.compute_depreciation_board()
return {'type': 'ir.actions.act_window_close'}
diff --git a/addons/account_asset/wizard/account_asset_change_duration_view.xml b/addons/account_asset/wizard/account_asset_change_duration_view.xml
index daa736fb804..46ebac2a8e1 100644
--- a/addons/account_asset/wizard/account_asset_change_duration_view.xml
+++ b/addons/account_asset/wizard/account_asset_change_duration_view.xml
@@ -1,46 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
-<openerp>
- <data>
+<odoo>
- <record model="ir.ui.view" id="asset_modify_form">
- <field name="name">wizard.asset.modify.form</field>
- <field name="model">asset.modify</field>
- <field name="arch" type="xml">
- <form string="Modify Asset">
- <field name="asset_method_time" invisible="1"/>
- <group string="Asset Durations to Modify" col="4">
- <group colspan="2" col="2">
- <field name="name"/>
- <field name="method_number" attrs="{'invisible': [('asset_method_time', '=', 'end')]}"/>
- </group>
- <group colspan="2" col="2">
- <field name="method_end" attrs="{'invisible': [('asset_method_time', '=', 'number')]}"/>
- <label for="method_period"/>
- <div>
- <field name="method_period" class="oe_inline"/> months
- </div>
- </group>
+ <record model="ir.ui.view" id="asset_modify_form">
+ <field name="name">wizard.asset.modify.form</field>
+ <field name="model">asset.modify</field>
+ <field name="arch" type="xml">
+ <form string="Modify Asset">
+ <field name="asset_method_time" invisible="1"/>
+ <group string="Asset Durations to Modify" col="4">
+ <group colspan="2" col="2">
+ <field name="name"/>
+ <field name="method_number" attrs="{'invisible': [('asset_method_time', '=', 'end')]}"/>
</group>
- <separator string="Notes"/>
- <field name="note"/>
- <footer>
- <button name="modify" string="Modify" type="object" class="oe_highlight"/>
- or
- <button string="Cancel" class="oe_link" special="cancel"/>
- </footer>
- </form>
- </field>
- </record>
-
- <record id="action_asset_modify" model="ir.actions.act_window">
- <field name="name">Modify Asset</field>
- <field name="res_model">asset.modify</field>
- <field name="type">ir.actions.act_window</field>
- <field name="view_type">form</field>
- <field name="view_mode">tree,form</field>
- <field name="view_id" ref="asset_modify_form"/>
- <field name="target">new</field>
- </record>
+ <group colspan="2" col="2">
+ <field name="method_end" attrs="{'invisible': [('asset_method_time', '=', 'number')]}"/>
+ <label for="method_period"/>
+ <div>
+ <field name="method_period" class="oe_inline"/> months
+ </div>
+ </group>
+ </group>
+ <separator string="Notes"/>
+ <field name="note"/>
+ <footer>
+ <button name="modify" string="Modify" type="object" class="oe_highlight"/>
+ or
+ <button string="Cancel" class="oe_link" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <record id="action_asset_modify" model="ir.actions.act_window">
+ <field name="name">Modify Asset</field>
+ <field name="res_model">asset.modify</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="view_id" ref="asset_modify_form"/>
+ <field name="target">new</field>
+ </record>
- </data>
-</openerp>
+</odoo>
diff --git a/addons/account_asset/wizard/wizard_asset_compute.py b/addons/account_asset/wizard/wizard_asset_compute.py
index 0a16f8bc2df..0a3711a7f76 100644
--- a/addons/account_asset/wizard/wizard_asset_compute.py
+++ b/addons/account_asset/wizard/wizard_asset_compute.py
@@ -1,50 +1,29 @@
-# -*- encoding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-##############################################################################
+# -*- coding: utf-8 -*-
+from openerp import api, fields, models, _
-from openerp.osv import fields, osv
-from openerp.tools.translate import _
-class asset_depreciation_confirmation_wizard(osv.osv_memory):
+class AssetDepreciationConfirmationWizard(models.TransientModel):
_name = "asset.depreciation.confirmation.wizard"
_description = "asset.depreciation.confirmation.wizard"
- _columns = {
- 'date': fields.date('Account Date', required=True, help="Choose the period for which you want to automatically post the depreciation lines of running assets"),
- }
-
- _defaults = {
- 'date': fields.date.context_today,
- }
- def asset_compute(self, cr, uid, ids, context):
- ass_obj = self.pool.get('account.asset.asset')
- asset_ids = context.get('active_ids', False)
- data = self.browse(cr, uid, ids, context=context)
- date = data[0].date
- created_move_ids = ass_obj._compute_entries(cr, uid, asset_ids, date, context=context)
+ date = fields.Date('Account Date', required=True, help="Choose the period for which you want to automatically post the depreciation lines of running assets", default=fields.Date.context_today)
+
+ @api.multi
+ def asset_compute(self):
+ self.ensure_one()
+ context = self._context
+ assets = self.env['account.asset.asset'].search([('state', '=', 'open'), ('category_id.type', '=', context.get('asset_type'))])
+ created_move_ids = assets._compute_entries(self.date)
+ if context.get('asset_type') == 'purchase':
+ title = _('Created Asset Moves')
+ else:
+ title = _('Created Revenue Moves')
return {
- 'name': _('Created Asset Moves'),
+ 'name': title,
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': 'account.move',
'view_id': False,
- 'domain': "[('id','in',["+','.join(map(str,created_move_ids))+"])]",
+ 'domain': "[('id','in',["+','.join(map(str, created_move_ids))+"])]",
'type': 'ir.actions.act_window',
}
diff --git a/addons/account_asset/wizard/wizard_asset_compute_view.xml b/addons/account_asset/wizard/wizard_asset_compute_view.xml
index a76719ebf8a..8e31c102bd7 100644
--- a/addons/account_asset/wizard/wizard_asset_compute_view.xml
+++ b/addons/account_asset/wizard/wizard_asset_compute_view.xml
@@ -1,40 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
-<openerp>
- <data>
- <record id="view_asset_depreciation_confirmation_wizard" model="ir.ui.view">
- <field name="name">asset.depreciation.confirmation.wizard</field>
- <field name="model">asset.depreciation.confirmation.wizard</field>
- <field name="arch" type="xml">
- <form string="Compute Asset">
- <group>
- <field name="date"/>
- </group>
- <footer>
- <button string="Compute" name="asset_compute" type="object" class="oe_highlight"/>
- or
- <button string="Cancel" class="oe_link" special="cancel"/>
- </footer>
- </form>
- </field>
- </record>
+<odoo>
+ <record id="view_asset_depreciation_confirmation_wizard" model="ir.ui.view">
+ <field name="name">asset.depreciation.confirmation.wizard</field>
+ <field name="model">asset.depreciation.confirmation.wizard</field>
+ <field name="arch" type="xml">
+ <form string="Compute Asset">
+ <div>
+ <p>
+ This wizard will post installment/depreciation lines for the selected month.<br/>
+ This will generate journal entries for all related installment lines on this period of asset/revenue recognition as well.
+ </p>
+ </div>
+ <group>
+ <field name="date"/>
+ </group>
+ <footer>
+ <button string="Generate Entries" name="asset_compute" type="object" class="oe_highlight"/>
+ or
+ <button string="Cancel" class="oe_link" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
- <record id="action_asset_depreciation_confirmation_wizard" model="ir.actions.act_window">
- <field name="name">Generate Depreciation Entries</field>
- <field name="res_model">asset.depreciation.confirmation.wizard</field>
- <field name="view_type">form</field>
- <field name="view_mode">tree,form</field>
- <field name="view_id" ref="view_asset_depreciation_confirmation_wizard"/>
- <field name="target">new</field>
- </record>
+ <record id="action_asset_depreciation_confirmation_wizard" model="ir.actions.act_window">
+ <field name="name">Post Depreciation Lines</field>
+ <field name="res_model">asset.depreciation.confirmation.wizard</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="view_id" ref="view_asset_depreciation_confirmation_wizard"/>
+ <field name="target">new</field>
+ <field name="context">{'asset_type': 'purchase'}</field>
+ </record>
- <record model="ir.values" id="action_asset_depreciation_confirmation_wizard_multi">
- <field name="model_id" ref="account_asset.model_account_asset_asset" />
- <field name="name">Generate Depreciation Entries</field>
- <field name="key2">client_action_multi</field>
- <field name="value" eval="'ir.actions.act_window,' +str(ref('action_asset_depreciation_confirmation_wizard'))" />
- <field name="key">action</field>
- <field name="model">account.asset.asset</field>
- </record>
+ <record id="action_recognition_depreciation_confirmation_wizard" model="ir.actions.act_window">
+ <field name="name">Post Installment Lines</field>
+ <field name="res_model">asset.depreciation.confirmation.wizard</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="view_id" ref="view_asset_depreciation_confirmation_wizard"/>
+ <field name="target">new</field>
+ <field name="context">{'asset_type': 'sale'}</field>
+ </record>
- </data>
-</openerp>
+ <menuitem name="Deferred Revenue Entries" action="action_recognition_depreciation_confirmation_wizard"
+ id="menu_recognition_depreciation_confirmation_wizard"
+ parent="account.menu_finance_payables" sequence="2"/>
+
+ <menuitem name="Assets Entries" action="action_asset_depreciation_confirmation_wizard"
+ id="menu_asset_depreciation_confirmation_wizard"
+ parent="account.menu_finance_receivables" sequence="1"/>
+
+</odoo>
diff --git a/addons/account_cancel/account_cancel_view.xml b/addons/account_cancel/account_cancel_view.xml
index 668c1053e08..d64d3101c64 100644
--- a/addons/account_cancel/account_cancel_view.xml
+++ b/addons/account_cancel/account_cancel_view.xml
@@ -39,6 +39,9 @@
<field name="model">account.bank.statement</field>
<field name="inherit_id" ref="account.view_bank_statement_form"/>
<field name="arch" type="xml">
+ <field name="line_ids" position="attributes">
+ <attribute name="options">{'reload_on_button': true}</attribute>
+ </field>
<xpath expr="//field[@name='bank_account_id']" position="after">
<button name="button_cancel_reconciliation" attrs="{'invisible': [('journal_entry_ids', '=', [])]}" string="Cancel" type="object" icon="gtk-undo"/>
</xpath>
diff --git a/addons/account_test/report/account_test_report.py b/addons/account_test/report/account_test_report.py
index a19e2f398af..208fec8152a 100644
--- a/addons/account_test/report/account_test_report.py
+++ b/addons/account_test/report/account_test_report.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import datetime
from openerp import api, models, _
+from openerp.tools.safe_eval import safe_eval as eval
#
# Use period and Journal for selection or resources
#
@@ -38,7 +39,7 @@ class ReportAssertAccount(models.AbstractModel):
'result': None, # used to store the result of the test
'column_order': None, # used to choose the display order of columns (in case you are returning a list of dict)
}
- exec code_exec in localdict
+ eval(code_exec, localdict, mode="exec", nocopy=True)
result = localdict['result']
column_order = localdict.get('column_order', None)
diff --git a/addons/analytic_contract_hr_expense/analytic_contract_hr_expense.py b/addons/analytic_contract_hr_expense/analytic_contract_hr_expense.py
index a9047db017a..4254e605441 100644
--- a/addons/analytic_contract_hr_expense/analytic_contract_hr_expense.py
+++ b/addons/analytic_contract_hr_expense/analytic_contract_hr_expense.py
@@ -114,8 +114,8 @@ class account_analytic_account(osv.osv):
digits=0),
}
- def on_change_template(self, cr, uid, id, template_id, date_start=False, context=None):
- res = super(account_analytic_account, self).on_change_template(cr, uid, id, template_id, date_start=date_start, context=context)
+ def on_change_template(self, cr, uid, ids, template_id, date_start=False, context=None):
+ res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, date_start=date_start, context=context)
if template_id and 'value' in res:
template = self.browse(cr, uid, template_id, context=context)
res['value']['charge_expenses'] = template.charge_expenses
diff --git a/addons/anonymization/anonymization.py b/addons/anonymization/anonymization.py
index 35c6f98d06e..a114c7712ff 100644
--- a/addons/anonymization/anonymization.py
+++ b/addons/anonymization/anonymization.py
@@ -31,6 +31,7 @@ import random
import datetime
from openerp.osv import fields, osv
from openerp.tools.translate import _
+from openerp.tools.safe_eval import safe_eval as eval
from itertools import groupby
from operator import itemgetter
diff --git a/addons/auth_ldap/users_ldap.py b/addons/auth_ldap/users_ldap.py
index 937e672ef89..6d2040d83c4 100644
--- a/addons/auth_ldap/users_ldap.py
+++ b/addons/auth_ldap/users_ldap.py
@@ -140,8 +140,8 @@ class CompanyLDAP(osv.osv):
results = []
try:
conn = self.connect(conf)
- conn.simple_bind_s(conf['ldap_binddn'] or '',
- conf['ldap_password'].encode('utf-8') or '')
+ ldap_password = conf['ldap_password'] or ''
+ conn.simple_bind_s(conf['ldap_binddn'] or '', ldap_password.encode('utf-8'))
results = conn.search_st(conf['ldap_base'], ldap.SCOPE_SUBTREE,
filter, retrieve_attributes, timeout=60)
conn.unbind()
diff --git a/addons/barcodes/static/src/js/barcode_parser.js b/addons/barcodes/static/src/js/barcode_parser.js
index e1703ae62b4..970019fb1f4 100644
--- a/addons/barcodes/static/src/js/barcode_parser.js
+++ b/addons/barcodes/static/src/js/barcode_parser.js
@@ -158,7 +158,14 @@ var BarcodeParser = Class.extend({
.replace("\.",".");
}
- match['match'] = match['base_code'].substr(0,base_pattern.length).match(base_pattern);
+ if (base_pattern[0] !== '^') {
+ base_pattern = "^" + base_pattern;
+ }
+ if (base_pattern[base_pattern.length -1] !== '$') {
+ base_pattern = base_pattern + ".*$";
+ }
+ match.match = match.base_code.match(base_pattern);
+
return match;
},
diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py
index dae5d0e7396..3e3a57ef369 100644
--- a/addons/base_action_rule/base_action_rule.py
+++ b/addons/base_action_rule/base_action_rule.py
@@ -27,6 +27,7 @@ import openerp
from openerp import SUPERUSER_ID
from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
+from openerp.tools.safe_eval import safe_eval as eval
_logger = logging.getLogger(__name__)
diff --git a/addons/base_import/models.py b/addons/base_import/models.py
index 9778391521a..784ae7f49f1 100644
--- a/addons/base_import/models.py
+++ b/addons/base_import/models.py
@@ -158,12 +158,19 @@ class ir_import(orm.TransientModel):
all the fields to traverse
:rtype: list(Field)
"""
+ string_match = None
for field in fields:
# FIXME: should match all translations & original
# TODO: use string distance (levenshtein? hamming?)
- if header.lower() == field['name'].lower() \
- or header.lower() == field['string'].lower():
+ if header.lower() == field['name'].lower():
return [field]
+ if header.lower() == field['string'].lower():
+ # matching string are not reliable way because
+ # strings have no unique constraint
+ string_match = field
+ if string_match:
+ # this behavior is only applied if there is no matching field['name']
+ return [string_match]
if '/' not in header:
return []
diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py
index 74157cfb226..84fd9a24f58 100644
--- a/addons/calendar/calendar.py
+++ b/addons/calendar/calendar.py
@@ -480,13 +480,13 @@ class calendar_alarm_manager(osv.AbstractModel):
def get_next_notif(self, cr, uid, context=None):
ajax_check_every_seconds = 300
- partner = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id
+ partner = self.pool['res.users'].read(cr, SUPERUSER_ID, uid, ['partner_id', 'calendar_last_notif_ack'], context=context)
all_notif = []
if not partner:
return []
- all_events = self.get_next_potential_limit_alarm(cr, uid, ajax_check_every_seconds, partner_id=partner.id, mail=False, context=context)
+ all_events = self.get_next_potential_limit_alarm(cr, uid, ajax_check_every_seconds, partner_id=partner['partner_id'][0], mail=False, context=context)
for event in all_events: # .values()
max_delta = all_events[event]['max_duration']
@@ -496,7 +496,7 @@ class calendar_alarm_manager(osv.AbstractModel):
LastFound = False
for one_date in self.pool.get("calendar.event").get_recurrent_date_by_event(cr, uid, curEvent, context=context):
in_date_format = one_date.replace(tzinfo=None)
- LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, ajax_check_every_seconds, after=partner.calendar_last_notif_ack, mail=False, context=context)
+ LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, ajax_check_every_seconds, after=partner['calendar_last_notif_ack'], mail=False, context=context)
if LastFound:
for alert in LastFound:
all_notif.append(self.do_notif_reminder(cr, uid, alert, context=context))
@@ -506,7 +506,7 @@ class calendar_alarm_manager(osv.AbstractModel):
break
else:
in_date_format = datetime.strptime(curEvent.start, DEFAULT_SERVER_DATETIME_FORMAT)
- LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, ajax_check_every_seconds, after=partner.calendar_last_notif_ack, mail=False, context=context)
+ LastFound = self.do_check_alarm_for_one_date(cr, uid, in_date_format, curEvent, max_delta, ajax_check_every_seconds, after=partner['calendar_last_notif_ack'], mail=False, context=context)
if LastFound:
for alert in LastFound:
all_notif.append(self.do_notif_reminder(cr, uid, alert, context=context))
@@ -586,7 +586,7 @@ class calendar_alarm(osv.Model):
def _update_cron(self, cr, uid, context=None):
try:
cron = self.pool['ir.model.data'].get_object(
- cr, uid, 'calendar', 'ir_cron_scheduler_alarm', context=context)
+ cr, SUPERUSER_ID, 'calendar', 'ir_cron_scheduler_alarm', context=context)
except ValueError:
return False
return cron.toggle(model=self._name, domain=[('type', '=', 'email')])
@@ -702,6 +702,9 @@ class calendar_event(osv.Model):
val = pytz.UTC.localize(val)
return val.astimezone(timezone)
+ if context is None:
+ context = {}
+
timezone = pytz.timezone(context.get('tz') or 'UTC')
startdate = pytz.UTC.localize(datetime.strptime(event.start, DEFAULT_SERVER_DATETIME_FORMAT)) # Add "+hh:mm" timezone
if not startdate:
diff --git a/addons/crm/calendar_event_menu.xml b/addons/crm/calendar_event_menu.xml
index aec88a5748e..eb3889f0857 100644
--- a/addons/crm/calendar_event_menu.xml
+++ b/addons/crm/calendar_event_menu.xml
@@ -5,5 +5,16 @@
<menuitem name="Import &amp; Synchronize" id="base.menu_import_crm"
parent="base.menu_base_partner"/>
+ <record id="view_crm_meeting_search" model="ir.ui.view">
+ <field name="name">CRM - Meetings Search</field>
+ <field name="model">calendar.event</field>
+ <field name="inherit_id" ref="calendar.view_calendar_event_search"/>
+ <field name="arch" type="xml">
+ <xpath expr="//field[@name='user_id']" position="after">
+ <field name="opportunity_id"/>
+ </xpath>
+ </field>
+ </record>
+
</data>
</openerp>
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py
index 67331b71ab7..432df5b4bee 100644
--- a/addons/crm/crm_lead.py
+++ b/addons/crm/crm_lead.py
@@ -82,31 +82,11 @@ class crm_lead(format_address, osv.osv):
context['empty_list_help_document_name'] = _("leads")
return super(crm_lead, self).get_empty_list_help(cr, uid, help, context=context)
- def _get_default_team_id(self, cr, uid, user_id=False, context=None):
- """ Gives default team by checking if present in the context """
- team_id = self._resolve_team_id_from_context(cr, uid, context=context) or False
- return team_id
-
def _get_default_stage_id(self, cr, uid, context=None):
""" Gives default stage_id """
- team_id = self._get_default_team_id(cr, uid, context=context)
+ team_id = self.pool['crm.team']._get_default_team_id(cr, uid, context=context)
return self.stage_find(cr, uid, [], team_id, [('fold', '=', False)], context=context)
- def _resolve_team_id_from_context(self, cr, uid, context=None):
- """ Returns ID of team based on the value of 'team_id'
- context key, or None if it cannot be resolved to a single
- Sales Team.
- """
- if context is None:
- context = {}
- if type(context.get('default_team_id')) in (int, long):
- return context.get('default_team_id')
- if isinstance(context.get('default_team_id'), basestring):
- team_ids = self.pool.get('crm.team').name_search(cr, uid, name=context['default_team_id'], context=context)
- if len(team_ids) == 1:
- return int(team_ids[0][0])
- return None
-
def _resolve_type_from_context(self, cr, uid, context=None):
""" Returns the type (lead or opportunity) from the type context
key. Returns None if it cannot be resolved.
@@ -127,7 +107,7 @@ class crm_lead(format_address, osv.osv):
# - OR ('case_default', '=', True), ('fold', '=', False): add default columns that are not folded
# - OR ('team_ids', '=', team_id), ('fold', '=', False) if team_id: add team columns that are not folded
search_domain = []
- team_id = self._resolve_team_id_from_context(cr, uid, context=context)
+ team_id = self.pool['crm.team']._resolve_team_id_from_context(cr, uid, context=context)
if team_id:
search_domain += ['|', ('team_ids', '=', team_id)]
search_domain += [('id', 'in', ids)]
@@ -282,7 +262,7 @@ class crm_lead(format_address, osv.osv):
'type': 'lead',
'user_id': lambda s, cr, uid, c: uid,
'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
- 'team_id': lambda s, cr, uid, c: s._get_default_team_id(cr, uid, context=c),
+ 'team_id': lambda s, cr, uid, c: s.pool['crm.team']._get_default_team_id(cr, uid, context=c),
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
'priority': lambda *a: crm.AVAILABLE_PRIORITIES[0][0],
'color': 0,
@@ -329,7 +309,7 @@ class crm_lead(format_address, osv.osv):
def on_change_user(self, cr, uid, ids, user_id, context=None):
""" When changing the user, also set a team_id or restrict team id
to the ones user_id is member of. """
- team_id = self._get_default_team_id(cr, uid, context=context)
+ team_id = self.pool['crm.team']._get_default_team_id(cr, uid, user_id=user_id, context=context)
if user_id and not team_id and self.pool['res.users'].has_group(cr, uid, 'base.group_multi_salesteams'):
team_ids = self.pool.get('crm.team').search(cr, uid, ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context)
if team_ids:
@@ -910,6 +890,7 @@ class crm_lead(format_address, osv.osv):
if lead.partner_id:
partner_ids.append(lead.partner_id.id)
res['context'] = {
+ 'search_default_opportunity_id': lead.type == 'opportunity' and lead.id or False,
'default_opportunity_id': lead.type == 'opportunity' and lead.id or False,
'default_partner_id': lead.partner_id and lead.partner_id.id or False,
'default_partner_ids': partner_ids,
diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml
index 9f30510a461..e419e181f14 100644
--- a/addons/crm/crm_lead_view.xml
+++ b/addons/crm/crm_lead_view.xml
@@ -410,8 +410,8 @@
<field name="arch" type="xml">
<form string="Opportunities">
<header>
- <button name="case_mark_won" string="Mark Won" type="object" class="oe_highlight"/>
- <button name="case_mark_lost" string="Mark Lost" type="object" class="oe_highlight"/>
+ <button name="case_mark_won" string="Mark Won" attrs="{'invisible': [('probability', '=', 100)]}" type="object" class="oe_highlight"/>
+ <button name="case_mark_lost" string="Mark Lost" attrs="{'invisible': [('probability', '=', 100)]}" type="object" class="oe_highlight"/>
<field name="stage_id" widget="statusbar" clickable="True"
options="{'fold_field': 'fold'}"
domain="['&amp;', ('team_ids', '=', team_id), '|', ('type', '=', type), ('type', '=', 'both')]"/>
diff --git a/addons/crm/crm_phonecall.py b/addons/crm/crm_phonecall.py
index f7e964aee12..b7885bf190b 100644
--- a/addons/crm/crm_phonecall.py
+++ b/addons/crm/crm_phonecall.py
@@ -77,6 +77,7 @@ class crm_phonecall(osv.osv):
'priority': '1',
'state': _get_default_state,
'user_id': lambda self, cr, uid, ctx: uid,
+ 'team_id': lambda s, cr, uid, c: s.pool['crm.team']._get_default_team_id(cr, uid, context=c),
'active': 1
}
diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv
index 5030138ae9a..6330d405714 100644
--- a/addons/crm/security/ir.model.access.csv
+++ b/addons/crm/security/ir.model.access.csv
@@ -7,6 +7,8 @@ access_crm_stage,crm.stage,model_crm_stage,,1,0,0,0
access_crm_stage_manager,crm.stage,model_crm_stage,base.group_sale_manager,1,1,1,1
access_crm_phonecall_report_user,crm.phonecall.report.user,model_crm_phonecall_report,base.group_sale_salesman,1,0,0,0
access_crm_phonecall_report_manager,crm.phonecall.report,model_crm_phonecall_report,base.group_sale_manager,1,1,1,1
+access_crm_opportunity_report_user,crm.opportunity.report,model_crm_opportunity_report,base.group_sale_salesman,1,0,0,0
+access_crm_opportunity_report_manager,crm.opportunity.report,model_crm_opportunity_report,base.group_sale_manager,1,1,1,1
access_res_partner_manager,res.partner.crm.manager,base.model_res_partner,base.group_sale_manager,1,0,0,0
access_res_partner_category_manager,res.partner.category.crm.manager,base.model_res_partner_category,base.group_sale_manager,1,0,0,0
access_res_partner,res.partner.crm.user,base.model_res_partner,base.group_sale_salesman,1,1,1,0
diff --git a/addons/crm_claim/crm_claim.py b/addons/crm_claim/crm_claim.py
index 286d97d996f..d417d8a1cac 100644
--- a/addons/crm_claim/crm_claim.py
+++ b/addons/crm_claim/crm_claim.py
@@ -59,13 +59,9 @@ class crm_claim(osv.osv):
_order = "priority,date desc"
_inherit = ['mail.thread']
- def _get_default_team_id(self, cr, uid, context=None):
- """ Gives default team by checking if present in the context """
- return self.pool.get('crm.lead')._resolve_team_id_from_context(cr, uid, context=context) or False
-
def _get_default_stage_id(self, cr, uid, context=None):
""" Gives default stage_id """
- team_id = self._get_default_team_id(cr, uid, context=context)
+ team_id = self.pool['crm.team']._get_default_team_id(cr, uid, context=context)
return self.stage_find(cr, uid, [], team_id, [('sequence', '=', '1')], context=context)
_columns = {
@@ -103,7 +99,7 @@ class crm_claim(osv.osv):
_defaults = {
'user_id': lambda s, cr, uid, c: uid,
- 'team_id': lambda s, cr, uid, c: s._get_default_team_id(cr, uid, c),
+ 'team_id': lambda s, cr, uid, c: s.pool['crm.team']._get_default_team_id(cr, uid, context=c),
'date': fields.datetime.now,
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case', context=c),
'priority': '1',
diff --git a/addons/crm_claim/test/process/claim.yml b/addons/crm_claim/test/process/claim.yml
index f6c6f6d315c..1a68de8ea04 100644
--- a/addons/crm_claim/test/process/claim.yml
+++ b/addons/crm_claim/test/process/claim.yml
@@ -15,7 +15,7 @@
claim_ids = self.search(cr, uid, [('email_from','=', 'Mr. John Right <info@customer.com>')])
assert claim_ids and len(claim_ids), "Claim is not created after getting request"
claim = self.browse(cr, uid, claim_ids[0], context=context)
- assert claim.name == tools.ustr("demande derèglement de votre produit."), "Subject does not match"
+ assert claim.name == tools.ustr("demande de règlement de votre produit."), "Subject does not match"
-
I open customer claim.
-
diff --git a/addons/crm_helpdesk/crm_helpdesk.py b/addons/crm_helpdesk/crm_helpdesk.py
index c622d45da3a..8d7c85bf7a7 100644
--- a/addons/crm_helpdesk/crm_helpdesk.py
+++ b/addons/crm_helpdesk/crm_helpdesk.py
@@ -81,6 +81,7 @@ class crm_helpdesk(osv.osv):
'user_id': lambda s, cr, uid, c: uid,
'state': lambda *a: 'draft',
'date': fields.datetime.now,
+ 'team_id': lambda s, cr, uid, c: s.pool['crm.team']._get_default_team_id(cr, uid, context=c),
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
'priority': '1',
}
diff --git a/addons/crm_partner_assign/crm_lead.py b/addons/crm_partner_assign/crm_lead.py
index c37cfdfd72f..ab7faab28ea 100644
--- a/addons/crm_partner_assign/crm_lead.py
+++ b/addons/crm_partner_assign/crm_lead.py
@@ -21,6 +21,7 @@
from openerp.osv import osv
from openerp.tools.translate import _
+from openerp.tools.safe_eval import safe_eval as eval
from openerp.exceptions import UserError
diff --git a/addons/delivery/__openerp__.py b/addons/delivery/__openerp__.py
index dc5878424a5..aa1906e32f5 100644
--- a/addons/delivery/__openerp__.py
+++ b/addons/delivery/__openerp__.py
@@ -43,7 +43,8 @@ invoices from picking, OpenERP is able to add and compute the shipping line.
'demo': ['delivery_demo.xml'],
'test': [
'../account/test/account_minimal_test.xml',
- 'test/delivery_cost.yml'
+ 'test/delivery_cost.yml',
+ 'test/stock_move_values_with_invoice_before_delivery.yml',
],
'installable': True,
'auto_install': False,
diff --git a/addons/delivery/delivery.py b/addons/delivery/delivery.py
index 81f88ca36dd..09d4e13b5fc 100644
--- a/addons/delivery/delivery.py
+++ b/addons/delivery/delivery.py
@@ -24,6 +24,7 @@ import time
from openerp.osv import fields,osv
from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp
+from openerp.tools.safe_eval import safe_eval as eval
from openerp.exceptions import UserError
_logger = logging.getLogger(__name__)
@@ -215,6 +216,8 @@ class delivery_grid(osv.osv):
total_delivery = 0.0
product_uom_obj = self.pool.get('product.uom')
for line in order.order_line:
+ if line.state == 'cancel':
+ continue
if line.is_delivery:
total_delivery += line.price_subtotal + self.pool['sale.order']._amount_line_tax(cr, uid, line, context=context)
if not line.product_id or line.is_delivery:
diff --git a/addons/delivery/sale.py b/addons/delivery/sale.py
index 6de57a21fff..f4855769d5d 100644
--- a/addons/delivery/sale.py
+++ b/addons/delivery/sale.py
@@ -66,6 +66,7 @@ class sale_order(osv.Model):
carrier_obj = self.pool.get('delivery.carrier')
acc_fp_obj = self.pool.get('account.fiscal.position')
self._delivery_unset(cr, uid, ids, context=context)
+ currency_obj = self.pool.get('res.currency')
for order in self.browse(cr, uid, ids, context=context):
grid_id = carrier_obj.grid_get(cr, uid, [order.carrier_id.id], order.partner_shipping_id.id)
if not grid_id:
@@ -79,6 +80,10 @@ class sale_order(osv.Model):
taxes = grid.carrier_id.product_id.taxes_id
fpos = order.fiscal_position_id or False
taxes_ids = acc_fp_obj.map_tax(cr, uid, fpos, taxes)
+ price_unit = grid_obj.get_price(cr, uid, grid.id, order, time.strftime('%Y-%m-%d'), context)
+ if order.company_id.currency_id.id != order.pricelist_id.currency_id.id:
+ price_unit = currency_obj.compute(cr, uid, order.company_id.currency_id.id, order.pricelist_id.currency_id.id,
+ price_unit, context=dict(context or {}, date=order.date_order))
#create the sale order line
line_obj.create(cr, uid, {
'order_id': order.id,
@@ -86,7 +91,7 @@ class sale_order(osv.Model):
'product_uom_qty': 1,
'product_uom': grid.carrier_id.product_id.uom_id.id,
'product_id': grid.carrier_id.product_id.id,
- 'price_unit': grid_obj.get_price(cr, uid, grid.id, order, time.strftime('%Y-%m-%d'), context),
+ 'price_unit': price_unit,
'tax_id': [(6, 0, taxes_ids)],
'is_delivery': True
}, context=context)
diff --git a/addons/delivery/test/stock_move_values_with_invoice_before_delivery.yml b/addons/delivery/test/stock_move_values_with_invoice_before_delivery.yml
new file mode 100644
index 00000000000..61fa086c5c2
--- /dev/null
+++ b/addons/delivery/test/stock_move_values_with_invoice_before_delivery.yml
@@ -0,0 +1,79 @@
+-
+ test that the store fields of stock moves are computed with invoice before delivery flow
+-
+ set a weight on ipod 16GB
+-
+ !python {model: product.product, id: product.product_product_11}: |
+ self.write({'weight': 0.25, 'weight_net': 0.2})
+-
+ create a SO with invoice before delivery
+-
+ !record {model: sale.order, id: sale_prepaid}:
+ partner_id: base.res_partner_18
+ partner_invoice_id: base.res_partner_18
+ partner_shipping_id: base.res_partner_18
+ pricelist_id: product.list0
+ order_policy: 'prepaid'
+ order_line:
+ - name: 'Ipod'
+ product_id: product.product_product_11
+ product_uom_qty: 2
+ product_uos_qty: 2
+ product_uom: product.product_uom_unit
+ price_unit: 750.00
+ carrier_id: normal_delivery_carrier
+-
+ I add delivery cost in Sale order.
+-
+ !python {model: sale.order, id: sale_prepaid}: |
+ self.delivery_set()
+-
+ I confirm the SO.
+-
+ !workflow {model: sale.order, action: order_confirm, ref: sale_prepaid}
+-
+ I check that the invoice was created
+-
+ !python {model: sale.order, id: sale_prepaid}: |
+ assert len(self.invoice_ids) == 1, "Invoice not created."
+-
+ I confirm the invoice
+-
+ !python {model: sale.order, id: sale_prepaid}: |
+ invoice = self.invoice_ids[0]
+ invoice.signal_workflow('invoice_open')
+-
+ I pay the invoice.
+-
+ !python {model: sale.order, id: sale_prepaid}: |
+ invoice = self.invoice_ids[0]
+ invoice.signal_workflow('invoice_open')
+ journal = self.env['account.journal'].search([('type', '=', 'cash'),
+ ('company_id', '=', self.company_id.id)],
+ limit=1)
+ invoice.pay_and_reconcile(journal, invoice.amount_total)
+-
+ Check the SO after paying the invoice
+-
+ !python {model: sale.order, id: sale_prepaid}: |
+ assert self.invoice_count != 0, 'order not invoiced'
+ assert self.invoiced, 'order is not paid'
+ assert len(self.picking_ids) == 1, 'pickings not generated'
+-
+ check the stock moves
+-
+ !python {model: sale.order, id: sale_prepaid}: |
+ move = self.picking_ids.move_lines
+ assert move.product_qty == 2, 'wrong product_qty'
+ assert move.weight == 0.5, 'wrong move weight'
+ assert move.weight_net == 0.4, 'wrong move weight_net'
+-
+ ship
+-
+ !python {model: sale.order, id: sale_prepaid}: |
+ picking = self.picking_ids.action_done()
+-
+ Check the SO after shipping
+-
+ !python {model: sale.order, id: sale_prepaid}: |
+ assert self.state == 'done'
diff --git a/addons/document/document.py b/addons/document/document.py
index 0d08d54e7fb..de9f03c773c 100644
--- a/addons/document/document.py
+++ b/addons/document/document.py
@@ -115,8 +115,11 @@ class document_file(osv.osv):
ids.extend(parents[parent_id])
# sort result according to the original sort ordering
- ids = [id for id in orig_ids if id in ids]
- return len(ids) if count else ids
+ if count:
+ return len(ids)
+ else:
+ set_ids = set(ids)
+ return [id for id in orig_ids if id in set_ids]
def copy(self, cr, uid, id, default=None, context=None):
if not default:
diff --git a/addons/event/event.py b/addons/event/event.py
index f9915ff38fc..2293d192366 100644
--- a/addons/event/event.py
+++ b/addons/event/event.py
@@ -250,7 +250,7 @@ class event_event(models.Model):
return res
@api.one
- def mail_attendees(self, template_id, force_send=False, filter_func=lambda self: True):
+ def mail_attendees(self, template_id, force_send=True, filter_func=lambda self: True):
for attendee in self.registration_ids.filtered(filter_func):
self.env['mail.template'].browse(template_id).send_mail(attendee.id, force_send=force_send)
@@ -336,6 +336,7 @@ class event_registration(models.Model):
body=_('New registration confirmed: %s.') % (self.name or ''),
subtype="event.mt_event_registration")
self.state = 'open'
+ self.env['event.mail'].run()
@api.one
def button_reg_close(self):
diff --git a/addons/event/event_view.xml b/addons/event/event_view.xml
index 6ec68c6b479..eed24fd3234 100644
--- a/addons/event/event_view.xml
+++ b/addons/event/event_view.xml
@@ -235,7 +235,7 @@
<separator/>
<filter string="My Events" help="My Events" domain="[('user_id','=',uid)]"/>
<separator/>
- <filter string="Upcoming" name="upcoming" domain="[('date_begin','&gt;=',datetime.datetime.now().replace(hour=0, minute=0, second=0))]" help="Upcoming events from today" />
+ <filter string="Upcoming" name="upcoming" domain="[('date_begin','&gt;=', datetime.datetime.combine(context_today(), datetime.time(0,0,0)))]" help="Upcoming events from today" />
<field name="type"/>
<field name="user_id"/>
<group expand="0" string="Group By">
diff --git a/addons/event/tests/test_mail_schedule.py b/addons/event/tests/test_mail_schedule.py
index 752403ed22b..c712e48919f 100644
--- a/addons/event/tests/test_mail_schedule.py
+++ b/addons/event/tests/test_mail_schedule.py
@@ -60,7 +60,7 @@ class TestMailSchedule(TestEventCommon):
self.assertTrue(schedulers[0].done, 'event: reminder scheduler should be done')
mails = self.env['mail.mail'].search([('subject', 'ilike', 'reminder'), ('date', '>=', datetime.datetime.strftime(now, tools.DEFAULT_SERVER_DATETIME_FORMAT))], order='date DESC', limit=3)
- self.assertEqual(len(mails), 2, 'event: wrong number of reminder mail sent')
+ self.assertEqual(len(mails), 0, 'event: reminder to sent is not empty')
# check subscription scheduler
schedulers = self.EventMail.search([('event_id', '=', test_event.id), ('interval_type', '=', 'after_sub')])
diff --git a/addons/google_calendar/google_calendar.py b/addons/google_calendar/google_calendar.py
index d4743f22bac..df90f78f5d1 100644
--- a/addons/google_calendar/google_calendar.py
+++ b/addons/google_calendar/google_calendar.py
@@ -414,7 +414,7 @@ class google_calendar(osv.AbstractModel):
user_obj = self.pool['res.users']
myPartnerID = user_obj.browse(cr, uid, uid, context).partner_id.id
attendee_record = []
- alarm_record = []
+ alarm_record = set()
partner_record = [(4, myPartnerID)]
result = {}
@@ -461,7 +461,7 @@ class google_calendar(osv.AbstractModel):
'name': "%s minutes - %s" % (google_alarm['minutes'], google_alarm['method'])
}
alarm_id = [calendar_alarm_obj.create(cr, uid, data, context=context)]
- alarm_record.append(alarm_id[0])
+ alarm_record.add(alarm_id[0])
UTC = pytz.timezone('UTC')
if single_event_dict.get('start') and single_event_dict.get('end'): # If not cancelled
@@ -489,7 +489,7 @@ class google_calendar(osv.AbstractModel):
result.update({
'attendee_ids': attendee_record,
'partner_ids': list(set(partner_record)),
- 'alarm_ids': [(6, 0, alarm_record)],
+ 'alarm_ids': [(6, 0, list(alarm_record))],
'name': single_event_dict.get('summary', 'Event'),
'description': single_event_dict.get('description', False),
@@ -976,7 +976,9 @@ class calendar_event(osv.Model):
_inherit = "calendar.event"
def get_fields_need_update_google(self, cr, uid, context=None):
- return ['name', 'description', 'allday', 'date', 'date_end', 'stop', 'attendee_ids', 'alarm_ids', 'location', 'class', 'active']
+ return ['name', 'description', 'allday', 'start', 'date_end', 'stop',
+ 'attendee_ids', 'alarm_ids', 'location', 'class', 'active',
+ 'start_date', 'start_datetime', 'stop_date', 'stop_datetime']
def write(self, cr, uid, ids, vals, context=None):
if context is None:
diff --git a/addons/google_drive/google_drive.py b/addons/google_drive/google_drive.py
index 8bfc6fd7697..b26cc44860e 100644
--- a/addons/google_drive/google_drive.py
+++ b/addons/google_drive/google_drive.py
@@ -23,6 +23,7 @@ from openerp import SUPERUSER_ID
from openerp.addons.google_account import TIMEOUT
from openerp.osv import fields, osv
from openerp.tools.translate import _
+from openerp.tools.safe_eval import safe_eval as eval
from openerp.exceptions import UserError
import werkzeug.urls
diff --git a/addons/hr_timesheet/hr_timesheet.py b/addons/hr_timesheet/hr_timesheet.py
index 3e2aec8b621..dc46c0e4cf9 100644
--- a/addons/hr_timesheet/hr_timesheet.py
+++ b/addons/hr_timesheet/hr_timesheet.py
@@ -159,12 +159,12 @@ class account_analytic_account(models.Model):
@api.onchange('invoice_on_timesheets')
def onchange_invoice_on_timesheets(self):
- result = {}
+ result = {'value': {}}
if not self.invoice_on_timesheets:
return {'value': {'to_invoice': False}}
try:
to_invoice = self.env['ir.model.data'].xmlid_to_res_id('hr_timesheet_invoice.timesheet_invoice_factor1')
- result['to_invoice'] = to_invoice
+ result['value']['to_invoice'] = to_invoice
except ValueError:
pass
return result
diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
index d622d9310bf..a706cbdbf95 100644
--- a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
+++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py
@@ -314,8 +314,8 @@ class account_analytic_line(osv.osv):
res['value']['to_invoice'] = st or False
if acc.state == 'pending':
res['warning'] = {
- 'title': 'Warning',
- 'message': 'The analytic account is in pending state.\nYou should not work on this account !'
+ 'title': _('Warning'),
+ 'message': _('The analytic account is in pending state.\nYou should not work on this account !')
}
elif acc.state == 'close' or acc.state == 'cancelled':
raise osv.except_osv(_('Invalid Analytic Account!'), _('You cannot select a Analytic Account which is in Close or Cancelled state.'))
diff --git a/addons/hw_proxy/controllers/main.py b/addons/hw_proxy/controllers/main.py
index 64f6cf0765c..337874cf67a 100644
--- a/addons/hw_proxy/controllers/main.py
+++ b/addons/hw_proxy/controllers/main.py
@@ -192,12 +192,12 @@ class Proxy(http.Controller):
print 'print_receipt' + str(receipt)
@http.route('/hw_proxy/is_scanner_connected', type='json', auth='none', cors='*')
- def print_receipt(self, receipt):
+ def is_scanner_connected(self, receipt):
print 'is_scanner_connected?'
return False
@http.route('/hw_proxy/scanner', type='json', auth='none', cors='*')
- def print_receipt(self, receipt):
+ def scanner(self, receipt):
print 'scanner'
time.sleep(10)
return ''
diff --git a/addons/hw_scale/__openerp__.py b/addons/hw_scale/__openerp__.py
index 6c83a854730..387a7e618d9 100644
--- a/addons/hw_scale/__openerp__.py
+++ b/addons/hw_scale/__openerp__.py
@@ -37,6 +37,7 @@ such as the Mettler Toledo Ariva.
""",
'author': 'OpenERP SA',
'depends': ['hw_proxy'],
+ 'external_dependencies': {'python': ['serial']},
'test': [
],
'installable': True,
diff --git a/addons/hw_scale/controllers/main.py b/addons/hw_scale/controllers/main.py
index 531f2739f55..44a031f5566 100644
--- a/addons/hw_scale/controllers/main.py
+++ b/addons/hw_scale/controllers/main.py
@@ -178,28 +178,34 @@ class Scale(Thread):
if not self.device:
time.sleep(5)
-s = Scale()
-
-hw_proxy.drivers['scale'] = s
+scale_thread = None
+if serial:
+ scale_thread = Scale()
+ hw_proxy.drivers['scale'] = scale_thread
class ScaleDriver(hw_proxy.Proxy):
@http.route('/hw_proxy/scale_read/', type='json', auth='none', cors='*')
def scale_read(self):
- return {'weight':s.get_weight(), 'unit':'kg', 'info':s.get_weight_info()}
+ if scale_thread:
+ return {'weight': scale_thread.get_weight(), 'unit':'kg', 'info': scale_thread.get_weight_info()}
+ return None
@http.route('/hw_proxy/scale_zero/', type='json', auth='none', cors='*')
def scale_zero(self):
- s.set_zero()
+ if scale_thread:
+ scale_thread.set_zero()
return True
@http.route('/hw_proxy/scale_tare/', type='json', auth='none', cors='*')
def scale_tare(self):
- s.set_tare()
+ if scale_thread:
+ scale_thread.set_tare()
return True
@http.route('/hw_proxy/scale_clear_tare/', type='json', auth='none', cors='*')
def scale_clear_tare(self):
- s.clear_tare()
+ if scale_thread:
+ scale_thread.clear_tare()
return True
diff --git a/addons/hw_scanner/__openerp__.py b/addons/hw_scanner/__openerp__.py
index aa74b0fbc5b..f38fa7dc420 100644
--- a/addons/hw_scanner/__openerp__.py
+++ b/addons/hw_scanner/__openerp__.py
@@ -38,6 +38,7 @@ point of sale module.
""",
'author': 'OpenERP SA',
'depends': ['hw_proxy'],
+ 'external_dependencies': {'python': ['evdev']},
'test': [
],
'installable': True,
diff --git a/addons/hw_scanner/controllers/main.py b/addons/hw_scanner/controllers/main.py
index 057674c1ca1..6e53c37006f 100644
--- a/addons/hw_scanner/controllers/main.py
+++ b/addons/hw_scanner/controllers/main.py
@@ -111,8 +111,6 @@ class Scanner(Thread):
def get_device(self):
try:
- if not evdev:
- return None
devices = [ device for device in listdir(self.input_dir)]
keyboards = [ device for device in devices if ('kbd' in device) and ('keyboard' not in device.lower())]
scanners = [ device for device in devices if ('barcode' in device.lower()) or ('scanner' in device.lower())]
@@ -135,7 +133,6 @@ class Scanner(Thread):
been returned before. This is necessary to catch barcodes scanned while the POS is
busy reading another barcode
"""
-
self.lockedstart()
while True:
@@ -204,12 +201,12 @@ class Scanner(Thread):
except Exception as e:
self.set_status('error',str(e))
-s = Scanner()
-
-hw_proxy.drivers['scanner'] = s
+scanner_thread = None
+if evdev:
+ scanner_thread = Scanner()
+ hw_proxy.drivers['scanner'] = scanner_thread
class ScannerDriver(hw_proxy.Proxy):
@http.route('/hw_proxy/scanner', type='json', auth='none', cors='*')
def scanner(self):
- return s.get_barcode()
-
+ return scanner_thread.get_barcode() if scanner_thread else None
diff --git a/addons/im_chat/static/src/xml/im_chat.xml b/addons/im_chat/static/src/xml/im_chat.xml
index 9adef41a24f..80447cc38d0 100644
--- a/addons/im_chat/static/src/xml/im_chat.xml
+++ b/addons/im_chat/static/src/xml/im_chat.xml
@@ -87,7 +87,7 @@
<div class="oe_im">
<div class="oe_im_frame_header">
<i class="fa fa-search oe_im_search_icon"/>
- <input class="oe_im_searchbox" t-att-placeholder="_t('Search users...')"/>
+ <input class="oe_im_searchbox" placeholder="Search users..."/>
</div>
<div class="oe_im_users"></div>
<div class="oe_im_content"></div>
diff --git a/addons/im_livechat/models/im_chat_session.py b/addons/im_livechat/models/im_chat_session.py
index 0cae7f76c46..f4c00329a11 100644
--- a/addons/im_livechat/models/im_chat_session.py
+++ b/addons/im_livechat/models/im_chat_session.py
@@ -49,8 +49,11 @@ class ImChatSession(models.Model):
"""
self.ensure_one()
users_infos = super(ImChatSession, self).session_user_info()
+ # identify the operator for the 'welcome message'
+ for user_profile in users_infos:
+ user_profile['is_operator'] = bool(user_profile['id'] == self.env._context.get('im_livechat_operator_id'))
if self.anonymous_name:
- users_infos.append({'id': False, 'name': self.anonymous_name, 'im_status': 'online'})
+ users_infos.append({'id': False, 'name': self.anonymous_name, 'im_status': 'online', 'is_operator': False})
return users_infos
@api.model
diff --git a/addons/im_livechat/models/im_livechat_channel.py b/addons/im_livechat/models/im_livechat_channel.py
index 258b4e2aeb1..85b202e002f 100644
--- a/addons/im_livechat/models/im_livechat_channel.py
+++ b/addons/im_livechat/models/im_livechat_channel.py
@@ -171,7 +171,7 @@ class ImLivechatChannel(models.Model):
user_to_add.append((4, self.env.uid))
# create the session, and add the link with the given channel
session = self.env["im_chat.session"].sudo().create({'user_ids': user_to_add, 'channel_id': channel_id, 'anonymous_name': anonymous_name})
- return session.sudo().session_info()
+ return session.sudo().with_context(im_livechat_operator_id=user_id).session_info()
@api.model
def get_channel_infos(self, channel_id):
diff --git a/addons/im_livechat/static/src/js/im_livechat.js b/addons/im_livechat/static/src/js/im_livechat.js
index 88c234378a0..15f3f5bae09 100644
--- a/addons/im_livechat/static/src/js/im_livechat.js
+++ b/addons/im_livechat/static/src/js/im_livechat.js
@@ -203,12 +203,13 @@ var ChatButton = Widget.extend({
if(this.session.users.length > 0){
if (self.options.defaultMessage) {
setTimeout(function(){
+ var operator = _.last(_.filter(self.session.users, function(user){return user.is_operator}));
self.conv.message_receive({
id : 1,
type: "message",
message: self.options.defaultMessage,
create_date: time.datetime_to_str(new Date()),
- from_id: [self.session.users[0].id, self.session.users[0].name],
+ from_id: [operator.id, operator.name],
to_id: [0, self.session.uuid]
});
}, 1000);
diff --git a/addons/im_odoo_support/static/src/js/im_odoo_support.js b/addons/im_odoo_support/static/src/js/im_odoo_support.js
index 4945ba49ca2..ec61c18af06 100644
--- a/addons/im_odoo_support/static/src/js/im_odoo_support.js
+++ b/addons/im_odoo_support/static/src/js/im_odoo_support.js
@@ -11,7 +11,7 @@ var Widget = require('web.Widget');
var _t = core._t;
-var COOKIE_NAME = 'livechat_conversation';
+var COOKIE_NAME = 'odoo_livechat_conversation';
var SERVICE_URL = 'https://services.odoo.com/';
var OdooSupport = Widget.extend({
diff --git a/addons/l10n_ca/account_chart_en.xml b/addons/l10n_ca/account_chart_en.xml
index fb7532e6ff8..86228db2c35 100644
--- a/addons/l10n_ca/account_chart_en.xml
+++ b/addons/l10n_ca/account_chart_en.xml
@@ -259,101 +259,119 @@
<field name="name">LABOUR TAXES TO PAY</field>
</record>
- <record id="chart2141_en" model="account.account.template">
- <field name="code">2141</field>
- <field name="user_type" ref="account.data_account_type_view"/>
- <field name="name">CANADIAN REVENU AGENCY</field>
- </record>
-
- <record id="chart21411_en" model="account.account.template">
- <field name="code">21411</field>
- <field name="user_type" ref="account.data_account_type_view"/>
- <field name="name">EMPLOYMENT INSURANCE TO PAY</field>
- </record>
-
- <record id="chart214111_en" model="account.account.template">
- <field name="code">214111</field>
+ <record id="chart2141_en" model="account.account.template">
+ <field name="code">2141</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">CANADA REVENUE AGENCY</field>
+ </record>
+
+ <record id="chart21411_en" model="account.account.template">
+ <field name="code">21411</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">EMPLOYMENT INSURANCE TO PAY</field>
+ </record>
+
+ <record id="chart214111_en" model="account.account.template">
+ <field name="code">214111</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">EI - Employees Contribution</field>
- </record>
+ <field name="name">EI - Employees Contribution</field>
+ </record>
- <record id="chart214112_en" model="account.account.template">
- <field name="code">214112</field>
+ <record id="chart214112_en" model="account.account.template">
+ <field name="code">214112</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">EI - Employer Contribution</field>
- </record>
+ <field name="name">EI - Employer Contribution</field>
+ </record>
- <record id="chart21412_en" model="account.account.template">
- <field name="code">21412</field>
+ <record id="chart21412_en" model="account.account.template">
+ <field name="code">21412</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">Federal Income Tax</field>
- </record>
-
- <record id="chart2142_en" model="account.account.template">
- <field name="code">2142</field>
- <field name="user_type" ref="account.data_account_type_view"/>
- <field name="name">PROVINCIAL REVENU AGENCY</field>
- </record>
-
- <record id="chart21421_en" model="account.account.template">
- <field name="code">21421</field>
+ <field name="name">Federal Income Tax</field>
+ </record>
+
+ <record id="chart21413_en" model="account.account.template">
+ <field name="code">21413</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">CANADA PENSION PLAN TO PAY</field>
+ </record>
+
+ <record id="chart214131_en" model="account.account.template">
+ <field name="code">214131</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">CPP - Employees Contribution</field>
+ </record>
+
+ <record id="chart214132_en" model="account.account.template">
+ <field name="code">214132</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">CPP - Employer Contribution</field>
+ </record>
+
+ <record id="chart2142_en" model="account.account.template">
+ <field name="code">2142</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">PROVINCIAL REVENU AGENCY</field>
+ </record>
+
+ <record id="chart21421_en" model="account.account.template">
+ <field name="code">21421</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">Health Services Fund to pay</field>
- </record>
+ <field name="name">Health Services Fund to pay</field>
+ </record>
- <record id="chart21422_en" model="account.account.template">
- <field name="code">21422</field>
- <field name="user_type" ref="account.data_account_type_view"/>
- <field name="name">ANNUITIES TO PAY</field>
- </record>
+ <record id="chart21422_en" model="account.account.template">
+ <field name="code">21422</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">PROVINCIAL PENSION PLAN TO PAY</field>
+ </record>
- <record id="chart214221_en" model="account.account.template">
- <field name="code">214221</field>
+ <record id="chart214221_en" model="account.account.template">
+ <field name="code">214221</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">Annuities - Employees Contribution</field>
- </record>
+ <field name="name">Provincial Pension Plan - Employees Contribution</field>
+ </record>
- <record id="chart214222_en" model="account.account.template">
- <field name="code">214222</field>
+ <record id="chart214222_en" model="account.account.template">
+ <field name="code">214222</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">Annuities - Employer Contribution</field>
- </record>
+ <field name="name">Provincial Pension Plan - Employer Contribution</field>
+ </record>
- <record id="chart21423_en" model="account.account.template">
- <field name="code">21423</field>
- <field name="user_type" ref="account.data_account_type_view"/>
- <field name="name">PARENTAL INSURANCE PLAN TO PAY</field>
- </record>
+ <record id="chart21423_en" model="account.account.template">
+ <field name="code">21423</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">PROVINCIAL PARENTAL INSURANCE PLAN TO PAY</field>
+ </record>
- <record id="chart214231_en" model="account.account.template">
- <field name="code">214231</field>
+ <record id="chart214231_en" model="account.account.template">
+ <field name="code">214231</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">PAP - Employee Contribution</field>
- </record>
+ <field name="name">Parental Insurance Plan - Employee Contribution</field>
+ </record>
- <record id="chart214232_en" model="account.account.template">
- <field name="code">214232</field>
+ <record id="chart214232_en" model="account.account.template">
+ <field name="code">214232</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">PAP - Employer Contribution</field>
- </record>
+ <field name="name">Parental Insurance Plan - Employer Contribution</field>
+ </record>
- <record id="chart21424_en" model="account.account.template">
- <field name="code">21424</field>
+ <record id="chart21424_en" model="account.account.template">
+ <field name="code">21424</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">Labour Health and Safety to pay</field>
- </record>
+ <field name="name">Labour Health and Safety to pay</field>
+ </record>
- <record id="chart21425_en" model="account.account.template">
- <field name="code">21425</field>
+ <record id="chart21425_en" model="account.account.template">
+ <field name="code">21425</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">Labour Standards to pay</field>
- </record>
+ <field name="name">Labour Standards to pay</field>
+ </record>
- <record id="chart21426_en" model="account.account.template">
- <field name="code">21426</field>
+ <record id="chart21426_en" model="account.account.template">
+ <field name="code">21426</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">Provincial Income Tax</field>
- </record>
+ <field name="name">Provincial Income Tax</field>
+ </record>
<record id="chart215_en" model="account.account.template">
<field name="code">215</field>
@@ -379,6 +397,66 @@
<field name="name">Stock Received But Not Billed</field>
</record>
+ <record id="chart218_en" model="account.account.template">
+ <field name="code">218</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">CURRENT LIABILITIES RELATED TO SALARIES</field>
+ </record>
+
+ <record id="chart2181_en" model="account.account.template">
+ <field name="code">2181</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Salaries to pay</field>
+ </record>
+
+ <record id="chart2183_en" model="account.account.template">
+ <field name="code">2183</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Bonus to pay</field>
+ </record>
+
+ <record id="chart2184_en" model="account.account.template">
+ <field name="code">2184</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Retroactive Payment to pay</field>
+ </record>
+
+ <record id="chart2185_en" model="account.account.template">
+ <field name="code">2185</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">GROUP PENSION PLAN TO PAY</field>
+ </record>
+
+ <record id="chart218501_en" model="account.account.template">
+ <field name="code">218501</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Group Pension Plan to pay - Employees Contribution</field>
+ </record>
+
+ <record id="chart218502_en" model="account.account.template">
+ <field name="code">218502</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Group Pension Plan to pay - Employer Contribution</field>
+ </record>
+
+ <record id="chart2186_en" model="account.account.template">
+ <field name="code">2186</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">EMPLOYEE BENEFITS</field>
+ </record>
+
+ <record id="chart218601_en" model="account.account.template">
+ <field name="code">218601</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Employee Benefits Provision - Employees Contribution</field>
+ </record>
+
+ <record id="chart218602_en" model="account.account.template">
+ <field name="code">218602</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Employee Benefits Provision - Employer Contribution</field>
+ </record>
+
<record id="chart25_en" model="account.account.template">
<field name="code">25</field>
<field name="user_type" ref="account.data_account_type_view"/>
@@ -387,8 +465,6 @@
<record id="chart251_en" model="account.account.template">
<field name="code">251</field>
- <field name="parent_id" ref="chart25_en"/>
- <field name="type">view</field>
<field name="user_type" ref="account.data_account_type_view"/>
<field name="name">NON-CURRENT FINANCIAL DEBTS</field>
</record>
@@ -399,12 +475,54 @@
<field name="name">PROVISIONS FOR PENSIONS AND OTHER POST-EMPLOYMENT ADVANTAGES</field>
</record>
- <record id="chart254_en" model="account.account.template">
- <field name="code">254</field>
+ <record id="chart2521_en" model="account.account.template">
+ <field name="code">2521</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Provision for pension plans</field>
+ </record>
+
+ <record id="chart253_en" model="account.account.template">
+ <field name="code">253</field>
<field name="user_type" ref="account.data_account_type_view"/>
<field name="name">DEFERRED TAXES</field>
</record>
+ <record id="chart254_en" model="account.account.template">
+ <field name="code">254</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">NON-CURRENT LIABILITIES RELATED TO SALARIES</field>
+ </record>
+
+ <record id="chart2541_en" model="account.account.template">
+ <field name="code">2541</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">Leaves Accruded</field>
+ </record>
+
+ <record id="chart254101_en" model="account.account.template">
+ <field name="code">254101</field>
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Vacations Accruded</field>
+ </record>
+
+ <record id="chart254102_en" model="account.account.template">
+ <field name="code">254102</field>
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Compensatory Days Accruded</field>
+ </record>
+
+ <record id="chart254103_en" model="account.account.template">
+ <field name="code">254103</field>
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Sick Leaves Accruded</field>
+ </record>
+
+ <record id="chart2542_en" model="account.account.template">
+ <field name="code">2542</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Bonus Accruded</field>
+ </record>
+
<record id="chart259_en" model="account.account.template">
<field name="code">259</field>
<field name="user_type" ref="account.data_account_type_view"/>
@@ -561,71 +679,127 @@
<field name="name">International Purchases</field>
</record>
- <record id="chart512_en" model="account.account.template">
- <field name="code">512</field>
- <field name="user_type" ref="account.data_account_type_view"/>
- <field name="name">LABOUR EXPENSES</field>
- </record>
-
- <record id="chart51201_en" model="account.account.template">
- <field name="code">51201</field>
+ <record id="chart512_en" model="account.account.template">
+ <field name="code">512</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">LABOUR EXPENSES</field>
+ </record>
+
+ <record id="chart5121_en" model="account.account.template">
+ <field name="code">5121</field>
+
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">Salaries</field>
+ </record>
+
+ <record id="chart512101_en" model="account.account.template">
+ <field name="code">512101</field>
+
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Regular Salaries</field>
+ </record>
+
+ <record id="chart512102_en" model="account.account.template">
+ <field name="code">512102</field>
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Bonus</field>
+ </record>
+
+ <record id="chart512103_en" model="account.account.template">
+ <field name="code">512103</field>
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Retroactive Pay</field>
+ </record>
+
+ <record id="chart5122_en" model="account.account.template">
+ <field name="code">5122</field>
+
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">Leaves Accruded</field>
+ </record>
+
+ <record id="chart512201_en" model="account.account.template">
+ <field name="code">512201</field>
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Vacations Accruded</field>
+ </record>
+
+ <record id="chart512202_en" model="account.account.template">
+ <field name="code">512202</field>
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+
+ <field name="name">Compensatory Days Accruded</field>
+ </record>
+
+ <record id="chart512203_en" model="account.account.template">
+ <field name="code">512203</field>
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Sick Leaves Accruded</field>
+ </record>
+
+ <record id="chart5123_en" model="account.account.template">
+ <field name="code">5123</field>
+
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">Employer Contributions</field>
+ </record>
+
+ <record id="chart512301_en" model="account.account.template">
+ <field name="code">512301</field>
+
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Canada Pension Plan</field>
+ </record>
+
+ <record id="chart512302_en" model="account.account.template">
+ <field name="code">512302</field>
+
+ <field name="user_type" ref="account.data_account_type_expe
+ <field name="name">Employment Insurance</field>
+ </record>
+
+ <record id="chart512303_en" model="account.account.template">
+ <field name="code">512303</field>
<field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Salaries, wages and commissions</field>
- </record>
+ <field name="name">Group Pension Plan</field>
+ </record>
- <record id="chart51202_en" model="account.account.template">
- <field name="code">51202</field>
+ <record id="chart512304_en" model="account.account.template">
+ <field name="code">512304</field>
<field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Holidays</field>
- </record>
+ <field name="name">Employee benefits expense</field>
+ </record>
- <record id="chart51203_en" model="account.account.template">
- <field name="code">51203</field>
+ <record id="chart512310_en" model="account.account.template">
+ <field name="code">512310</field>
<field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Employment Insurance</field>
- </record>
+ <field name="name">Provincial Pension Plan</field>
+ </record>
- <record id="chart51204_en" model="account.account.template">
- <field name="code">51204</field>
+ <record id="chart512311_en" model="account.account.template">
+ <field name="code">512311</field>
<field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Health Services Fund</field>
- </record>
+ <field name="name">Provincial Parental Insurance Plan</field>
+ </record>
- <record id="chart51205_en" model="account.account.template">
- <field name="code">51205</field>
+ <record id="chart512312_en" model="account.account.template">
+ <field name="code">512312</field>
<field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Annuities</field>
- </record>
-
- <record id="chart51206_en" model="account.account.template">
- <field name="code">51206</field>
- <field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Parental Insurance</field>
- </record>
+ <field name="name">Labour Health and Safety</field>
+ </record>
- <record id="chart51207_en" model="account.account.template">
- <field name="code">51207</field>
+ <record id="chart512313_en" model="account.account.template">
+ <field name="code">512313</field>
<field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Labour Health and Safety</field>
- </record>
+ <field name="name">Labour Standards</field>
+ </record>
- <record id="chart51208_en" model="account.account.template">
- <field name="code">51208</field>
+ <record id="chart512314_en" model="account.account.template">
+ <field name="code">512314</field>
<field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Labour Standards</field>
- </record>
+ <field name="name">Health Service Fund</field>
+ </record>
- <record id="chart51209_en" model="account.account.template">
- <field name="code">51209</field>
- <field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Federal Income Tax</field>
- </record>
-
- <record id="chart51210_en" model="account.account.template">
- <field name="code">51210</field>
- <field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Provincial Income Tax</field>
- </record>
<record id="chart513_en" model="account.account.template">
<field name="code">513</field>
<field name="user_type" ref="account.data_account_type_view"/>
diff --git a/addons/l10n_ca/account_chart_fr.xml b/addons/l10n_ca/account_chart_fr.xml
index 00674ac4e12..b24de09d845 100644
--- a/addons/l10n_ca/account_chart_fr.xml
+++ b/addons/l10n_ca/account_chart_fr.xml
@@ -253,107 +253,127 @@
<field name="name">TVH à payer - 15%</field>
</record>
- <record id="chart214_fr" model="account.account.template">
- <field name="code">214</field>
- <field name="user_type" ref="account.data_account_type_view"/>
- <field name="name">IMPÔTS LIÉS AUX SALAIRES À PAYER</field>
- </record>
-
- <record id="chart2141_fr" model="account.account.template">
- <field name="code">2141</field>
- <field name="user_type" ref="account.data_account_type_view"/>
- <field name="name">AGENCE DU REVENU DU CANADA</field>
- </record>
-
- <record id="chart21411_fr" model="account.account.template">
- <field name="code">21411</field>
- <field name="user_type" ref="account.data_account_type_view"/>
- <field name="name">ASSURANCE EMPLOI À PAYER</field>
- </record>
-
- <record id="chart214111_fr" model="account.account.template">
- <field name="code">214111</field>
+ <record id="chart214_fr" model="account.account.template">
+ <field name="code">214</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">DAS ET CONTRIBUTIONS DE L'EMPLOYEUR À PAYER</field>
+ </record>
+
+ <record id="chart2141_fr" model="account.account.template">
+ <field name="code">2141</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">AGENCE DU REVENU DU CANADA</field>
+ </record>
+
+ <record id="chart21411_fr" model="account.account.template">
+ <field name="code">21411</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">ASSURANCE EMPLOI À PAYER</field>
+ </record>
+
+ <record id="chart214111_fr" model="account.account.template">
+ <field name="code">214111</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">AE - Contribution des employés</field>
- </record>
+ <field name="name">AE - Contribution des employés</field>
+ </record>
- <record id="chart214112_fr" model="account.account.template">
- <field name="code">214112</field>
+ <record id="chart214112_fr" model="account.account.template">
+ <field name="code">214112</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">AE - Contribution de l'employeur</field>
- </record>
+ <field name="name">AE - Contribution de l'employeur</field>
+ </record>
- <record id="chart21412_fr" model="account.account.template">
- <field name="code">21412</field>
+ <record id="chart21412_fr" model="account.account.template">
+ <field name="code">21412</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">Impôt fédéral sur les revenus</field>
- </record>
+ <field name="name">Impôt fédéral sur les revenus</field>
+ </record>
- <record id="chart2142_fr" model="account.account.template">
- <field name="code">2142</field>
- <field name="user_type" ref="account.data_account_type_view"/>
- <field name="name">AGENCE DU REVENU PROVINCIAL</field>
- </record>
+ <record id="chart21413_fr" model="account.account.template">
+ <field name="code">21413</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">RÉGIME DE PENSIONS DU CANADA À PAYER</field>
+ </record>
+
+ <record id="chart214131_fr" model="account.account.template">
+ <field name="code">214131</field>
+
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">RPC - Contribution des employés</field>
+ </record>
+
+ <record id="chart214132_fr" model="account.account.template">
+ <field name="code">214132</field>
- <record id="chart21421_fr" model="account.account.template">
- <field name="code">21421</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">RPC - Contribution de l'employeur</field>
+ </record>
+
+ <record id="chart2142_fr" model="account.account.template">
+ <field name="code">2142</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">AGENCE PROVINCIALE</field>
+ </record>
+
+ <record id="chart21421_fr" model="account.account.template">
+ <field name="code">21421</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">Fond des Services de Santé à payer</field>
- </record>
+ <field name="name">Fond des Services de Santé à payer</field>
+ </record>
- <record id="chart21422_fr" model="account.account.template">
- <field name="code">21422</field>
- <field name="user_type" ref="account.data_account_type_view"/>
- <field name="name">RENTES À PAYER</field>
- </record>
+ <record id="chart21422_fr" model="account.account.template">
+ <field name="code">21422</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">RÉGIME DE PENSION PROVINCIAL À PAYER</field>
+ </record>
- <record id="chart214221_fr" model="account.account.template">
- <field name="code">214221</field>
+ <record id="chart214221_fr" model="account.account.template">
+ <field name="code">214221</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">Rentes - Contribution des employés</field>
- </record>
+ <field name="name">Régime de pension provincial - Contribution des employés</field>
+ </record>
- <record id="chart214222_fr" model="account.account.template">
- <field name="code">214222</field>
+ <record id="chart214222_fr" model="account.account.template">
+ <field name="code">214222</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">Rentes - Contribution de l'employeur</field>
- </record>
+ <field name="name">Régime de pension provincial - Contribution de l'employeur</field>
+ </record>
- <record id="chart21423_fr" model="account.account.template">
- <field name="code">21423</field>
- <field name="user_type" ref="account.data_account_type_view"/>
- <field name="name">ASSURANCE PARENTALE À PAYER</field>
- </record>
-
- <record id="chart214231_fr" model="account.account.template">
- <field name="code">214231</field>
+ <record id="chart21423_fr" model="account.account.template">
+ <field name="code">21423</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">RÉGIME PROVINCIAL D'ASSURANCE PARENTALE À PAYER</field>
+ </record>
+
+ <record id="chart214231_fr" model="account.account.template">
+ <field name="code">214231</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">AP - Contribution des employés</field>
- </record>
-
- <record id="chart214232_fr" model="account.account.template">
- <field name="code">214232</field>
+ <field name="name">Assurance parentale - Contribution des employés</field>
+ </record>
+
+ <record id="chart214232_fr" model="account.account.template">
+ <field name="code">214232</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">AP - Contribution de l'employeur</field>
- </record>
+ <field name="name">Assurance parentale - Contribution de l'employeur</field>
+ </record>
- <record id="chart21424_fr" model="account.account.template">
- <field name="code">21424</field>
+ <record id="chart21424_fr" model="account.account.template">
+ <field name="code">21424</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">Santé et Sécurité au Travail à payer</field>
- </record>
+ <field name="name">Santé et Sécurité au Travail à payer</field>
+ </record>
- <record id="chart21425_fr" model="account.account.template">
- <field name="code">21425</field>
+ <record id="chart21425_fr" model="account.account.template">
+ <field name="code">21425</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">Normes du Travail à payer</field>
- </record>
+ <field name="name">Normes du Travail à payer</field>
+ </record>
- <record id="chart21426_fr" model="account.account.template">
- <field name="code">21426</field>
+ <record id="chart21426_fr" model="account.account.template">
+ <field name="code">21426</field>
<field name="user_type" ref="account.data_account_type_current_liabilities"/>
- <field name="name">Impôt provincial sur les revenus</field>
- </record>
+ <field name="name">Impôt provincial sur les revenus</field>
+ </record>
<record id="chart215_fr" model="account.account.template">
<field name="code">215</field>
@@ -379,6 +399,67 @@
<field name="name">Stock reçu non facturé</field>
</record>
+ <record id="chart218_fr" model="account.account.template">
+ <field name="code">218</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">PASSIFS COURANTS LIÉS AUX SALAIRES</field>
+ </record>
+
+ <record id="chart2181_fr" model="account.account.template">
+ <field name="code">2181</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Salaires à payer</field>
+ </record>
+
+ <record id="chart2183_fr" model="account.account.template">
+ <field name="code">2183</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Bonis à payer</field>
+ </record>
+
+ <record id="chart2184_fr" model="account.account.template">
+ <field name="code">2184</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Paie Rétroactive à payer</field>
+ </record>
+
+ <!-- Montants à verser dans un RVER, RPA ou autre régime collectif -->
+ <record id="chart2185_fr" model="account.account.template">
+ <field name="code">2185</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">RÉGIMES DE PENSION COLLECTIFS À PAYER</field>
+ </record>
+
+ <record id="chart218501_fr" model="account.account.template">
+ <field name="code">218501</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Régimes de pension collectifs à payer - Contribution des employés</field>
+ </record>
+
+ <record id="chart218502_fr" model="account.account.template">
+ <field name="code">218502</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Régimes de pension collectifs à payer - Contribution de l'employeur</field>
+ </record>
+
+ <record id="chart2186_fr" model="account.account.template">
+ <field name="code">2186</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">PROVISION POUR AVANTAGES SOCIAUX</field>
+ </record>
+
+ <record id="chart218601_fr" model="account.account.template">
+ <field name="code">218601</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Provision pour avantages sociaux - Contribution de l'employé</field>
+ </record>
+
+ <record id="chart218602_fr" model="account.account.template">
+ <field name="code">218602</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Provision pour avantages sociaux - Contribution de l'employeur</field>
+ </record>
+
<record id="chart25_fr" model="account.account.template">
<field name="code">25</field>
<field name="user_type" ref="account.data_account_type_view"/>
@@ -397,17 +478,53 @@
<field name="name">PROVISIONS POUR RETRAITES ET AUTRES AVANTAGES POSTÉRIEURS À L'EMPLOI</field>
</record>
- <record id="chart253_fr" model="account.account.template">
- <field name="code">253</field>
- <field name="user_type" ref="account.data_account_type_view"/>
- <field name="name">IMPÔTS DIFFÉRÉS</field>
- </record>
-
- <record id="chart254_fr" model="account.account.template">
- <field name="code">254</field>
- <field name="user_type" ref="account.data_account_type_view"/>
- <field name="name">AUTRES PASSIFS NON-COURANTS</field>
- </record>
+ <record id="chart253_fr" model="account.account.template">
+ <field name="code">253</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">IMPÔTS DIFFÉRÉS</field>
+ </record>
+
+ <record id="chart254_fr" model="account.account.template">
+ <field name="code">254</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">PASSIFS NON-COURANTS LIÉS AUX SALAIRES</field>
+ </record>
+
+ <record id="chart2541_fr" model="account.account.template">
+ <field name="code">2541</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">Congés Accumulés</field>
+ </record>
+
+ <record id="chart254101_fr" model="account.account.template">
+ <field name="code">254101</field>
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Vacances Accumulées</field>
+ </record>
+
+ <record id="chart254102_fr" model="account.account.template">
+ <field name="code">254102</field>
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Jours Compensatoires Accumulés</field>
+ </record>
+
+ <record id="chart254103_fr" model="account.account.template">
+ <field name="code">254103</field>
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Congés de Maladie Accumulés</field>
+ </record>
+
+ <record id="chart2542_fr" model="account.account.template">
+ <field name="code">2542</field>
+ <field name="user_type" ref="account.data_account_type_current_liabilities"/>
+ <field name="name">Bonis Accumulés</field>
+ </record>
+
+ <record id="chart259_fr" model="account.account.template">
+ <field name="code">259</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">AUTRES PASSIFS NON-COURANTS</field>
+ </record>
<!-- CAPITAUX PROPRES -->
@@ -565,71 +682,125 @@
<field name="name">Achats à l'étranger</field>
</record>
- <record id="chart512_fr" model="account.account.template">
- <field name="code">512</field>
- <field name="user_type" ref="account.data_account_type_view"/>
- <field name="name">SALAIRES ET CHARGES SOCIALES</field>
- </record>
-
- <record id="chart51201_fr" model="account.account.template">
- <field name="code">51201</field>
- <field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Salaires</field>
- </record>
-
- <record id="chart51202_fr" model="account.account.template">
- <field name="code">51202</field>
- <field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Vacances</field>
- </record>
-
- <record id="chart51203_fr" model="account.account.template">
- <field name="code">51203</field>
- <field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Assurance Emploi</field>
- </record>
-
- <record id="chart51204_fr" model="account.account.template">
- <field name="code">51204</field>
+ <record id="chart512_fr" model="account.account.template">
+ <field name="code">512</field>
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">SALAIRES ET CHARGES SOCIALES</field>
+ </record>
+
+ <record id="chart5121_fr" model="account.account.template">
+ <field name="code">5121</field>
+
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">Salaires</field>
+ </record>
+
+ <record id="chart512101_fr" model="account.account.template">
+ <field name="code">512101</field>
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Salaires Réguliers</field>
+ </record>
+
+ <record id="chart512102_fr" model="account.account.template">
+ <field name="code">512102</field>
+
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Bonis</field>
+ </record>
+
+ <record id="chart512103_fr" model="account.account.template">
+ <field name="code">512103</field>
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Paies Rétroactives</field>
+ </record>
+
+ <record id="chart5122_fr" model="account.account.template">
+ <field name="code">5122</field>
+
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">Congés Accumulées</field>
+ </record>
+
+ <record id="chart512201_fr" model="account.account.template">
+ <field name="code">512201</field>
+<field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Vacances Accumulées</field>
+ </record>
+
+ <record id="chart512202_fr" model="account.account.template">
+ <field name="code">512202</field>
+
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Jours Compensatoires Accumulées</field>
+ </record>
+
+ <record id="chart512203_fr" model="account.account.template">
+ <field name="code">512203</field>
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Congés de Maladie Accumulés</field>
+ </record>
+
+ <record id="chart5123_fr" model="account.account.template">
+ <field name="code">5123</field>
+
+ <field name="user_type" ref="account.data_account_type_view"/>
+ <field name="name">Contributions de l'Employeur</field>
+ </record>
+
+ <record id="chart512301_fr" model="account.account.template">
+ <field name="code">512301</field>
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Régime de Pensions du Canada</field>
+ </record>
+
+ <record id="chart512302_fr" model="account.account.template">
+ <field name="code">512302</field>
+
+ <field name="user_type" ref="account.data_account_type_expenses"/>
+ <field name="name">Assurance Emploi</field>
+ </record>
+
+ <record id="chart512303_fr" model="account.account.template">
+ <field name="code">512303</field>
<field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Fonds des services de santé</field>
- </record>
+ <field name="name">Régimes de pension collectifs</field>
+ </record>
- <record id="chart51205_fr" model="account.account.template">
- <field name="code">51205</field>
+ <record id="chart512304_fr" model="account.account.template">
+ <field name="code">512304</field>
<field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Rentes</field>
- </record>
+ <field name="name">Dépense d'avantages sociaux</field>
+ </record>
- <record id="chart51206_fr" model="account.account.template">
- <field name="code">51206</field>
+ <record id="chart512310_fr" model="account.account.template">
+ <field name="code">512310</field>
<field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Assurance parentale</field>
- </record>
+ <field name="name">Régime de pension provincial</field>
+ </record>
- <record id="chart51207_fr" model="account.account.template">
- <field name="code">51207</field>
+ <record id="chart512311_fr" model="account.account.template">
+ <field name="code">512311</field>
<field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Santé et sécurité au travail</field>
- </record>
+ <field name="name">Régime d'assurance parental provincial</field>
+ </record>
- <record id="chart51208_fr" model="account.account.template">
- <field name="code">51208</field>
+ <record id="chart512312_fr" model="account.account.template">
+ <field name="code">512312</field>
<field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Normes du travail</field>
- </record>
+ <field name="name">Santé et sécurité au travail</field>
+ </record>
- <record id="chart51209_fr" model="account.account.template">
- <field name="code">51209</field>
+ <record id="chart512313_fr" model="account.account.template">
+ <field name="code">512313</field>
<field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Impôt fédéral</field>
- </record>
+ <field name="name">Normes du travail</field>
+ </record>
- <record id="chart51210_fr" model="account.account.template">
- <field name="code">51210</field>
+ <record id="chart512314_fr" model="account.account.template">
+ <field name="code">512314</field>
<field name="user_type" ref="account.data_account_type_expenses"/>
- <field name="name">Impôt provincial</field>
- </record>
+ <field name="name">Fonds des services de santé</field>
+ </record>
<record id="chart513_fr" model="account.account.template">
<field name="code">513</field>
diff --git a/addons/l10n_ca/account_tax_en.xml b/addons/l10n_ca/account_tax_en.xml
index 54073d0f545..10e7bc79483 100644
--- a/addons/l10n_ca/account_tax_en.xml
+++ b/addons/l10n_ca/account_tax_en.xml
@@ -14,6 +14,8 @@
<field name="amount">1</field>
<field name="type">percent</field>
<field name="child_depend">1</field>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="gstpst_sale_bc_gst_en" model="account.tax.template">
@@ -28,6 +30,8 @@
<field name="account_collected_id" ref="chart2131_en"/>
<field name="account_paid_id" ref="chart2131_en"/>
<field name="parent_id" ref="gstpst_bc_sale_en"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="pst_bc_sale_en" model="account.tax.template">
@@ -41,6 +45,8 @@
<field name="account_collected_id" ref="chart2132_en"/>
<field name="account_paid_id" ref="chart2132_en"/>
<field name="parent_id" ref="gstpst_bc_sale_en"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<!-- Manitoba PST -->
@@ -53,6 +59,8 @@
<field name="amount">1</field>
<field name="type">percent</field>
<field name="child_depend" eval="True"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="gstpst_sale_mb_gst_en" model="account.tax.template">
@@ -67,6 +75,8 @@
<field name="account_collected_id" ref="chart2131_en"/>
<field name="account_paid_id" ref="chart2131_en"/>
<field name="parent_id" ref="gstpst_mb_sale_en"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="pst_mb_sale_en" model="account.tax.template">
@@ -80,6 +90,8 @@
<field name="account_collected_id" ref="chart2132_en"/>
<field name="account_paid_id" ref="chart2132_en"/>
<field name="parent_id" ref="gstpst_mb_sale_en"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<!-- Quebec PST -->
@@ -92,6 +104,8 @@
<field name="amount">1</field>
<field name="type">percent</field>
<field name="child_depend" eval="True"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="gstqst_sale_gst_en" model="account.tax.template">
@@ -105,6 +119,8 @@
<field name="account_collected_id" ref="chart2131_en"/>
<field name="account_paid_id" ref="chart2131_en"/>
<field name="parent_id" ref="gstqst_sale_en"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="qst_sale_en" model="account.tax.template">
@@ -118,6 +134,8 @@
<field name="account_collected_id" ref="chart2132_en"/>
<field name="account_paid_id" ref="chart2132_en"/>
<field name="parent_id" ref="gstqst_sale_en"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<!-- Saskatchewan PST -->
@@ -130,6 +148,8 @@
<field name="amount">1</field>
<field name="type">percent</field>
<field name="child_depend" eval="True"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="gstpst_sale_sk_gst_en" model="account.tax.template">
@@ -144,6 +164,8 @@
<field name="account_collected_id" ref="chart2131_en"/>
<field name="account_paid_id" ref="chart2131_en"/>
<field name="parent_id" ref="gstpst_sk_sale_en"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="pst_sk_sale_en" model="account.tax.template">
@@ -157,6 +179,8 @@
<field name="account_collected_id" ref="chart2132_en"/>
<field name="account_paid_id" ref="chart2132_en"/>
<field name="parent_id" ref="gstpst_sk_sale_en"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<!-- HST -->
@@ -170,6 +194,8 @@
<field name="type">percent</field>
<field name="account_collected_id" ref="chart21331_en"/>
<field name="account_paid_id" ref="chart21331_en"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="hst14_sale_en" model="account.tax.template">
@@ -181,6 +207,8 @@
<field name="type">percent</field>
<field name="account_collected_id" ref="chart21332_en"/>
<field name="account_paid_id" ref="chart21332_en"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="hst15_sale_en" model="account.tax.template">
@@ -192,6 +220,8 @@
<field name="type">percent</field>
<field name="account_collected_id" ref="chart21333_en"/>
<field name="account_paid_id" ref="chart21333_en"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<!-- GST -->
@@ -205,6 +235,8 @@
<field name="type">percent</field>
<field name="account_collected_id" ref="chart2131_en"/>
<field name="account_paid_id" ref="chart2131_en"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<!-- PURCHASE TAXES -->
@@ -219,6 +251,8 @@
<field name="amount">1</field>
<field name="type">percent</field>
<field name="child_depend">1</field>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="gstpst_purc_bc_gst_en" model="account.tax.template">
@@ -233,6 +267,8 @@
<field name="account_collected_id" ref="chart1181_en"/>
<field name="account_paid_id" ref="chart1181_en"/>
<field name="parent_id" ref="gstpst_bc_purc_en"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="pst_bc_purc_en" model="account.tax.template">
@@ -246,6 +282,8 @@
<field name="account_collected_id" ref="chart1182_en"/>
<field name="account_paid_id" ref="chart1182_en"/>
<field name="parent_id" ref="gstpst_bc_purc_en"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<!-- Manitoba PST -->
@@ -258,6 +296,8 @@
<field name="amount">1</field>
<field name="type">percent</field>
<field name="child_depend" eval="True"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="gstpst_purc_mb_gst_en" model="account.tax.template">
@@ -272,6 +312,8 @@
<field name="account_collected_id" ref="chart1181_en"/>
<field name="account_paid_id" ref="chart1181_en"/>
<field name="parent_id" ref="gstpst_mb_purc_en"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="pst_mb_purc_en" model="account.tax.template">
@@ -285,6 +327,8 @@
<field name="account_collected_id" ref="chart1182_en"/>
<field name="account_paid_id" ref="chart1182_en"/>
<field name="parent_id" ref="gstpst_mb_purc_en"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<!-- Quebec PST -->
@@ -297,6 +341,8 @@
<field name="amount">1</field>
<field name="type">percent</field>
<field name="child_depend" eval="True"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="gstqst_purc_gst_en" model="account.tax.template">
@@ -310,6 +356,8 @@
<field name="account_collected_id" ref="chart1181_en"/>
<field name="account_paid_id" ref="chart1181_en"/>
<field name="parent_id" ref="gstqst_purc_en"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="qst_purc_en" model="account.tax.template">
@@ -323,6 +371,8 @@
<field name="account_collected_id" ref="chart1182_en"/>
<field name="account_paid_id" ref="chart1182_en"/>
<field name="parent_id" ref="gstqst_purc_en"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<!-- Saskatchewan PST -->
@@ -335,6 +385,8 @@
<field name="amount">1</field>
<field name="type">percent</field>
<field name="child_depend" eval="True"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="gstpst_purc_sk_gst_en" model="account.tax.template">
@@ -349,6 +401,8 @@
<field name="account_collected_id" ref="chart1181_en"/>
<field name="account_paid_id" ref="chart1181_en"/>
<field name="parent_id" ref="gstpst_sk_purc_en"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="pst_sk_purc_en" model="account.tax.template">
@@ -362,6 +416,8 @@
<field name="account_collected_id" ref="chart1182_en"/>
<field name="account_paid_id" ref="chart1182_en"/>
<field name="parent_id" ref="gstpst_sk_purc_en"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<!-- HST -->
@@ -375,6 +431,8 @@
<field name="type">percent</field>
<field name="account_collected_id" ref="chart11831_en"/>
<field name="account_paid_id" ref="chart11831_en"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="hst14_purc_en" model="account.tax.template">
@@ -386,6 +444,8 @@
<field name="type">percent</field>
<field name="account_collected_id" ref="chart11832_en"/>
<field name="account_paid_id" ref="chart11832_en"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="hst15_purc_en" model="account.tax.template">
@@ -397,6 +457,8 @@
<field name="type">percent</field>
<field name="account_collected_id" ref="chart11833_en"/>
<field name="account_paid_id" ref="chart11833_en"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<!-- GST -->
@@ -410,6 +472,8 @@
<field name="type">percent</field>
<field name="account_collected_id" ref="chart1181_en"/>
<field name="account_paid_id" ref="chart1181_en"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
</data>
diff --git a/addons/l10n_ca/account_tax_fr.xml b/addons/l10n_ca/account_tax_fr.xml
index 793bb1c750e..2a72eb1af77 100644
--- a/addons/l10n_ca/account_tax_fr.xml
+++ b/addons/l10n_ca/account_tax_fr.xml
@@ -14,6 +14,8 @@
<field name="amount">1</field>
<field name="child_depend">1</field>
<field name="type">percent</field>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="gstpst_sale_bc_gst_fr" model="account.tax.template">
@@ -28,6 +30,8 @@
<field name="account_collected_id" ref="chart2131_fr"/>
<field name="account_paid_id" ref="chart2131_fr"/>
<field name="parent_id" ref="gstpst_bc_sale_fr"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="pst_bc_sale_fr" model="account.tax.template">
@@ -41,6 +45,8 @@
<field name="account_collected_id" ref="chart2132_fr"/>
<field name="account_paid_id" ref="chart2132_fr"/>
<field name="parent_id" ref="gstpst_bc_sale_fr"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<!-- Manitoba PST -->
@@ -53,6 +59,8 @@
<field name="amount">1</field>
<field name="type">percent</field>
<field name="child_depend" eval="True"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="gstpst_sale_mb_gst_fr" model="account.tax.template">
@@ -67,6 +75,8 @@
<field name="account_collected_id" ref="chart2131_fr"/>
<field name="account_paid_id" ref="chart2131_fr"/>
<field name="parent_id" ref="gstpst_mb_sale_fr"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="pst_mb_sale_fr" model="account.tax.template">
@@ -80,6 +90,8 @@
<field name="account_collected_id" ref="chart2132_fr"/>
<field name="account_paid_id" ref="chart2132_fr"/>
<field name="parent_id" ref="gstpst_mb_sale_fr"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<!-- Quebec PST -->
@@ -92,6 +104,8 @@
<field name="amount">1</field>
<field name="type">percent</field>
<field name="child_depend" eval="True"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="gstqst_sale_gst_fr" model="account.tax.template">
@@ -105,6 +119,8 @@
<field name="account_collected_id" ref="chart2131_fr"/>
<field name="account_paid_id" ref="chart2131_fr"/>
<field name="parent_id" ref="gstqst_sale_fr"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="tvq_sale_fr" model="account.tax.template">
@@ -118,6 +134,8 @@
<field name="account_collected_id" ref="chart2132_fr"/>
<field name="account_paid_id" ref="chart2132_fr"/>
<field name="parent_id" ref="gstqst_sale_fr"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<!-- Saskatchewan PST -->
@@ -130,6 +148,8 @@
<field name="amount">1</field>
<field name="type">percent</field>
<field name="child_depend" eval="True"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="gstpst_sale_sk_gst_fr" model="account.tax.template">
@@ -144,6 +164,8 @@
<field name="account_collected_id" ref="chart2131_fr"/>
<field name="account_paid_id" ref="chart2131_fr"/>
<field name="parent_id" ref="gstpst_sk_sale_fr"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="pst_sk_sale_fr" model="account.tax.template">
@@ -157,6 +179,8 @@
<field name="account_collected_id" ref="chart2132_fr"/>
<field name="account_paid_id" ref="chart2132_fr"/>
<field name="parent_id" ref="gstpst_sk_sale_fr"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<!-- HST -->
@@ -170,6 +194,8 @@
<field name="type">percent</field>
<field name="account_collected_id" ref="chart21331_fr"/>
<field name="account_paid_id" ref="chart21331_fr"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="hst14_sale_fr" model="account.tax.template">
@@ -181,6 +207,8 @@
<field name="type">percent</field>
<field name="account_collected_id" ref="chart21332_fr"/>
<field name="account_paid_id" ref="chart21332_fr"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<record id="hst15_sale_fr" model="account.tax.template">
@@ -192,6 +220,8 @@
<field name="type">percent</field>
<field name="account_collected_id" ref="chart21333_fr"/>
<field name="account_paid_id" ref="chart21333_fr"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
<!-- GST -->
@@ -205,6 +235,8 @@
<field name="type">percent</field>
<field name="account_collected_id" ref="chart2131_fr"/>
<field name="account_paid_id" ref="chart2131_fr"/>
+ <field name="ref_base_sign" eval="-1"/>
+ <field name="ref_tax_sign" eval="-1"/>
</record>
@@ -220,6 +252,8 @@
<field name="amount">1</field>
<field name="type">percent</field>
<field name="child_depend">1</field>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="gstpst_purc_bc_gst_fr" model="account.tax.template">
@@ -234,6 +268,8 @@
<field name="account_collected_id" ref="chart1181_fr"/>
<field name="account_paid_id" ref="chart1181_fr"/>
<field name="parent_id" ref="gstpst_bc_purc_fr"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="pst_bc_purc_fr" model="account.tax.template">
@@ -247,6 +283,8 @@
<field name="account_collected_id" ref="chart1182_fr"/>
<field name="account_paid_id" ref="chart1182_fr"/>
<field name="parent_id" ref="gstpst_bc_purc_fr"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<!-- Manitoba PST -->
@@ -259,6 +297,8 @@
<field name="amount">1</field>
<field name="type">percent</field>
<field name="child_depend" eval="True"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="gstpst_purc_mb_gst_fr" model="account.tax.template">
@@ -273,6 +313,8 @@
<field name="account_collected_id" ref="chart1181_fr"/>
<field name="account_paid_id" ref="chart1181_fr"/>
<field name="parent_id" ref="gstpst_mb_purc_fr"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="pst_mb_purc_fr" model="account.tax.template">
@@ -286,6 +328,8 @@
<field name="account_collected_id" ref="chart1182_fr"/>
<field name="account_paid_id" ref="chart1182_fr"/>
<field name="parent_id" ref="gstpst_mb_purc_fr"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<!-- Quebec PST -->
@@ -298,6 +342,8 @@
<field name="amount">1</field>
<field name="type">percent</field>
<field name="child_depend" eval="True"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="gstqst_purc_gst_fr" model="account.tax.template">
@@ -311,6 +357,8 @@
<field name="account_collected_id" ref="chart1181_fr"/>
<field name="account_paid_id" ref="chart1181_fr"/>
<field name="parent_id" ref="gstqst_purc_fr"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="tvq_purc_fr" model="account.tax.template">
@@ -324,6 +372,8 @@
<field name="account_collected_id" ref="chart1182_fr"/>
<field name="account_paid_id" ref="chart1182_fr"/>
<field name="parent_id" ref="gstqst_purc_fr"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<!-- Saskatchewan PST -->
@@ -336,6 +386,8 @@
<field name="amount">1</field>
<field name="type">percent</field>
<field name="child_depend" eval="True"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="gstpst_purc_sk_gst_fr" model="account.tax.template">
@@ -350,6 +402,8 @@
<field name="account_collected_id" ref="chart1181_fr"/>
<field name="account_paid_id" ref="chart1181_fr"/>
<field name="parent_id" ref="gstpst_sk_purc_fr"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="pst_sk_purc_fr" model="account.tax.template">
@@ -363,6 +417,8 @@
<field name="account_collected_id" ref="chart1182_fr"/>
<field name="account_paid_id" ref="chart1182_fr"/>
<field name="parent_id" ref="gstpst_sk_purc_fr"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<!-- HST -->
@@ -376,6 +432,8 @@
<field name="type">percent</field>
<field name="account_collected_id" ref="chart11831_fr"/>
<field name="account_paid_id" ref="chart11831_fr"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="hst14_purc_fr" model="account.tax.template">
@@ -387,6 +445,8 @@
<field name="type">percent</field>
<field name="account_collected_id" ref="chart11832_fr"/>
<field name="account_paid_id" ref="chart11832_fr"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<record id="hst15_purc_fr" model="account.tax.template">
@@ -398,6 +458,8 @@
<field name="type">percent</field>
<field name="account_collected_id" ref="chart11833_fr"/>
<field name="account_paid_id" ref="chart11833_fr"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
<!-- GST -->
@@ -411,6 +473,8 @@
<field name="type">percent</field>
<field name="account_collected_id" ref="chart1181_fr"/>
<field name="account_paid_id" ref="chart1181_fr"/>
+ <field name="base_sign" eval="-1"/>
+ <field name="tax_sign" eval="-1"/>
</record>
</data>
diff --git a/addons/l10n_fr/report/base_report.py b/addons/l10n_fr/report/base_report.py
index 8e9c75161e1..d38dcd5c007 100644
--- a/addons/l10n_fr/report/base_report.py
+++ b/addons/l10n_fr/report/base_report.py
@@ -28,6 +28,7 @@
import time
from openerp.report import report_sxw
+from openerp.tools.safe_eval import safe_eval as eval
class base_report(report_sxw.rml_parse):
diff --git a/addons/l10n_jp/__init__.py b/addons/l10n_jp/__init__.py
new file mode 100644
index 00000000000..03d6a3a323c
--- /dev/null
+++ b/addons/l10n_jp/__init__.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) Rooms For (Hong Kong) Limited T/A OSCG
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/l10n_jp/__openerp__.py b/addons/l10n_jp/__openerp__.py
new file mode 100644
index 00000000000..85acc113fc5
--- /dev/null
+++ b/addons/l10n_jp/__openerp__.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) Rooms For (Hong Kong) Limited T/A OSCG
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+{
+ 'name': 'Japan - Accounting',
+ 'version': '1.2',
+ 'category': 'Localization/Account Charts',
+ 'description': """
+
+Overview:
+---------
+
+* Chart of Accounts and Taxes template for companies in Japan.
+* This probably does not cover all the necessary accounts for a company. \
+You are expected to add/delete/modify accounts based on this template.
+
+Note:
+-----
+
+* Fiscal positions '内税' and '外税' have been added to handle special \
+requirements which might arise from POS implementation. [1] You may not \
+need to use these at all under normal circumstances.
+
+[1] See https://github.com/odoo/odoo/pull/6470 for detail.
+
+ """,
+ 'author': 'Rooms For (Hong Kong) Limited T/A OSCG',
+ 'website': 'http://www.openerp-asia.net/',
+ 'depends': ['account'],
+ 'data': [
+ 'data/account.account.template.csv',
+ 'data/account.tax.code.template.csv',
+ 'data/account.chart.template.csv',
+ 'data/account.tax.template.csv',
+ 'data/account.fiscal.position.template.csv',
+ ],
+ 'installable': False,
+}
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/l10n_jp/data/account.account.template.csv b/addons/l10n_jp/data/account.account.template.csv
new file mode 100644
index 00000000000..06a61786351
--- /dev/null
+++ b/addons/l10n_jp/data/account.account.template.csv
@@ -0,0 +1,157 @@
+id,code,name,parent_id:id,type,user_type/id,tax_ids/id,reconcile,currency_id/id
+COM0,0,日本勘定科目表,,View,account.data_account_type_view,,FALSE,
+A,A,貸借対照表,COM0,View,account.data_account_type_view,,FALSE,
+A1,A1,資産の部,A,View,account.account_type_asset_view1,,FALSE,
+A11,A11,流動資産,A1,View,account.account_type_asset_view1,,FALSE,
+A111,A111,現金・預金,A11,View,account.account_type_asset_view1,,FALSE,
+A11101,A11101,普通預金,A111,Liquidity,account.data_account_type_cash,,FALSE,
+A11102,A11102,当座預金,A111,Liquidity,account.data_account_type_cash,,FALSE,
+A11103,A11103,定期預金,A111,Liquidity,account.data_account_type_cash,,FALSE,
+A11105,A11105,小口現金,A111,Liquidity,account.data_account_type_cash,,FALSE,
+A112,A112,売掛金等,A11,View,account.account_type_asset_view1,,FALSE,
+A11211,A11211,売掛金,A112,Receivable,account.data_account_type_receivable,,TRUE,
+A11212,A11212,売掛金(USD),A112,Receivable,account.data_account_type_receivable,,TRUE,base.USD
+A11215,A11215,受取手形,A112,Receivable,account.data_account_type_receivable,,TRUE,
+A11216,A11216,割引手形,A112,Receivable,account.data_account_type_receivable,,TRUE,
+A11219,A11219,出庫請求仮,A112,Regular,account.data_account_type_receivable,,FALSE,
+A11301,A11301,未収収益,A11,Regular,account.data_account_type_asset,,FALSE,
+A11401,A11401,有価証券,A11,Regular,account.data_account_type_asset,,FALSE,
+A115,A115,在庫,A11,View,account.account_type_asset_view1,,FALSE,
+A11501,A11501,商品,A115,Regular,account.data_account_type_asset,,FALSE,
+A11503,A11503,製品,A115,Regular,account.data_account_type_asset,,FALSE,
+A11504,A11504,仕掛品,A115,Regular,account.data_account_type_asset,,FALSE,
+A11505,A11505,原材料,A115,Regular,account.data_account_type_asset,,FALSE,
+A11506,A11506,資材,A115,Regular,account.data_account_type_asset,,FALSE,
+A11503,A11503,積送品,A115,Regular,account.data_account_type_asset,,FALSE,
+A11601,A11601,未収金,A11,Receivable,account.data_account_type_receivable,,TRUE,
+A118,A118,その他流動資産,A11,View,account.account_type_asset_view1,,FALSE,
+A11802,A11802,前渡金,A118,Regular,account.data_account_type_asset,,FALSE,
+A11804,A11804,前払費用,A118,Regular,account.data_account_type_asset,,FALSE,
+A11805,A11805,立替金,A118,Regular,account.data_account_type_asset,,FALSE,
+A11806,A11806,仮払金,A118,Regular,account.data_account_type_asset,,FALSE,
+A11807,A11807,仮払消費税,A118,Regular,account.conf_account_type_tax,,FALSE,
+A11901,A11901,貸倒引当金,A11,Regular,account.data_account_type_asset,,FALSE,
+A12,A12,固定資産,A1,View,account.account_type_asset_view1,,FALSE,
+A121,A121,有形固定資産,A12,View,account.account_type_asset_view1,,FALSE,
+A1211,A1211,建物及び構築物(純額),A121,View,account.account_type_asset_view1,,FALSE,
+A12111,A12111,建物及び構築物,A1211,Regular,account.data_account_type_asset,,FALSE,
+A12112,A12112,建物及び構築物減償累計額,A1211,Regular,account.data_account_type_asset,,FALSE,
+A1212,A1212,機械及び装置(純額),A121,View,account.account_type_asset_view1,,FALSE,
+A12121,A12121,機械及び装置,A1212,Regular,account.data_account_type_asset,,FALSE,
+A12122,A12122,機械及び装置物減償累計額,A1212,Regular,account.data_account_type_asset,,FALSE,
+A1213,A1213,工具、機器及び備品(純額),A121,View,account.account_type_asset_view1,,FALSE,
+A12131,A12131,工具、機器及び備品,A1213,Regular,account.data_account_type_asset,,FALSE,
+A12132,A12132,工具、機器及び備品減償累計額,A1213,Regular,account.data_account_type_asset,,FALSE,
+A12141,A12141,土地,A121,Regular,account.data_account_type_asset,,FALSE,
+A122,A122,無形固定資産,A12,View,account.account_type_asset_view1,,FALSE,
+A1221,A1221,ソフトウェア,A122,Regular,account.data_account_type_asset,,FALSE,
+A1222,A1222,ソフトウェア仮勘定,A122,Regular,account.data_account_type_asset,,FALSE,
+A1223,A1223,その他無形固定資産,A122,Regular,account.data_account_type_asset,,FALSE,
+A123,A123,投資その他資産,A12,View,account.account_type_asset_view1,,FALSE,
+A12301,A12301,投資有価証券,A123,Regular,account.data_account_type_asset,,FALSE,
+A12302,A12302,関係会社株式,A123,Regular,account.data_account_type_asset,,FALSE,
+A12303,A12303,長期貸付金,A123,Regular,account.data_account_type_asset,,FALSE,
+A13,A13,繰延資産,A1,View,account.account_type_asset_view1,,FALSE,
+A13001,A13001,創立費,A13,Regular,account.data_account_type_asset,,FALSE,
+A2,A2,負債の部,A,View,account.account_type_liability_view1,,FALSE,
+A21,A21,流動負債,A2,View,account.account_type_liability_view1,,FALSE,
+A212,A212,買掛金等,A21,View,account.account_type_liability_view1,,FALSE,
+A21211,A21211,買掛金,A212,Payable,account.data_account_type_payable,,TRUE,
+A21212,A21212,買掛金(USD),A212,Payable,account.data_account_type_payable,,TRUE,base.USD
+A21215,A21215,支払手形,A212,Payable,account.data_account_type_payable,,TRUE,
+A21219,A21219,入庫請求仮,A212,Regular,account.data_account_type_payable,,FALSE,
+A213,A213,短期借入金等,A212,Payable,account.data_account_type_payable,,TRUE,
+A21301,A21301,短期借入金,A21,Payable,account.data_account_type_payable,,TRUE,
+A21302,A21302,一年以内返済長期借入金,A21,Payable,account.data_account_type_payable,,TRUE,
+A214,A214,未払金等,A21,View,account.account_type_liability_view1,,FALSE,
+A21401,A21401,未払金,A214,Payable,account.data_account_type_payable,,TRUE,
+A21404,A21404,未払金(クレジット),A214,Payable,account.data_account_type_payable,,TRUE,
+A21407,A21407,未払配当金,A214,Payable,account.data_account_type_payable,,TRUE,
+A21501,A21501,未払費用,A21,Regular,account.data_account_type_liability,,FALSE,
+A21601,A21601,未払法人税等,A21,Regular,account.conf_account_type_tax,,FALSE,
+A218,A218,その他流動負債,A21,View,account.account_type_liability_view1,,FALSE,
+A21802,A21802,前受金,A218,Regular,account.data_account_type_liability,,FALSE,
+A21803,A21803,預り金,A218,Regular,account.data_account_type_liability,,FALSE,
+A21804,A21804,仮受金,A218,Regular,account.data_account_type_liability,,FALSE,
+A21806,A21806,賞与引当金,A218,Regular,account.data_account_type_liability,,FALSE,
+A21807,A21807,仮受消費税,A218,Regular,account.conf_account_type_tax,,FALSE,
+A22,A22,固定負債,A2,View,account.account_type_liability_view1,,FALSE,
+A22001,A22001,社債,A22,Regular,account.data_account_type_liability,,FALSE,
+A22002,A22002,長期借入金,A22,Regular,account.data_account_type_liability,,FALSE,
+A22005,A22005,退職給付引当金,A22,Regular,account.data_account_type_liability,,FALSE,
+A3,A3,純資産の部,A,View,account.account_type_liability_view1,,FALSE,
+A31,A31,株主資本,A3,View,account.account_type_liability_view1,,FALSE,
+A31001,A31001,資本金,A31,Regular,account.conf_account_type_equity,,FALSE,
+A32,A32,資本剰余金,A3,View,account.account_type_liability_view1,,FALSE,
+A32101,A32101,資本準備金,A32,Regular,account.conf_account_type_equity,,FALSE,
+A32109,A32109,その他資本剰余金,A32,Regular,account.conf_account_type_equity,,FALSE,
+A33,A33,利益剰余金,A3,View,account.account_type_liability_view1,,FALSE,
+A33101,A33101,利益準備金,A33,Regular,account.conf_account_type_equity,,FALSE,
+A332,A332,その他利益剰余金,A33,View,account.account_type_liability_view1,,FALSE,
+A33202,A33202,特別償却準備金,A332,Regular,account.conf_account_type_equity,,FALSE,
+A33204,A33204,任意積立金,A332,Regular,account.conf_account_type_equity,,FALSE,
+A33209,A33209,繰越利益剰余金,A332,Regular,account.conf_account_type_equity,,FALSE,
+B,B,損益計算書,COM0,View,account.data_account_type_view,,FALSE,
+B4,B4,売上総利益,B,View,account.account_type_income_view1,,FALSE,
+B41001,B41001,売上高,B4,Regular,account.data_account_type_income,,FALSE,
+B42001,B42001,売上原価,B4,Regular,account.data_account_type_income,,FALSE,
+B42091,B42091,購買価格差異,B4,Regular,account.data_account_type_income,,FALSE,
+B42092,B42092,棚卸調整,B4,Regular,account.data_account_type_income,,FALSE,
+B5,B5,販売費及び一般管理費,B,View,account.account_type_expense_view1,,FALSE,
+B50011,B50011,貸倒引当金繰入額,B5,Regular,account.data_account_type_expense,,FALSE,
+B50041,B50041,役員報酬,B5,Regular,account.data_account_type_expense,,FALSE,
+B50071,B50071,給料及び手当,B5,Regular,account.data_account_type_expense,,FALSE,
+B50081,B50081,賞与,B5,Regular,account.data_account_type_expense,,FALSE,
+B50091,B50091,退職金,B5,Regular,account.data_account_type_expense,,FALSE,
+B50111,B50111,賞与引当金繰入,B5,Regular,account.data_account_type_expense,,FALSE,
+B50131,B50131,退職給付引当金繰入,B5,Regular,account.data_account_type_expense,,FALSE,
+B50151,B50151,法定福利費,B5,Regular,account.data_account_type_expense,,FALSE,
+B50161,B50161,福利厚生費,B5,Regular,account.data_account_type_expense,,FALSE,
+B50171,B50171,保険料,B5,Regular,account.data_account_type_expense,,FALSE,
+B50191,B50191,外注費,B5,Regular,account.data_account_type_expense,,FALSE,
+B50211,B50211,荷造運賃,B5,Regular,account.data_account_type_expense,,FALSE,
+B50221,B50221,棚卸減耗費,B5,Regular,account.data_account_type_expense,,FALSE,
+B50231,B50231,商品評価損,B5,Regular,account.data_account_type_expense,,FALSE,
+B50241,B50241,地代家賃,B5,Regular,account.data_account_type_expense,,FALSE,
+B50251,B50251,リース料,B5,Regular,account.data_account_type_expense,,FALSE,
+B50271,B50271,広告宣伝費,B5,Regular,account.data_account_type_expense,,FALSE,
+B50281,B50281,通信費,B5,Regular,account.data_account_type_expense,,FALSE,
+B50311,B50311,消耗品費,B5,Regular,account.data_account_type_expense,,FALSE,
+B50341,B50341,旅費交通費,B5,Regular,account.data_account_type_expense,,FALSE,
+B50371,B50371,交際費,B5,Regular,account.data_account_type_expense,,FALSE,
+B50401,B50401,支払手数料,B5,Regular,account.data_account_type_expense,,FALSE,
+B50431,B50431,諸会費,B5,Regular,account.data_account_type_expense,,FALSE,
+B50461,B50461,新聞図書費,B5,Regular,account.data_account_type_expense,,FALSE,
+B50491,B50491,租税公課,B5,Regular,account.data_account_type_expense,,FALSE,
+B50511,B50511,水道光熱費,B5,Regular,account.data_account_type_expense,,FALSE,
+B50521,B50521,会議費,B5,Regular,account.data_account_type_expense,,FALSE,
+B50551,B50551,雑費,B5,Regular,account.data_account_type_expense,,FALSE,
+B61,B61,営業外収益,B,View,account.account_type_income_view1,,FALSE,
+B61101,B61101,受取利息,B61,Regular,account.data_account_type_income,,FALSE,
+B61201,B61201,受取配当金,B61,Regular,account.data_account_type_income,,FALSE,
+B61401,B61401,未実現為替差益,B61,Regular,account.data_account_type_income,,FALSE,
+B61501,B61501,実現為替差益,B61,Regular,account.data_account_type_income,,FALSE,
+B61601,B61601,投資有価証券売却益(外益),B61,Regular,account.data_account_type_income,,FALSE,
+B61602,B61602,投資有価証券評価益(外益),B61,Regular,account.data_account_type_income,,FALSE,
+B61801,B61801,雑収入,B61,Regular,account.data_account_type_income,,FALSE,
+B62,B62,営業外費用,B,View,account.account_type_expense_view1,,FALSE,
+B62101,B62101,支払利息,B62,Regular,account.data_account_type_expense,,FALSE,
+B62201,B62201,創立費償却,B62,Regular,account.data_account_type_expense,,FALSE,
+B62401,B62401,未実現為替差損,B62,Regular,account.data_account_type_expense,,FALSE,
+B62402,B62402,実現為替差損,B62,Regular,account.data_account_type_expense,,FALSE,
+B62601,B62601,投資有価証券売却損(外損),B62,Regular,account.data_account_type_expense,,FALSE,
+B62602,B62602,投資有価証券評価損(外損),B62,Regular,account.data_account_type_expense,,FALSE,
+B62801,B62801,雑費用,B62,Regular,account.data_account_type_expense,,FALSE,
+B71,B71,特別利益,B,View,account.account_type_income_view1,,FALSE,
+B71101,B71101,固定資産売却益,B71,Regular,account.data_account_type_income,,FALSE,
+B71102,B71102,固定資産評価益,B71,Regular,account.data_account_type_income,,FALSE,
+B71201,B71201,投資有価証券売却益(特益),B71,Regular,account.data_account_type_income,,FALSE,
+B71202,B71202,投資有価証券評価益(特益),B71,Regular,account.data_account_type_income,,FALSE,
+B72,B72,特別損失,B,View,account.account_type_expense_view1,,FALSE,
+B72101,B72101,固定資産売却損,B72,Regular,account.data_account_type_expense,,FALSE,
+B72102,B72102,固定資産評価損,B72,Regular,account.data_account_type_expense,,FALSE,
+B72201,B72201,投資有価証券売却損(特損),B72,Regular,account.data_account_type_expense,,FALSE,
+B72202,B72202,投資有価証券評価損(特損),B72,Regular,account.data_account_type_expense,,FALSE,
+B8,B8,法人税等,B,View,account.account_type_expense_view1,,FALSE,
+B80101,B80101,法人税、住民税及び事業税,B8,Regular,account.data_account_type_expense,,FALSE,
+B90001,B90001,期間利益,B,Regular,account.data_account_type_expense,,FALSE,
diff --git a/addons/l10n_jp/data/account.chart.template.csv b/addons/l10n_jp/data/account.chart.template.csv
new file mode 100644
index 00000000000..8488d0445aa
--- /dev/null
+++ b/addons/l10n_jp/data/account.chart.template.csv
@@ -0,0 +1,2 @@
+id,name,account_root_id/id,tax_code_root_id/id,bank_account_view_id/id,property_account_receivable/id,property_account_payable/id,property_account_expense_categ/id,property_account_income_categ/id,property_account_expense/id,property_account_income/id
+l10n_jp1,日本勘定設定テンプレート,COM0,jpt_root,A111,A11211,A21211,A21219,B41001,A21219,B41001
diff --git a/addons/l10n_jp/data/account.fiscal.position.template.csv b/addons/l10n_jp/data/account.fiscal.position.template.csv
new file mode 100644
index 00000000000..07e7b11531d
--- /dev/null
+++ b/addons/l10n_jp/data/account.fiscal.position.template.csv
@@ -0,0 +1,7 @@
+id,chart_template_id/id,name,note,tax_ids/tax_src_id/id,tax_ids/tax_dest_id/id
+fiscal_position_tax_inclusive_template,l10n_jp1,内税,,tax_in_e,tax_in_i
+,,,,tax_out_e,tax_out_i
+fiscal_position_tax_exclusive_template,l10n_jp1,外税,,tax_in_i,tax_in_e
+,,,,tax_out_i,tax_out_e
+fiscal_position_tax_exempt_template,l10n_jp1,海外取引先,,tax_in_e,tax_in_x
+,,,,tax_out_e,tax_out_im
diff --git a/addons/l10n_jp/data/account.tax.code.template.csv b/addons/l10n_jp/data/account.tax.code.template.csv
new file mode 100644
index 00000000000..71bf73cff47
--- /dev/null
+++ b/addons/l10n_jp/data/account.tax.code.template.csv
@@ -0,0 +1,20 @@
+id,name,parent_id/id,code,notprintable,sign
+jpt_root,日本税チャート,,,FALSE,1
+jpt_tax_code_balance_net,支払対象税額,jpt_root,,FALSE,1
+jpt_tax_code_input,仮受税額,jpt_tax_code_balance_net,,FALSE,-1
+jpt_tax_code_input_A,仮受消費税(8%),jpt_tax_code_input,,FALSE,1
+jpt_tax_code_input_X,免税,jpt_tax_code_input,,FALSE,1
+jpt_tax_code_input_O,不課税,jpt_tax_code_input,,FALSE,1
+jpt_tax_code_output,仮払税額,jpt_tax_code_balance_net,,FALSE,1
+jpt_tax_code_output_A,仮払消費税(8%),jpt_tax_code_output,,FALSE,1
+jpt_tax_code_output_I,輸入,jpt_tax_code_output,,FALSE,1
+jpt_tax_code_output_O,不課税,jpt_tax_code_output,,FALSE,1
+jpt_tax_code_base_net,税計算基準額,jpt_root,,FALSE,1
+jpt_tax_code_base_sales,販売基準額,jpt_tax_code_base_net,,FALSE,1
+jpt_tax_code_sales_A,課税対象売上(8%),jpt_tax_code_base_sales,,FALSE,1
+jpt_tax_code_sales_X,免税売上,jpt_tax_code_base_sales,,FALSE,1
+jpt_tax_code_sales_O,不課税売上,jpt_tax_code_base_sales,,FALSE,1
+jpt_tax_code_base_purchases,購買基準額,jpt_tax_code_base_net,,FALSE,1
+jpt_tax_code_purch_A,課税対象仕入(8%),jpt_tax_code_base_purchases,,FALSE,1
+jpt_tax_code_purch_I,輸入仕入,jpt_tax_code_base_purchases,,FALSE,1
+jpt_tax_code_purch_O,不課税仕入,jpt_tax_code_base_purchases,,FALSE,1
diff --git a/addons/l10n_jp/data/account.tax.template.csv b/addons/l10n_jp/data/account.tax.template.csv
new file mode 100644
index 00000000000..d029c0d41e0
--- /dev/null
+++ b/addons/l10n_jp/data/account.tax.template.csv
@@ -0,0 +1,9 @@
+id,name,description,chart_template_id/id,type_tax_use,type,amount,price_include,sequence,include_base_amount,account_collected_id/id,base_code_id/id,base_sign,tax_code_id/id,tax_sign,account_paid_id/id,ref_base_code_id/id,ref_base_sign,ref_tax_code_id/id,ref_tax_sign,child_depend
+tax_in_e,仮受消費税(外),,l10n_jp1,sale,percent,0.08,FALSE,1,FALSE,A11807,jpt_tax_code_sales_A,1,jpt_tax_code_input_A,1,A11807,jpt_tax_code_sales_A,1,jpt_tax_code_input_A,1,FALSE
+tax_in_i,仮受消費税(内),,l10n_jp1,sale,percent,0.08,TRUE,1,FALSE,A11807,jpt_tax_code_sales_A,1,jpt_tax_code_input_A,1,A11807,jpt_tax_code_sales_A,1,jpt_tax_code_input_A,1,FALSE
+tax_in_x,輸出免税,,l10n_jp1,sale,percent,,FALSE,1,FALSE,,jpt_tax_code_sales_X,1,jpt_tax_code_input_X,1,,jpt_tax_code_sales_X,1,jpt_tax_code_input_X,1,FALSE
+tax_in_o,非課税販売,,l10n_jp1,sale,percent,,FALSE,1,FALSE,,jpt_tax_code_sales_O,1,jpt_tax_code_input_O,1,,jpt_tax_code_sales_O,1,jpt_tax_code_input_O,1,FALSE
+tax_out_e,仮払消費税(外),,l10n_jp1,purchase,percent,0.08,FALSE,1,FALSE,A21807,jpt_tax_code_purch_A,1,jpt_tax_code_output_A,1,A21807,jpt_tax_code_purch_A,1,jpt_tax_code_output_A,1,FALSE
+tax_out_i,仮払消費税(内),,l10n_jp1,purchase,percent,0.08,TRUE,1,FALSE,A21807,jpt_tax_code_purch_A,1,jpt_tax_code_output_A,1,A21807,jpt_tax_code_purch_A,1,jpt_tax_code_output_A,1,FALSE
+tax_out_im,海外仕入,,l10n_jp1,purchase,percent,,FALSE,1,FALSE,,jpt_tax_code_purch_I,1,jpt_tax_code_output_I,1,,jpt_tax_code_purch_I,1,jpt_tax_code_output_I,1,FALSE
+tax_out_o,非課税購買,,l10n_jp1,purchase,percent,,FALSE,1,FALSE,,jpt_tax_code_purch_O,1,jpt_tax_code_output_O,1,,jpt_tax_code_purch_O,1,jpt_tax_code_output_O,1,FALSE
diff --git a/addons/l10n_jp/static/description/icon.png b/addons/l10n_jp/static/description/icon.png
new file mode 100644
index 00000000000..96bc158944e
--- /dev/null
+++ b/addons/l10n_jp/static/description/icon.png
Binary files differ
diff --git a/addons/mail/models/mail_alias.py b/addons/mail/models/mail_alias.py
index 3136174a657..56844ad5243 100644
--- a/addons/mail/models/mail_alias.py
+++ b/addons/mail/models/mail_alias.py
@@ -8,6 +8,7 @@ from openerp import _, api, fields, models, SUPERUSER_ID
from openerp.exceptions import UserError
from openerp.modules.registry import RegistryManager
from openerp.tools import ustr
+from openerp.tools.safe_eval import safe_eval as eval
_logger = logging.getLogger(__name__)
diff --git a/addons/mail/models/mail_followers.py b/addons/mail/models/mail_followers.py
index 441caadbccc..db1c20e8f00 100644
--- a/addons/mail/models/mail_followers.py
+++ b/addons/mail/models/mail_followers.py
@@ -181,6 +181,7 @@ class Notification(models.Model):
mail_values = {
'mail_message_id': message.id,
'auto_delete': self._context.get('mail_auto_delete', True),
+ 'mail_server_id': self._context.get('mail_server_id', False),
'body_html': body_html,
'recipient_ids': [(4, id) for id in chunk],
'references': references,
diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py
index bb010372025..7e191f9517d 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -17,7 +17,9 @@ def decode(text):
# TDE proposal: move to tools ?
if text:
text = decode_header(text.replace('\r', ''))
- return ''.join([tools.ustr(x[0], x[1]) for x in text])
+ # The joining space will not be needed as of Python 3.3
+ # See https://hg.python.org/cpython/rev/8c03fe231877
+ return ' '.join([tools.ustr(x[0], x[1]) for x in text])
class Message(models.Model):
diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js
index 4872200ef15..ab3ddf4f65c 100644
--- a/addons/mail/static/src/js/mail.js
+++ b/addons/mail/static/src/js/mail.js
@@ -49,7 +49,7 @@ var TimelineRecordThread = form_common.AbstractField.extend ({
'readonly': this.node.attrs.readonly || false,
'compose_placeholder' : this.node.attrs.placeholder || false,
'display_log_button' : this.options.display_log_button || true,
- 'show_compose_message': this.view.is_action_enabled('edit') || false,
+ 'show_compose_message': true,
'show_link': this.parent.is_action_enabled('edit') || true,
}, this.node.params);
},
@@ -1434,7 +1434,6 @@ var ComposeMessage = Attachment.extend ({
}
}
}
-
this.$(".o_timeline_msg_attachment_list").html(this.display_attachments(this));
var $input = this.$('input.oe_form_binary_file');
diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py
index 83af81104ec..2a8957fa35f 100644
--- a/addons/mail/wizard/mail_compose_message.py
+++ b/addons/mail/wizard/mail_compose_message.py
@@ -216,7 +216,7 @@ class MailComposer(models.TransientModel):
if wizard.template_id:
# template user_signature is added when generating body_html
# mass mailing: use template auto_delete value -> note, for emails mass mailing only
- Mail = Mail.with_context(mail_notify_user_signature=False, mail_auto_delete=wizard.template_id.auto_delete)
+ Mail = Mail.with_context(mail_notify_user_signature=False, mail_auto_delete=wizard.template_id.auto_delete, mail_server_id=wizard.template_id.mail_server_id.id)
if wizard.attachment_ids and wizard.composition_mode != 'mass_mail' and wizard.template_id:
new_attachment_ids = []
for attachment in wizard.attachment_ids:
@@ -285,7 +285,7 @@ class MailComposer(models.TransientModel):
# static wizard (mail.message) values
mail_values = {
'subject': self.subject,
- 'body': self.body,
+ 'body': self.body or '',
'parent_id': self.parent_id and self.parent_id.id,
'partner_ids': [partner.id for partner in self.partner_ids],
'attachment_ids': [attach.id for attach in self.attachment_ids],
diff --git a/addons/mass_mailing/models/mass_mailing_stats.py b/addons/mass_mailing/models/mass_mailing_stats.py
index 7b929f5f24f..2136110ae14 100644
--- a/addons/mass_mailing/models/mass_mailing_stats.py
+++ b/addons/mass_mailing/models/mass_mailing_stats.py
@@ -19,9 +19,7 @@
#
##############################################################################
-import openerp
from openerp.osv import fields, osv
-from openerp import models, api, _
class MailMailStats(osv.Model):
@@ -35,28 +33,27 @@ class MailMailStats(osv.Model):
_rec_name = 'message_id'
_order = 'message_id'
- def _compute_state(self, cr, uid, stat_ids, field_names, arg, context=None):
- stats = self.browse(cr, uid, stat_ids, context=context)
- res = dict([(stat, False) for stat in stat_ids])
+ def _compute_state(self, cr, uid, ids, field_names, arg, context=None):
+ res = dict((i, {'state': 'outgoing', 'state_update': fields.datetime.now()}) for i in ids)
- self.write(cr, uid, stat_ids, {'state_update': fields.datetime.now()}, context=context)
-
- for stat in stats:
+ for stat in self.browse(cr, uid, ids, context=context):
if stat.exception:
- res[stat.id] = 'exception'
+ res[stat.id]['state'] = 'exception'
if stat.sent:
- res[stat.id] = 'sent'
+ res[stat.id]['state'] = 'sent'
if stat.opened:
- res[stat.id] = 'opened'
+ res[stat.id]['state'] = 'opened'
if stat.replied:
- res[stat.id] = 'replied'
+ res[stat.id]['state'] = 'replied'
if stat.bounced:
- res[stat.id] = 'bounced'
+ res[stat.id]['state'] = 'bounced'
return res
+ __store = {_name: ((lambda s, c, u, i, t: i), ['exception', 'sent', 'opened', 'replied', 'bounced'], 10)}
+
_columns = {
- 'mail_mail_id': fields.many2one('mail.mail', 'Mail', ondelete='set null'),
+ 'mail_mail_id': fields.many2one('mail.mail', 'Mail', ondelete='set null', select=True),
'mail_mail_id_int': fields.integer(
'Mail ID (tech)',
help='ID of the related mail_mail. This field is an integer field because'
@@ -86,10 +83,17 @@ class MailMailStats(osv.Model):
'replied': fields.datetime('Replied', help='Date when this email has been replied for the first time.'),
'bounced': fields.datetime('Bounced', help='Date when this email has bounced.'),
'links_click_ids': fields.one2many('website.links.click', 'mail_stat_id', 'Links click'),
- 'state': fields.function(_compute_state, string='State', type="selection",
- selection=[('outgoing', 'Outgoing'), ('exception', 'Exception'), ('sent', 'Sent'), ('opened', 'Opened'), ('replied', 'Replied'), ('bounced', 'Bounced')],
- store={'mail.mail.statistics': (lambda self, cr, uid, ids, context=None: ids, ['exception', 'sent', 'opened', 'replied', 'bounced'], 10)}),
- 'state_update': fields.datetime('State Update', help='Last state update of the mail'),
+ 'state': fields.function(_compute_state, string='State', type="selection", multi="state",
+ selection=[('outgoing', 'Outgoing'),
+ ('exception', 'Exception'),
+ ('sent', 'Sent'),
+ ('opened', 'Opened'),
+ ('replied', 'Replied'),
+ ('bounced', 'Bounced')],
+ store=__store),
+ 'state_update': fields.function(_compute_state, string='State Update', type='datetime',
+ multi='state', help='Last state update of the mail',
+ store=__store),
}
_defaults = {
diff --git a/addons/mass_mailing/views/mass_mailing.xml b/addons/mass_mailing/views/mass_mailing.xml
index dbda4773f07..6b8f23f9116 100644
--- a/addons/mass_mailing/views/mass_mailing.xml
+++ b/addons/mass_mailing/views/mass_mailing.xml
@@ -271,7 +271,7 @@
</record>
<record model="ir.actions.act_window" id="action_view_mass_mailing_lists">
- <field name="name">Contact Lists</field>
+ <field name="name">Mailing Lists</field>
<field name="res_model">mail.mass_mailing.list</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
diff --git a/addons/membership/membership_view.xml b/addons/membership/membership_view.xml
index c06e65f0c3d..51295ca03c7 100644
--- a/addons/membership/membership_view.xml
+++ b/addons/membership/membership_view.xml
@@ -262,7 +262,7 @@
<field name="date"/>
<field name="member_price"/>
<field name="membership_id"/>
- <field name="account_invoice_id"/>
+ <field name="account_invoice_id" context="{'form_view_ref': 'account.invoice_form'}"/>
<field name="state" colspan="4"/>
</form>
</field>
diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py
index 504c43e065d..1817d212669 100644
--- a/addons/mrp/mrp.py
+++ b/addons/mrp/mrp.py
@@ -25,7 +25,7 @@ from collections import OrderedDict
import openerp.addons.decimal_precision as dp
from openerp.osv import fields, osv
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
-from openerp.tools import float_compare
+from openerp.tools import float_compare, float_is_zero
from openerp.tools.translate import _
from openerp import tools, SUPERUSER_ID
from openerp.addons.product import _common
@@ -215,6 +215,8 @@ class mrp_bom(osv.osv):
@param properties: List of related properties.
@return: False or BoM id.
"""
+ if not context:
+ context = {}
if properties is None:
properties = []
if product_id:
@@ -232,19 +234,21 @@ class mrp_bom(osv.osv):
else:
# neither product nor template, makes no sense to search
return False
+ if context.get('company_id'):
+ domain = domain + [('company_id', '=', context['company_id'])]
domain = domain + [ '|', ('date_start', '=', False), ('date_start', '<=', time.strftime(DEFAULT_SERVER_DATE_FORMAT)),
'|', ('date_stop', '=', False), ('date_stop', '>=', time.strftime(DEFAULT_SERVER_DATE_FORMAT))]
# order to prioritize bom with product_id over the one without
- ids = self.search(cr, uid, domain, order='product_id', context=context)
+ ids = self.search(cr, uid, domain, order='sequence, product_id', context=context)
# Search a BoM which has all properties specified, or if you can not find one, you could
- # pass a BoM without any properties
+ # pass a BoM without any properties with the smallest sequence
bom_empty_prop = False
for bom in self.pool.get('mrp.bom').browse(cr, uid, ids, context=context):
if not set(map(int, bom.property_ids or [])) - set(properties or []):
- if properties and not bom.property_ids:
- bom_empty_prop = bom.id
- else:
+ if not properties or bom.property_ids:
return bom.id
+ elif not bom_empty_prop:
+ bom_empty_prop = bom.id
return bom_empty_prop
def _bom_explode(self, cr, uid, bom, product, factor, properties=None, level=0, routing_id=False, previous_products=None, master_bom=None, context=None):
@@ -300,6 +304,9 @@ class mrp_bom(osv.osv):
if not product or (set(map(int,bom_line_id.attribute_value_ids or [])) - set(map(int,product.attribute_value_ids))):
continue
+ if set(map(int, bom_line_id.property_ids or [])) - set(properties or []):
+ continue
+
if previous_products and bom_line_id.product_id.product_tmpl_id.id in previous_products:
raise UserError(_('BoM "%s" contains a BoM line with a product recursion: "%s".') % (master_bom.name,bom_line_id.product_id.name_get()[0][1]))
@@ -421,6 +428,7 @@ class mrp_bom_line(osv.osv):
'product_rounding': lambda *a: 0.0,
'type': lambda *a: 'normal',
'product_uom': _get_uom_id,
+ 'sequence': 1,
}
_sql_constraints = [
('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \
@@ -602,7 +610,7 @@ class mrp_production(osv.osv):
'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
'product_qty': lambda *a: 1.0,
'user_id': lambda self, cr, uid, c: uid,
- 'name': lambda x, y, z, c: x.pool.get('ir.sequence').next_by_code(y, z, 'mrp.production') or '/',
+ 'name': lambda self, cr, uid, context: self.pool['ir.sequence'].next_by_code(cr, uid, 'mrp.production', context=context) or '/',
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.production', context=c),
'location_src_id': _src_id_default,
'location_dest_id': _dest_id_default
@@ -745,7 +753,7 @@ class mrp_production(osv.osv):
#reset workcenter_lines in production order
for line in results2:
line['production_id'] = production.id
- workcenter_line_obj.create(cr, uid, line)
+ workcenter_line_obj.create(cr, uid, line, context)
return results
def action_compute(self, cr, uid, ids, properties=None, context=None):
@@ -948,6 +956,7 @@ class mrp_production(osv.osv):
uom_obj = self.pool.get("product.uom")
production = self.browse(cr, uid, production_id, context=context)
production_qty_uom = uom_obj._compute_qty(cr, uid, production.product_uom.id, production_qty, production.product_id.uom_id.id)
+ precision = self.pool['decimal.precision'].precision_get(cr, uid, 'Product Unit of Measure')
main_production_move = False
if production_mode == 'consume_produce':
@@ -969,7 +978,8 @@ class mrp_production(osv.osv):
location_id=produce_product.location_id.id, restrict_lot_id=lot_id, context=context)
stock_mov_obj.write(cr, uid, new_moves, {'production_id': production_id}, context=context)
remaining_qty = subproduct_factor * production_qty_uom - qty
- if remaining_qty: # In case you need to make more than planned
+ if not float_is_zero(remaining_qty, precision_rounding=precision):
+ # In case you need to make more than planned
#consumed more in wizard than previously planned
extra_move_id = stock_mov_obj.copy(cr, uid, produce_product.id, default={'product_uom_qty': remaining_qty,
'production_id': production_id}, context=context)
@@ -999,7 +1009,7 @@ class mrp_production(osv.osv):
stock_mov_obj.action_consume(cr, uid, [raw_material_line.id], consumed_qty, raw_material_line.location_id.id,
restrict_lot_id=consume['lot_id'], consumed_for=main_production_move, context=context)
remaining_qty -= consumed_qty
- if remaining_qty:
+ if not float_is_zero(remaining_qty, precision_rounding=precision):
#consumed more in wizard than previously planned
product = self.pool.get('product.product').browse(cr, uid, consume['product_id'], context=context)
extra_move_id = self._make_consume_line_from_data(cr, uid, production, product, product.uom_id.id, remaining_qty, False, 0, context=context)
@@ -1242,6 +1252,8 @@ class mrp_production(osv.osv):
""" Confirms production order.
@return: Newly generated Shipment Id.
"""
+ user_lang = self.pool.get('res.users').browse(cr, uid, [uid]).partner_id.lang
+ context = dict(context, lang=user_lang)
uncompute_ids = filter(lambda x: x, [not x.product_lines and x.id or False for x in self.browse(cr, uid, ids, context=context)])
self.action_compute(cr, uid, uncompute_ids, context=context)
for production in self.browse(cr, uid, ids, context=context):
diff --git a/addons/mrp/mrp_demo.xml b/addons/mrp/mrp_demo.xml
index 6732a5c3447..77b7ad4f2f6 100644
--- a/addons/mrp/mrp_demo.xml
+++ b/addons/mrp/mrp_demo.xml
@@ -452,6 +452,25 @@
<field name="type">normal</field>
</record>
+ <record id="mrp_bom_property_0" model="mrp.bom">
+ <field name="name">PC Assemble + DDR 512MB</field>
+ <field name="product_tmpl_id" ref="product.product_product_3_product_template"/>
+ <field name="product_uom" ref="product.product_uom_unit"/>
+ <field name="sequence">5</field>
+ <field name="type">normal</field>
+ <field name="property_ids" eval="[(6,0,[ref('mrp_property_0')])]"/>
+ </record>
+
+ <record id="mrp_bom_property_line" model="mrp.bom">
+ <field name="name">RAM SR2 PLUS</field>
+ <field name="product_id" ref="product.product_product_14"/>
+ <field name="product_tmpl_id" ref="product.product_product_14_product_template"/>
+ <field name="product_uom" ref="product.product_uom_unit"/>
+ <field name="sequence">5</field>
+ <field name="type">phantom</field>
+ <field name="property_ids" eval="[(6,0,[ref('mrp_property_0')])]"/>
+ </record>
+
<record id="mrp_bom_line_17" model="mrp.bom.line">
<field name="product_id" ref="product.product_product_6"/>
<field name="product_qty">1</field>
@@ -501,7 +520,7 @@
<field name="name">PC Assemble + 512MB RAM</field>
<field name="product_tmpl_id" ref="product.product_product_3_product_template"/>
<field name="product_uom" ref="product.product_uom_unit"/>
- <field name="sequence">5</field>
+ <field name="sequence">4</field>
<field name="routing_id" ref="mrp_routing_2"/>
<field name="type">phantom</field>
</record>
@@ -639,6 +658,35 @@
<field name="bom_id" ref="mrp_bom_11"/>
</record>
+ <record id="mrp_bom_line_prop_1" model="mrp.bom.line">
+ <field name="product_id" ref="product.product_product_13"/>
+ <field name="product_qty">1</field>
+ <field name="product_uom" ref="product.product_uom_unit"/>
+ <field name="sequence">1</field>
+ <field name="type">normal</field>
+ <field name="property_ids" eval="[(6,0,[ref('mrp_property_8')])]"/>
+ <field name="bom_id" ref="mrp_bom_property_line"/>
+ </record>
+
+ <record id="mrp_bom_line_prop_2" model="mrp.bom.line">
+ <field name="product_id" ref="product.product_product_12"/>
+ <field name="product_qty">1</field>
+ <field name="product_uom" ref="product.product_uom_unit"/>
+ <field name="sequence">1</field>
+ <field name="type">normal</field>
+ <field name="property_ids" eval="[(6,0,[ref('mrp_property_0')])]"/>
+ <field name="bom_id" ref="mrp_bom_property_line"/>
+ </record>
+
+ <record id="mrp_bom_line_prop_3" model="mrp.bom.line">
+ <field name="product_id" ref="product.product_product_11"/>
+ <field name="product_qty">1</field>
+ <field name="product_uom" ref="product.product_uom_unit"/>
+ <field name="sequence">1</field>
+ <field name="type">normal</field>
+ <field name="bom_id" ref="mrp_bom_property_line"/>
+ </record>
+
<record id="mrp_production_1" model="mrp.production">
<field name="product_id" ref="product.product_product_3"/>
diff --git a/addons/mrp/mrp_view.xml b/addons/mrp/mrp_view.xml
index 168276eb2db..c4bb0ba5655 100644
--- a/addons/mrp/mrp_view.xml
+++ b/addons/mrp/mrp_view.xml
@@ -400,6 +400,7 @@
<field name="date_start"/>
<field name="date_stop"/>
<field name="attribute_value_ids" widget="many2many_tags" domain="[('product_ids.product_tmpl_id', '=', parent.product_tmpl_id)]"/>
+ <field name="property_ids" widget="many2many_tags"/>
</tree>
</field>
</page>
diff --git a/addons/mrp/procurement.py b/addons/mrp/procurement.py
index 7969a7f724a..a0a9863491e 100644
--- a/addons/mrp/procurement.py
+++ b/addons/mrp/procurement.py
@@ -87,7 +87,7 @@ class procurement_order(osv.osv):
else:
properties = [x.id for x in procurement.property_ids]
bom_id = bom_obj._bom_find(cr, uid, product_id=procurement.product_id.id,
- properties=properties, context=context)
+ properties=properties, context=dict(context, company_id=procurement.company_id.id))
bom = bom_obj.browse(cr, uid, bom_id, context=context)
routing_id = bom.routing_id.id
return {
@@ -117,7 +117,7 @@ class procurement_order(osv.osv):
if self.check_bom_exists(cr, uid, [procurement.id], context=context):
#create the MO as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example)
vals = self._prepare_mo_vals(cr, uid, procurement, context=context)
- produce_id = production_obj.create(cr, SUPERUSER_ID, vals, context=context)
+ produce_id = production_obj.create(cr, SUPERUSER_ID, vals, context=dict(context, force_company=procurement.company_id.id))
res[procurement.id] = produce_id
self.write(cr, uid, [procurement.id], {'production_id': produce_id})
self.production_order_create_note(cr, uid, procurement, context=context)
diff --git a/addons/mrp/stock.py b/addons/mrp/stock.py
index 0aa91010c14..77e3b0d27b6 100644
--- a/addons/mrp/stock.py
+++ b/addons/mrp/stock.py
@@ -44,6 +44,7 @@ class StockMove(osv.osv):
if move.raw_material_production_id and move.location_dest_id.usage == 'production' and move.raw_material_production_id.product_id.track_production and not move.consumed_for:
raise UserError(_("Because the product %s requires it, you must assign a serial number to your raw material %s to proceed further in your production. Please use the 'Produce' button to do so.") % (move.raw_material_production_id.product_id.name, move.product_id.name))
+ # TODO master: remove me, no longer used
def _check_phantom_bom(self, cr, uid, move, context=None):
"""check if product associated to move has a phantom bom
return list of ids of mrp.bom for that product """
@@ -59,24 +60,28 @@ class StockMove(osv.osv):
('company_id', '=', user_company)]
return self.pool.get('mrp.bom').search(cr, SUPERUSER_ID, domain, context=context)
+
def _action_explode(self, cr, uid, move, context=None):
""" Explodes pickings.
@param move: Stock moves
@return: True
"""
+ if context is None:
+ context = {}
bom_obj = self.pool.get('mrp.bom')
move_obj = self.pool.get('stock.move')
prod_obj = self.pool.get("product.product")
proc_obj = self.pool.get("procurement.order")
uom_obj = self.pool.get("product.uom")
to_explode_again_ids = []
- processed_ids = []
- bis = self._check_phantom_bom(cr, uid, move, context=context)
- if bis:
- bom_point = bom_obj.browse(cr, SUPERUSER_ID, bis[0], context=context)
+ property_ids = context.get('property_ids') or []
+ bis = bom_obj._bom_find(cr, SUPERUSER_ID, product_id=move.product_id.id, properties=property_ids)
+ bom_point = bom_obj.browse(cr, SUPERUSER_ID, bis, context=context)
+ if bis and bom_point.type == 'phantom':
+ processed_ids = []
factor = uom_obj._compute_qty(cr, SUPERUSER_ID, move.product_uom.id, move.product_uom_qty, bom_point.product_uom.id) / bom_point.product_qty
- res = bom_obj._bom_explode(cr, SUPERUSER_ID, bom_point, move.product_id, factor, [], context=context)
-
+ res = bom_obj._bom_explode(cr, SUPERUSER_ID, bom_point, move.product_id, factor, property_ids, context=context)
+
for line in res[0]:
product = prod_obj.browse(cr, uid, line['product_id'], context=context)
if product.type != 'service':
@@ -115,7 +120,6 @@ class StockMove(osv.osv):
else:
proc = proc_obj.create(cr, uid, valdef, context=context)
proc_obj.run(cr, uid, [proc], context=context) #could be omitted
-
#check if new moves needs to be exploded
if to_explode_again_ids:
@@ -134,8 +138,10 @@ class StockMove(osv.osv):
#delete the move with original product which is not relevant anymore
move_obj.unlink(cr, SUPERUSER_ID, [move.id], context=context)
- #return list of newly created move or the move id otherwise, unless there is no move anymore
- return processed_ids or (not bis and [move.id]) or []
+ #return list of newly created move
+ return processed_ids
+
+ return [move.id]
def action_confirm(self, cr, uid, ids, context=None):
move_ids = []
diff --git a/addons/mrp_operations/mrp_operations.py b/addons/mrp_operations/mrp_operations.py
index 86a50626558..d86c8e6cd63 100644
--- a/addons/mrp_operations/mrp_operations.py
+++ b/addons/mrp_operations/mrp_operations.py
@@ -337,7 +337,7 @@ class mrp_production(osv.osv):
for po in self.browse(cr, uid, ids, context=context):
direction[po.id] = cmp(po.date_start, vals.get('date_start', False))
result = super(mrp_production, self).write(cr, uid, ids, vals, context=context)
- if (vals.get('workcenter_lines', False) or vals.get('date_start', False)) and update:
+ if (vals.get('workcenter_lines', False) or vals.get('date_start', False) or vals.get('date_planned', False)) and update:
self._compute_planned_workcenter(cr, uid, ids, context=context, mini=mini)
for d in direction:
if direction[d] == 1:
diff --git a/addons/mrp_repair/mrp_repair.py b/addons/mrp_repair/mrp_repair.py
index 74ba8806528..73380dcfda7 100644
--- a/addons/mrp_repair/mrp_repair.py
+++ b/addons/mrp_repair/mrp_repair.py
@@ -409,6 +409,7 @@ class mrp_repair(osv.osv):
'price_subtotal': fee.product_uom_qty * fee.price_unit
})
repair_fee_obj.write(cr, uid, [fee.id], {'invoiced': True, 'invoice_line_id': invoice_fee_id})
+ #inv_obj.button_reset_taxes(cr, uid, inv_id, context=context)
res[repair.id] = inv_id
return res
@@ -559,11 +560,16 @@ class mrp_repair_line(osv.osv, ProductChangeMixin):
@return: Dictionary of values.
"""
res = {}
- cur_obj = self.pool.get('res.currency')
+ tax_obj = self.pool.get('account.tax')
+ # cur_obj = self.pool.get('res.currency')
for line in self.browse(cr, uid, ids, context=context):
- res[line.id] = line.to_invoice and line.price_unit * line.product_uom_qty or 0
- cur = line.repair_id.pricelist_id.currency_id
- res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
+ if line.to_invoice:
+ cur = line.repair_id.pricelist_id.currency_id
+ taxes = tax_obj.compute_all(cr, uid, line.tax_id, line.price_unit, cur.id, line.product_uom_qty, line.product_id, line.repair_id.partner_id)
+ #res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
+ res[line.id] = taxes['total_included']
+ else:
+ res[line.id] = 0
return res
_columns = {
@@ -650,11 +656,15 @@ class mrp_repair_fee(osv.osv, ProductChangeMixin):
@return: Dictionary of values.
"""
res = {}
+ tax_obj = self.pool.get('account.tax')
cur_obj = self.pool.get('res.currency')
for line in self.browse(cr, uid, ids, context=context):
- res[line.id] = line.to_invoice and line.price_unit * line.product_uom_qty or 0
- cur = line.repair_id.pricelist_id.currency_id
- res[line.id] = cur_obj.round(cr, uid, cur, res[line.id])
+ if line.to_invoice:
+ cur = line.repair_id.pricelist_id.currency_id
+ taxes = tax_obj.compute_all(cr, uid, line.tax_id, line.price_unit, cur.id, line.product_uom_qty, line.product_id, line.repair_id.partner_id)
+ res[line.id] = taxes['total_included']
+ else:
+ res[line.id] = 0
return res
_columns = {
diff --git a/addons/note/note.py b/addons/note/note.py
index 2db3173a907..fa69b96050e 100644
--- a/addons/note/note.py
+++ b/addons/note/note.py
@@ -168,7 +168,7 @@ class note_note(osv.osv):
return result
else:
- return super(note_note, self).read_group(self, cr, uid, domain, fields, groupby,
+ return super(note_note, self).read_group(cr, uid, domain, fields, groupby,
offset=offset, limit=limit, context=context, orderby=orderby,lazy=lazy)
diff --git a/addons/payment/models/res_config.py b/addons/payment/models/res_config.py
index b64b698c83d..888e0a89fda 100644
--- a/addons/payment/models/res_config.py
+++ b/addons/payment/models/res_config.py
@@ -19,7 +19,7 @@ class AccountPaymentConfig(osv.TransientModel):
'module_payment_buckaroo': fields.boolean(
'Manage Payments Using Buckaroo',
help='-It installs the module payment_buckaroo.'),
- 'module_payment_authorize': fields.boolean(
+ 'module_payment_authorize': fields.dummy(
'Manage Payments Using Authorize.Net',
help='-It installs the module payment_authorize.'),
}
diff --git a/addons/payment/views/res_config_view.xml b/addons/payment/views/res_config_view.xml
index 8f5e5f31321..e4e6fc96652 100644
--- a/addons/payment/views/res_config_view.xml
+++ b/addons/payment/views/res_config_view.xml
@@ -24,10 +24,6 @@
<field name="module_payment_buckaroo" class="oe_inline"/>
<label for="module_payment_buckaroo"/>
</div>
- <div>
- <field name="module_payment_authorize" class="oe_inline"/>
- <label for="module_payment_authorize"/>
- </div>
</xpath>
</field>
</record>
diff --git a/addons/payment_adyen/controllers/main.py b/addons/payment_adyen/controllers/main.py
index e6e74eba9a7..63b8c9a8fb5 100644
--- a/addons/payment_adyen/controllers/main.py
+++ b/addons/payment_adyen/controllers/main.py
@@ -20,11 +20,10 @@ class AdyenController(http.Controller):
@http.route([
'/payment/adyen/return/',
], type='http', auth='none')
- def adyen_return(self, pspReference, **post):
- """ Paypal IPN."""
- post["pspReference"] = pspReference
+ def adyen_return(self, **post):
_logger.info('Beginning Adyen form_feedback with post data %s', pprint.pformat(post)) # debug
- request.registry['payment.transaction'].form_feedback(request.cr, SUPERUSER_ID, post, 'adyen', context=request.context)
+ if post.get('authResult') not in ['CANCELLED']:
+ request.registry['payment.transaction'].form_feedback(request.cr, SUPERUSER_ID, post, 'adyen', context=request.context)
return_url = post.pop('return_url', '')
if not return_url:
custom = json.loads(post.pop('merchantReturnData', '{}'))
diff --git a/addons/payment_adyen/models/adyen.py b/addons/payment_adyen/models/adyen.py
index 7a995809222..6539ebb5ae0 100644
--- a/addons/payment_adyen/models/adyen.py
+++ b/addons/payment_adyen/models/adyen.py
@@ -28,7 +28,7 @@ class AcquirerAdyen(osv.Model):
- yhpp: hosted payment page: pay.shtml for single, select.shtml for multiple
"""
return {
- 'adyen_form_url': 'https://%s.adyen.com/hpp/pay.shtml' % environment,
+ 'adyen_form_url': 'https://%s.adyen.com/hpp/pay.shtml' % 'live' if environment == 'prod' else environment,
}
def _get_providers(self, cr, uid, context=None):
@@ -172,7 +172,7 @@ class TxAdyen(osv.Model):
})
return True
else:
- error = _('Paypal: feedback error')
+ error = _('Adyen: feedback error')
_logger.info(error)
tx.write({
'state': 'error',
diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml
index 98f33118065..9c82d247414 100644
--- a/addons/point_of_sale/point_of_sale_view.xml
+++ b/addons/point_of_sale/point_of_sale_view.xml
@@ -904,7 +904,7 @@
<field name="name" />
<filter string="Open" domain="[('state', '=', 'opened')]" />
<separator/>
- <filter string="Today" domain="[('start_at', '>=', datetime.datetime.now().replace(hour=0, minute=0, second=0))]" />
+ <filter string="Today" domain="[('start_at', '>=', datetime.datetime.combine(context_today(), datetime.time(0,0,0)))]" />
<field name="config_id" />
<field name="user_id" />
<group expand="0" string="Group By">
diff --git a/addons/point_of_sale/report/pos_details.py b/addons/point_of_sale/report/pos_details.py
index 8ff15c9036c..4d9ce4372fa 100644
--- a/addons/point_of_sale/report/pos_details.py
+++ b/addons/point_of_sale/report/pos_details.py
@@ -129,13 +129,7 @@ class pos_details(report_sxw.rml_parse):
return {}
def _total_of_the_day(self, objects):
- if self.total:
- if self.total == self.total_invoiced:
- return self.total
- else:
- return ((self.total or 0.00) - (self.total_invoiced or 0.00))
- else:
- return False
+ return self.total or 0.00
def _sum_invoice(self, objects):
return reduce(lambda acc, obj:
diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js
index cfd842f86da..9a8df01feae 100644
--- a/addons/point_of_sale/static/src/js/db.js
+++ b/addons/point_of_sale/static/src/js/db.js
@@ -171,7 +171,7 @@ var PosDB = core.Class.extend({
for (var i = 0; i < packagings.length; i++) {
str += '|' + packagings[i].barcode;
}
- str = product.id + ':' + str.replace(':','') + '\n';
+ str = product.id + ':' + str.replace(/:/g,'') + '\n';
return str;
},
add_products: function(products){
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 511e0934970..a8560479c52 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -153,7 +153,7 @@ exports.PosModel = Backbone.Model.extend({
loaded: function(self,users){ self.user = users[0]; },
},{
model: 'res.company',
- fields: [ 'currency_id', 'email', 'website', 'company_registry', 'vat', 'name', 'phone', 'partner_id' , 'country_id'],
+ fields: [ 'currency_id', 'email', 'website', 'company_registry', 'vat', 'name', 'phone', 'partner_id' , 'country_id', 'tax_calculation_rounding_method'],
ids: function(self){ return [self.user.company_id[0]]; },
loaded: function(self,companies){ self.company = companies[0]; },
},{
@@ -1284,6 +1284,9 @@ exports.Orderline = Backbone.Model.extend({
var total_included = total_excluded;
var base = total_excluded;
var list_taxes = [];
+ if (this.pos.company.tax_calculation_rounding_method == "round_globally"){
+ currency_rounding = currency_rounding * 0.00001;
+ }
_(taxes).each(function(tax) {
if (tax.amount_type === 'group'){
var ret = self.compute_all(tax.children_tax_ids, price_unit, quantity, currency_rounding);
@@ -1811,34 +1814,34 @@ exports.Order = Backbone.Model.extend({
},
/* ---- Payment Status --- */
get_subtotal : function(){
- return this.orderlines.reduce((function(sum, orderLine){
+ return round_pr(this.orderlines.reduce((function(sum, orderLine){
return sum + orderLine.get_display_price();
- }), 0);
+ }), 0), this.pos.currency.rounding);
},
get_total_with_tax: function() {
- return this.orderlines.reduce((function(sum, orderLine) {
+ return round_pr(this.orderlines.reduce((function(sum, orderLine) {
return sum + orderLine.get_price_with_tax();
- }), 0);
+ }), 0), this.pos.currency.rounding);
},
get_total_without_tax: function() {
- return this.orderlines.reduce((function(sum, orderLine) {
+ return round_pr(this.orderlines.reduce((function(sum, orderLine) {
return sum + orderLine.get_price_without_tax();
- }), 0);
+ }), 0), this.pos.currency.rounding);
},
get_total_discount: function() {
- return this.orderlines.reduce((function(sum, orderLine) {
+ return round_pr(this.orderlines.reduce((function(sum, orderLine) {
return sum + (orderLine.get_unit_price() * (orderLine.get_discount()/100) * orderLine.get_quantity());
- }), 0);
+ }), 0), this.pos.currency.rounding);
},
get_total_tax: function() {
- return this.orderlines.reduce((function(sum, orderLine) {
+ return round_pr(this.orderlines.reduce((function(sum, orderLine) {
return sum + orderLine.get_tax();
- }), 0);
+ }), 0), this.pos.currency.rounding);
},
get_total_paid: function() {
- return this.paymentlines.reduce((function(sum, paymentLine) {
+ return round_pr(this.paymentlines.reduce((function(sum, paymentLine) {
return sum + paymentLine.get_amount();
- }), 0);
+ }), 0), this.pos.currency.rounding);
},
get_tax_details: function(){
var details = {};
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index b951ed30d8c..5ab69194a25 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -1480,6 +1480,7 @@ var PaymentScreenWidget = ScreenWidget.extend({
this.inputbuffer = "";
this.firstinput = true;
+ this.decimal_point = instance.web._t.database.parameters.decimal_point;
// This is a keydown handler that prevents backspace from
// doing a back navigation
@@ -1545,7 +1546,14 @@ var PaymentScreenWidget = ScreenWidget.extend({
this.inputbuffer = newbuf;
var order = this.pos.get_order();
if (order.selected_paymentline) {
- order.selected_paymentline.set_amount(parseFloat(this.inputbuffer));
+ var amount;
+ try{
+ amount = instance.web.parse_value(this.inputbuffer, {type: "float"});
+ }
+ catch(e){
+ amount = 0;
+ }
+ order.selected_paymentline.set_amount(amount);
this.order_changes();
this.render_paymentlines();
this.$('.paymentline.selected .edit').text(this.inputbuffer);
diff --git a/addons/point_of_sale/static/src/js/widget_base.js b/addons/point_of_sale/static/src/js/widget_base.js
index 7c885989f2b..f051695df96 100644
--- a/addons/point_of_sale/static/src/js/widget_base.js
+++ b/addons/point_of_sale/static/src/js/widget_base.js
@@ -50,6 +50,7 @@ var PosBaseWidget = Widget.extend({
if (typeof amount === 'number') {
amount = round_di(amount,decimals).toFixed(decimals);
+ amount = openerp.instances[this.session.name].web.format_value(parseFloat(amount), { type : 'float' });
}
return amount;
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index da039276c3e..79eacc551d4 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -323,7 +323,7 @@
<select class='detail client-address-country' name='country_id'>
<option value=''>None</option>
<t t-foreach='widget.pos.countries' t-as='country'>
- <option t-att-value='country.id' t-att-selected="partner_country_id ? ((country.id === partner.country_id[0]) ? true : undefined) : undefined">
+ <option t-att-value='country.id' t-att-selected="partner.country_id ? ((country.id === partner.country_id[0]) ? true : undefined) : undefined">
<t t-esc='country.name'/>
</option>
</t>
@@ -1189,7 +1189,10 @@
<div class='paymentline-name'>
<t t-esc="line.name"/>
</div>
- <input class='paymentline-input' type="number" step="0.01" t-att-value="line.get_amount_str()" />
+ <input class='paymentline-input'
+ t-att-type="widget.decimal_point === '.' ? 'number' : 'text'"
+ t-attf-pattern="[0-9]+([\\#{widget.decimal_point || '.' }][0-9]+)?"
+ step="0.01" t-att-value="line.get_amount_str()" />
<span class='paymentline-delete'>
<img src="/point_of_sale/static/src/img/search_reset.gif" />
</span>
diff --git a/addons/point_of_sale/wizard/pos_details.py b/addons/point_of_sale/wizard/pos_details.py
index 7b1dde7e0d6..db9dde51a4d 100644
--- a/addons/point_of_sale/wizard/pos_details.py
+++ b/addons/point_of_sale/wizard/pos_details.py
@@ -33,8 +33,8 @@ class pos_details(osv.osv_memory):
'user_ids': fields.many2many('res.users', 'pos_details_report_user_rel', 'user_id', 'wizard_id', 'Salespeople'),
}
_defaults = {
- 'date_start': lambda *a: time.strftime('%Y-%m-%d'),
- 'date_end': lambda *a: time.strftime('%Y-%m-%d'),
+ 'date_start': fields.date.context_today,
+ 'date_end': fields.date.context_today,
}
def print_report(self, cr, uid, ids, context=None):
diff --git a/addons/portal/wizard/share_wizard.py b/addons/portal/wizard/share_wizard.py
index 7fafff9a862..188ee64983d 100644
--- a/addons/portal/wizard/share_wizard.py
+++ b/addons/portal/wizard/share_wizard.py
@@ -19,12 +19,13 @@
#
##############################################################################
+from openerp import SUPERUSER_ID
from openerp.osv import fields, osv
from openerp.tools.translate import _
import logging
_logger = logging.getLogger(__name__)
-UID_ROOT = 1
+UID_ROOT = SUPERUSER_ID
SHARED_DOCS_MENU = "Documents"
SHARED_DOCS_CHILD_MENU = "Shared Documents"
@@ -56,9 +57,18 @@ class share_wizard_portal(osv.TransientModel):
return super(share_wizard_portal, self)._check_preconditions(cr, uid, wizard_data, context=context)
def _create_or_get_submenu_named(self, cr, uid, parent_menu_id, menu_name, context=None):
- if not parent_menu_id:
- return
+ if context is None:
+ context = {}
Menus = self.pool.get('ir.ui.menu')
+ if not parent_menu_id and context.get('group_id'):
+ cxt = dict(context)
+ cxt['ir.ui.menu.full_list'] = True
+ parent_menu_ids = Menus.search(cr, SUPERUSER_ID,
+ [('groups_id', 'in', [context.get('group_id')]), ('parent_id', '=', False)], limit=1, context=cxt)
+ parent_menu_id = parent_menu_ids and parent_menu_ids[0] or False
+ if not parent_menu_id:
+ return False
+
parent_menu = Menus.browse(cr, uid, parent_menu_id) # No context
menu_id = None
max_seq = 10
@@ -79,9 +89,12 @@ class share_wizard_portal(osv.TransientModel):
def _sharing_root_menu_id(self, cr, uid, portal, context=None):
"""Create or retrieve root ID of sharing menu in portal menu
- :param portal: browse_record of portal, constructed with a context WITHOUT language
+ :param portal: browse_record of shared group, constructed with a context WITHOUT language
"""
- parent_menu_id = self._create_or_get_submenu_named(cr, uid, portal.parent_menu_id.id, SHARED_DOCS_MENU, context=context)
+ if context is None:
+ context = {}
+ ctx = dict(context, group_id=portal.id)
+ parent_menu_id = self._create_or_get_submenu_named(cr, uid, False, SHARED_DOCS_MENU, context=ctx)
if parent_menu_id:
child_menu_id = self._create_or_get_submenu_named(cr, uid, parent_menu_id, SHARED_DOCS_CHILD_MENU, context=context)
return child_menu_id
@@ -90,7 +103,7 @@ class share_wizard_portal(osv.TransientModel):
"""Create sharing menus in portal menu according to share wizard options.
:param wizard_data: browse_record of share.wizard
- :param portal: browse_record of portal, constructed with a context WITHOUT language
+ :param portal: browse_record of shared group, constructed with a context WITHOUT language
"""
root_menu_id = self._sharing_root_menu_id(cr, uid, portal, context=context)
if not root_menu_id:
@@ -126,13 +139,16 @@ class share_wizard_portal(osv.TransientModel):
# setup the menu for portal groups
for group in wizard_data.group_ids:
if group.id in all_portal_group_ids:
- self._create_shared_data_menu(cr, uid, wizard_data, group.id, context=context)
+ self._create_shared_data_menu(cr, uid, wizard_data, group, context=context)
for user in group.users:
new_line = {'user_id': user.id,
'newly_created': False}
wizard_data.write({'result_line_ids': [(0,0,new_line)]})
+ selected_group_ids = [x.id for x in wizard_data.group_ids]
+ res_groups.write(cr, SUPERUSER_ID, selected_group_ids, {'implied_ids': [(4, super_result[0])]})
+
elif wizard_data.user_ids:
# must take care of existing users, by adding them to the new group, which is super_result[0],
# and adding the shortcut
diff --git a/addons/pos_restaurant/static/src/js/splitbill.js b/addons/pos_restaurant/static/src/js/splitbill.js
index 9dd7e7449e2..745c8282c20 100644
--- a/addons/pos_restaurant/static/src/js/splitbill.js
+++ b/addons/pos_restaurant/static/src/js/splitbill.js
@@ -52,7 +52,7 @@ var SplitbillScreenWidget = screens.ScreenWidget.extend({
}
}else{
if( split.quantity < line.get_quantity()){
- split.quantity += line.get_unit().rounding;
+ split.quantity += line.get_unit().is_unit ? 1 : line.get_unit().rounding;
if(split.quantity > line.get_quantity()){
split.quantity = line.get_quantity();
}
diff --git a/addons/product/partner_view.xml b/addons/product/partner_view.xml
index 00f957aaa4b..a713943fd1b 100644
--- a/addons/product/partner_view.xml
+++ b/addons/product/partner_view.xml
@@ -5,6 +5,7 @@
<field name="name">res.partner.product.property.form.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
+ <field name="groups_id" eval="[(4, ref('product.group_sale_pricelist')), (4, ref('product.group_purchase_pricelist'))]"/>
<field name="arch" type="xml">
<group name="sale">
<field name="property_product_pricelist" groups="product.group_sale_pricelist" attrs="{'invisible': [('is_company','=',False),('parent_id','!=',False)]}"/>
diff --git a/addons/product/pricelist.py b/addons/product/pricelist.py
index 3b4bf2451d9..fee4caee12a 100644
--- a/addons/product/pricelist.py
+++ b/addons/product/pricelist.py
@@ -19,6 +19,7 @@
#
##############################################################################
+from itertools import chain
import time
from openerp import tools
@@ -224,7 +225,9 @@ class product_pricelist(osv.osv):
is_product_template = products[0]._name == "product.template"
if is_product_template:
prod_tmpl_ids = [tmpl.id for tmpl in products]
- prod_ids = [product.id for product in tmpl.product_variant_ids for tmpl in products]
+ # all variants of all products
+ prod_ids = [p.id for p in
+ list(chain.from_iterable([t.product_variant_ids for t in products]))]
else:
prod_ids = [product.id for product in products]
prod_tmpl_ids = [product.product_tmpl_id.id for product in products]
@@ -274,7 +277,9 @@ class product_pricelist(osv.osv):
if is_product_template:
if rule.product_tmpl_id and product.id != rule.product_tmpl_id.id:
continue
- if rule.product_id:
+ if rule.product_id and \
+ (product.product_variant_count > 1 or product.product_variant_ids[0].id != rule.product_id.id):
+ # product rule acceptable on template if has only one variant
continue
else:
if rule.product_tmpl_id and product.product_tmpl_id.id != rule.product_tmpl_id.id:
diff --git a/addons/product/product.py b/addons/product/product.py
index 4e11f550dc0..4cea70d6d5a 100644
--- a/addons/product/product.py
+++ b/addons/product/product.py
@@ -655,6 +655,9 @@ class product_template(osv.osv):
res = False
return res
+ def onchange_type(self, cr, uid, ids, type):
+ return {}
+
def onchange_uom(self, cursor, user, ids, uom_id, uom_po_id):
if uom_id:
return {'value': {'uom_po_id': uom_id}}
@@ -1013,6 +1016,9 @@ class product_product(osv.osv):
self.pool.get('product.template').unlink(cr, uid, unlink_product_tmpl_ids, context=context)
return res
+ def onchange_type(self, cr, uid, ids, type):
+ return {}
+
def onchange_uom(self, cursor, user, ids, uom_id, uom_po_id):
if uom_id and uom_po_id:
uom_obj=self.pool.get('product.uom')
diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml
index 2a1dadb4452..bbc34f95985 100644
--- a/addons/product/product_view.xml
+++ b/addons/product/product_view.xml
@@ -72,7 +72,7 @@
<page string="Information">
<group colspan="4">
<group>
- <field name="type"/>
+ <field name="type" on_change="onchange_type(type)"/>
<field name="uom_id" on_change="onchange_uom(uom_id,uom_po_id)" groups="product.group_uom"/>
<field name="list_price"/>
</group>
diff --git a/addons/product_visible_discount/product_visible_discount.py b/addons/product_visible_discount/product_visible_discount.py
index f9dfb3b409c..cced048ab11 100644
--- a/addons/product_visible_discount/product_visible_discount.py
+++ b/addons/product_visible_discount/product_visible_discount.py
@@ -42,26 +42,34 @@ class sale_order_line(osv.osv):
lang=False, update_tax=True, date_order=False, packaging=False,
fiscal_position_id=False, flag=False, context=None):
- def get_real_price(res_dict, product_id, qty, uom, pricelist):
+ def get_real_price_curency(res_dict, product_id, qty, uom, pricelist):
"""Retrieve the price before applying the pricelist"""
item_obj = self.pool.get('product.pricelist.item')
price_type_obj = self.pool.get('product.price.type')
product_obj = self.pool.get('product.product')
field_name = 'list_price'
rule_id = res_dict.get(pricelist) and res_dict[pricelist][1] or False
+ currency_id = None
if rule_id:
item_base = item_obj.read(cr, uid, [rule_id], ['base'])[0]['base']
if item_base > 0:
- field_name = price_type_obj.browse(cr, uid, item_base).field
+ price_type = price_type_obj.browse(cr, uid, item_base)
+ field_name = price_type.field
+ currency_id = price_type.currency_id
product = product_obj.browse(cr, uid, product_id, context)
product_read = product_obj.read(cr, uid, [product_id], [field_name], context=context)[0]
+ if not currency_id:
+ currency_id = product.company_id.currency_id.id
factor = 1.0
if uom and uom != product.uom_id.id:
# the unit price is in a different uom
factor = self.pool['product.uom']._compute_qty(cr, uid, uom, 1.0, product.uom_id.id)
- return product_read[field_name] * factor
+ return product_read[field_name] * factor, currency_id
+
+ def get_real_price(res_dict, product_id, qty, uom, pricelist):
+ return get_real_price_curency(res_dict, product_id, qty, uom, pricelist)[0]
res=super(sale_order_line, self).product_id_change(cr, uid, ids, pricelist, product, qty,
@@ -85,14 +93,14 @@ class sale_order_line(osv.osv):
so_pricelist = pricelist_obj.browse(cr, uid, pricelist, context=context)
- new_list_price = get_real_price(list_price, product.id, qty, uom, pricelist)
+ new_list_price, currency_id = get_real_price_curency(list_price, product.id, qty, uom, pricelist)
if so_pricelist.visible_discount and list_price[pricelist][0] != 0 and new_list_price != 0:
if product.company_id and so_pricelist.currency_id.id != product.company_id.currency_id.id:
# new_list_price is in company's currency while price in pricelist currency
ctx = context.copy()
ctx['date'] = date_order
new_list_price = self.pool['res.currency'].compute(cr, uid,
- product.company_id.currency_id.id, so_pricelist.currency_id.id,
+ currency_id.id, so_pricelist.currency_id.id,
new_list_price, context=ctx)
discount = (new_list_price - price) / new_list_price * 100
if discount > 0:
diff --git a/addons/project/project.py b/addons/project/project.py
index 7abab996c56..d57e91d007d 100644
--- a/addons/project/project.py
+++ b/addons/project/project.py
@@ -900,13 +900,20 @@ class task(osv.osv):
context = context or {}
result = ""
ident = ' '*ident
+ company = self.pool["res.users"].browse(cr, uid, uid, context=context).company_id
+ duration_uom = {
+ 'day(s)': 'd', 'days': 'd', 'day': 'd', 'd': 'd',
+ 'month(s)': 'm', 'months': 'm', 'month': 'month', 'm': 'm',
+ 'week(s)': 'w', 'weeks': 'w', 'week': 'w', 'w': 'w',
+ 'hour(s)': 'H', 'hours': 'H', 'hour': 'H', 'h': 'H',
+ }.get(company.project_time_mode_id.name.lower(), "hour(s)")
for task in tasks:
if task.stage_id and task.stage_id.fold:
continue
result += '''
%sdef Task_%s():
-%s todo = \"%.2fH\"
-%s effort = \"%.2fH\"''' % (ident,task.id, ident,task.remaining_hours, ident, task._get_total_hours())
+%s todo = \"%.2f%s\"
+%s effort = \"%.2f%s\"''' % (ident, task.id, ident, task.remaining_hours, duration_uom, ident, task._get_total_hours(), duration_uom)
start = []
for t2 in task.parent_ids:
start.append("up.Task_%s.end" % (t2.id,))
diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml
index 374474849e7..f37480874e7 100644
--- a/addons/project/project_view.xml
+++ b/addons/project/project_view.xml
@@ -640,7 +640,7 @@
<field name="arch" type="xml">
<xpath expr="//group[@name='account_grp']" position="after">
<group name="project_grp" string="Projects">
- <field name="project_time_mode_id" domain="[('category_id','=','Working Time')]"/>
+ <field name="project_time_mode_id" domain="[('category_id','=', %(product.uom_categ_wtime)d)]"/>
</group>
</xpath>
</field>
diff --git a/addons/project/static/src/js/web_planner_project.js b/addons/project/static/src/js/web_planner_project.js
index 00947447c9a..28e5a1f9e4f 100644
--- a/addons/project/static/src/js/web_planner_project.js
+++ b/addons/project/static/src/js/web_planner_project.js
@@ -1,7 +1,7 @@
odoo.define('planner_project.planner', function (require) {
"use strict";
-var planner = require('web.planner');
+var planner = require('web.planner.common');
var core = require('web.core');
var _t = core._t;
diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py
index 1b47a124cda..92d11c9029d 100644
--- a/addons/project_issue/project_issue.py
+++ b/addons/project_issue/project_issue.py
@@ -219,6 +219,7 @@ class project_issue(osv.Model):
_defaults = {
'active': 1,
+ 'team_id': lambda s, cr, uid, c: s.pool['crm.team']._get_default_team_id(cr, uid, context=c),
'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
'priority': '0',
diff --git a/addons/purchase/partner_view.xml b/addons/purchase/partner_view.xml
index 5e9cce58df6..060a7e3a2fa 100644
--- a/addons/purchase/partner_view.xml
+++ b/addons/purchase/partner_view.xml
@@ -6,9 +6,10 @@
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="priority">36</field>
+ <field name="groups_id" eval="[(4, ref('product.group_purchase_pricelist'))]"/>
<field name="arch" type="xml">
<group name="purchase" position="inside">
- <field name="property_product_pricelist_purchase" groups="product.group_purchase_pricelist"/>
+ <field name="property_product_pricelist_purchase"/>
</group>
</field>
</record>
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index fec5f60fbde..abb0209b14e 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -402,11 +402,16 @@ class purchase_order(osv.osv):
'fiscal_position_id': False,
'payment_term_id': False,
}}
+
+ company_id = self.pool.get('res.users')._get_company(cr, uid, context=context)
+ if not company_id:
+ raise osv.except_osv(_('Error!'), _('There is no default company for the current user!'))
+ fp = self.pool['account.fiscal.position'].get_fiscal_position(cr, uid, company_id, partner_id, context=context)
supplier_address = partner.address_get(cr, uid, [partner_id], ['default'], context=context)
supplier = partner.browse(cr, uid, partner_id, context=context)
return {'value': {
'pricelist_id': supplier.property_product_pricelist_purchase.id,
- 'fiscal_position_id': supplier.property_account_position and supplier.property_account_position.id or False,
+ 'fiscal_position_id': fp or supplier.property_account_position and supplier.property_account_position.id or False,
'payment_term_id': supplier.property_supplier_payment_term.id or False,
}}
@@ -1027,7 +1032,7 @@ class purchase_order_line(osv.osv):
def unlink(self, cr, uid, ids, context=None):
for line in self.browse(cr, uid, ids, context=context):
- if line.state not in ['draft', 'cancel']:
+ if line.order_id.state in ['approved', 'done'] and line.state not in ['draft', 'cancel']:
raise UserError(_('Cannot delete a purchase order line which is in state \'%s\'.') %(line.state,))
procurement_obj = self.pool.get('procurement.order')
procurement_ids_to_except = procurement_obj.search(cr, uid, [('purchase_line_id', 'in', ids)], context=context)
@@ -1200,18 +1205,19 @@ class procurement_order(osv.osv):
uom_obj = self.pool.get("product.uom")
for procurement in self.browse(cr, uid, ids, context=context):
if procurement.rule_id.action == 'buy' and procurement.purchase_line_id:
- uom = procurement.purchase_line_id.product_uom
- product_qty = uom_obj._compute_qty_obj(cr, uid, procurement.product_uom, procurement.product_qty, uom, context=context)
if procurement.purchase_line_id.state not in ('draft', 'cancel'):
raise UserError(
_('Can not cancel this procurement like this as the related purchase order has been confirmed already. Please cancel the purchase order first. '))
- if float_compare(procurement.purchase_line_id.product_qty, product_qty, 0, precision_rounding=uom.rounding) > 0:
- purchase_line_obj.write(cr, uid, [procurement.purchase_line_id.id], {'product_qty': procurement.purchase_line_id.product_qty - product_qty}, context=context)
- else:
+
+ new_qty, new_price = self._calc_new_qty_price(cr, uid, procurement, cancel=True, context=context)
+ if new_qty != procurement.purchase_line_id.product_qty:
+ purchase_line_obj.write(cr, uid, [procurement.purchase_line_id.id], {'product_qty': new_qty, 'price_unit': new_price}, context=context)
+ if float_compare(new_qty, 0.0, precision_rounding=procurement.product_uom.rounding) != 1:
if procurement.purchase_line_id.id not in lines_to_cancel:
lines_to_cancel += [procurement.purchase_line_id.id]
if lines_to_cancel:
purchase_line_obj.action_cancel(cr, uid, lines_to_cancel, context=context)
+ purchase_line_obj.unlink(cr, uid, lines_to_cancel, context=context)
return super(procurement_order, self).propagate_cancels(cr, uid, ids, context=context)
def _run(self, cr, uid, procurement, context=None):
@@ -1375,6 +1381,41 @@ class procurement_order(osv.osv):
res[procurement.id] = values
return res
+ def _calc_new_qty_price(self, cr, uid, procurement, po_line=None, cancel=False, context=None):
+ if not po_line:
+ po_line = procurement.purchase_line_id
+
+ uom_obj = self.pool.get('product.uom')
+ qty = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty,
+ procurement.product_id.uom_po_id.id)
+ if cancel:
+ qty = -qty
+
+ # Make sure we use the minimum quantity of the partner corresponding to the PO
+ if po_line.product_id.seller_id.id == po_line.order_id.partner_id.id:
+ supplierinfo_min_qty = po_line.product_id.seller_qty
+ else:
+ supplierinfo_obj = self.pool.get('product.supplierinfo')
+ supplierinfo_ids = supplierinfo_obj.search(cr, uid, [('name', '=', po_line.order_id.partner_id.id), ('product_tmpl_id', '=', po_line.product_id.product_tmpl_id.id)])
+ supplierinfo_min_qty = supplierinfo_obj.browse(cr, uid, supplierinfo_ids).min_qty
+
+ if supplierinfo_min_qty == 0.0:
+ qty += po_line.product_qty
+ else:
+ # Recompute quantity by adding existing running procurements.
+ for proc in po_line.procurement_ids:
+ qty += uom_obj._compute_qty(cr, uid, proc.product_uom.id, proc.product_qty,
+ proc.product_id.uom_po_id.id) if proc.state == 'running' else 0.0
+ qty = max(qty, supplierinfo_min_qty) if qty > 0.0 else 0.0
+
+ price = po_line.price_unit
+ if qty != po_line.product_qty:
+ pricelist_obj = self.pool.get('product.pricelist')
+ pricelist_id = po_line.order_id.partner_id.property_product_pricelist_purchase.id
+ price = pricelist_obj.price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, po_line.order_id.partner_id.id, {'uom': procurement.product_uom.id})[pricelist_id]
+
+ return qty, price
+
def _get_grouping_dicts(self, cr, uid, ids, context=None):
"""
It will group the procurements according to the pos they should go into. That way, lines going to the same
@@ -1459,8 +1500,11 @@ class procurement_order(osv.osv):
for proc in procurements:
if po_prod_dict.get(proc.product_id.id):
po_line = po_prod_dict[proc.product_id.id]
- uom_id = po_line.product_uom #Convert to UoM of existing line
+ # FIXME: compute quantity using `_calc_new_qty_price` method.
+ # new_qty, new_price = self._calc_new_qty_price(cr, uid, proc, po_line=po_line, context=context)
+ uom_id = po_line.product_uom # Convert to UoM of existing line
qty = uom_obj._compute_qty_obj(cr, uid, proc.product_uom, proc.product_qty, uom_id)
+
if lines_to_update.get(po_line):
lines_to_update[po_line] += [(proc.id, qty)]
else:
@@ -1645,15 +1689,21 @@ class account_invoice(osv.Model):
else:
user_id = uid
po_ids = purchase_order_obj.search(cr, user_id, [('invoice_ids', 'in', ids)], context=context)
- for order in purchase_order_obj.browse(cr, uid, po_ids, context=context):
+ for order in purchase_order_obj.browse(cr, user_id, po_ids, context=context):
purchase_order_obj.message_post(cr, user_id, order.id, body=_("Invoice received"), context=context)
invoiced = []
+ shipped = True
+ # for invoice method manual or order, don't care about shipping state
+ # for invoices based on incoming shippment, beware of partial deliveries
+ if (order.invoice_method == 'picking' and
+ not all(picking.invoice_state in ['invoiced'] for picking in order.picking_ids)):
+ shipped = False
for po_line in order.order_line:
- if any(line.invoice_id.state not in ['draft', 'cancel'] for line in po_line.invoice_lines):
+ if all(line.invoice_id.state not in ['draft', 'cancel'] for line in po_line.invoice_lines):
invoiced.append(po_line.id)
- if invoiced:
- self.pool['purchase.order.line'].write(cr, uid, invoiced, {'invoiced': True})
- workflow.trg_write(uid, 'purchase.order', order.id, cr)
+ if invoiced and shipped:
+ self.pool['purchase.order.line'].write(cr, user_id, invoiced, {'invoiced': True})
+ workflow.trg_write(user_id, 'purchase.order', order.id, cr)
return res
def confirm_paid(self, cr, uid, ids, context=None):
diff --git a/addons/purchase/report/purchase_report.py b/addons/purchase/report/purchase_report.py
index 096f0d86e7b..90b3d28a66a 100644
--- a/addons/purchase/report/purchase_report.py
+++ b/addons/purchase/report/purchase_report.py
@@ -79,7 +79,7 @@ class purchase_report(osv.osv):
s.dest_address_id,
s.pricelist_id,
s.validator,
- s.picking_type_id as picking_type_id,
+ spt.warehouse_id as picking_type_id,
s.partner_id as partner_id,
s.create_uid as user_id,
s.company_id as company_id,
@@ -108,6 +108,7 @@ class purchase_report(osv.osv):
LEFT JOIN ir_property ip ON (ip.name='standard_price' AND ip.res_id=CONCAT('product.template,',t.id) AND ip.company_id=s.company_id)
left join product_uom u on (u.id=l.product_uom)
left join product_uom u2 on (u2.id=t.uom_id)
+ left join stock_picking_type spt on (spt.id=s.picking_type_id)
left join account_analytic_account analytic_account on (l.account_analytic_id = analytic_account.id)
group by
s.company_id,
@@ -129,7 +130,7 @@ class purchase_report(osv.osv):
t.categ_id,
s.date_order,
s.state,
- s.picking_type_id,
+ spt.warehouse_id,
u.uom_type,
u.category_id,
t.uom_id,
diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py
index 16b0fd70d87..dac3203074f 100644
--- a/addons/purchase/stock.py
+++ b/addons/purchase/stock.py
@@ -157,6 +157,10 @@ class stock_move(osv.osv):
return self.write(cr, uid, [move.id], {'price_unit': price}, context=context)
super(stock_move, self).attribute_price(cr, uid, move, context=context)
+ def _get_taxes(self, cr, uid, move, context=None):
+ if move.origin_returned_move_id.purchase_line_id.taxes_id:
+ return [tax.id for tax in move.origin_returned_move_id.purchase_line_id.taxes_id]
+ return super(stock_move, self)._get_taxes(cr, uid, move, context=context)
class stock_picking(osv.osv):
_inherit = 'stock.picking'
diff --git a/addons/report/models/report.py b/addons/report/models/report.py
index d39bbd17f72..555d6ae7a99 100644
--- a/addons/report/models/report.py
+++ b/addons/report/models/report.py
@@ -22,7 +22,7 @@
from openerp import api
from openerp import SUPERUSER_ID
from openerp.exceptions import AccessError
-from openerp.osv import osv
+from openerp.osv import osv, fields
from openerp.tools import config
from openerp.tools.misc import find_in_path
from openerp.tools.translate import _
@@ -140,6 +140,7 @@ class Report(osv.Model):
context = dict(context, translatable=context.get('lang') != request.website.default_lang_code)
values.update(
time=time,
+ context_timestamp=lambda t: fields.datetime.context_timestamp(cr, uid, t, context),
translate_doc=translate_doc,
editable=True,
user=user,
diff --git a/addons/report/static/src/css/reset.min.css b/addons/report/static/src/css/reset.min.css
index cc7842e37ef..ee21ad74e7d 100644
--- a/addons/report/static/src/css/reset.min.css
+++ b/addons/report/static/src/css/reset.min.css
@@ -1,2 +1,106 @@
-/* reset5 2011 opensource.736cs.com MIT - https://code.google.com/p/reset5 */
-html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,audio,canvas,details,figcaption,figure,footer,header,hgroup,mark,menu,meter,nav,output,progress,section,summary,time,video{border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent;margin:0;padding:0;}body{line-height:1;}article,aside,dialog,figure,footer,header,hgroup,nav,section,blockquote{display:block;}nav ul{list-style:none;}ol{list-style:decimal;}ul{list-style:disc;}ul ul{list-style:circle;}blockquote,q{quotes:none;}blockquote:before,blockquote:after,q:before,q:after{content:none;}ins{text-decoration:underline;}del{text-decoration:line-through;}mark{background:none;}abbr[title],dfn[title]{border-bottom:1px dotted #000;cursor:help;}table{border-collapse:collapse;border-spacing:0;}hr{display:block;height:1px;border:0;border-top:1px solid #ccc;margin:1em 0;padding:0;}input[type=submit],input[type=button],button{margin:0!important;padding:0!important;}input,select,a img{vertical-align:middle;}
+/*
+
+HTML5 CSS Reset
+Based on Eric Meyer's CSS Reset
+and html5doctor.com HTML5 Reset
+
+Copyright (c) 2011 736 Computing Services Limited
+Released under the MIT license. http://opensource.736cs.com/licenses/mit
+
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, font, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, i, center, dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, audio, canvas, details, figcaption,
+figure, footer, header, hgroup, mark, menu, meter, nav,
+output, progress, section, summary, time, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-size: 100%;
+ vertical-align: baseline;
+ background: transparent;
+}
+
+body {
+ line-height: 1;
+}
+
+article, aside, dialog, figure, footer, header,
+hgroup, nav, section, blockquote {
+ display: block;
+}
+
+nav ul {
+ list-style: none;
+}
+
+ol {
+ list-style: decimal;
+}
+
+ul {
+ list-style: disc;
+}
+
+ul ul {
+ list-style: circle;
+}
+
+blockquote, q {
+ quotes: none;
+}
+
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+
+ins {
+ text-decoration: underline;
+}
+
+del {
+ text-decoration: line-through;
+}
+
+mark {
+ background: none;
+}
+
+abbr[title], dfn[title] {
+ border-bottom:1px dotted #000;
+ cursor:help;
+}
+
+/* tables still need 'cellspacing="0"' in the markup */
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+hr {
+ display: block;
+ height: 1px;
+ border: 0;
+ border-top: 1px solid #ccc;
+ margin: 1em 0;
+ padding: 0;
+}
+
+input[type="submit"], input[type="button"], button {
+ padding: 0 !important;
+ margin: 0 !important;
+}
+
+input, select, a img {
+ vertical-align: middle;
+}
diff --git a/addons/report/views/layouts.xml b/addons/report/views/layouts.xml
index 9e1924e8a3f..e61b4d796db 100644
--- a/addons/report/views/layouts.xml
+++ b/addons/report/views/layouts.xml
@@ -136,8 +136,7 @@
<div class="header">
<div class="row">
<div class="col-xs-3">
- <span t-esc="time.strftime('%Y-%m-%d')"/>
- <span t-esc="time.strftime('%H:%M')"/>
+ <span t-esc="context_timestamp(datetime.datetime.now()).strftime('%Y-%m-%d %H:%M')"/>
</div>
<div class="col-xs-2 col-xs-offset-2 text-center">
<span t-esc="company.name"/>
@@ -166,7 +165,22 @@
<link href="/base/static/src/css/description.css" rel="stylesheet"/>
<style type='text/css'><t t-raw="css"/></style>
<t t-if="subst is True">
- <script src='/report/static/src/js/subst.js'></script>
+ <script>
+ function subst() {
+ var vars = {};
+ var x = document.location.search.substring(1).split('&amp;');
+ for (var i in x) {
+ var z = x[i].split('=', 2);
+ vars[z[0]] = unescape(z[1]);
+ }
+ var x=['frompage', 'topage', 'page', 'webpage', 'section', 'subsection', 'subsubsection'];
+ for (var i in x) {
+ var y = document.getElementsByClassName(x[i]);
+ for (var j=0; j&lt;y.length; ++j)
+ y[j].textContent = vars[x[i]];
+ }
+ }
+ </script>
</t>
</head>
<body class="container" onload="subst()">
diff --git a/addons/sale/__openerp__.py b/addons/sale/__openerp__.py
index dbcb1f1415c..f67ea360266 100644
--- a/addons/sale/__openerp__.py
+++ b/addons/sale/__openerp__.py
@@ -87,6 +87,7 @@ The Dashboard for the Sales Manager will include
'test/manual_order_policy.yml',
'test/cancel_order.yml',
'test/delete_order.yml',
+ 'test/canceled_lines_order.yml',
],
'css': ['static/src/css/sale.css'],
'installable': True,
diff --git a/addons/sale/report/sale_report.py b/addons/sale/report/sale_report.py
index a06c58b7be9..5892febd852 100644
--- a/addons/sale/report/sale_report.py
+++ b/addons/sale/report/sale_report.py
@@ -77,7 +77,7 @@ class sale_report(osv.osv):
s.user_id as user_id,
s.company_id as company_id,
extract(epoch from avg(date_trunc('day',s.date_confirm)-date_trunc('day',s.create_date)))/(24*60*60)::decimal(16,2) as delay,
- s.state,
+ l.state,
t.categ_id as categ_id,
s.pricelist_id as pricelist_id,
s.project_id as analytic_account_id,
@@ -113,7 +113,7 @@ class sale_report(osv.osv):
s.partner_id,
s.user_id,
s.company_id,
- s.state,
+ l.state,
s.pricelist_id,
s.project_id,
s.team_id,
diff --git a/addons/sale/sale.py b/addons/sale/sale.py
index 482a7cfe5de..99d00473861 100644
--- a/addons/sale/sale.py
+++ b/addons/sale/sale.py
@@ -111,26 +111,6 @@ class sale_order(osv.osv):
raise UserError(_('There is no default company for the current user!'))
return company_id
- def _get_default_team_id(self, cr, uid, context=None):
- """ Gives default team by checking if present in the context """
- team_id = self._resolve_team_id_from_context(cr, uid, context=context) or False
- return team_id
-
- def _resolve_team_id_from_context(self, cr, uid, context=None):
- """ Returns ID of team based on the value of 'team_id'
- context key, or None if it cannot be resolved to a single
- Sales Team.
- """
- if context is None:
- context = {}
- if type(context.get('default_team_id')) in (int, long):
- return context.get('default_team_id')
- if isinstance(context.get('default_team_id'), basestring):
- team_ids = self.pool.get('crm.team').name_search(cr, uid, name=context['default_team_id'], context=context)
- if len(team_ids) == 1:
- return int(team_ids[0][0])
- return None
-
def _get_invoiced(self, cr, uid, ids, field_name, arg, context=None):
res = {}
for order in self.browse(cr, uid, ids, context=context):
@@ -220,7 +200,7 @@ class sale_order(osv.osv):
'partner_invoice_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').address_get(cr, uid, [context['partner_id']], ['invoice'])['invoice'],
'partner_shipping_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').address_get(cr, uid, [context['partner_id']], ['delivery'])['delivery'],
'note': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.sale_note,
- 'team_id': lambda s, cr, uid, c: s._get_default_team_id(cr, uid, c),
+ 'team_id': lambda s, cr, uid, c: s.pool['crm.team']._get_default_team_id(cr, uid, context=c),
}
_sql_constraints = [
('name_uniq', 'unique(name, company_id)', 'Order Reference must be unique per Company!'),
@@ -315,6 +295,8 @@ class sale_order(osv.osv):
val.update(delivery_onchange['value'])
if pricelist:
val['pricelist_id'] = pricelist
+ if not self.pool['crm.team']._get_default_team_id(cr, uid, context=context) and part.team_id:
+ val['team_id'] = part.team_id.id
sale_note = self.get_salenote(cr, uid, ids, part.id, context=context)
if sale_note: val.update({'note': sale_note})
return {'value': val}
@@ -467,6 +449,8 @@ class sale_order(osv.osv):
def test_no_product(self, cr, uid, order, context):
for line in order.order_line:
+ if line.state == 'cancel':
+ continue
if line.product_id and (line.product_id.type != 'service'):
return False
return True
@@ -548,15 +532,13 @@ class sale_order(osv.osv):
context = {}
sale_order_line_obj = self.pool.get('sale.order.line')
account_invoice_obj = self.pool.get('account.invoice')
- procurement_obj = self.pool.get('procurement.order')
for sale in self.browse(cr, uid, ids, context=context):
for inv in sale.invoice_ids:
if inv.state not in ('draft', 'cancel'):
raise UserError(_('Cannot cancel this sales order!') + ':' + _('First cancel all invoices attached to this sales order.'))
inv.signal_workflow('invoice_cancel')
- procurement_obj.cancel(cr, uid, sum([l.procurement_ids.ids for l in sale.order_line],[]))
- sale_order_line_obj.write(cr, uid, [l.id for l in sale.order_line],
- {'state': 'cancel'})
+ line_ids = [l.id for l in sale.order_line if l.state != 'cancel']
+ sale_order_line_obj.button_cancel(cr, uid, line_ids, context=context)
self.write(cr, uid, ids, {'state': 'cancel'})
return True
@@ -568,14 +550,14 @@ class sale_order(osv.osv):
def action_wait(self, cr, uid, ids, context=None):
context = context or {}
for o in self.browse(cr, uid, ids):
- if not o.order_line:
+ if not any(line.state != 'cancel' for line in o.order_line):
raise UserError(_('You cannot confirm a sales order which has no line.'))
noprod = self.test_no_product(cr, uid, o, context)
if (o.order_policy == 'manual') or noprod:
self.write(cr, uid, [o.id], {'state': 'manual', 'date_confirm': fields.date.context_today(self, cr, uid, context=context)})
else:
self.write(cr, uid, [o.id], {'state': 'progress', 'date_confirm': fields.date.context_today(self, cr, uid, context=context)})
- self.pool.get('sale.order.line').button_confirm(cr, uid, [x.id for x in o.order_line])
+ self.pool.get('sale.order.line').button_confirm(cr, uid, [x.id for x in o.order_line if x.state != 'cancel'])
return True
def action_quotation_send(self, cr, uid, ids, context=None):
@@ -614,7 +596,7 @@ class sale_order(osv.osv):
def action_done(self, cr, uid, ids, context=None):
for order in self.browse(cr, uid, ids, context=context):
- self.pool.get('sale.order.line').write(cr, uid, [line.id for line in order.order_line], {'state': 'done'}, context=context)
+ self.pool.get('sale.order.line').write(cr, uid, [line.id for line in order.order_line if line.state != 'cancel'], {'state': 'done'}, context=context)
return self.write(cr, uid, ids, {'state': 'done'}, context=context)
def _prepare_order_line_procurement(self, cr, uid, order, line, group_id=False, context=None):
@@ -646,7 +628,7 @@ class sale_order(osv.osv):
sale_line_obj = self.pool.get('sale.order.line')
res = []
for order in self.browse(cr, uid, ids, context=context):
- res.append(sale_line_obj.need_procurement(cr, uid, [line.id for line in order.order_line], context=context))
+ res.append(sale_line_obj.need_procurement(cr, uid, [line.id for line in order.order_line if line.state != 'cancel'], context=context))
return any(res)
def action_ignore_delivery_exception(self, cr, uid, ids, context=None):
@@ -673,6 +655,8 @@ class sale_order(osv.osv):
order.write({'procurement_group_id': group_id})
for line in order.order_line:
+ if line.state == 'cancel':
+ continue
#Try to fix exception procurement (possible when after a shipping exception the user choose to recreate)
if line.procurement_ids:
#first check them to see if they are in exception or not (one of the related moves is cancelled)
@@ -754,6 +738,8 @@ class sale_order(osv.osv):
def test_procurements_done(self, cr, uid, ids, context=None):
for sale in self.browse(cr, uid, ids, context=context):
for line in sale.order_line:
+ if line.state == 'cancel':
+ continue
if not all([x.state == 'done' for x in line.procurement_ids]):
return False
return True
@@ -761,6 +747,8 @@ class sale_order(osv.osv):
def test_procurements_except(self, cr, uid, ids, context=None):
for sale in self.browse(cr, uid, ids, context=context):
for line in sale.order_line:
+ if line.state == 'cancel':
+ continue
if any([x.state == 'cancel' for x in line.procurement_ids]):
return True
return False
@@ -957,9 +945,12 @@ class sale_order_line(osv.osv):
return create_ids
def button_cancel(self, cr, uid, ids, context=None):
- for line in self.browse(cr, uid, ids, context=context):
+ lines = self.browse(cr, uid, ids, context=context)
+ for line in lines:
if line.invoiced:
raise UserError(_('You cannot cancel a sales order line that has already been invoiced.'))
+ procurement_obj = self.pool['procurement.order']
+ procurement_obj.cancel(cr, uid, sum([l.procurement_ids.ids for l in lines], []), context=context)
return self.write(cr, uid, ids, {'state': 'cancel'})
def button_confirm(self, cr, uid, ids, context=None):
@@ -1144,32 +1135,12 @@ class mail_compose_message(osv.Model):
class account_invoice(osv.Model):
_inherit = 'account.invoice'
- def _get_default_team_id(self, cr, uid, context=None):
- """ Gives default team by checking if present in the context """
- team_id = self._resolve_team_id_from_context(cr, uid, context=context) or False
- return team_id
-
- def _resolve_team_id_from_context(self, cr, uid, context=None):
- """ Returns ID of team based on the value of 'team_id'
- context key, or None if it cannot be resolved to a single
- Sales Team.
- """
- if context is None:
- context = {}
- if type(context.get('default_team_id')) in (int, long):
- return context.get('default_team_id')
- if isinstance(context.get('default_team_id'), basestring):
- team_ids = self.pool.get('crm.team').name_search(cr, uid, name=context['default_team_id'], context=context)
- if len(team_ids) == 1:
- return int(team_ids[0][0])
- return None
-
_columns = {
'team_id': fields.many2one('crm.team', 'Sales Team', oldname='section_id'),
}
_defaults = {
- 'team_id': lambda self, cr, uid, c=None: self._get_default_team_id(cr, uid, context=c)
+ 'team_id': lambda s, cr, uid, c: s.pool['crm.team']._get_default_team_id(cr, uid, context=c),
}
def confirm_paid(self, cr, uid, ids, context=None):
diff --git a/addons/sale/test/canceled_lines_order.yml b/addons/sale/test/canceled_lines_order.yml
new file mode 100644
index 00000000000..c608b686d53
--- /dev/null
+++ b/addons/sale/test/canceled_lines_order.yml
@@ -0,0 +1,60 @@
+-
+ I create a draft Sale Order with 2 lines but 1 canceled in order to check if the canceled lines are not considered in the logic
+-
+ !record {model: sale.order, id: sale_order_cl_2}:
+ partner_id: base.res_partner_15
+ partner_invoice_id: base.res_partner_address_25
+ partner_shipping_id: base.res_partner_address_25
+ pricelist_id: product.list0
+ order_policy: manual
+-
+ !record {model: sale.order.line, id: sale_order_cl_2_line_1}:
+ order_id: sale_order_cl_2
+ product_id: product.product_product_27
+ product_uom_qty: 1
+ product_uom: 1
+ price_unit: 3645
+ name: 'Laptop Customized'
+-
+ !record {model: sale.order.line, id: sale_order_cl_2_line_2}:
+ order_id: sale_order_cl_2
+ product_id: product.product_product_12
+ product_uom_qty: 1
+ product_uom: 1
+ price_unit: 12.50
+ name: 'Mouse, Wireless'
+-
+ I cancel the first line
+-
+ !python {model: sale.order.line, id: sale_order_cl_2_line_1}: |
+ self.button_cancel()
+-
+ I confirm the sale order
+-
+ !workflow {model: sale.order, action: order_confirm, ref: sale_order_cl_2}
+-
+ Invoice the whole sale order
+-
+ !python {model: sale.advance.payment.inv}: |
+ ctx = context.copy()
+ ctx.update({"active_model": 'sale.order',
+ "active_ids": [ref("sale_order_cl_2")],
+ "active_id":ref("sale_order_cl_2")})
+ pay_id = self.create(cr, uid, {'advance_payment_method': 'all'})
+ self.create_invoices(cr, uid, [pay_id], context=ctx)
+-
+ I check the invoice
+-
+ !python {model: sale.order, id: sale_order_cl_2}: |
+ invoice = self.invoice_ids
+ assert len(invoice.invoice_line_ids) == 1, "Only 1 line should be invoiced because the other one is canceled, got %d" % len(invoice.invoice_line_ids)
+-
+ I set the sale to done
+-
+ !python {model: sale.order, id: sale_order_cl_2}: |
+ self.action_done()
+-
+ And check if the canceled line is still canceled
+-
+ !assert {model: sale.order.line, id: sale_order_cl_2_line_1, string: The canceled line should still be canceled}:
+ - state == 'cancel'
diff --git a/addons/sale/test/manual_order_policy.yml b/addons/sale/test/manual_order_policy.yml
index ca3fc37f26c..a00b0b4fada 100644
--- a/addons/sale/test/manual_order_policy.yml
+++ b/addons/sale/test/manual_order_policy.yml
@@ -7,7 +7,7 @@
-
!python {model: sale.order}: |
sale_order = self.browse(cr, uid, ref("sale_order_2"))
- assert len(sale_order.invoice_ids) == False, "Invoice should not created."
+ assert len(sale_order.invoice_ids) == 0, "Invoice should not created."
-
I create advance invoice where type is 'Fixed Price'.
-
diff --git a/addons/sale/wizard/sale_line_invoice.py b/addons/sale/wizard/sale_line_invoice.py
index 579a8e83ff5..d20c8f909a8 100644
--- a/addons/sale/wizard/sale_line_invoice.py
+++ b/addons/sale/wizard/sale_line_invoice.py
@@ -103,7 +103,7 @@ class sale_order_line_make_invoice(osv.osv_memory):
sales_order_obj.message_post(cr, uid, [order.id], body=_("Invoice created"), context=context)
data_sale = sales_order_obj.browse(cr, uid, order.id, context=context)
for line in data_sale.order_line:
- if not line.invoiced:
+ if not line.invoiced and line.state != 'cancel':
flag = False
break
if flag:
diff --git a/addons/sale_layout/security/ir.model.access.csv b/addons/sale_layout/security/ir.model.access.csv
index 10a5f7b06b0..9ec03a6e378 100644
--- a/addons/sale_layout/security/ir.model.access.csv
+++ b/addons/sale_layout/security/ir.model.access.csv
@@ -1,6 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
report_layout_category_1,report_layout_category_1,model_sale_layout_category,base.group_sale_manager,1,1,1,1
report_layout_category_2,report_layout_category_2,model_sale_layout_category,account.group_account_manager,1,1,1,1
-report_layout_category_3,report_layout_category_3,model_sale_layout_category,base.group_sale_salesman,1,1,1,O
+report_layout_category_3,report_layout_category_3,model_sale_layout_category,base.group_sale_salesman,1,1,1,0
report_layout_category_4,report_layout_category_4,model_sale_layout_category,base.group_sale_salesman_all_leads,1,1,1,0
report_layout_category_5,report_layout_category_5,model_sale_layout_category,account.group_account_invoice,1,0,0,0
diff --git a/addons/sale_margin/sale_margin.py b/addons/sale_margin/sale_margin.py
index 9a20e736994..68917daf38f 100644
--- a/addons/sale_margin/sale_margin.py
+++ b/addons/sale_margin/sale_margin.py
@@ -19,6 +19,7 @@
##############################################################################
from openerp.osv import fields, osv
+import openerp.addons.decimal_precision as dp
class sale_order_line(osv.osv):
_inherit = "sale.order.line"
@@ -48,17 +49,20 @@ class sale_order_line(osv.osv):
return res
def _product_margin(self, cr, uid, ids, field_name, arg, context=None):
+ cur_obj = self.pool.get('res.currency')
res = {}
for line in self.browse(cr, uid, ids, context=context):
+ cur = line.order_id.pricelist_id.currency_id
res[line.id] = 0
if line.product_id:
- res[line.id] = round(line.price_subtotal - ((line.purchase_price or line.product_id.standard_price) * line.product_uos_qty), 2)
+ tmp_margin = line.price_subtotal - ((line.purchase_price or line.product_id.standard_price) * line.product_uos_qty)
+ res[line.id] = cur_obj.round(cr, uid, cur, tmp_margin)
return res
_columns = {
- 'margin': fields.function(_product_margin, string='Margin',
+ 'margin': fields.function(_product_margin, string='Margin', digits_compute= dp.get_precision('Product Price'),
store = True),
- 'purchase_price': fields.float('Cost Price', digits=(16,2))
+ 'purchase_price': fields.float('Cost Price', digits_compute= dp.get_precision('Product Price'))
}
@@ -70,6 +74,8 @@ class sale_order(osv.osv):
for sale in self.browse(cr, uid, ids, context=context):
result[sale.id] = 0.0
for line in sale.order_line:
+ if line.state == 'cancel':
+ continue
result[sale.id] += line.margin or 0.0
return result
@@ -83,5 +89,5 @@ class sale_order(osv.osv):
'margin': fields.function(_product_margin, string='Margin', help="It gives profitability by calculating the difference between the Unit Price and the cost price.", store={
'sale.order.line': (_get_order, ['margin', 'purchase_price'], 20),
'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 20),
- }),
+ }, digits_compute= dp.get_precision('Product Price')),
}
diff --git a/addons/sale_mrp/sale_mrp.py b/addons/sale_mrp/sale_mrp.py
index 082323ad580..acc0db65742 100644
--- a/addons/sale_mrp/sale_mrp.py
+++ b/addons/sale_mrp/sale_mrp.py
@@ -100,3 +100,13 @@ class stock_move(osv.osv):
if res and move.procurement_id and move.procurement_id.property_ids:
res['property_ids'] = [(6, 0, [x.id for x in move.procurement_id.property_ids])]
return res
+
+ def _action_explode(self, cr, uid, move, context=None):
+ """ Explodes pickings.
+ @param move: Stock moves
+ @return: True
+ """
+ if context is None:
+ context = {}
+ property_ids = map(int, move.procurement_id.sale_line_id.property_ids or [])
+ return super(stock_move, self)._action_explode(cr, uid, move, context=dict(context, property_ids=property_ids))
diff --git a/addons/sale_mrp/tests/test_move_explode.py b/addons/sale_mrp/tests/test_move_explode.py
index 88dab7135b1..ff9cfe0feef 100644
--- a/addons/sale_mrp/tests/test_move_explode.py
+++ b/addons/sale_mrp/tests/test_move_explode.py
@@ -33,6 +33,7 @@ class TestMoveExplode(common.TransactionCase):
self.sale_order_line = self.registry('sale.order.line')
self.sale_order = self.registry('sale.order')
self.mrp_bom = self.registry('mrp.bom')
+ self.product = self.registry('product.product')
#product that has a phantom bom
self.product_bom_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_3')[1]
@@ -40,9 +41,24 @@ class TestMoveExplode(common.TransactionCase):
self.bom_id = self.ir_model_data.get_object_reference(cr, uid, 'mrp', 'mrp_bom_9')[1]
#partner agrolait
self.partner_id = self.ir_model_data.get_object_reference(cr, uid, 'base', 'res_partner_1')[1]
+ #bom: PC Assemble (with property: DDR 512MB)
+ self.bom_prop_id = self.ir_model_data.get_object_reference(cr, uid, 'mrp', 'mrp_bom_property_0')[1]
+
+ self.template_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_3_product_template')[1]
+ #property: DDR 512MB
+ self.mrp_property_id = self.ir_model_data.get_object_reference(cr, uid, 'mrp', 'mrp_property_0')[1]
+ #product: RAM SR2
+ self.product_bom_prop_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_14')[1]
+ #phantom bom for RAM SR2 with three lines containing properties
+ self.bom_prop_line_id = self.ir_model_data.get_object_reference(cr, uid, 'mrp', 'mrp_bom_property_line')[1]
+ #product: iPod included in the phantom bom
+ self.product_A_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_11')[1]
+ #product: Mouse, Wireless included in the phantom bom
+ self.product_B_id = self.ir_model_data.get_object_reference(cr, uid, 'product', 'product_product_12')[1]
+
def test_00_sale_move_explode(self):
- """check that when creating a sale order with a product that has a phantom BoM, move explode into content of the
+ """check that when creating a sale order with a product that has a phantom BoM, move explode into content of the
BoM"""
cr, uid, context = self.cr, self.uid, {}
#create sale order with one sale order line containing product with a phantom bom
@@ -57,3 +73,19 @@ class TestMoveExplode(common.TransactionCase):
bom = self.mrp_bom.browse(cr, uid, self.bom_id, context=context)
bom_component_length = self.mrp_bom._bom_explode(cr, uid, bom, self.product_bom_id, 1, [])
self.assertEqual(len(move_ids), len(bom_component_length[0]))
+
+ def test_00_bom_find(self):
+ """Check that _bom_find searches the bom corresponding to the properties passed or takes the bom with the smallest
+ sequence."""
+ cr, uid, context = self.cr, self.uid, {}
+ res_id = self.mrp_bom._bom_find(cr, uid, product_tmpl_id=self.template_id, product_id=None, properties=[self.mrp_property_id], context=context)
+ self.assertEqual(res_id, self.bom_prop_id)
+
+ def test_00_bom_explode(self):
+ """Check that _bom_explode only takes the lines with the right properties."""
+ cr, uid, context = self.cr, self.uid, {}
+ bom = self.mrp_bom.browse(cr, uid, self.bom_prop_line_id)
+ product = self.product.browse(cr, uid, self.product_bom_prop_id)
+ res = self.mrp_bom._bom_explode(cr, uid, bom, product, 1, properties=[self.mrp_property_id], context=context)
+ res = set([p['product_id'] for p in res[0]])
+ self.assertEqual(res, set([self.product_A_id, self.product_B_id]))
diff --git a/addons/sale_order_dates/sale_order_dates.py b/addons/sale_order_dates/sale_order_dates.py
index 9b341e69974..2a2416d1125 100644
--- a/addons/sale_order_dates/sale_order_dates.py
+++ b/addons/sale_order_dates/sale_order_dates.py
@@ -61,6 +61,8 @@ class sale_order_dates(osv.osv):
dates_list = []
order_datetime = datetime.strptime(order.date_order, DEFAULT_SERVER_DATETIME_FORMAT)
for line in order.order_line:
+ if line.state == 'cancel':
+ continue
dt = order_datetime + timedelta(days=line.delay or 0.0)
dt_s = dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
dates_list.append(dt_s)
diff --git a/addons/sale_stock/__openerp__.py b/addons/sale_stock/__openerp__.py
index 665e423639f..4bdd595d3ce 100644
--- a/addons/sale_stock/__openerp__.py
+++ b/addons/sale_stock/__openerp__.py
@@ -64,6 +64,7 @@ You can choose flexible invoicing methods:
'test/picking_order_policy.yml',
'test/prepaid_order_policy.yml',
'test/sale_order_onchange.yml',
+ 'test/sale_order_canceled_line.yml',
],
'installable': True,
'auto_install': True,
diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py
index 0ef83c5f507..ef6edf9e5d9 100644
--- a/addons/sale_stock/sale_stock.py
+++ b/addons/sale_stock/sale_stock.py
@@ -196,6 +196,8 @@ class sale_order(osv.osv):
def has_stockable_products(self, cr, uid, ids, *args):
for order in self.browse(cr, uid, ids):
for order_line in order.order_line:
+ if order_line.state == 'cancel':
+ continue
if order_line.product_id and order_line.product_id.type in ('product', 'consu'):
return True
return False
@@ -280,6 +282,29 @@ class sale_order_line(osv.osv):
return {'value': result, 'warning': warning}
+ def _check_routing(self, cr, uid, ids, product, warehouse_id, context=None):
+ """ Verify the route of the product based on the warehouse
+ return True if the product availibility in stock does not need to be verified
+ """
+ is_available = False
+ if warehouse_id:
+ warehouse = self.pool['stock.warehouse'].browse(cr, uid, warehouse_id, context=context)
+ for product_route in product.route_ids:
+ if warehouse.mto_pull_id and warehouse.mto_pull_id.route_id and warehouse.mto_pull_id.route_id.id == product_route.id:
+ is_available = True
+ break
+ else:
+ try:
+ mto_route_id = self.pool['stock.warehouse']._get_mto_route(cr, uid, context=context)
+ except osv.except_osv:
+ # if route MTO not found in ir_model_data, we treat the product as in MTS
+ mto_route_id = False
+ if mto_route_id:
+ for product_route in product.route_ids:
+ if product_route.id == mto_route_id:
+ is_available = True
+ break
+ return is_available
def product_id_change_with_wh(self, cr, uid, ids, pricelist, product, qty=0,
uom=False, qty_uos=0, uos=False, name='', partner_id=False,
@@ -287,7 +312,6 @@ class sale_order_line(osv.osv):
context = context or {}
product_uom_obj = self.pool.get('product.uom')
product_obj = self.pool.get('product.product')
- warehouse_obj = self.pool['stock.warehouse']
warning = {}
#UoM False due to hack which makes sure uom changes price, ... in product_id_change
res = self.product_id_change(cr, uid, ids, pricelist, product, qty=qty,
@@ -316,28 +340,11 @@ class sale_order_line(osv.osv):
warning_msgs = res_packing.get('warning') and res_packing['warning']['message'] or ''
if product_obj.type == 'product':
- #determine if the product is MTO or not (for a further check)
- isMto = False
- if warehouse_id:
- warehouse = warehouse_obj.browse(cr, uid, warehouse_id, context=context)
- for product_route in product_obj.route_ids:
- if warehouse.mto_pull_id and warehouse.mto_pull_id.route_id and warehouse.mto_pull_id.route_id.id == product_route.id:
- isMto = True
- break
- else:
- try:
- mto_route_id = warehouse_obj._get_mto_route(cr, uid, context=context)
- except:
- # if route MTO not found in ir_model_data, we treat the product as in MTS
- mto_route_id = False
- if mto_route_id:
- for product_route in product_obj.route_ids:
- if product_route.id == mto_route_id:
- isMto = True
- break
+ #determine if the product needs further check for stock availibility
+ is_available = self._check_routing(cr, uid, ids, product_obj, warehouse_id, context=context)
#check if product is available, and if not: raise a warning, but do this only for products that aren't processed in MTO
- if not isMto:
+ if not is_available:
uom_record = False
if uom:
uom_record = product_uom_obj.browse(cr, uid, uom, context=context)
@@ -362,6 +369,14 @@ class sale_order_line(osv.osv):
res.update({'warning': warning})
return res
+ def button_cancel(self, cr, uid, ids, context=None):
+ lines = self.browse(cr, uid, ids, context=context)
+ for procurement in lines.mapped('procurement_ids'):
+ for move in procurement.move_ids:
+ if move.state == 'done' and not move.scrapped:
+ raise osv.except_osv(_('Invalid Action!'), _('You cannot cancel a sale order line which is linked to a stock move already done.'))
+ return super(sale_order_line, self).button_cancel(cr, uid, ids, context=context)
+
class stock_move(osv.osv):
_inherit = 'stock.move'
@@ -388,7 +403,7 @@ class stock_move(osv.osv):
if move.procurement_id and move.procurement_id.sale_line_id and move.procurement_id.sale_line_id.order_id.order_policy == 'picking':
sale_order = move.procurement_id.sale_line_id.order_id
return sale_order.partner_invoice_id, sale_order.user_id.id, sale_order.pricelist_id.currency_id.id
- elif move.picking_id.sale_id:
+ elif move.picking_id.sale_id and context.get('inv_type') in ('out_invoice', 'out_refund'):
# In case of extra move, it is better to use the same data as the original moves
sale_order = move.picking_id.sale_id
return sale_order.partner_invoice_id, sale_order.user_id.id, sale_order.pricelist_id.currency_id.id
@@ -396,7 +411,7 @@ class stock_move(osv.osv):
def _get_invoice_line_vals(self, cr, uid, move, partner, inv_type, context=None):
res = super(stock_move, self)._get_invoice_line_vals(cr, uid, move, partner, inv_type, context=context)
- if move.procurement_id and move.procurement_id.sale_line_id:
+ if inv_type in ('out_invoice', 'out_refund') and move.procurement_id and move.procurement_id.sale_line_id:
sale_line = move.procurement_id.sale_line_id
res['invoice_line_tax_ids'] = [(6, 0, [x.id for x in sale_line.tax_id])]
res['account_analytic_id'] = sale_line.order_id.project_id and sale_line.order_id.project_id.id or False
@@ -420,6 +435,10 @@ class stock_move(osv.osv):
extra_move_tax[move.picking_id, move.product_id] = [(6, 0, [x.id for x in move.procurement_id.sale_line_id.tax_id])]
return (is_extra_move, extra_move_tax)
+ def _get_taxes(self, cr, uid, move, context=None):
+ if move.procurement_id.sale_line_id.tax_id:
+ return [tax.id for tax in move.procurement_id.sale_line_id.tax_id]
+ return super(stock_move, self)._get_taxes(cr, uid, move, context=context)
class stock_location_route(osv.osv):
_inherit = "stock.location.route"
@@ -474,6 +493,7 @@ class stock_picking(osv.osv):
'user_id': sale.user_id.id,
'team_id': sale.team_id.id,
'name': sale.client_order_ref or '',
+ 'comment': sale.note,
})
return inv_vals
diff --git a/addons/sale_stock/test/sale_order_canceled_line.yml b/addons/sale_stock/test/sale_order_canceled_line.yml
new file mode 100644
index 00000000000..4e229fc4d66
--- /dev/null
+++ b/addons/sale_stock/test/sale_order_canceled_line.yml
@@ -0,0 +1,45 @@
+-
+ I create a draft Sale Order with 2 lines but 1 canceled in order to check if the canceled lines are not considered in the logic
+-
+ !record {model: sale.order, id: sale_order_cl_3}:
+ partner_id: base.res_partner_15
+ partner_invoice_id: base.res_partner_address_25
+ partner_shipping_id: base.res_partner_address_25
+ pricelist_id: product.list0
+ order_policy: manual
+-
+ !record {model: sale.order.line, id: sale_order_cl_3_line_1}:
+ order_id: sale_order_cl_3
+ product_id: product.product_product_27
+ product_uom_qty: 1
+ product_uom: 1
+ price_unit: 3645
+ name: 'Laptop Customized'
+-
+ !record {model: sale.order.line, id: sale_order_cl_3_line_2}:
+ order_id: sale_order_cl_3
+ product_id: product.product_product_12
+ product_uom_qty: 1
+ product_uom: 1
+ price_unit: 12.50
+ name: 'Mouse, Wireless'
+-
+ I cancel the first line
+-
+ !python {model: sale.order.line, id: sale_order_cl_3_line_1}: |
+ self.button_cancel()
+-
+ I confirm the sale order
+-
+ !workflow {model: sale.order, action: order_confirm, ref: sale_order_cl_3}
+-
+ I check that no procurement has been generated for the canceled line
+-
+ !assert {model: sale.order.line, id: sale_order_cl_3_line_1, string: The canceled line should not have a procurement}:
+ - not procurement_ids
+-
+ I check that we have only 1 stock move, for the not canceled line
+-
+ !python {model: sale.order, id: sale_order_cl_3}: |
+ moves = self.picking_ids.mapped('move_lines')
+ assert len(moves) == 1, "We should have 1 move, got %s" % len(moves)
diff --git a/addons/sales_team/sales_team.py b/addons/sales_team/sales_team.py
index 24c5519e977..2ad65c3c2d0 100644
--- a/addons/sales_team/sales_team.py
+++ b/addons/sales_team/sales_team.py
@@ -34,6 +34,28 @@ class crm_team(osv.Model):
def get_full_name(self, cr, uid, ids, field_name, arg, context=None):
return dict(self.name_get(cr, uid, ids, context=context))
+ def _get_default_team_id(self, cr, uid, user_id=None, context=None):
+ team_id = self._resolve_team_id_from_context(cr, uid, context=context) or False
+ if not team_id:
+ team_ids = self.search(cr, uid, [('member_ids', '=', user_id or uid)], limit=1, context=context)
+ team_id = team_ids[0] if team_ids else False
+ return team_id
+
+ def _resolve_team_id_from_context(self, cr, uid, context=None):
+ """ Returns ID of team based on the value of 'default_team_id'
+ context key, or None if it cannot be resolved to a single
+ Sales Team.
+ """
+ if context is None:
+ context = {}
+ if type(context.get('default_team_id')) in (int, long):
+ return context.get('default_team_id')
+ if isinstance(context.get('default_team_id'), basestring):
+ team_ids = self.name_search(cr, uid, name=context['default_team_id'], context=context)
+ if len(team_ids) == 1:
+ return int(team_ids[0][0])
+ return None
+
_columns = {
'name': fields.char('Sales Team', size=64, required=True, translate=True),
'complete_name': fields.function(get_full_name, type='char', size=256, readonly=True, store=True, string="Full Name"),
diff --git a/addons/share/wizard/share_wizard.py b/addons/share/wizard/share_wizard.py
index 4813dc865bf..b9b6cf4f6e9 100644
--- a/addons/share/wizard/share_wizard.py
+++ b/addons/share/wizard/share_wizard.py
@@ -493,7 +493,7 @@ class share_wizard(osv.TransientModel):
# already granted
for dummy, model in fields_relations:
# mail.message is transversal: it should not received directly the access rights
- if model.model in ['mail.message', 'mail.notification']: continue
+ if model.model in ['mail.message', 'mail.notification', 'res.company']: continue
values = {
'name': _('Copied access for sharing'),
'group_id': group_id,
@@ -600,8 +600,8 @@ class share_wizard(osv.TransientModel):
# other groups, so we duplicate if needed
rule = self._check_personal_rule_or_duplicate(cr, group_id, rule, context=context)
eval_ctx = rule_obj._eval_context_for_combinations()
- org_domain = expression.normalize_domain(eval(rule.domain_force, eval_ctx))
- new_clause = expression.normalize_domain(eval(domain, eval_ctx))
+ org_domain = expression.normalize_domain(safe_eval(rule.domain_force, eval_ctx))
+ new_clause = expression.normalize_domain(safe_eval(domain, eval_ctx))
combined_domain = expression.AND([new_clause, org_domain])
rule.write({'domain_force': combined_domain, 'name': rule.name + _('(Modified)')})
_logger.debug("Combining sharing rule %s on model %s with domain: %s", rule.id, model_id, domain)
@@ -625,7 +625,7 @@ class share_wizard(osv.TransientModel):
if domain:
for rel_field, model in fields_relations:
# mail.message is transversal: it should not received directly the access rights
- if model.model in ['mail.message', 'mail.notification']: continue
+ if model.model in ['mail.message', 'mail.notification', 'res.company']: continue
related_domain = []
if not rel_field: continue
for element in domain:
diff --git a/addons/stock/procurement.py b/addons/stock/procurement.py
index 7f3a1534991..32a4a5c4817 100644
--- a/addons/stock/procurement.py
+++ b/addons/stock/procurement.py
@@ -510,7 +510,7 @@ class procurement_order(osv.osv):
qty -= subtract_qty[op.id]
qty_rounded = float_round(qty, precision_rounding=op.product_uom.rounding)
- if qty_rounded >= 0:
+ if qty_rounded > 0:
proc_id = procurement_obj.create(cr, uid,
self._prepare_orderpoint_procurement(cr, uid, op, qty_rounded, context=context),
context=context)
diff --git a/addons/stock/product.py b/addons/stock/product.py
index 0129ae84965..748a41f751d 100644
--- a/addons/stock/product.py
+++ b/addons/stock/product.py
@@ -280,7 +280,9 @@ class product_product(osv.osv):
}
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
- res = super(product_product,self).fields_view_get(cr, uid, view_id, view_type, context, toolbar=toolbar, submenu=submenu)
+ res = super(product_product, self).fields_view_get(
+ cr, uid, view_id=view_id, view_type=view_type, context=context,
+ toolbar=toolbar, submenu=submenu)
if context is None:
context = {}
if ('location' in context) and context['location']:
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index aa31f70caed..471b1282821 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -794,7 +794,7 @@ class stock_picking(osv.osv):
'name': fields.char('Reference', select=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, copy=False),
'origin': fields.char('Source Document', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="Reference of the document", select=True),
'backorder_id': fields.many2one('stock.picking', 'Back Order of', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="If this shipment was split, then this field links to the shipment which contains the already processed part.", select=True, copy=False),
- 'note': fields.text('Notes', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}),
+ 'note': fields.text('Notes'),
'move_type': fields.selection([('direct', 'Partial'), ('one', 'All at once')], 'Delivery Method', required=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="It specifies goods to be deliver partially or all at once"),
'state': fields.function(_state_get, type="selection", copy=False,
store={
@@ -953,6 +953,12 @@ class stock_picking(osv.osv):
return super(stock_picking, self).unlink(cr, uid, ids, context=context)
def write(self, cr, uid, ids, vals, context=None):
+ if vals.get('move_lines') and not vals.get('pack_operation_ids'):
+ # pack operations are directly dependant of move lines, it needs to be recomputed
+ pack_operation_obj = self.pool['stock.pack.operation']
+ existing_package_ids = pack_operation_obj.search(cr, uid, [('picking_id', 'in', ids)], context=context)
+ if existing_package_ids:
+ pack_operation_obj.unlink(cr, uid, existing_package_ids, context)
res = super(stock_picking, self).write(cr, uid, ids, vals, context=context)
#if we changed the move lines or the pack operations, we need to recompute the remaining quantities of both
if 'move_lines' in vals or 'pack_operation_ids' in vals:
@@ -1621,7 +1627,7 @@ class stock_production_lot(osv.osv):
# Move
# ----------------------------------------------------
-class stock_move(models.Model):
+class stock_move(osv.osv):
_name = "stock.move"
_description = "Stock Move"
_order = 'picking_id, sequence, id'
@@ -1642,11 +1648,12 @@ class stock_move(models.Model):
res.append((line.id, name))
return res
- @api.depends('product_id', 'product_uom_qty', 'product_uom', 'product_id.uom_id')
- def _quantity_normalize(self):
+ def _quantity_normalize(self, cr, uid, ids, name, args, context=None):
uom_obj = self.pool.get('product.uom')
- for m in self:
- m.product_qty = uom_obj._compute_qty_obj(self.env.cr, self.env.uid, m.product_uom, m.product_uom_qty, m.product_id.uom_id, context=self.env.context)
+ res = {}
+ for m in self.browse(cr, uid, ids, context=context):
+ res[m.id] = uom_obj._compute_qty_obj(cr, uid, m.product_uom, m.product_uom_qty, m.product_id.uom_id, context=context)
+ return res
def _get_remaining_qty(self, cr, uid, ids, field_name, args, context=None):
uom_obj = self.pool.get('product.uom')
@@ -1730,6 +1737,11 @@ class stock_move(models.Model):
res += [x.id for x in picking.move_lines]
return res
+ def _get_moves_from_prod(self, cr, uid, ids, context=None):
+ if ids:
+ return self.pool.get('stock.move').search(cr, uid, [('product_id', 'in', ids)], context=context)
+ return []
+
def _set_product_qty(self, cr, uid, id, field, value, arg, context=None):
""" The meaning of product_qty field changed lately and is now a functional field computing the quantity
in the default product UoM. This code has been added to raise an error if a write is made given a value
@@ -1738,9 +1750,6 @@ class stock_move(models.Model):
"""
raise UserError(_('The requested operation cannot be processed because of a programming error setting the `product_qty` field instead of the `product_uom_qty`.'))
- product_qty = new_fields.Float(compute='_quantity_normalize', inverse='_set_product_qty', digits=0, store=True, string='Quantity',
- help='Quantity in the default UoM of the product')
-
_columns = {
'sequence': fields.integer('Sequence'),
'name': fields.char('Description', required=True, select=True),
@@ -1749,6 +1758,8 @@ class stock_move(models.Model):
'date': fields.datetime('Date', required=True, select=True, help="Move date: scheduled date until move is done, then date of actual move processing", states={'done': [('readonly', True)]}),
'date_expected': fields.datetime('Expected Date', states={'done': [('readonly', True)]}, required=True, select=True, help="Scheduled date for the processing of this move"),
'product_id': fields.many2one('product.product', 'Product', required=True, select=True, domain=[('type', '<>', 'service')], states={'done': [('readonly', True)]}),
+ 'product_qty': fields.function(_quantity_normalize, fnct_inv=_set_product_qty, type='float', digits=0, store=True, string='Quantity',
+ help='Quantity in the default UoM of the product'),
'product_uom_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'),
required=True, states={'done': [('readonly', True)]},
help="This is the quantity of products from an inventory "
@@ -2353,7 +2364,7 @@ class stock_move(models.Model):
if todo:
ids = self.action_confirm(cr, uid, todo, context=context)
pickings = set()
- procurement_ids = []
+ procurement_ids = set()
#Search operations that are linked to the moves
operations = set()
move_qty = {}
@@ -2417,7 +2428,7 @@ class stock_move(models.Model):
move_dest_ids.add(move.move_dest_id.id)
if move.procurement_id:
- procurement_ids.append(move.procurement_id.id)
+ procurement_ids.add(move.procurement_id.id)
#unreserve the quants and make them available for other operations/moves
quant_obj.quants_unreserve(cr, uid, move, context=context)
@@ -2425,7 +2436,7 @@ class stock_move(models.Model):
self._check_package_from_moves(cr, uid, ids, context=context)
#set the move as done
self.write(cr, uid, ids, {'state': 'done', 'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
- self.pool.get('procurement.order').check(cr, uid, procurement_ids, context=context)
+ self.pool.get('procurement.order').check(cr, uid, list(procurement_ids), context=context)
#assign destination moves
if move_dest_ids:
self.action_assign(cr, uid, list(move_dest_ids), context=context)
@@ -2564,6 +2575,8 @@ class stock_move(models.Model):
code = 'incoming'
return code
+ def _get_taxes(self, cr, uid, move, context=None):
+ return []
class stock_inventory(osv.osv):
_name = "stock.inventory"
@@ -2753,6 +2766,36 @@ class stock_inventory(osv.osv):
vals.append(product_line)
return vals
+ def _check_filter_product(self, cr, uid, ids, context=None):
+ for inventory in self.browse(cr, uid, ids, context=context):
+ if inventory.filter == 'none' and inventory.product_id and inventory.location_id and inventory.lot_id:
+ return True
+ if inventory.filter not in ('product', 'product_owner') and inventory.product_id:
+ return False
+ if inventory.filter != 'lot' and inventory.lot_id:
+ return False
+ if inventory.filter not in ('owner', 'product_owner') and inventory.partner_id:
+ return False
+ if inventory.filter != 'pack' and inventory.package_id:
+ return False
+ return True
+
+ def onchange_filter(self, cr, uid, ids, filter, context=None):
+ to_clean = { 'value': {} }
+ if filter not in ('product', 'product_owner'):
+ to_clean['value']['product_id'] = False
+ if filter != 'lot':
+ to_clean['value']['lot_id'] = False
+ if filter not in ('owner', 'product_owner'):
+ to_clean['value']['partner_id'] = False
+ if filter != 'pack':
+ to_clean['value']['package_id'] = False
+ return to_clean
+
+ _constraints = [
+ (_check_filter_product, 'The selected inventory options are not coherent.',
+ ['filter', 'product_id', 'lot_id', 'partner_id', 'package_id']),
+ ]
class stock_inventory_line(osv.osv):
_name = "stock.inventory.line"
@@ -2811,6 +2854,7 @@ class stock_inventory_line(osv.osv):
_defaults = {
'product_qty': 0,
+ 'product_uom_id': lambda self, cr, uid, ctx=None: self.pool['ir.model.data'].get_object_reference(cr, uid, 'product', 'product_uom_unit')[1]
}
def create(self, cr, uid, values, context=None):
diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml
index 9780a458deb..6beb2b0fa96 100644
--- a/addons/stock/stock_view.xml
+++ b/addons/stock/stock_view.xml
@@ -109,7 +109,7 @@
<group>
<group>
<field name="location_id" domain="[('usage','not in', ['supplier','production'])]"/>
- <field name="filter" string="Inventory of" widget='radio' attrs="{'readonly': [('state', '!=', 'draft')]}"/>
+ <field name="filter" string="Inventory of" widget='radio' attrs="{'readonly': [('state', '!=', 'draft')]}" on_change="onchange_filter(filter)"/>
</group>
<group>
<field name="date"/>
@@ -1222,7 +1222,7 @@
<filter icon="terp-stock" string="To Do" name="future" domain="[('state','in',('assigned','confirmed','waiting'))]" help="Stock moves that are Confirmed, Available or Waiting"/>
<filter icon="terp-dialog-close" string="Done" name="done" domain="[('state','=','done')]" help="Stock moves that have been processed"/>
<separator/>
- <filter string="Today" domain="[('date','&lt;=',datetime.datetime.now().replace(hour=23, minute=59, second=59)),('date','&gt;=',datetime.datetime.now().replace(hour=0, minute=0, second=0))]" help="Orders processed Today or planned for Today"/>
+ <filter string="Today" domain="[('date','&lt;=', datetime.datetime.combine(context_today(), datetime.time(23,59,59))), ('date','&gt;=', datetime.datetime.combine(context_today(), datetime.time(0,0,0)))]" help="Orders processed Today or planned for Today"/>
<field name="product_id"/>
<field name="name" string="Location" filter_domain="['|',('location_id','ilike',self),('location_dest_id','ilike',self)]"/>
<field name="partner_id" string="Partner" filter_domain="[('picking_id.partner_id','child_of',self)]"/>
@@ -1436,6 +1436,7 @@
<group>
<field name="code" on_change="onchange_picking_code(code)"/>
<field name="return_picking_type_id"/>
+ <field name="barcode_nomenclature_id"/>
</group>
</group>
<separator string="Locations"/>
diff --git a/addons/stock/test/inventory.yml b/addons/stock/test/inventory.yml
index a0b0f58512b..4bfc70d3dd1 100644
--- a/addons/stock/test/inventory.yml
+++ b/addons/stock/test/inventory.yml
@@ -9,6 +9,7 @@
-
!record {model: stock.inventory, id: inventory_test0}:
name: Test
+ filter: product
product_id: inventory_product
-
I confirm the inventory and check that my inventory has no line, as the product is a new one.
@@ -60,6 +61,7 @@
-
!record {model: stock.inventory, id: inventory_test1}:
name: second test inventory
+ filter: product
product_id: inventory_product
-
I confirm the inventory and check that my inventory has one line, with a quantity of 10.
diff --git a/addons/stock/wizard/stock_change_product_qty.py b/addons/stock/wizard/stock_change_product_qty.py
index d541338ed2e..63604ca2cb0 100644
--- a/addons/stock/wizard/stock_change_product_qty.py
+++ b/addons/stock/wizard/stock_change_product_qty.py
@@ -94,8 +94,15 @@ class stock_change_product_qty(osv.osv_memory):
ctx = context.copy()
ctx['location'] = data.location_id.id
ctx['lot_id'] = data.lot_id.id
+ if data.product_id.id and data.lot_id.id:
+ filter = 'none'
+ elif data.product_id.id:
+ filter = 'product'
+ else:
+ filter = 'none'
inventory_id = inventory_obj.create(cr, uid, {
'name': _('INV: %s') % tools.ustr(data.product_id.name),
+ 'filter': filter,
'product_id': data.product_id.id,
'location_id': data.location_id.id,
'lot_id': data.lot_id.id}, context=context)
@@ -113,3 +120,11 @@ class stock_change_product_qty(osv.osv_memory):
inventory_line_obj.create(cr , uid, line_data, context=context)
inventory_obj.action_done(cr, uid, [inventory_id], context=context)
return {}
+
+ def onchange_location_id(self, cr, uid, ids, location_id, product_id, context=None):
+ if location_id:
+ qty_wh = 0.0
+ qty = self.pool.get('product.product')._product_available(cr, uid, [product_id], context=dict(context or {}, location=location_id))
+ if product_id in qty:
+ qty_wh = qty[product_id]['qty_available']
+ return { 'value': { 'new_quantity': qty_wh } }
diff --git a/addons/stock/wizard/stock_change_product_qty_view.xml b/addons/stock/wizard/stock_change_product_qty_view.xml
index 7c11189d1c3..4bf556fcaef 100644
--- a/addons/stock/wizard/stock_change_product_qty_view.xml
+++ b/addons/stock/wizard/stock_change_product_qty_view.xml
@@ -7,9 +7,9 @@
<field name="arch" type="xml">
<form string="Update Product Quantity">
<group>
- <field name="new_quantity" />
<field name="product_id" invisible="1"/>
- <field name="location_id" groups="stock.group_locations"/>
+ <field name="location_id" groups="stock.group_locations" on_change="onchange_location_id(location_id, product_id, context)"/>
+ <field name="new_quantity"/>
<field name="lot_id" context="{'search_default_product_id':product_id,'default_product_id':product_id}" groups="stock.group_production_lot"/>
<p groups="stock.group_production_lot" class="oe_grey">
When you select a serial number (lot), the quantity is corrected with respect to
diff --git a/addons/stock/wizard/stock_transfer_details.py b/addons/stock/wizard/stock_transfer_details.py
index ed03413f5fa..437509aacf3 100644
--- a/addons/stock/wizard/stock_transfer_details.py
+++ b/addons/stock/wizard/stock_transfer_details.py
@@ -48,7 +48,8 @@ class stock_transfer_details(models.TransientModel):
picking = self.pool.get('stock.picking').browse(cr, uid, picking_id, context=context)
items = []
packs = []
- picking.do_prepare_partial()
+ if not picking.pack_operation_ids:
+ picking.do_prepare_partial()
for op in picking.pack_operation_ids:
item = {
'packop_id': op.id,
@@ -90,7 +91,7 @@ class stock_transfer_details(models.TransientModel):
'owner_id': prod.owner_id.id,
}
if prod.packop_id:
- prod.packop_id.write(pack_datas)
+ prod.packop_id.with_context(no_recompute=True).write(pack_datas)
processed_ids.append(prod.packop_id.id)
else:
pack_datas['picking_id'] = self.picking_id.id
@@ -98,8 +99,7 @@ class stock_transfer_details(models.TransientModel):
processed_ids.append(packop_id.id)
# Delete the others
packops = self.env['stock.pack.operation'].search(['&', ('picking_id', '=', self.picking_id.id), '!', ('id', 'in', processed_ids)])
- for packop in packops:
- packop.unlink()
+ packops.unlink()
# Execute the transfer of the picking
self.picking_id.do_transfer()
diff --git a/addons/stock_account/product.py b/addons/stock_account/product.py
index 60d93990831..52e1432d7af 100644
--- a/addons/stock_account/product.py
+++ b/addons/stock_account/product.py
@@ -60,6 +60,12 @@ class product_template(osv.osv):
'valuation': 'manual_periodic',
}
+ def onchange_type(self, cr, uid, ids, type):
+ res = super(product_template, self).onchange_type(cr, uid, ids, type)
+ if type in ('consu', 'service'):
+ res = {'value': {'valuation': 'manual_periodic'}}
+ return res
+
@api.multi
def _get_product_accounts(self):
""" To get the stock input account, stock output account and stock journal related to product.
@@ -132,6 +138,13 @@ class product_template(osv.osv):
return True
+class product_product(osv.osv):
+ _inherit = 'product.product'
+ def onchange_type(self, cr, uid, ids, type):
+ res = super(product_product, self).onchange_type(cr, uid, ids, type)
+ if type in ('consu', 'service'):
+ res = {'value': {'valuation': 'manual_periodic'}}
+ return res
class product_category(osv.osv):
_inherit = 'product.category'
_columns = {
diff --git a/addons/stock_account/product_view.xml b/addons/stock_account/product_view.xml
index e562b508cf2..02942a67919 100644
--- a/addons/stock_account/product_view.xml
+++ b/addons/stock_account/product_view.xml
@@ -39,9 +39,9 @@
</xpath>
<xpath expr="//group[@name='properties']" position="before">
<group groups="stock_account.group_inventory_valuation" attrs="{'invisible': [('type','=','service')]}">
- <separator string="Inventory Valuation" colspan="4"/>
+ <separator string="Inventory Valuation" attrs="{'invisible':[('type', 'in', ('service', 'consu'))]}" colspan="4"/>
<group colspan="2" col="2">
- <field name="valuation" attrs="{'readonly':[('type', '=', 'service')]}"/>
+ <field name="valuation" attrs="{'invisible':[('type', 'in', ('service', 'consu'))]}"/>
</group>
<group colspan="2" col="2">
<field name="property_stock_account_input" attrs="{'invisible':[('valuation', '!=', 'real_time')]}"
diff --git a/addons/stock_account/stock.py b/addons/stock_account/stock.py
index ed8d7ecaaf1..0d379299d0a 100644
--- a/addons/stock_account/stock.py
+++ b/addons/stock_account/stock.py
@@ -147,6 +147,9 @@ class stock_move(osv.osv):
if move.product_uos:
uos_id = move.product_uos.id
quantity = move.product_uos_qty
+
+ taxes_ids = self._get_taxes(cr, uid, move, context=context)
+
return {
'name': move.name,
'account_id': account_id,
@@ -154,6 +157,7 @@ class stock_move(osv.osv):
'uos_id': uos_id,
'quantity': quantity,
'price_unit': self._get_price_unit_invoice(cr, uid, move, inv_type),
+ 'invoice_line_tax_ids': [(6, 0, taxes_ids)],
'discount': 0.0,
'account_analytic_id': False,
'move_id': move.id,
@@ -306,6 +310,7 @@ class stock_picking(osv.osv):
move_obj = self.pool.get('stock.move')
invoices = {}
is_extra_move, extra_move_tax = move_obj._get_moves_taxes(cr, uid, moves, context=context)
+ product_price_unit = {}
for move in moves:
company = move.company_id
origin = move.picking_id.name
@@ -331,8 +336,12 @@ class stock_picking(osv.osv):
invoice_line_vals = move_obj._get_invoice_line_vals(cr, uid, move, partner, inv_type, context=context)
invoice_line_vals['invoice_id'] = invoices[key]
invoice_line_vals['origin'] = origin
+ if not is_extra_move[move.id]:
+ product_price_unit[invoice_line_vals['product_id']] = invoice_line_vals['price_unit']
+ if is_extra_move[move.id] and invoice_line_vals['product_id'] in product_price_unit:
+ invoice_line_vals['price_unit'] = product_price_unit[invoice_line_vals['product_id']]
if is_extra_move[move.id] and extra_move_tax[move.picking_id, move.product_id]:
- invoice_line_vals['invoice_line_tax_id'] = extra_move_tax[move.picking_id, move.product_id]
+ invoice_line_vals['invoice_line_tax_ids'] = extra_move_tax[move.picking_id, move.product_id]
move_obj._create_invoice_line_from_vals(cr, uid, move, invoice_line_vals, context=context)
move_obj.write(cr, uid, move.id, {'invoice_state': 'invoiced'}, context=context)
diff --git a/addons/stock_account/stock_account.py b/addons/stock_account/stock_account.py
index 50148549fd1..e98eeae4ff5 100644
--- a/addons/stock_account/stock_account.py
+++ b/addons/stock_account/stock_account.py
@@ -308,9 +308,9 @@ class stock_quant(osv.osv):
valuation_amount = context.get('force_valuation_amount')
else:
if move.product_id.cost_method == 'average':
- valuation_amount = move.location_id.usage != 'internal' and move.location_dest_id.usage == 'internal' and cost or move.product_id.standard_price
+ valuation_amount = cost if move.location_id.usage != 'internal' and move.location_dest_id.usage == 'internal' else move.product_id.standard_price
else:
- valuation_amount = move.product_id.cost_method == 'real' and cost or move.product_id.standard_price
+ valuation_amount = cost if move.product_id.cost_method == 'real' else move.product_id.standard_price
#the standard_price of the product may be in another decimal precision, or not compatible with the coinage of
#the company currency... so we need to use round() before creating the accounting entries.
valuation_amount = currency_obj.round(cr, uid, move.company_id.currency_id, valuation_amount * qty)
@@ -392,7 +392,8 @@ class stock_move(osv.osv):
average_valuation_price += q.qty * q.cost
average_valuation_price = average_valuation_price / move.product_qty
# Write the standard price, as SUPERUSER_ID because a warehouse manager may not have the right to write on products
- product_obj.write(cr, SUPERUSER_ID, [move.product_id.id], {'standard_price': average_valuation_price}, context=context)
+ ctx = dict(context, force_company=move.company_id.id)
+ product_obj.write(cr, SUPERUSER_ID, [move.product_id.id], {'standard_price': average_valuation_price}, context=ctx)
self.write(cr, uid, [move.id], {'price_unit': average_valuation_price}, context=context)
def product_price_update_before_done(self, cr, uid, ids, context=None):
@@ -417,7 +418,8 @@ class stock_move(osv.osv):
new_std_price = ((amount_unit * product_avail) + (move.price_unit * move.product_qty)) / (product_avail + move.product_qty)
tmpl_dict[prod_tmpl_id] += move.product_qty
# Write the standard price, as SUPERUSER_ID because a warehouse manager may not have the right to write on products
- product_obj.write(cr, SUPERUSER_ID, [product.id], {'standard_price': new_std_price}, context=context)
+ ctx = dict(context or {}, force_company=move.company_id.id)
+ product_obj.write(cr, SUPERUSER_ID, [product.id], {'standard_price': new_std_price}, context=ctx)
def product_price_update_after_done(self, cr, uid, ids, context=None):
'''
diff --git a/addons/stock_account/wizard/stock_valuation_history.py b/addons/stock_account/wizard/stock_valuation_history.py
index a5f6774db53..99aa9511a9d 100644
--- a/addons/stock_account/wizard/stock_valuation_history.py
+++ b/addons/stock_account/wizard/stock_valuation_history.py
@@ -139,7 +139,7 @@ class stock_history(osv.osv):
product_product ON product_product.id = stock_move.product_id
LEFT JOIN
product_template ON product_template.id = product_product.product_tmpl_id
- WHERE stock_move.state = 'done' AND dest_location.usage in ('internal', 'transit')
+ WHERE quant.qty>0 AND stock_move.state = 'done' AND dest_location.usage in ('internal', 'transit')
AND ((source_location.company_id is null and dest_location.company_id is not null) or
(source_location.company_id is not null and dest_location.company_id is null) or source_location.company_id != dest_location.company_id)
) UNION
@@ -173,7 +173,7 @@ class stock_history(osv.osv):
product_product ON product_product.id = stock_move.product_id
LEFT JOIN
product_template ON product_template.id = product_product.product_tmpl_id
- WHERE stock_move.state = 'done' AND source_location.usage in ('internal', 'transit')
+ WHERE quant.qty>0 AND stock_move.state = 'done' AND source_location.usage in ('internal', 'transit')
AND ((dest_location.company_id is null and source_location.company_id is not null) or
(dest_location.company_id is not null and source_location.company_id is null) or dest_location.company_id != source_location.company_id)
))
diff --git a/addons/stock_dropshipping/__init__.py b/addons/stock_dropshipping/__init__.py
index 15df586b7f0..f4a62bbcd49 100644
--- a/addons/stock_dropshipping/__init__.py
+++ b/addons/stock_dropshipping/__init__.py
@@ -18,4 +18,5 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
+import stock_dropshipping
import wizard
diff --git a/addons/stock_dropshipping/stock_dropshipping.py b/addons/stock_dropshipping/stock_dropshipping.py
new file mode 100644
index 00000000000..2fe37ea8565
--- /dev/null
+++ b/addons/stock_dropshipping/stock_dropshipping.py
@@ -0,0 +1,22 @@
+# coding: utf-8
+
+from openerp import models, api
+
+
+class sale_order_line(models.Model):
+ _inherit = 'sale.order.line'
+
+ @api.multi
+ def _check_routing(self, product, warehouse):
+ """ skip stock verification if the route goes from supplier to customer
+ As the product never goes in stock, no need to verify it's availibility
+ """
+ res = super(sale_order_line, self)._check_routing(product, warehouse)
+ if not res:
+ for line in self:
+ for pull_rule in line.route_id.pull_ids:
+ if (pull_rule.picking_type_id.default_location_src_id.usage == 'supplier' and
+ pull_rule.picking_type_id.default_location_dest_id.usage == 'customer'):
+ res = True
+ break
+ return res
diff --git a/addons/stock_dropshipping/test/crossdock.yml b/addons/stock_dropshipping/test/crossdock.yml
index 5ad1f1db187..2c79a1a3754 100644
--- a/addons/stock_dropshipping/test/crossdock.yml
+++ b/addons/stock_dropshipping/test/crossdock.yml
@@ -1,42 +1,58 @@
--
- Create new product without any routes
--
- !record {model: product.product, id: cross_shop_product}:
- name: PCE
- type: product
- categ_id: product.product_category_1
- list_price: 100.0
- standard_price: 70.0
- seller_ids:
- - delay: 1
- name: base.res_partner_2
- min_qty: 2.0
- qty: 5.0
- type: product
- uom_id: product.product_uom_unit
- uom_po_id: product.product_uom_unit
--
- Create a sales order with a line of 100 PCE incoming shipment, with route_id crossdock shipping.
--
- !record {model: sale.order, id: sale_order_crossdock_shpng}:
- partner_id: base.res_partner_4
- note: Create Sales order
- order_line:
- - product_id: cross_shop_product
- product_uom_qty: 100.00
+-
+ Create a supplier.
+-
+ !record {model: res.partner, id: supplier_crossdock}:
+ name: Crossdocking supplier
+-
+ Use the warehouse created in cancellation_propagated.yml.
+-
+ !python {model: stock.warehouse, id: wh_pps}: |
+ assert self.crossdock_route_id.active, 'Crossdock route is not active'
+-
+ Create new product without any routes.
+-
+ !record {model: product.product, id: cross_shop_product}:
+ name: PCE
+ type: product
+ categ_id: product.product_category_1
+ list_price: 100.0
+ standard_price: 70.0
+ seller_ids:
+ - delay: 1
+ name: supplier_crossdock
+ min_qty: 2.0
+ qty: 5.0
+ type: product
+ uom_id: product.product_uom_unit
+ uom_po_id: product.product_uom_unit
+-
+ Create a sales order with a line of 100 PCE incoming shipment, with route_id crossdock shipping.
+-
+ !record {model: sale.order, id: sale_order_crossdock_shpng}:
+ partner_id: base.res_partner_4
+ warehouse_id: wh_pps
+ note: Create Sales order
+ order_line:
+ - product_id: cross_shop_product
+ product_uom_qty: 100.00
-
!python {model: sale.order.line}: |
- route_warehouse0_crossdock = self.pool.get('stock.warehouse').browse(cr, uid, ref('stock.warehouse0')).crossdock_route_id.id
+ route_wh_pps_crossdock = self.pool.get('stock.warehouse').browse(cr, uid, ref('stock_dropshipping.wh_pps')).crossdock_route_id.id
order = self.pool.get('sale.order').browse(cr, uid, ref('sale_order_crossdock_shpng'))
line_ids = [x.id for x in order.order_line]
- self.write(cr, uid, line_ids, {'route_id': route_warehouse0_crossdock})
--
- Confirm sales order
--
- !workflow {model: sale.order, action: order_confirm, ref: sale_order_crossdock_shpng}
--
- Check a quotation was created to a certain supplier and confirm so it becomes a confirmed purchase order
+ self.write(cr, uid, line_ids, {'route_id': route_wh_pps_crossdock})
+-
+ Confirm sales order.
+-
+ !workflow {model: sale.order, action: order_confirm, ref: sale_order_crossdock_shpng}
+-
+ Run the scheduler.
+-
+ !python {model: procurement.order}: |
+ self.run_scheduler(cr, uid)
+-
+ Check a quotation was created for the created supplier and confirm it.
-
!python {model: purchase.order}: |
- po_id = self.search(cr, uid, [('partner_id', '=', ref('base.res_partner_2'))])
+ po_id = self.search(cr, uid, [('partner_id', '=', ref('supplier_crossdock')), ('state','=','draft')])
self.wkf_confirm_order(cr, uid, po_id)
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index e49af164971..b01e8ade9ff 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -35,6 +35,7 @@ from openerp.addons.base.ir.ir_qweb import AssetsBundle, QWebTemplateNotFound
from openerp.modules import get_module_resource
from openerp.tools import topological_sort
from openerp.tools.translate import _
+from openerp.tools import ustr
from openerp import http
from openerp.http import request, serialize_exception as _serialize_exception
@@ -444,14 +445,14 @@ def xml2json_from_elementtree(el, preserve_whitespaces=False):
return res
def content_disposition(filename):
- filename = filename.encode('utf8')
- escaped = urllib2.quote(filename)
+ filename = ustr(filename)
+ escaped = urllib2.quote(filename.encode('utf8'))
browser = request.httprequest.user_agent.browser
version = int((request.httprequest.user_agent.version or '0').split('.')[0])
if browser == 'msie' and version < 9:
return "attachment; filename=%s" % escaped
elif browser == 'safari':
- return "attachment; filename=%s" % filename
+ return u"attachment; filename=%s" % filename
else:
return "attachment; filename*=UTF-8''%s" % escaped
@@ -1182,6 +1183,7 @@ class Binary(http.Controller):
}
except Exception:
args = {'error': "Something horrible happened"}
+ _logger.exception("Fail to upload attachment %s" % ufile.filename)
return out % (simplejson.dumps(callback), simplejson.dumps(args))
@http.route([
@@ -1440,6 +1442,9 @@ class ExportFormat(object):
context = dict(request.context or {}, **params.get('context', {}))
ids = ids or Model.search(domain, 0, False, False, context)
+ if not request.env[model]._is_an_ordinary_table():
+ fields = [field for field in fields if field['name'] != 'id']
+
field_names = map(operator.itemgetter('name'), fields)
import_data = Model.export_data(ids, field_names, self.raw_data, context=context).get('datas',[])
diff --git a/addons/web/static/lib/datejs/globalization/bs-BA.js b/addons/web/static/lib/datejs/globalization/bs-BA.js
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/addons/web/static/lib/datejs/globalization/bs-BA.js
diff --git a/addons/web/static/lib/datejs/globalization/kab_DZ.js b/addons/web/static/lib/datejs/globalization/kab_DZ.js
new file mode 100644
index 00000000000..7cc18e666af
--- /dev/null
+++ b/addons/web/static/lib/datejs/globalization/kab_DZ.js
@@ -0,0 +1,194 @@
+Date.CultureInfo = {
+ /* Culture Name */
+ name: "kab",
+ englishName: "Kabyle (Algaria)",
+ nativeName: "Taqbaylit (Ledzayer)",
+
+ /* Day Name Strings */
+ dayNames: ["acer", "arim", "aram", "ahad", "amhad", "sem", "sed"],
+ abbreviatedDayNames: ["ace.", "ari.", "ara.", "aha.", "amh.", "sem.", "sed."],
+ shortestDayNames: ["ac", "ar", "ra", "ah", "am", "sm", "sd"],
+ firstLetterDayNames: ["c", "r", "m", "h", "a", "s", "d"],
+
+ /* Month Name Strings */
+ monthNames: ["yennayer", "furar", "meɣres", "yebrir", "maggu", "yunyu", "yulyu", "ɣuct", "ctember", "tuber", "number", "bujember"],
+ abbreviatedMonthNames: ["yen.", "fur.", "meɣ.", "yeb.", "mag.", "yun.", "yul.", "ɣuc", "cte.", "tub.", "num.", "buj."],
+ /* AM/PM Designators */
+ amDesignator: "",
+ pmDesignator: "",
+
+ firstDayOfWeek: 1,
+ twoDigitYearMax: 2029,
+
+ /**
+ * The dateElementOrder is based on the order of the
+ * format specifiers in the formatPatterns.DatePattern.
+ *
+ * Example:
+ <pre>
+ shortDatePattern dateElementOrder
+ ------------------ ----------------
+ "M/d/yyyy" "mdy"
+ "dd/MM/yyyy" "dmy"
+ "yyyy-MM-dd" "ymd"
+ </pre>
+ *
+ * The correct dateElementOrder is required by the parser to
+ * determine the expected order of the date elements in the
+ * string being parsed.
+ */
+ dateElementOrder: "dmy",
+
+ /* Standard date and time format patterns */
+ formatPatterns: {
+ shortDate: "dd/MM/yyyy",
+ longDate: "dddd d MMMM yyyy",
+ shortTime: "HH:mm",
+ longTime: "HH:mm:ss",
+ fullDateTime: "dddd d MMMM yyyy HH:mm:ss",
+ sortableDateTime: "yyyy-MM-ddTHH:mm:ss",
+ universalSortableDateTime: "yyyy-MM-dd HH:mm:ssZ",
+ rfc1123: "ddd, dd MMM yyyy HH:mm:ss GMT",
+ monthDay: "d MMMM",
+ yearMonth: "MMMM yyyy"
+ },
+
+ /**
+ * NOTE: If a string format is not parsing correctly, but
+ * you would expect it parse, the problem likely lies below.
+ *
+ * The following regex patterns control most of the string matching
+ * within the parser.
+ *
+ * The Month name and Day name patterns were automatically generated
+ * and in general should be (mostly) correct.
+ *
+ * Beyond the month and day name patterns are natural language strings.
+ * Example: "next", "today", "months"
+ *
+ * These natural language string may NOT be correct for this culture.
+ * If they are not correct, please translate and edit this file
+ * providing the correct regular expression pattern.
+ *
+ * If you modify this file, please post your revised CultureInfo file
+ * to the Datejs Forum located at http://www.datejs.com/forums/.
+ *
+ * Please mark the subject of the post with [CultureInfo]. Example:
+ * Subject: [CultureInfo] Translated "da-DK" Danish(Denmark)
+ *
+ * We will add the modified patterns to the master source files.
+ *
+ * As well, please review the list of "Future Strings" section below.
+ */
+ regexPatterns: {
+ jan: /^yenna(.(yer)?)?/i,
+ feb: /^fur(.(ar)?)?/i,
+ mar: /^meɣ(.(res)?)?/i,
+ apr: /^yeb(.(rir)?)?/i,
+ may: /^mag(.(gu)?)?/i,
+ jun: /^yun(.(yu)?)?/i,
+ jul: /^yul(.(yu)?)?/i,
+ aug: /^ɣuct/i,
+ sep: /^ctem(.(ber)?)?/i,
+ oct: /^tu(.(ber)?)?/i,
+ nov: /^num(.(ber)?)?/i,
+ dec: /^bujem(.(ber)?)?/i,
+
+ sun: /^ac(.(er)?)?/i,
+ mon: /^ar(.(im)?)?/i,
+ tue: /^ar(.(am)?)?/i,
+ wed: /^ah(.(ad)?)?/i,
+ thu: /^am(.(had)?)?/i,
+ fri: /^se(.(m)?)?/i,
+ sat: /^se(.(d)?)?/i,
+
+ future: /^next/i,
+ past: /^last|past|prev(ious)?/i,
+ add: /^(\+|aft(er)?|from|hence)/i,
+ subtract: /^(\-|bef(ore)?|ago)/i,
+
+ yesterday: /^yes(terday)?/i,
+ today: /^t(od(ay)?)?/i,
+ tomorrow: /^tom(orrow)?/i,
+ now: /^n(ow)?/i,
+
+ millisecond: /^ms|milli(second)?s?/i,
+ second: /^sec(ond)?s?/i,
+ minute: /^mn|min(ute)?s?/i,
+ hour: /^h(our)?s?/i,
+ week: /^w(eek)?s?/i,
+ month: /^m(onth)?s?/i,
+ day: /^d(ay)?s?/i,
+ year: /^y(ear)?s?/i,
+
+ shortMeridian: /^(a|p)/i,
+ longMeridian: /^(a\.?m?\.?|p\.?m?\.?)/i,
+ timezone: /^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt|utc)/i,
+ ordinalSuffix: /^\s*(st|nd|rd|th)/i,
+ timeContext: /^\s*(\:|a(?!u|p)|p)/i
+ },
+
+ timezones: [{name:"UTC", offset:"-000"}, {name:"GMT", offset:"-000"}, {name:"EST", offset:"-0500"}, {name:"EDT", offset:"-0400"}, {name:"CST", offset:"-0600"}, {name:"CDT", offset:"-0500"}, {name:"MST", offset:"-0700"}, {name:"MDT", offset:"-0600"}, {name:"PST", offset:"-0800"}, {name:"PDT", offset:"-0700"}]
+};
+
+/********************
+ ** Future Strings **
+ ********************
+ *
+ * The following list of strings may not be currently being used, but
+ * may be incorporated into the Datejs library later.
+ *
+ * We would appreciate any help translating the strings below.
+ *
+ * If you modify this file, please post your revised CultureInfo file
+ * to the Datejs Forum located at http://www.datejs.com/forums/.
+ *
+ * Please mark the subject of the post with [CultureInfo]. Example:
+ * Subject: [CultureInfo] Translated "da-DK" Danish(Denmark)b
+ *
+ * English Name Translated
+ * ------------------ -----------------
+ * about about
+ * ago ago
+ * date date
+ * time time
+ * calendar calendar
+ * show show
+ * hourly hourly
+ * daily daily
+ * weekly weekly
+ * bi-weekly bi-weekly
+ * fortnight fortnight
+ * monthly monthly
+ * bi-monthly bi-monthly
+ * quarter quarter
+ * quarterly quarterly
+ * yearly yearly
+ * annual annual
+ * annually annually
+ * annum annum
+ * again again
+ * between between
+ * after after
+ * from now from now
+ * repeat repeat
+ * times times
+ * per per
+ * min (abbrev minute) min
+ * morning morning
+ * noon noon
+ * night night
+ * midnight midnight
+ * mid-night mid-night
+ * evening evening
+ * final final
+ * future future
+ * spring spring
+ * summer summer
+ * fall fall
+ * winter winter
+ * end of end of
+ * end end
+ * long long
+ * short short
+ */
diff --git a/addons/web/static/lib/datejs/globalization/lo-LA.js b/addons/web/static/lib/datejs/globalization/lo-LA.js
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/addons/web/static/lib/datejs/globalization/lo-LA.js
diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css
index 805790930e2..81a1460b0bd 100644
--- a/addons/web/static/src/css/base.css
+++ b/addons/web/static/src/css/base.css
@@ -52,6 +52,7 @@
box-shadow: 0 0 1px black;
left: 50%;
transform: translate(-50%, 0);
+ -webkit-filter: blur(0);
}
.oe_notification {
@@ -3048,6 +3049,12 @@ body.oe_single_form .oe_single_form_container {
@media print {
body {
height: auto !important;
+ overflow: visible !important;
+ }
+
+ tr, td {
+ page-break-inside: avoid;
+ page-break-after: auto;
}
.openerp#announcement_bar_table {
@@ -3057,6 +3064,13 @@ body.oe_single_form .oe_single_form_container {
.openerp {
text-shadow: none;
}
+ .openerp.openerp_webclient_container {
+ display: block;
+ height: auto !important;
+ }
+ .openerp .oe-view-manager {
+ display: block;
+ }
.openerp .oe_application, .openerp .oe_view_manager_wrapper {
height: auto !important;
}
@@ -3082,6 +3096,9 @@ body.oe_single_form .oe_single_form_container {
.openerp .oe_notebook > li.ui-tabs-selected {
display: block;
}
+ .openerp .oe_application {
+ width: auto;
+ }
.openerp .oe_application .oe_form_sheet, .openerp .oe_application .oe_form_sheetbg {
border: 0px !important;
box-shadow: 0px 0px 0px;
@@ -3099,6 +3116,9 @@ body.oe_single_form .oe_single_form_container {
.openerp .openerp div.oe_mail_wall {
overflow: hidden !important;
}
+ .openerp .oe-view-manager .oe-view-manager-content {
+ overflow: visible;
+ }
.openerp.openerp_webclient_container {
overflow: visible;
diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass
index d848bbe6fc0..1f7a0c1560f 100644
--- a/addons/web/static/src/css/base.sass
+++ b/addons/web/static/src/css/base.sass
@@ -161,6 +161,7 @@ $sheet-padding: 16px
box-shadow: 0 0 1px black
left: 50%
transform: translate(-50%, 0)
+ -webkit-filter: blur(0)
.oe_notification
z-index: 1600
@@ -2520,9 +2521,18 @@ body.oe_single_form
@media print
body
height: auto !important
+ overflow: visible !important
+ tr, td
+ page-break-inside: avoid
+ page-break-after: auto
.openerp#announcement_bar_table
display: none
.openerp
+ &.openerp_webclient_container
+ display: block
+ height: auto !important
+ .oe-view-manager
+ display: block
.oe_application, .oe_view_manager_wrapper
height: auto !important
.oe_application > div > .oe_view_manager > .oe_view_manager_wrapper > div > .oe_view_manager_body
@@ -2550,6 +2560,7 @@ body.oe_single_form
box-shadow: 0px 0px 0px
.oe_list
overflow-x: visible
+ width: auto
.oe-control-panel, .oe-o2m-control-panel
border: 0px !important
box-shadow: 0px 0px 0px
@@ -2558,6 +2569,8 @@ body.oe_single_form
background: none
.openerp div.oe_mail_wall
overflow: hidden !important
+ .oe-view-manager .oe-view-manager-content
+ overflow: visible
.openerp.openerp_webclient_container
overflow: visible
// }}}
diff --git a/addons/web/static/src/js/action_manager.js b/addons/web/static/src/js/action_manager.js
index 7b9cfa9cbf8..6700932b4bc 100644
--- a/addons/web/static/src/js/action_manager.js
+++ b/addons/web/static/src/js/action_manager.js
@@ -507,7 +507,8 @@ var ActionManager = Widget.extend({
pager : (!popup || !form) && !inline,
display_title : !popup,
headless: (popup || inline) && form,
- search_disable_custom_filters: action.context && action.context.search_disable_custom_filters
+ search_disable_custom_filters: action.context && action.context.search_disable_custom_filters,
+ default_view: action.context && action.context.params && action.context.params.view_type,
});
}
diff --git a/addons/web/static/src/js/framework/crash_manager.js b/addons/web/static/src/js/framework/crash_manager.js
index 01fef03267b..303cd66a4a3 100644
--- a/addons/web/static/src/js/framework/crash_manager.js
+++ b/addons/web/static/src/js/framework/crash_manager.js
@@ -36,19 +36,24 @@ var CrashManager = core.Class.extend({
this.active = false;
},
rpc_error: function(error) {
+ var self = this;
if (!this.active) {
return;
}
+ if (this.$indicator){
+ return;
+ }
if (error.code == -32098) {
$.blockUI({ message: '' , overlayCSS: {'z-index': 9999, backgroundColor: '#FFFFFF', opacity: 0.0, cursor: 'wait'}});
- var $indicator = $('<div class="oe_indicator">' + _t("Trying to reconnect... ") + '<i class="fa fa-refresh"></i></div>');
- $indicator.prependTo("body");
+ this.$indicator = $('<div class="oe_indicator">' + _t("Trying to reconnect... ") + '<i class="fa fa-refresh"></i></div>');
+ this.$indicator.prependTo("body");
var timeinterval = setInterval(function(){
ajax.jsonRpc('/web/webclient/version_info').then(function() {
clearInterval(timeinterval);
- $indicator.html(_t("You are back online"));
- $indicator.delay(2000).fadeOut('slow',function(){
- $indicator.remove();
+ self.$indicator.html(_t("You are back online"));
+ self.$indicator.delay(2000).fadeOut('slow',function(){
+ $(this).remove();
+ self.$indicator.remove();
});
$.unblockUI();
});
diff --git a/addons/web/static/src/js/tour.js b/addons/web/static/src/js/tour.js
index ea64a55f22c..234b681f7d2 100644
--- a/addons/web/static/src/js/tour.js
+++ b/addons/web/static/src/js/tour.js
@@ -76,6 +76,7 @@ var localStorage = window.localStorage;
var Tour = {
tours: {},
defaultDelay: 50,
+ autoRunning: true,
retryRunningDelay: 1000,
errorDelay: 5000,
state: null,
@@ -378,6 +379,7 @@ var Tour = {
},
logError: function (step, message, all) {
var state = Tour.getState();
+ console.log(state.tour.steps.slice());
message += '\ntour: ' + state.id
+ (step ? '\nstep: ' + step.id + ": '" + (step._title || step.title) + "'" : '' )
+ (all ? '\nhref: ' + window.location.href : '' )
@@ -711,7 +713,11 @@ var Tour = {
/////////////////////////////////////////////////
-$(document).ready(Tour.running);
+$(document).ready(function () {
+ if (Tour.autoRunning) {
+ Tour.running();
+ };
+});
return Tour;
diff --git a/addons/web/static/src/js/views/form_view.js b/addons/web/static/src/js/views/form_view.js
index aa06074401f..df570967f94 100644
--- a/addons/web/static/src/js/views/form_view.js
+++ b/addons/web/static/src/js/views/form_view.js
@@ -637,7 +637,7 @@ var FormView = View.extend(common.FieldManagerMixin, {
// If field is not defined in the view, just ignore it
if (field) {
var value_ = values[f];
- if (field.get_value() != value_) {
+ if (field.get_value() !== value_) {
field._inhibit_on_change_flag = true;
field.set_value(value_);
field._inhibit_on_change_flag = false;
diff --git a/addons/web/static/src/js/views/form_widgets.js b/addons/web/static/src/js/views/form_widgets.js
index b23bff4cc84..ee04f8376b4 100644
--- a/addons/web/static/src/js/views/form_widgets.js
+++ b/addons/web/static/src/js/views/form_widgets.js
@@ -1230,9 +1230,11 @@ var FieldBinary = common.AbstractField.extend(common.ReinitializeFieldMixin, {
set_filename: function(value) {
var filename = this.node.attrs.filename;
if (filename) {
- var tmp = {};
- tmp[filename] = value;
- this.field_manager.set_values(tmp);
+ var field = this.field_manager.fields[filename];
+ if (field) {
+ field.set_value(value);
+ field._dirty_flag = true;
+ }
}
},
on_clear: function() {
@@ -1279,10 +1281,10 @@ var FieldBinaryFile = FieldBinary.extend({
},
on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
this.binary_value = true;
+ this.set_filename(name);
this.internal_set_value(file_base64);
var show_value = name + " (" + utils.human_size(size) + ")";
this.$el.find('input').eq(0).val(show_value);
- this.set_filename(name);
},
on_clear: function() {
this._super.apply(this, arguments);
diff --git a/addons/web/static/src/js/views/graph_widget.js b/addons/web/static/src/js/views/graph_widget.js
index 1e712b5af79..3f8a7566d97 100644
--- a/addons/web/static/src/js/views/graph_widget.js
+++ b/addons/web/static/src/js/views/graph_widget.js
@@ -168,6 +168,7 @@ return Widget.extend({
// rotateLabels: 40,
showControls: (this.groupbys.length > 1)
});
+ chart.yAxis.tickFormat(function(d) { return openerp.web.format_value(d, { type : 'float' });});
chart(svg);
this.to_remove = chart.update;
diff --git a/addons/web/static/src/js/views/pivot_view.js b/addons/web/static/src/js/views/pivot_view.js
index 1b55895d847..22b74ecbf15 100644
--- a/addons/web/static/src/js/views/pivot_view.js
+++ b/addons/web/static/src/js/views/pivot_view.js
@@ -60,6 +60,8 @@ var PivotView = View.extend({
this.last_header_selected = null;
this.sorted_column = {};
+
+ this.numbering = {};
},
willStart: function () {
var self = this;
@@ -490,13 +492,22 @@ var PivotView = View.extend({
},
sanitize_value: function (value, field) {
if (value === false) return _t("Undefined");
- if (value instanceof Array) return value[1];
+ if (value instanceof Array) return this.get_numbered_value(value, field);
if (field && this.fields[field] && (this.fields[field].type === 'selection')) {
var selected = _.where(this.fields[field].selection, {0: value})[0];
return selected ? selected[1] : value;
}
return value;
},
+ get_numbered_value: function(value, field) {
+ var id= value[0];
+ var name= value[1]
+ this.numbering[field] = this.numbering[field] || {};
+ this.numbering[field][name] = this.numbering[field][name] || {};
+ var numbers = this.numbering[field][name];
+ numbers[id] = numbers[id] || _.size(numbers) + 1;
+ return name + (numbers[id] > 1 ? " (" + numbers[id] + ")" : "");
+ },
make_header: function (data_pt, root, i, j, parent_header) {
var attrs = data_pt.attributes,
value = attrs.value,
diff --git a/addons/web/static/src/js/views/search_filters.js b/addons/web/static/src/js/views/search_filters.js
index 0409fa90a0d..506ddecd805 100644
--- a/addons/web/static/src/js/views/search_filters.js
+++ b/addons/web/static/src/js/views/search_filters.js
@@ -244,6 +244,10 @@ ExtendedSearchProposition.Float = ExtendedSearchProposition.Field.extend({
{value: "∃", text: _lt("is set")},
{value: "∄", text: _lt("is not set")}
],
+ init: function (parent) {
+ this._super(parent);
+ this.decimal_point = _t.database.parameters.decimal_point;
+ },
toString: function () {
return this.$el.val();
},
diff --git a/addons/web/static/src/js/views/search_inputs.js b/addons/web/static/src/js/views/search_inputs.js
index 16f95340dd6..4127b020970 100644
--- a/addons/web/static/src/js/views/search_inputs.js
+++ b/addons/web/static/src/js/views/search_inputs.js
@@ -22,6 +22,7 @@ var Input = Widget.extend( /** @lends instance.web.search.Input# */{
*/
init: function (parent) {
this._super(parent);
+ this.searchview = parent;
this.load_attrs({});
},
/**
@@ -406,9 +407,17 @@ var ManyToOneField = CharField.extend({
// FIXME: "concurrent" searches (multiple requests, mis-ordered responses)
var context = pyeval.eval(
'contexts', [this.searchview.dataset.get_context()]);
+ var args = this.attrs.domain;
+ if (typeof args === 'string') {
+ try {
+ args = pyeval.eval('domain', args);
+ } catch(e) {
+ args = [];
+ }
+ }
return this.model.call('name_search', [], {
name: needle,
- args: (typeof this.attrs.domain === 'string') ? [] : this.attrs.domain,
+ args: args,
limit: 8,
context: context
}).then(function (results) {
@@ -433,7 +442,8 @@ var ManyToOneField = CharField.extend({
// to handle this as if it were a single value.
value = value[0];
}
- return this.model.call('name_get', [value]).then(function (names) {
+ var context = pyeval.eval('contexts', [this.searchview.dataset.get_context()]);
+ return this.model.call('name_get', [value], {context: context}).then(function (names) {
if (_(names).isEmpty()) { return null; }
return facet_from(self, names[0]);
});
diff --git a/addons/web/static/src/js/views/search_view.js b/addons/web/static/src/js/views/search_view.js
index 1dd1b217951..3511bbca01c 100644
--- a/addons/web/static/src/js/views/search_view.js
+++ b/addons/web/static/src/js/views/search_view.js
@@ -888,14 +888,15 @@ return Widget.extend({
},
expand: function () {
var self = this;
- this.current_result.expand(this.get_search_string()).then(function (results) {
+ var current_result = this.current_result;
+ current_result.expand(this.get_search_string()).then(function (results) {
(results || [{label: '(no result)'}]).reverse().forEach(function (result) {
result.indent = true;
var $li = self.make_list_item(result);
- self.current_result.$el.after($li);
+ current_result.$el.after($li);
});
- self.current_result.expanded = true;
- self.current_result.$el.find('span.oe-expand').html('▼');
+ current_result.expanded = true;
+ current_result.$el.find('span.oe-expand').html('▼');
});
},
fold: function () {
diff --git a/addons/web/static/src/js/widgets/sidebar.js b/addons/web/static/src/js/widgets/sidebar.js
index e0bdcd3f57c..5dc6c147f39 100644
--- a/addons/web/static/src/js/widgets/sidebar.js
+++ b/addons/web/static/src/js/widgets/sidebar.js
@@ -16,6 +16,7 @@ var Sidebar = Widget.extend({
init: function(parent) {
var self = this;
this._super(parent);
+ this.view = this.getParent();
this.sections = [
{ 'name' : 'print', 'label' : _t('Print'), },
{ 'name' : 'other', 'label' : _t('More'), }
diff --git a/addons/web/static/src/js/widgets/user_menu.js b/addons/web/static/src/js/widgets/user_menu.js
index fa5ea2bbdf7..dba1704c719 100644
--- a/addons/web/static/src/js/widgets/user_menu.js
+++ b/addons/web/static/src/js/widgets/user_menu.js
@@ -87,7 +87,7 @@ var SystrayMenu = Widget.extend({
framework.redirect('https://accounts.odoo.com/oauth2/auth?'+$.param(params));
}).fail(function(result, ev){
ev.preventDefault();
- framework.redirect('https://accounts.odoo.com/web');
+ framework.redirect('https://accounts.odoo.com/account');
});
}
},
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 7bc79aec9ea..f8f3c7191f3 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -609,12 +609,12 @@
</t>
<a class="oe_file_attachment" t-att-title="item.title or ''" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url">
<t t-raw="item.label"/>
- <span t-if="section.name == 'files' and !item.callback" class="oe_sidebar_delete_item" t-att-data-id="item.id" title="Delete this attachment">
+ <span t-if="widget.view.is_action_enabled('edit') &amp;&amp; section.name == 'files' and !item.callback" class="oe_sidebar_delete_item" t-att-data-id="item.id" title="Delete this attachment">
<i class="fa fa-trash-o"/>
</span>
</a>
</li>
- <li t-if="section.name == 'files'" class="oe_sidebar_add_attachment">
+ <li t-if="widget.view.is_action_enabled('edit') &amp;&amp; section.name == 'files'" class="oe_sidebar_add_attachment">
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
<t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
@@ -1755,7 +1755,11 @@
<input type="number" class="field_integer form-control" value = "0" step="1"/>
</t>
<t t-name="SearchView.extended_search.proposition.float">
- <input type="number" class="field_float form-control" value = "0.0" step="0.01"/>
+ <input class="field_float form-control"
+ t-att-type="widget.decimal_point === '.' ? 'number' : 'text'"
+ t-attf-title="Number using #{widget.decimal_point || '.' } as decimal separator."
+ t-attf-pattern="[0-9]+([\\#{widget.decimal_point || '.' }][0-9]+)?"
+ t-attf-value="0#{widget.decimal_point || '.' }0" step="0.01"/>
</t>
<t t-name="SearchView.extended_search.proposition.selection">
<select class="form-control">
diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js
index 84c6d0d3668..e1c6368aa09 100644
--- a/addons/web/static/test/search.js
+++ b/addons/web/static/test/search.js
@@ -339,7 +339,7 @@ odoo.define_section('search.defaults', ['web.search_inputs', 'web.SearchView', '
var ManyToOneField = core.search_widgets_registry.get('many2one');
var Facet = SearchView.Facet;
- var view = {inputs: []}, id = 4;
+ var view = {inputs: [], dataset: {get_context: function () {}}}, id = 4;
var f = new ManyToOneField(
{attrs: {name: 'dummy', string: 'Dummy'}},
{relation: 'dummy.model.name'},
@@ -375,7 +375,7 @@ odoo.define_section('search.defaults', ['web.search_inputs', 'web.SearchView', '
var ManyToOneField = core.search_widgets_registry.get('many2one');
var Facet = SearchView.Facet;
- var view = {inputs: []}, id = 5;
+ var view = {inputs: [], dataset: {get_context: function () {}}}, id = 5;
var f = new ManyToOneField(
{attrs: {name: 'dummy', string: 'Dummy'}},
{relation: 'dummy.model.name'},
@@ -402,7 +402,7 @@ odoo.define_section('search.defaults', ['web.search_inputs', 'web.SearchView', '
var ManyToOneField = core.search_widgets_registry.get('many2one');
- var view = {inputs: []}, id = 4;
+ var view = {inputs: [], dataset: {get_context: function () {}}}, id = 4;
var f = new ManyToOneField(
{attrs: {name: 'dummy', string: 'Dummy'}},
{relation: 'dummy.model.name'},
diff --git a/addons/web_diagram/controllers/main.py b/addons/web_diagram/controllers/main.py
index 0e89eb13db2..c45790c7264 100644
--- a/addons/web_diagram/controllers/main.py
+++ b/addons/web_diagram/controllers/main.py
@@ -1,4 +1,5 @@
import openerp
+from openerp.tools.safe_eval import safe_eval as eval
class DiagramView(openerp.http.Controller):
diff --git a/addons/web_kanban/static/src/js/kanban_view.js b/addons/web_kanban/static/src/js/kanban_view.js
index bbfc22f6163..a488267752a 100644
--- a/addons/web_kanban/static/src/js/kanban_view.js
+++ b/addons/web_kanban/static/src/js/kanban_view.js
@@ -355,6 +355,7 @@ var KanbanView = View.extend({
}).done(null, function() {
def.reject();
});
+ return def;
});
return def;
},
diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py
index 7eccae13072..f300487e7b8 100644
--- a/addons/website/controllers/main.py
+++ b/addons/website/controllers/main.py
@@ -70,7 +70,7 @@ class Website(openerp.addons.web.controllers.main.Home):
redirect.set_cookie('website_lang', lang)
return redirect
- @http.route('/page/<page:page>', type='http', auth="public", website=True)
+ @http.route('/page/<page:page>', type='http', auth="public", website=True, cache=300)
def page(self, page, **opt):
values = {
'path': page,
diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py
index a5119e40760..2668e5008d6 100644
--- a/addons/website/models/ir_http.py
+++ b/addons/website/models/ir_http.py
@@ -2,6 +2,7 @@
import logging
import os
import re
+import time
import traceback
import werkzeug
@@ -15,6 +16,7 @@ from openerp.addons.website.models.website import slug, url_for, _UNSLUG_RE
from openerp.http import request
from openerp.tools import config
from openerp.osv import orm
+from openerp.tools.safe_eval import safe_eval as eval
logger = logging.getLogger(__name__)
@@ -26,7 +28,7 @@ class ir_http(orm.AbstractModel):
_inherit = 'ir.http'
rerouting_limit = 10
- geo_ip_resolver = None
+ _geoip_resolver = None
def _get_converters(self):
return dict(
@@ -45,7 +47,7 @@ class ir_http(orm.AbstractModel):
else:
request.uid = request.session.uid
- bots = "bot|crawl|slurp|spider|curl|wget".split("|")
+ bots = "bot|crawl|slurp|spider|curl|wget|facebookexternalhit".split("|")
def is_a_bot(self):
# We don't use regexp and ustr voluntarily
# timeit has been done to check the optimum method
@@ -55,6 +57,39 @@ class ir_http(orm.AbstractModel):
except UnicodeDecodeError:
return any(bot in ua.encode('ascii', 'ignore') for bot in self.bots)
+ def get_nearest_lang(self, lang):
+ # Try to find a similar lang. Eg: fr_BE and fr_FR
+ if lang in request.website.get_languages():
+ return lang
+
+ short = lang.split('_')[0]
+ for code, name in request.website.get_languages():
+ if code.startswith(short):
+ return code
+ return False
+
+ def _geoip_setup_resolver(self):
+ if self._geoip_resolver is None:
+ try:
+ import GeoIP
+ # updated database can be downloaded on MaxMind website
+ # http://dev.maxmind.com/geoip/legacy/install/city/
+ geofile = config.get('geoip_database')
+ if os.path.exists(geofile):
+ self._geoip_resolver = GeoIP.open(geofile, GeoIP.GEOIP_STANDARD)
+ else:
+ self._geoip_resolver = False
+ logger.warning('GeoIP database file %r does not exists, apt-get install geoip-database-contrib or download it from http://dev.maxmind.com/geoip/legacy/install/city/', geofile)
+ except ImportError:
+ self._geoip_resolver = False
+
+ def _geoip_resolve(self):
+ if 'geoip' not in request.session:
+ record = {}
+ if self._geoip_resolver and request.httprequest.remote_addr:
+ record = self._geoip_resolver.record_by_addr(request.httprequest.remote_addr) or {}
+ request.session['geoip'] = record
+
def _dispatch(self):
first_pass = not hasattr(request, 'website')
request.website = None
@@ -75,24 +110,8 @@ class ir_http(orm.AbstractModel):
func and func.routing.get('multilang', func.routing['type'] == 'http')
)
- if 'geoip' not in request.session:
- record = {}
- if self.geo_ip_resolver is None:
- try:
- import GeoIP
- # updated database can be downloaded on MaxMind website
- # http://dev.maxmind.com/geoip/legacy/install/city/
- geofile = config.get('geoip_database')
- if os.path.exists(geofile):
- self.geo_ip_resolver = GeoIP.open(geofile, GeoIP.GEOIP_STANDARD)
- else:
- self.geo_ip_resolver = False
- logger.warning('GeoIP database file %r does not exists', geofile)
- except ImportError:
- self.geo_ip_resolver = False
- if self.geo_ip_resolver and request.httprequest.remote_addr:
- record = self.geo_ip_resolver.record_by_addr(request.httprequest.remote_addr) or {}
- request.session['geoip'] = record
+ self._geoip_setup_resolver()
+ self._geoip_resolve()
cook_lang = request.httprequest.cookies.get('website_lang')
if request.website_enabled:
@@ -109,50 +128,64 @@ class ir_http(orm.AbstractModel):
request.context['website_id'] = request.website.id
langs = [lg[0] for lg in request.website.get_languages()]
path = request.httprequest.path.split('/')
-
if first_pass:
- if request.website_multilang:
- is_a_bot = self.is_a_bot()
- # If the url doesn't contains the lang and that it's the first connection, we to retreive the user preference if it exists.
- if not path[1] in langs and not is_a_bot:
- request.lang = cook_lang or request.lang
- if request.lang not in langs:
- # Try to find a similar lang. Eg: fr_BE and fr_FR
- short = request.lang.split('_')[0]
- langs_withshort = [lg[0] for lg in request.website.get_languages() if lg[0].startswith(short)]
- if len(langs_withshort):
- request.lang = langs_withshort[0]
- else:
- request.lang = request.website.default_lang_code
- else:
- request.lang = request.website.default_lang_code
-
+ nearest_lang = not func and self.get_nearest_lang(path[1])
+ url_lang = nearest_lang and path[1]
+ preferred_lang = ((cook_lang if cook_lang in langs else False)
+ or self.get_nearest_lang(request.lang)
+ or request.website.default_lang_code)
+
+ is_a_bot = self.is_a_bot()
+
+ request.lang = request.context['lang'] = nearest_lang or preferred_lang
+ # if lang in url but not the displayed or default language --> change or remove
+ # or no lang in url, and lang to dispay not the default language --> add lang
+ # and not a POST request
+ # and not a bot or bot but default lang in url
+ if ((url_lang and (url_lang != request.lang or url_lang == request.website.default_lang_code))
+ or (not url_lang and request.website_multilang and request.lang != request.website.default_lang_code)
+ and request.httprequest.method != 'POST') \
+ and (not is_a_bot or (url_lang and url_lang == request.website.default_lang_code)):
+ if url_lang:
+ path.pop(1)
if request.lang != request.website.default_lang_code:
path.insert(1, request.lang)
- path = '/'.join(path) or '/'
- redirect = request.redirect(path + '?' + request.httprequest.query_string)
- redirect.set_cookie('website_lang', request.lang)
- return redirect
+ path = '/'.join(path) or '/'
+ redirect = request.redirect(path + '?' + request.httprequest.query_string)
+ redirect.set_cookie('website_lang', request.lang)
+ return redirect
+ elif url_lang:
+ path.pop(1)
+ return self.reroute('/'.join(path) or '/')
- request.context['lang'] = request.lang
if not request.context.get('tz'):
request.context['tz'] = request.session['geoip'].get('time_zone')
- if not func:
- if path[1] in langs:
- request.lang = request.context['lang'] = path.pop(1)
- path = '/'.join(path) or '/'
- if request.lang == request.website.default_lang_code:
- # If language is in the url and it is the default language, redirect
- # to url without language so google doesn't see duplicate content
- resp = request.redirect(path + '?' + request.httprequest.query_string, code=301)
- if cook_lang != request.lang: # If default lang setted in url directly
- resp.set_cookie('website_lang', request.lang)
- return resp
- return self.reroute(path)
# bind modified context
request.website = request.website.with_context(request.context)
- resp = super(ir_http, self)._dispatch()
- if not cook_lang:
+
+ # cache for auth public
+ cache_time = getattr(func, 'routing', {}).get('cache')
+ cache_enable = cache_time and request.httprequest.method == "GET" and request.website.user_id.id == request.uid
+ cache_response = None
+ if cache_enable:
+ key = (self._name, "cache", request.uid, request.lang, request.httprequest.full_path)
+ try:
+ r = self.pool.cache[key]
+ if r['time'] + cache_time > time.time():
+ cache_response = openerp.http.Response(r['content'], mimetype=r['mimetype'])
+ else:
+ del self.pool.cache[key]
+ except KeyError:
+ pass
+
+ if cache_response:
+ request.cache_save = False
+ resp = cache_response
+ else:
+ request.cache_save = key if cache_enable else False
+ resp = super(ir_http, self)._dispatch()
+
+ if request.website_enabled and cook_lang != request.lang and hasattr(resp, 'set_cookie'):
resp.set_cookie('website_lang', request.lang)
return resp
diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py
index f8b52978192..c63f67a191c 100644
--- a/addons/website/models/ir_ui_view.py
+++ b/addons/website/models/ir_ui_view.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# -*- coding: ascii -*-
import copy
import logging
@@ -43,7 +43,7 @@ class view(osv.osv):
# Try to fallback on key instead of xml_id
rec_id = self.search(cr, uid, [('key', '=', view_id)], context=context)
if rec_id:
- _logger.info("Could not find view with `xml_id´ '%s', fallback on `key´" % (view_id))
+ _logger.info("Could not find view with `xml_id' '%s', fallback on `key'" % (view_id))
return self.browse(cr, uid, rec_id, context=context)[0]
else:
raise
diff --git a/addons/website/static/src/css/editor.css b/addons/website/static/src/css/editor.css
index 6b631348651..b49c6a14afe 100644
--- a/addons/website/static/src/css/editor.css
+++ b/addons/website/static/src/css/editor.css
@@ -636,8 +636,7 @@ ul.oe_menu_editor .disclose {
}
.oe_ace_view_editor.oe_ace_closed {
z-index: -1000;
- filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
- opacity: 0;
+ display: none
}
.oe_include_bundles {
diff --git a/addons/website/static/src/css/editor.sass b/addons/website/static/src/css/editor.sass
index 73c000f737a..23762bb5fe4 100644
--- a/addons/website/static/src/css/editor.sass
+++ b/addons/website/static/src/css/editor.sass
@@ -546,7 +546,7 @@ $infobar_height: 20px
+opacity(0.97)
&.oe_ace_closed
z-index: -1000
- +opacity(0)
+ display: none
.oe_include_bundles
font-weight: normal
diff --git a/addons/website/static/src/css/website.css b/addons/website/static/src/css/website.css
index a7d05da9eed..872d82b460f 100644
--- a/addons/website/static/src/css/website.css
+++ b/addons/website/static/src/css/website.css
@@ -213,6 +213,10 @@ li > p {
margin: 0;
}
+font[style*='background'], font[class*='bg-'] {
+ padding: 0 2px
+}
+
.o_ul_toggle_self, .o_ul_toggle_next {
display: none;
}
@@ -342,8 +346,10 @@ body > nav.navbar {
z-index: 10000;
}
-nav.navbar + #wrapwrap {
- padding-top: 34px;
+@media (min-width: 767px) {
+ nav.navbar + #wrapwrap {
+ padding-top: 34px;
+ }
}
/* ----- BOOTSTRAP FIX ----- */
diff --git a/addons/website/static/src/css/website.sass b/addons/website/static/src/css/website.sass
index 022b07066ad..223bab94a0b 100644
--- a/addons/website/static/src/css/website.sass
+++ b/addons/website/static/src/css/website.sass
@@ -130,6 +130,9 @@ li > * > li > * > li > * > li > * > li > * > li > ol
li > p
margin: 0
+font[style*='background'], font[class*='bg-']
+ padding: 0 2px
+
.o_ul_toggle_self, .o_ul_toggle_next
display: none
.o_ul_folded
@@ -238,8 +241,9 @@ body > nav.navbar
width: 100%
position: absolute
z-index: 10000
-nav.navbar + #wrapwrap
- padding-top: 34px
+@media (max-min: 767px)
+ nav.navbar + #wrapwrap
+ padding-top: 34px
/* ----- BOOTSTRAP FIX ----- */
diff --git a/addons/website/static/src/img/deers.jpg b/addons/website/static/src/img/deers.jpg
index 042f427c0b9..9f4b368a18f 100644
--- a/addons/website/static/src/img/deers.jpg
+++ b/addons/website/static/src/img/deers.jpg
Binary files differ
diff --git a/addons/website/static/src/img/deers_thumb.jpg b/addons/website/static/src/img/deers_thumb.jpg
index 0771162ae49..4a955f5c892 100644
--- a/addons/website/static/src/img/deers_thumb.jpg
+++ b/addons/website/static/src/img/deers_thumb.jpg
Binary files differ
diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js
index 9cc84746454..5e12647a63a 100644
--- a/addons/website/static/src/js/website.editor.js
+++ b/addons/website/static/src/js/website.editor.js
@@ -937,6 +937,7 @@ function change_default_bootstrap_animation_to_edit() {
website.no_editor = !!$(document.documentElement).data('editable-no-editor');
+ website.add_template_file('/website/static/src/xml/website.xml');
website.add_template_file('/website/static/src/xml/website.editor.xml');
website.dom_ready.done(function () {
website.ready().then(init_editor);
@@ -965,6 +966,16 @@ website.dom_ready.done(function () {
});
});
+ website.error = function(data, url) {
+ var $error = $(openerp.qweb.render('website.error_dialog', {
+ 'title': data.data ? data.data.arguments[0] : "",
+ 'message': data.data ? data.data.arguments[1] : data.statusText,
+ 'backend_url': url
+ }));
+ $error.appendTo("body");
+ $error.modal('show');
+ };
+
/* ----- TOP EDITOR BAR FOR ADMIN ---- */
var EditorBar = Widget.extend({
@@ -1411,6 +1422,12 @@ var Dialog = Widget.extend({
},
close: function () {
this.$el.modal('hide');
+ },
+ destroy: function () {
+ this.$el.modal('hide').remove();
+ if($(".modal.in").length>0){
+ $('body').addClass('modal-open');
+ }
},
});
diff --git a/addons/website/static/src/js/website.js b/addons/website/static/src/js/website.js
index 3212bf824a1..f0876b44747 100644
--- a/addons/website/static/src/js/website.js
+++ b/addons/website/static/src/js/website.js
@@ -4,6 +4,7 @@ odoo.define('website.website', function (require) {
var ajax = require('web.ajax');
var core = require('web.core');
var session = require('web.session');
+var Tour = require('web.Tour');
var _t = core._t;
@@ -169,16 +170,6 @@ function prompt(options, qweb) {
return def;
}
-function error(data, url) {
- var $error = $(core.qweb.render('website.error_dialog', {
- 'title': data.data ? data.data.arguments[0] : "",
- 'message': data.data ? data.data.arguments[1] : data.statusText,
- 'backend_url': url
- }));
- $error.appendTo("body");
- $error.modal('show');
-}
-
function form(url, method, params) {
var htmlform = document.createElement('form');
htmlform.setAttribute('action', url);
@@ -270,8 +261,6 @@ function add_template_file(template) {
return def;
}
-add_template_file('/website/static/src/xml/website.xml');
-
var dom_ready = $.Deferred();
$(document).ready(function () {
dom_ready.resolve();
@@ -402,6 +391,11 @@ dom_ready.then(function () {
$('#oe_applications').before($collapse);
$collapse.wrap('<div class="visible-xs"/>');
$('[data-target="#oe_applications"]').attr("data-target", "#oe_applications_collapse");
+ });
+
+ Tour.autoRunning = false;
+ ready().then(function () {
+ setTimeout(Tour.running,0);
});
return {
@@ -412,7 +406,6 @@ return {
parseHash: parseHash,
reload: reload,
prompt: prompt,
- error: error,
form: form,
init_kanban: init_kanban,
add_template_file: add_template_file,
@@ -433,4 +426,4 @@ var Session = require('web.Session');
return new Session(null, null, {modules: ['website']});
-}); \ No newline at end of file
+});
diff --git a/addons/website/static/src/js/website.seo.js b/addons/website/static/src/js/website.seo.js
index 5a848157493..31a8ebd3e92 100644
--- a/addons/website/static/src/js/website.seo.js
+++ b/addons/website/static/src/js/website.seo.js
@@ -499,7 +499,7 @@ var Configurator = Widget.extend({
descriptionChanged: function () {
var self = this;
setTimeout(function () {
- var description = self.$('textarea[name=seo_page_description]').attr('value');
+ var description = self.$('textarea[name=seo_page_description]').val();
self.htmlPage.changeDescription(description);
self.renderPreview();
}, 0);
diff --git a/addons/website/static/src/js/website.snippets.animation.js b/addons/website/static/src/js/website.snippets.animation.js
index 8d5c98111b3..0b1c7befaa1 100644
--- a/addons/website/static/src/js/website.snippets.animation.js
+++ b/addons/website/static/src/js/website.snippets.animation.js
@@ -168,7 +168,8 @@ animationRegistry.share = Animation.extend({
var title = encodeURIComponent($("title").text());
this.$("a").each(function () {
var $a = $(this);
- $a.attr("href", $(this).attr("href").replace("{url}", url).replace("{title}", title));
+ var url_regex = /\{url\}|%7Burl%7D/, title_regex = /\{title\}|%7Btitle%7D/;
+ $a.attr("href", $(this).attr("href").replace(url_regex, url).replace(title_regex, title));
if ($a.attr("target") && $a.attr("target").match(/_blank/i) && !$a.closest('.o_editable').length) {
$a.on('click', function () {
window.open(this.href,'','menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=550,width=600');
diff --git a/addons/website/static/src/js/website.translator.js b/addons/website/static/src/js/website.translator.js
index 050a649715a..775012e1aa8 100644
--- a/addons/website/static/src/js/website.translator.js
+++ b/addons/website/static/src/js/website.translator.js
@@ -67,7 +67,10 @@ editor.EditorBar.include({
var link = $('.js_language_selector a[data-default-lang]')[0];
if (link) {
link.search += (link.search ? '&' : '?') + 'enable_editor=1';
- window.location = link.attributes.href.value;
+ var url = link.pathname + link.search + window.location.hash;
+ link.pathname = '/website/lang/default';
+ link.search = '?' + $.param({r: url});
+ window.location = link.href;
}
},
translate: function () {
@@ -89,7 +92,8 @@ editor.EditorBar.include({
var $editables = $('[data-oe-model="ir.ui.view"]')
.not('link, script')
.not('.oe_snippets,.oe_snippet, .oe_snippet *, .navbar-toggle')
- .not('[data-oe-type]');
+ .not('[data-oe-type]')
+ .add($('[data-oe-translate="1"]:not([data-oe-model="ir.ui.view"])').addClass('o_editable'));
$editables.each(function () {
var $node = $(this);
@@ -112,7 +116,7 @@ editor.EditorBar.include({
self.sanitizeNode($node[0]);
}
if (self.getInitialContent($node[0]) !== $node.text()) {
- $node.addClass('oe_dirty').removeClass('oe_translatable_todo oe_translatable_inprogress');
+ $node.addClass('o_dirty').removeClass('oe_translatable_todo oe_translatable_inprogress');
}
}, 0);
});
@@ -133,7 +137,7 @@ editor.EditorBar.include({
// TODO: link nodes with same content
node.className += ' oe_translatable_text';
node.setAttribute('data-oe-translation-view-id', view_id);
- var content = node.childNodes[0].data.trim();
+ var content = $(node).text().trim();
var trans = this.translations.filter(function (t) {
return t.res_id === view_id && t.value.trim() === content;
});
@@ -152,10 +156,9 @@ editor.EditorBar.include({
},
save: function () {
var self = this;
- var mysuper = this._super;
var trans = {};
// this._super.apply(this, arguments);
- $('.oe_translatable_text.oe_dirty').each(function () {
+ $('.oe_translatable_text.o_dirty').each(function () {
var $node = $(this);
var data = $node.data();
if (!trans[data.oeTranslationViewId]) {
@@ -171,8 +174,7 @@ editor.EditorBar.include({
'data': trans,
'lang': website.get_context()['lang'],
}).then(function () {
- window.onbeforeunload = null;
- website.reload();
+ self._save();
}).fail(function () {
// TODO: bootstrap alert with error message
alert("Could not save translation");
@@ -185,6 +187,7 @@ editor.EditorBar.include({
if (node.attributes['data-oe-translate'].value == '1') {
node.className += ' oe_translatable_field';
}
+ this.markTranslatableNode(node, view_id);
} else if (node.childNodes.length === 1
&& this.isTextNode(node.childNodes[0])
&& !node.getAttribute('data-oe-model')) {
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml
index 7d0916044b9..aa7d2e8f92b 100644
--- a/addons/website/views/website_templates.xml
+++ b/addons/website/views/website_templates.xml
@@ -91,8 +91,8 @@
<t t-set="languages" t-value="website.get_languages() if website else None"/>
<t t-if="request and request.website_multilang and website">
- <t t-foreach="website.get_alternate_languages(request.httprequest)" t-as="lang">
- <link rel="alternate" t-att-hreflang="lang['hreflang']" t-att-href="lang['href']"/>
+ <t t-foreach="website.get_alternate_languages(request.httprequest)" t-as="lg">
+ <link rel="alternate" t-att-hreflang="lg['hreflang']" t-att-href="lg['href']"/>
</t>
</t>
@@ -511,7 +511,7 @@
<template id="publish_short">
<t groups="base.group_website_publisher" t-ignore="true">
- <div t-attf-class="btn-group pull-right js_publish_management #{object.website_published and 'css_published' or 'css_unpublished'}" t-att-data-id="object.id" t-att-data-object="object._name" t-att-data-controller="publish_controller">
+ <div t-attf-class="pull-right js_publish_management #{object.website_published and 'css_published' or 'css_unpublished'}" t-att-data-id="object.id" t-att-data-object="object._name" t-att-data-controller="publish_controller">
<button class="btn btn-danger js_publish_btn">Not Published</button>
<button class="btn btn-success js_publish_btn">Published</button>
</div>
diff --git a/addons/website_crm_score/controllers/main.py b/addons/website_crm_score/controllers/main.py
index bbb2c6a7c16..3707a8f648f 100644
--- a/addons/website_crm_score/controllers/main.py
+++ b/addons/website_crm_score/controllers/main.py
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
-from openerp import addons, http, SUPERUSER_ID, fields
+from openerp import http, SUPERUSER_ID, fields
from openerp.http import request
from openerp.tools import html_escape
+from openerp.addons.website.controllers.main import Website
+from openerp.addons.website_crm.controllers.main import contactus
-
-class PageController(addons.website.controllers.main.Website):
+class PageController(Website):
@http.route('/page/<page:page>', auth="public", website=True)
def page(self, page, **opt):
@@ -32,7 +33,7 @@ class PageController(addons.website.controllers.main.Website):
return response
-class ContactController(addons.website_crm.controllers.main.contactus):
+class ContactController(contactus):
@http.route(['/crm/contactus'], type='http', auth="public", website=True)
def contactus(self, **kwargs):
diff --git a/addons/website_crm_score/static/src/js/display_filters.js b/addons/website_crm_score/static/src/js/display_filters.js
index d7817aa4dcf..859ec6fd218 100644
--- a/addons/website_crm_score/static/src/js/display_filters.js
+++ b/addons/website_crm_score/static/src/js/display_filters.js
@@ -22,14 +22,25 @@ var Filters = common.AbstractField.extend({
var val = this.field.raw_value;
var self = this;
if (val) {
- val = eval(val);
+ // This widget is temporary
+ // To keep only while the widget domain filter doesn't exist !
+
+ // Ugly hack to have (more) python domains which can be evaluated in JS
+ val = val.replace('(', '[').replace(')', ']').replace('False', 'false').replace('True', 'true')
+ try {
+ val = eval(val);
+ }
+ catch(err) {
+ // don't block UI if domain is not evaluable in JS
+ console.debug(err.message);
+ val = [['error','=', err.message]];
+ }
if (val.length <= this.MAX_LEN) {
var i = 0;
while (i < val.length) {
var res = this.interpret(val, i);
i = res[0];
var $span = res[1];
- // var $span = '<h2>' + res[1] + '</h2>';
self.$el.append($span);
}
}
diff --git a/addons/website_crm_score/static/src/xml/track_page.xml b/addons/website_crm_score/static/src/xml/track_page.xml
index 9aaeb8136e1..18d67a6251e 100644
--- a/addons/website_crm_score/static/src/xml/track_page.xml
+++ b/addons/website_crm_score/static/src/xml/track_page.xml
@@ -4,11 +4,13 @@
<t t-extend="website.seo_configuration">
<t t-jquery="div.modal-body" t-operation="append">
- <section class="set_score">
- <h3 class="track-page">4. Track Page <small>to better score your leads: </small>
- <!-- checkbox is added in js in set_score.js -->
- </h3>
- </section>
+ <t t-if="widget.getMainObject() &amp;&amp; widget.getMainObject().model === 'ir.ui.view'">
+ <section class="set_score">
+ <h3 class="track-page">4. Track Page <small>to better score your leads: </small>
+ <!-- checkbox is added in js in set_score.js -->
+ </h3>
+ </section>
+ </t>
</t>
</t>
diff --git a/addons/website_event_sale/controllers/main.py b/addons/website_event_sale/controllers/main.py
index 8a5275c9afb..d3cedeb272c 100644
--- a/addons/website_event_sale/controllers/main.py
+++ b/addons/website_event_sale/controllers/main.py
@@ -42,7 +42,7 @@ class website_event(website_event):
def _process_tickets_details(self, data):
ticket_post = {}
for key, value in data.iteritems():
- if not key.startswith('nb_register') or not '-' in key:
+ if not key.startswith('nb_register') or '-' not in key:
continue
items = key.split('-')
if len(items) < 2:
@@ -61,7 +61,7 @@ class website_event(website_event):
for registration in registrations:
ticket = request.registry['event.event.ticket'].browse(cr, SUPERUSER_ID, int(registration['ticket_id']), context=context)
cart_values = order.with_context(event_ticket_id=ticket.id)._cart_update(product_id=ticket.product_id.id, add_qty=1, registration_data=[registration])
- attendee_ids &= set(cart_values.get('attendees', []))
+ attendee_ids |= set(cart_values.get('attendee_ids', []))
# free tickets -> order with amount = 0: auto-confirm, no checkout
if not order.amount_total:
diff --git a/addons/website_forum/controllers/main.py b/addons/website_forum/controllers/main.py
index 3d269740e79..be6ddd11840 100644
--- a/addons/website_forum/controllers/main.py
+++ b/addons/website_forum/controllers/main.py
@@ -12,6 +12,7 @@ from openerp.addons.web import http
from openerp.addons.web.controllers.main import login_redirect
from openerp.addons.web.http import request
from openerp.addons.website.models.website import slug
+from openerp.tools.translate import _
class WebsiteForum(http.Controller):
@@ -317,6 +318,8 @@ class WebsiteForum(http.Controller):
def post_create(self, forum, post_parent=None, post_type=None, **post):
if not request.session.uid:
return login_redirect()
+ if post_type == 'question' and not post.get('post_name', '').strip():
+ return request.website.render('website.http_error', {'status_code': _('Bad Request'), 'status_message': _('Title should not be empty.')})
post_tag_ids = forum._tag_to_write_vals(post.get('post_tags', ''))
new_question = request.env['forum.post'].create({
'forum_id': forum.id,
@@ -380,7 +383,9 @@ class WebsiteForum(http.Controller):
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/save', type='http', auth="user", methods=['POST'], website=True)
def post_save(self, forum, post, **kwargs):
- post_tags = forum._tag_to_write_vals(kwargs.get('post_tags', ''))
+ if 'post_name' in kwargs and not kwargs.get('post_name').strip():
+ return request.website.render('website.http_error', {'status_code': _('Bad Request'), 'status_message': _('Title should not be empty.')})
+ post_tags = forum._tag_to_write_vals(kwargs.get('post_tag', ''))
vals = {
'tag_ids': post_tags,
'name': kwargs.get('post_name'),
@@ -428,7 +433,7 @@ class WebsiteForum(http.Controller):
def users(self, forum, page=1, **searches):
User = request.env['res.users']
step = 30
- tag_count = User.search_count([('karma', '>', 1), ('website_published', '=', True)])
+ tag_count = User.sudo().search_count([('karma', '>', 1), ('website_published', '=', True)])
pager = request.website.pager(url="/forum/%s/users" % slug(forum), total=tag_count, page=page, step=step, scope=30)
user_obj = User.sudo().search([('karma', '>', 1), ('website_published', '=', True)], limit=step, offset=pager['offset'], order='karma DESC')
# put the users in block of 3 to display them as a table
diff --git a/addons/website_forum/static/src/css/website_forum.css b/addons/website_forum/static/src/css/website_forum.css
index 395167cf615..2e1a4ebaa38 100644
--- a/addons/website_forum/static/src/css/website_forum.css
+++ b/addons/website_forum/static/src/css/website_forum.css
@@ -1,113 +1,145 @@
.vote {
min-width: 38px;
- margin-right: 12px; }
- .vote .vote_count {
- font-size: 160%;
- font-weight: bold;
- line-height: 12px; }
- .vote .vote_up, .vote .vote_down {
- font-size: 130%; }
+ margin-right: 12px;
+}
+.vote .vote_count {
+ font-size: 160%;
+ font-weight: bold;
+ line-height: 12px;
+}
+.vote .vote_up, .vote .vote_down {
+ font-size: 130%;
+}
.author-box {
min-width: 200px;
padding: 4px;
- background-color: whitesmoke; }
+ background-color: #f5f5f5;
+}
.question-block {
- margin-left: 50px; }
+ margin-left: 50px;
+}
+
+.question {
+ clear: left;
+}
.question .question-name {
- font-size: 150%; }
+ font-size: 150%;
+}
.question .badge {
- background-color: #cccccc;
- margin-left: 4px; }
+ background-color: #ccc;
+ margin-left: 4px;
+}
.question .badge-active {
- background-color: #428bca; }
+ background-color: #428bca;
+}
.question img {
max-width: 600px;
- height: auto !important; }
+ height: auto !important;
+}
.forum_answer img {
max-width: 600px;
- height: auto !important; }
+ height: auto !important;
+}
img.img-avatar {
max-height: 40px;
- margin-right: 10px; }
+ margin-right: 10px;
+}
.oe_grey {
- background-color: #eeeeee; }
+ background-color: #eeeeee;
+}
.badge-gold {
- color: #ffcc00; }
+ color: #ffcc00;
+}
.badge-silver {
- color: #cccccc; }
+ color: #cccccc;
+}
.badge-bronze {
- color: #eea91e; }
+ color: #eea91e;
+}
.oe_answer_true {
- color: #3c763d; }
+ color: #3c763d;
+}
.forum_answer .oe_answer_true:hover {
color: #4D964E;
}
.oe_answer_false {
- color: #bbbbbb; }
+ color: #bbbbbb;
+}
.oe_answer_false:hover {
color: #9E9C9C;
}
.favourite_question {
- color: #bbbbbb !important; }
+ color: #bbbbbb !important;
+}
.forum_favourite_question {
- color: #ffcc00 !important; }
+ color: #ffcc00 !important;
+}
a.no-decoration {
cursor: pointer;
- text-decoration: none !important; }
+ text-decoration: none !important;
+}
.faq-question:hover {
text-decoration: none !important;
- color: #428bca; }
+ color: #428bca;
+}
.oe_comment_grey {
- background-color: whitesmoke;
- padding: 4px; }
+ background-color: #f5f5f5;
+ padding: 4px;
+}
.close.comment_delete {
- font-size: 16px; }
+ font-size: 16px;
+}
.country_flag {
max-height: 16px;
display: inline-block;
- margin-left: 2px; }
+ margin-left: 2px;
+}
.tag_text .text-core .text-wrap textarea, .tag_text .text-core .text-wrap input, .tag_text .text-core .text-wrap .text-dropdown, .tag_text .text-core .text-wrap .text-prompt {
- font: 1.2em "Helvetica Neue", Helvetica, Arial, sans-serif !important; }
+ font: 1.2em "Helvetica Neue", Helvetica, Arial, sans-serif !important;
+}
.tag_text .text-core .text-wrap .text-tags .text-tag .text-button {
font: 1.2em "Helvetica Neue", Helvetica, Arial, sans-serif !important;
- height: 1.2em !important; }
+ height: 1.2em !important;
+}
.oe_forum_alert {
position: absolute;
margin-top: -30px;
margin-left: 90px;
width: 300px;
- z-index: 9999; }
+ z-index: 9999;
+}
.oe_forum_email_required {
position: absolute;
margin-top: 155px;
margin-left: 500px;
margin-right: 100px;
- z-index: 5; }
+ z-index: 5;
+}
button.btn-link.text-muted {
- color: #999999;
+ color: #999;
}
diff --git a/addons/website_forum/static/src/css/website_forum.sass b/addons/website_forum/static/src/css/website_forum.sass
index 1313516a084..4cfa6e97c74 100644
--- a/addons/website_forum/static/src/css/website_forum.sass
+++ b/addons/website_forum/static/src/css/website_forum.sass
@@ -17,6 +17,9 @@
margin-left: 50px
.question
+ clear: left
+
+.question
.question-name
font-size: 150%
.badge
diff --git a/addons/website_forum/static/src/js/website_forum.share.js b/addons/website_forum/static/src/js/website_forum.share.js
index 3ad1f3d0e7d..766008b297f 100644
--- a/addons/website_forum/static/src/js/website_forum.share.js
+++ b/addons/website_forum/static/src/js/website_forum.share.js
@@ -5,6 +5,7 @@ var ajax = require('web.ajax');
var core = require('web.core');
var SocialShare = require('website.share');
var website = require('website.website');
+website.if_dom_contains('.website_forum', function () {
var qweb = core.qweb;
website.add_template_file('/website_forum/static/src/xml/website_forum_share_templates.xml');
@@ -61,6 +62,7 @@ website.ready().done(function() {
}
});
-return ForumShare;
+});
+return {};
});
diff --git a/addons/website_forum/views/website_forum.xml b/addons/website_forum/views/website_forum.xml
index f1d570d517d..3421be65390 100644
--- a/addons/website_forum/views/website_forum.xml
+++ b/addons/website_forum/views/website_forum.xml
@@ -509,8 +509,8 @@
<li>Provide enough details and, if possible, give an example.</li>
</ul>
<form t-attf-action="/forum/#{slug(forum)}/new?post_type=question" method="post" role="form" class="tag_text">
- <input type="text" name="post_name" required="True" t-attf-value="#{post_name}"
- class="form-control mb16" placeholder="Your Question Title..."/>
+ <input type="text" name="post_name" required="True" pattern=".*\S.*" t-attf-value="#{post_name}"
+ class="form-control mb16" placeholder="Your Question Title..." title="Title must not be empty"/>
<input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
<textarea name="content" required="True" id="content" class="form-control load_editor"
@@ -535,8 +535,8 @@
<h3 t-if="is_answer">Edit reply</h3>
<form t-attf-action="/forum/#{slug(forum)}/post/#{slug(post)}/save" method="post" role="form" class="tag_text">
<div t-if="not is_answer">
- <input type="text" name="post_name" required="True"
- t-attf-value="#{post.name}" class="form-control mb8" placeholder="Edit your Post"/>
+ <input type="text" name="post_name" required="True" pattern=".*\S.*" t-attf-value="#{post.name}"
+ class="form-control mb8" placeholder="Edit your Post" title="Title must not be empty"/>
<h5 t-if="post.post_type == 'question'" class="mt20">Please enter a descriptive question (should finish by a '?')</h5>
</div>
<input type="hidden" name="karma" t-attf-value="#{user.karma}" id="karma"/>
diff --git a/addons/website_hr_recruitment/controllers/main.py b/addons/website_hr_recruitment/controllers/main.py
index 1db4eb2f02f..4bef1dfe0d8 100644
--- a/addons/website_hr_recruitment/controllers/main.py
+++ b/addons/website_hr_recruitment/controllers/main.py
@@ -116,22 +116,6 @@ class website_hr_recruitment(http.Controller):
# Retro-compatibility for saas-3. "phone" field should be replace by "partner_phone" in the template in trunk.
value['partner_phone'] = post.pop('phone', False)
- # If the email is already known in our database, match it to the existing partner, else create a new one
- Partner = env['res.partner']
- existing_partner = Partner.name_search(name=post.get('email_from').strip(), args=[('is_company', '=', False)], limit=1)
-
- if not existing_partner:
- value_partner = {
- 'name': post['partner_name'],
- 'email': post['email_from'],
- 'phone': value['partner_phone'],
- }
- partner = Partner.create(value_partner)
- value['partner_id'] = partner.id
- else:
- value['partner_id'] = existing_partner[0][0]
-
- # Create applicant
applicant = env['hr.applicant'].create(value)
if post['ufile']:
name = applicant.partner_name if applicant.partner_name else applicant.name
diff --git a/addons/website_links/models/website_links.py b/addons/website_links/models/website_links.py
index ac36ff2af8c..99148d1ae71 100644
--- a/addons/website_links/models/website_links.py
+++ b/addons/website_links/models/website_links.py
@@ -100,7 +100,7 @@ class website_links(models.Model):
if attr:
utms[key] = attr
- self.redirected_url = '%s://%s%s?%s%s#%s' % (parsed.scheme, parsed.netloc, parsed.path, urlencode(utms), parsed.query, parsed.fragment)
+ self.redirected_url = '%s://%s%s?%s&%s#%s' % (parsed.scheme, parsed.netloc, parsed.path, urlencode(utms), parsed.query, parsed.fragment)
@api.model
@api.depends('url')
diff --git a/addons/website_membership/__init__.py b/addons/website_membership/__init__.py
index ee5959455ad..9f86759e32b 100644
--- a/addons/website_membership/__init__.py
+++ b/addons/website_membership/__init__.py
@@ -1 +1,2 @@
import controllers
+import models
diff --git a/addons/website_membership/controllers/main.py b/addons/website_membership/controllers/main.py
index beedd50a04f..1df80c36c71 100644
--- a/addons/website_membership/controllers/main.py
+++ b/addons/website_membership/controllers/main.py
@@ -27,7 +27,7 @@ class WebsiteMembership(http.Controller):
'/members/association/<membership_id>/country/<country_name>-<int:country_id>/page/<int:page>',
'/members/association/<membership_id>/country/<int:country_id>/page/<int:page>',
], type='http', auth="public", website=True)
- def members(self, membership_id=None, country_name=None, country_id=0, page=0, **post):
+ def members(self, membership_id=None, country_name=None, country_id=0, page=1, **post):
cr, uid, context = request.cr, request.uid, request.context
product_obj = request.registry['product.product']
country_obj = request.registry['res.country']
@@ -56,7 +56,7 @@ class WebsiteMembership(http.Controller):
membership_line_ids = []
country_domain = ('free_member', '=', True)
countries = partner_obj.read_group(
- cr, uid, [country_domain, ("website_published", "=", True)], ["id", "country_id"],
+ cr, SUPERUSER_ID, [country_domain, ("website_published", "=", True)], ["id", "country_id"],
groupby="country_id", orderby="country_id", context=request.context)
countries_total = sum(country_dict['country_id_count'] for country_dict in countries)
@@ -64,11 +64,12 @@ class WebsiteMembership(http.Controller):
if country_id:
line_domain.append(('partner.country_id', '=', country_id))
current_country = country_obj.read(cr, uid, country_id, ['id', 'name'], context)
- if not any(x['country_id'][0] == country_id for x in countries):
+ if not any(x['country_id'][0] == country_id for x in countries if x['country_id']):
countries.append({
'country_id_count': 0,
'country_id': (country_id, current_country["name"])
})
+ countries = filter(lambda d:d['country_id'], countries)
countries.sort(key=lambda d: d['country_id'][1])
countries.insert(0, {
@@ -82,15 +83,32 @@ class WebsiteMembership(http.Controller):
# make sure we don't access to lines with unpublished membershipts
line_domain.append(('membership_id', 'in', membership_ids))
- # displayed membership lines
+ limit = self._references_per_page
+ offset = limit * (page - 1)
+
+ count_members = 0
+ membership_line_ids = []
+ # displayed non-free membership lines
if membership_id != 'free':
- membership_line_ids = membership_line_obj.search(cr, SUPERUSER_ID, line_domain, context=context)
+ count_members = membership_line_obj.search_count(cr, SUPERUSER_ID, line_domain, context=context)
+ if offset <= count_members:
+ membership_line_ids = tuple(membership_line_obj.search(cr, SUPERUSER_ID, line_domain, offset, limit, context=context))
membership_lines = membership_line_obj.browse(cr, uid, membership_line_ids, context=context)
# TODO: Following line can be deleted in master. Kept for retrocompatibility.
membership_lines = sorted(membership_lines, key=lambda x: x.membership_id.website_sequence)
- partner_ids = [m.partner.id for m in membership_lines]
- free_partner_ids = partner_obj.search(cr, SUPERUSER_ID, [('free_member', '=', True), ('website_published', '=', True)], context=context)
- google_map_partner_ids = ",".join(map(str, partner_ids))
+ page_partner_ids = set(m.partner.id for m in membership_lines)
+
+ google_map_partner_ids = []
+ if request.env.ref('website_membership.opt_index_google_map').customize_show:
+ membership_lines_ids = membership_line_obj.search(cr, uid, line_domain, context=context)
+ google_map_partner_ids = membership_line_obj.get_published_companies(cr, uid, membership_line_ids, limit=2000, context=context)
+
+ search_domain = [('free_member', '=', True), ('website_published', '=', True)]
+ if post_name:
+ search_domain += ['|', ('name', 'ilike', post_name), ('website_description', 'ilike', post_name)]
+ if country_id:
+ search_domain += [('country_id', '=', country_id)]
+ free_partner_ids = partner_obj.search(cr, SUPERUSER_ID, search_domain, context=context)
memberships_data = []
for membership_record in memberships:
memberships_data.append({'id': membership_record.id, 'name': membership_record.name})
@@ -100,12 +118,23 @@ class WebsiteMembership(http.Controller):
if free_partner_ids:
memberships_data.append({'id': 'free', 'name': _('Free Members')})
if not membership_id or membership_id == 'free':
- memberships_partner_ids['free'] = free_partner_ids
+ if count_members < offset + limit:
+ free_start = max(offset - count_members, 0)
+ free_end = max(offset + limit - count_members, 0)
+ memberships_partner_ids['free'] = free_partner_ids[free_start:free_end]
+ page_partner_ids |= set(memberships_partner_ids['free'])
+ google_map_partner_ids += free_partner_ids[:2000-len(google_map_partner_ids)]
+ count_members += len(free_partner_ids)
+
+ google_map_partner_ids = ",".join(map(str, google_map_partner_ids))
+
+ partners = { p.id: p for p in partner_obj.browse(request.cr, SUPERUSER_ID, list(page_partner_ids), request.context)}
- partners = dict((p.id, p) for p in partner_obj.browse(request.cr, SUPERUSER_ID, list(set(partner_ids + free_partner_ids)), request.context))
+ base_url = '/members%s%s' % ('/association/%s' % membership_id if membership_id else '',
+ '/country/%s' % country_id if country_id else '')
# request pager for lines
- pager = request.website.pager(url="/members", total=len(membership_line_ids), page=page, step=self._references_per_page, scope=7, url_args=post)
+ pager = request.website.pager(url=base_url, total=count_members, page=page, step=limit, scope=7, url_args=post)
values = {
'partners': partners,
diff --git a/addons/website_membership/models/__init__.py b/addons/website_membership/models/__init__.py
new file mode 100644
index 00000000000..58702d876e4
--- /dev/null
+++ b/addons/website_membership/models/__init__.py
@@ -0,0 +1 @@
+import membership
diff --git a/addons/website_membership/models/membership.py b/addons/website_membership/models/membership.py
new file mode 100644
index 00000000000..feba5ce6ea6
--- /dev/null
+++ b/addons/website_membership/models/membership.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import osv, fields
+
+class membership_membership_line(osv.Model):
+ _inherit = 'membership.membership_line'
+
+ def get_published_companies(self, cr, uid, ids, limit=None, context=None):
+ if not ids:
+ return []
+ limit_clause = '' if limit is None else ' LIMIT %d' % limit
+ cr.execute('SELECT DISTINCT p.id \
+ FROM res_partner p INNER JOIN membership_membership_line m \
+ ON p.id = m.partner \
+ WHERE website_published AND is_company AND m.id IN %s ' + limit_clause, (tuple(ids),))
+ return [partner_id[0] for partner_id in cr.fetchall()]
diff --git a/addons/website_quote/controllers/main.py b/addons/website_quote/controllers/main.py
index 045116e3acc..f5fc8d46741 100644
--- a/addons/website_quote/controllers/main.py
+++ b/addons/website_quote/controllers/main.py
@@ -60,7 +60,7 @@ class sale_quote(http.Controller):
'message': message and int(message) or False,
'option': bool(filter(lambda x: not x.line_id, order.options)),
'order_valid': (not order.validity_date) or (now <= order.validity_date),
- 'days_valid': max(days, 0),
+ 'days_valid': days,
'action': action
}
return request.website.render('website_quote.so_quotation', values)
diff --git a/addons/website_quote/models/order.py b/addons/website_quote/models/order.py
index bb549e8fbeb..7a6e32db1b5 100644
--- a/addons/website_quote/models/order.py
+++ b/addons/website_quote/models/order.py
@@ -278,6 +278,8 @@ class sale_quote_option(osv.osv):
'name': product_obj.name,
'uom_id': product_obj.product_tmpl_id.uom_id.id,
})
+ if product_obj.description_sale:
+ vals['name'] += '\n'+product_obj.description_sale
return {'value': vals}
class sale_order_option(osv.osv):
@@ -310,6 +312,8 @@ class sale_order_option(osv.osv):
'name': product_obj.name,
'uom_id': product_obj.product_tmpl_id.uom_id.id,
})
+ if product_obj.description_sale:
+ vals['name'] += '\n'+product_obj.description_sale
return {'value': vals}
class product_template(osv.Model):
diff --git a/addons/website_quote/static/src/css/website_quotation.css b/addons/website_quote/static/src/css/website_quotation.css
index c4bbd4d9c64..defa37feef5 100644
--- a/addons/website_quote/static/src/css/website_quotation.css
+++ b/addons/website_quote/static/src/css/website_quotation.css
@@ -42,6 +42,11 @@
padding-left: 30px;
font-size: 90%;
}
+@media (max-width: 991px) {
+ .o_website_quote .bs-sidebar {
+ position: static;
+ }
+}
@media (min-width: 992px) {
.o_website_quote .bs-sidebar .nav > .active > ul {
display: block;
diff --git a/addons/website_quote/views/website_quotation.xml b/addons/website_quote/views/website_quotation.xml
index ab00e2800d8..8d663dcf775 100644
--- a/addons/website_quote/views/website_quotation.xml
+++ b/addons/website_quote/views/website_quotation.xml
@@ -333,10 +333,12 @@
<t t-if="quotation.state not in ('draft','sent','cancel')">Your Order</t>
<em t-esc="quotation.name"/>
<small t-field="quotation.state"/>
- <div groups="base.group_website_publisher" t-ignore="true" class="pull-right css_editable_mode_hidden hidden-print">
- <a class="btn btn-info" target="_blank" t-att-href="'/quote/%s/%s' % (quotation.id,quotation.access_token)+'?pdf=True'">Print</a>
- <a t-if="quotation.state not in ('manual')" class="btn btn-info" t-att-href="'/web#return_label=Website&amp;model=%s&amp;id=%s&amp;action=%s&amp;view_type=form' % (quotation._name, quotation.id, action)">Update Quote</a>
- <a t-if="quotation.state in ('manual')" class=" btn btn-info" t-att-href="'/web#return_label=Website&amp;model=%s&amp;id=%s&amp;action=%s&amp;view_type=form' % (quotation._name, quotation.id, action)">Back to Sale Order</a>
+ <div t-ignore="true" class="pull-right css_editable_mode_hidden hidden-print">
+ <div groups="base.group_website_publisher">
+ <a t-if="quotation.state not in ('manual')" class="btn btn-info" t-att-href="'/web#return_label=Website&amp;model=%s&amp;id=%s&amp;action=%s&amp;view_type=form' % (quotation._name, quotation.id, action)">Update Quote</a>
+ <a t-if="quotation.state in ('manual')" class=" btn btn-info" t-att-href="'/web#return_label=Website&amp;model=%s&amp;id=%s&amp;action=%s&amp;view_type=form' % (quotation._name, quotation.id, action)">Back to Sale Order</a>
+ </div>
+ <a class="btn btn-info" target="_blank" t-att-href="'/quote/%s/%s' % (quotation.id,quotation.access_token)+'?pdf=True'">Print</a>
</div>
</h1>
</div>
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index fda3c1d2228..3d887272144 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -360,6 +360,10 @@ class website_sale(http.Controller):
@http.route(['/shop/cart/update_json'], type='json', auth="public", methods=['POST'], website=True)
def cart_update_json(self, product_id, line_id, add_qty=None, set_qty=None, display=True):
order = request.website.sale_get_order(force_create=1)
+ if order.state != 'draft':
+ request.website.sale_reset()
+ return {}
+
value = order._cart_update(product_id=product_id, line_id=line_id, add_qty=add_qty, set_qty=set_qty)
if not display:
return None
@@ -1006,10 +1010,13 @@ class website_sale(http.Controller):
return ret
@http.route(['/shop/get_unit_price'], type='json', auth="public", methods=['POST'], website=True)
- def get_unit_price(self, product_ids, add_qty, **kw):
+ def get_unit_price(self, product_ids, add_qty, use_order_pricelist=False, **kw):
cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
products = pool['product.product'].browse(cr, uid, product_ids, context=context)
partner = pool['res.users'].browse(cr, uid, uid, context=context).partner_id
- pricelist_id = request.website.get_current_pricelist(context=context).id
+ if use_order_pricelist:
+ pricelist_id = request.website.get_current_pricelist(context=context).id
+ else:
+ pricelist_id = partner.property_product_pricelist.id
prices = pool['product.pricelist'].price_rule_get_multi(cr, uid, [], [(product, add_qty, partner) for product in products], context=context)
return {product_id: prices[product_id][pricelist_id][0] for product_id in product_ids}
diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py
index 0214e9246e4..3f35d27eab8 100644
--- a/addons/website_sale/models/sale_order.py
+++ b/addons/website_sale/models/sale_order.py
@@ -6,6 +6,7 @@ from openerp import SUPERUSER_ID, tools
from openerp.osv import osv, orm, fields
from openerp.addons.web.http import request
from openerp.tools.translate import _
+from openerp.exceptions import UserError
class sale_order(osv.Model):
@@ -82,6 +83,8 @@ class sale_order(osv.Model):
quantity = 0
for so in self.browse(cr, uid, ids, context=context):
+ if so.state != 'draft':
+ raise UserError(_('It is forbidden to modify a sale order which is not in draft status'))
if line_id is not False:
line_ids = so._cart_find_product_line(product_id, line_id, context=context, **kwargs)
if line_ids:
@@ -292,7 +295,7 @@ class website(orm.Model):
values['partner_id'] = partner.id
sale_order_obj.write(cr, SUPERUSER_ID, [sale_order_id], values, context=context)
- if flag_pricelist or values.get('fiscal_position_id') != fiscal_position:
+ if flag_pricelist or values.get('fiscal_position_id', False) != fiscal_position:
update_pricelist = True
if (code and code != sale_order.pricelist_id.code) or \
diff --git a/addons/website_sale/security/ir.model.access.csv b/addons/website_sale/security/ir.model.access.csv
index 110de4a75b0..47aca893c43 100644
--- a/addons/website_sale/security/ir.model.access.csv
+++ b/addons/website_sale/security/ir.model.access.csv
@@ -15,4 +15,6 @@ access_product_attribute_value_public,product.attribute value public,product.mod
access_product_attribute_price_public,product.attribute price public,product.model_product_attribute_price,,1,0,0,0
access_product_attribute_line_public,product.attribute line public,product.model_product_attribute_line,,1,0,0,0
access_website_pricelist,website_pricelist,model_website_pricelist,,1,0,0,0
-access_website_pricelist_sale_manager,website_pricelist,model_website_pricelist,,base.group_sale_manager,1,1,1,1 \ No newline at end of file
+access_website_pricelist_sale_manager,website_pricelist,model_website_pricelist,,base.group_sale_manager,1,1,1,1
+access_fiscal_position_public,fiscal position public,account.model_account_fiscal_position,base.group_portal,1,0,0,0
+access_payment_term,payment term public,account.model_account_payment_term,base.group_portal,1,0,0,0 \ No newline at end of file
diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js
index 70ae24f0a59..5ac79ef1c53 100644
--- a/addons/website_sale/static/src/js/website_sale.js
+++ b/addons/website_sale/static/src/js/website_sale.js
@@ -69,7 +69,7 @@ $('.oe_website_sale').each(function () {
var product_id = parseInt($input.data('product-id'),10);
var product_ids = [product_id];
$dom_optional.each(function(){
- product_ids.push($(this).find('span[data-oe-model="product.product"]').data('oe-id'));
+ product_ids.push($(this).find('span[data-product-id]').data('product-id'));
});
if (isNaN(value)) value = 0;
@@ -79,14 +79,15 @@ $('.oe_website_sale').each(function () {
else {
var gup = ajax.jsonRpc("/shop/get_unit_price", 'call', {
'product_ids': product_ids,
- 'add_qty': value})
+ 'add_qty': value,
+ 'use_order_pricelist': true})
.then(function (res) {
//basic case
$dom.find('span.oe_currency_value').last().text(res[product_id].toFixed(2));
$dom.find('.text-danger').toggle(res[product_id]<default_price && (default_price-res[product_id] > default_price/100));
//optional case
$dom_optional.each(function(){
- var id = $(this).find('span[data-oe-model="product.product"]').data('oe-id');
+ var id = $(this).find('span[data-product-id]').data('product-id');
var price = parseFloat($(this).find(".text-danger > span.oe_currency_value").text());
$(this).find("span.oe_currency_value").last().text(res[id].toFixed(2));
$(this).find('.text-danger').toggle(res[id]<price && (price-res[id]>price/100));
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index 919d29d5cce..b5be678815b 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -307,8 +307,7 @@
<t t-call="website.layout">
<t t-set="additional_title" t-value="product.name"/>
<div itemscope="itemscope" itemtype="http://schema.org/Product" id="wrap" class="js_sale">
-
- <section class="container mt8">
+ <section t-attf-class="container mt8 oe_website_sale #{(compute_currency(product.lst_price) - product.price) &gt; 0.1 and 'discount'}" id="product_detail">
<div class="row">
<div class="col-sm-4">
<ol class="breadcrumb">
@@ -328,9 +327,6 @@
</t>
</div>
</div>
- </section>
-
- <section t-attf-class="container oe_website_sale #{(compute_currency(product.lst_price) - product.price) &gt; 0.1 and 'discount'}" id="product_detail">
<div class="row">
<div class="col-sm-7 col-md-7 col-lg-7">
<span itemprop="image" t-field="product.image" t-field-options='{"widget": "image", "class": "product_detail_img"}'/>
@@ -729,7 +725,7 @@
<td>
<div>
<a t-attf-href="/shop/product/#{ slug(product.product_tmpl_id) }">
- <strong t-esc="product.name_get()[0][1]"/>
+ <strong t-field="product.display_name"/>
</a>
</div>
<div class="text-muted" t-field="product.description_sale"/>
diff --git a/addons/website_sale_delivery/views/website_sale_delivery.xml b/addons/website_sale_delivery/views/website_sale_delivery.xml
index 4cd2e13792b..d32ef491c0f 100644
--- a/addons/website_sale_delivery/views/website_sale_delivery.xml
+++ b/addons/website_sale_delivery/views/website_sale_delivery.xml
@@ -40,6 +40,7 @@
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
+ <div t-field="delivery.website_description" class="text-muted"/>
</label>
</t>
</li>
diff --git a/addons/website_sale_options/static/src/js/website_sale.js b/addons/website_sale_options/static/src/js/website_sale.js
index ed979095b6b..c8a2175f384 100644
--- a/addons/website_sale_options/static/src/js/website_sale.js
+++ b/addons/website_sale_options/static/src/js/website_sale.js
@@ -71,7 +71,7 @@ $('.oe_website_sale #add_to_cart, .oe_website_sale #products_grid .a-submit')
});
$modal.on("change", 'input[name="add_qty"]', function (event) {
- var product_id = $($modal.find('span.oe_price[data-oe-model="product.product"]').first()).data('oe-id');
+ var product_id = $($modal.find('span.oe_price[data-product-id]')).first().data('product-id');
var default_price = parseInt($('.text-danger.oe_default_price > span.oe_currency_value').text());
var $dom = $(event.target).closest('tr');
var qty = $dom.find('input[name="add_qty"]').val();
diff --git a/addons/website_sale_options/views/templates.xml b/addons/website_sale_options/views/templates.xml
index 5af23c8841d..1ce5886466d 100644
--- a/addons/website_sale_options/views/templates.xml
+++ b/addons/website_sale_options/views/templates.xml
@@ -19,7 +19,7 @@
</xpath>
<xpath expr="//table[@id='cart_products']/tbody//td[last()]" position="inside">
<t t-if="line.linked_line_id">
- <span class="js_quantity text-muted" t-att-data-line-id="line.id" t-esc="int(line.product_uom_qty)"/>
+ <span class="js_quantity text-muted" t-att-data-line-id="line.id" t-att-data-product-id="line.product_id.id" t-esc="int(line.product_uom_qty)"/>
</t>
</xpath>
</template>
@@ -48,7 +48,7 @@
<span t-field="product.image_medium" t-field-options='{"widget": "image" }'/>
</td>
<td colspan="2">
- <strong t-field="product.name"/>
+ <strong t-field="product.display_name"/>
<div class="text-muted">
<div t-field="product.description_sale"/>
<div class="js_attributes"/>
@@ -63,6 +63,7 @@
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>
<span class="oe_price" style="white-space: nowrap;"
+ t-att-data-product-id="product.id"
t-field="product.price"
t-field-options='{
"widget": "monetary",
diff --git a/addons/website_slides/static/src/js/slides_upload.js b/addons/website_slides/static/src/js/slides_upload.js
index 2cece16ca91..c1d0c68d028 100644
--- a/addons/website_slides/static/src/js/slides_upload.js
+++ b/addons/website_slides/static/src/js/slides_upload.js
@@ -10,7 +10,7 @@ var slides = require('website_slides.slides');
var _t = core._t;
-$(document).ready(function () {
+website.if_dom_contains('.oe_slide_js_upload', function () {
website.add_template_file('/website_slides/static/src/xml/website_slides.xml');