summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2016-10-27 18:23:44 +0200
committerChristophe Simonis <chs@odoo.com>2016-10-27 18:23:44 +0200
commitea479ce6f3ffdcf36c5f918b6ce68888e42a56ce (patch)
treeaddf64e3f038bf53c293cc7ba1e81fd0b9413b32 /addons
parent68b1c5c1fa643e99fd5e597949c39e222477dcab (diff)
parent3bb022d57c43c51a5fd4be39e4123b1206db4782 (diff)
[MERGE] forward port branch 10.0 up to 3bb022d
Diffstat (limited to 'addons')
-rw-r--r--addons/account/models/account.py12
-rw-r--r--addons/account/models/account_bank_statement.py2
-rw-r--r--addons/account/models/account_invoice.py16
-rw-r--r--addons/account/models/account_move.py4
-rw-r--r--addons/account/report/account_invoice_report.py2
-rw-r--r--addons/account/tests/test_tax.py36
-rw-r--r--addons/account/views/account_invoice_view.xml8
-rw-r--r--addons/account/views/account_payment_view.xml6
-rw-r--r--addons/account/views/web_planner_data.xml2
-rw-r--r--addons/account_voucher/models/account_voucher.py4
-rw-r--r--addons/base_import/static/src/xml/base_import.xml2
-rw-r--r--addons/board/static/src/js/dashboard.js14
-rw-r--r--addons/crm/report/crm_opportunity_report.py3
-rw-r--r--addons/hr_expense/models/hr_expense.py122
-rw-r--r--addons/hr_expense/views/hr_expense_views.xml9
-rw-r--r--addons/hr_timesheet_attendance/models/hr_timesheet_sheet.py20
-rw-r--r--addons/l10n_in/data/account_tax_template_data.xml6
-rw-r--r--addons/l10n_nl/data/account_chart_template.xml13
-rw-r--r--addons/l10n_nz/data/l10n_nz_chart_data.xml11
-rw-r--r--addons/mail/models/mail_channel.py3
-rw-r--r--addons/mail/static/src/js/chatter.js10
-rw-r--r--addons/mrp/models/stock_move.py19
-rw-r--r--addons/mrp/models/stock_warehouse.py4
-rw-r--r--addons/mrp_repair/models/mrp_repair.py22
-rw-r--r--addons/payment/models/payment_acquirer.py4
-rw-r--r--addons/payment_stripe/models/payment.py6
-rw-r--r--addons/point_of_sale/models/pos_order.py90
-rw-r--r--addons/point_of_sale/models/pos_session.py4
-rw-r--r--addons/point_of_sale/static/src/xml/pos.xml2
-rw-r--r--addons/point_of_sale/tests/test_point_of_sale_flow.py201
-rw-r--r--addons/product/models/product.py5
-rw-r--r--addons/product/models/product_attribute.py4
-rw-r--r--addons/purchase/models/stock.py8
-rw-r--r--addons/resource/models/resource.py2
-rw-r--r--addons/sale/models/sale.py2
-rw-r--r--addons/sale/wizard/sale_make_invoice_advance.py5
-rw-r--r--addons/sale_timesheet/views/sale_order_views.xml2
-rw-r--r--addons/stock/models/procurement.py10
-rw-r--r--addons/stock/models/stock_quant.py25
-rw-r--r--addons/stock/models/stock_warehouse.py46
-rw-r--r--addons/stock_account/models/account_invoice.py2
-rw-r--r--addons/web/static/src/js/framework/data.js11
-rw-r--r--addons/web/static/src/js/views/list_view.js7
-rw-r--r--addons/web/static/test/list-editable.js21
-rw-r--r--addons/web/static/test/list.js6
-rw-r--r--addons/web/tests/test_js.py3
-rw-r--r--addons/web_diagram/static/src/js/diagram.js40
-rw-r--r--addons/web_editor/static/src/js/ace.js14
-rw-r--r--addons/web_planner/models/web_planner.py2
-rw-r--r--addons/web_planner/static/src/js/web_planner_common.js36
-rw-r--r--addons/website/static/src/js/website.ace.js6
-rw-r--r--addons/website/static/src/js/website.backendDashboard.js2
-rw-r--r--addons/website/static/src/js/website.js4
-rw-r--r--addons/website/static/src/less/website.ui.components.less2
-rw-r--r--addons/website_event_track/models/website_event_track.py3
-rw-r--r--addons/website_mail_channel/controllers/main.py5
-rw-r--r--addons/website_sale/views/templates.xml2
57 files changed, 666 insertions, 266 deletions
diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index eec37901431..c65819c3bfe 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -703,7 +703,12 @@ class AccountTax(models.Model):
if not round_tax:
prec += 5
- total_excluded = total_included = base = round(price_unit * quantity, prec)
+
+ base_values = self.env.context.get('base_values')
+ if not base_values:
+ total_excluded = total_included = base = round(price_unit * quantity, prec)
+ else:
+ total_excluded, total_included, base = base_values
# Sorting key is mandatory in this case. When no key is provided, sorted() will perform a
# search. However, the search method is overridden in account.tax in order to add a domain
@@ -711,9 +716,10 @@ class AccountTax(models.Model):
# case of group taxes.
for tax in self.sorted(key=lambda r: r.sequence):
if tax.amount_type == 'group':
- ret = tax.children_tax_ids.compute_all(price_unit, currency, quantity, product, partner)
+ children = tax.children_tax_ids.with_context(base_values=(total_excluded, total_included, base))
+ ret = children.compute_all(price_unit, currency, quantity, product, partner)
total_excluded = ret['total_excluded']
- base = ret['base']
+ base = ret['base'] if tax.include_base_amount else base
total_included = ret['total_included']
tax_amount = total_included - total_excluded
taxes += ret['taxes']
diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py
index 204f0161a85..931ae7f41df 100644
--- a/addons/account/models/account_bank_statement.py
+++ b/addons/account/models/account_bank_statement.py
@@ -472,7 +472,7 @@ class AccountBankStatementLine(models.Model):
'details': {
'name': _("Automatically reconciled items"),
'model': 'account.move',
- 'ids': automatic_reconciliation_entries.ids
+ 'ids': automatic_reconciliation_entries.mapped('journal_entry_ids').ids
}
}]
return {
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 489f5785252..8028355d76e 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -612,6 +612,14 @@ class AccountInvoice(models.Model):
'account_analytic_id': tax['analytic'] and line.account_analytic_id.id or False,
'account_id': self.type in ('out_invoice', 'in_invoice') and (tax['account_id'] or line.account_id.id) or (tax['refund_account_id'] or line.account_id.id),
}
+
+ # If the taxes generate moves on the same financial account as the invoice line,
+ # propagate the analytic account from the invoice line to the tax line.
+ # This is necessary in situations were (part of) the taxes cannot be reclaimed,
+ # to ensure the tax move is allocated to the proper analytic account.
+ if not vals.get('account_analytic_id') and line.account_analytic_id and vals['account_id'] == line.account_id.id:
+ vals['account_analytic_id'] = line.account_analytic_id.id
+
return vals
@api.multi
@@ -622,14 +630,6 @@ class AccountInvoice(models.Model):
taxes = line.invoice_line_tax_ids.compute_all(price_unit, self.currency_id, line.quantity, line.product_id, self.partner_id)['taxes']
for tax in taxes:
val = self._prepare_tax_line_vals(line, tax)
-
- # If the taxes generate moves on the same financial account as the invoice line,
- # propagate the analytic account from the invoice line to the tax line.
- # This is necessary in situations were (part of) the taxes cannot be reclaimed,
- # to ensure the tax move is allocated to the proper analytic account.
- if not val.get('account_analytic_id') and line.account_analytic_id and val['account_id'] == line.account_id.id:
- val['account_analytic_id'] = line.account_analytic_id.id
-
key = self.env['account.tax'].browse(tax['id']).get_grouping_key(val)
if key not in tax_grouped:
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 36ca9718b74..e825ebc4ac1 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -154,6 +154,7 @@ class AccountMove(models.Model):
'SET state=%s '\
'WHERE id IN %s', ('draft', tuple(self.ids),))
self.invalidate_cache()
+ self._check_lock_date()
return True
@api.multi
@@ -988,6 +989,9 @@ class AccountMoveLine(models.Model):
return True
rec_move_ids = self.env['account.partial.reconcile']
for account_move_line in self:
+ for invoice in account_move_line.payment_id.invoice_ids:
+ if account_move_line in invoice.payment_move_line_ids:
+ account_move_line.payment_id.write({'invoice_ids': [(3, invoice.id, None)]})
rec_move_ids += account_move_line.matched_debit_ids
rec_move_ids += account_move_line.matched_credit_ids
return rec_move_ids.unlink()
diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py
index 9d0b3d3da27..eb969cadec4 100644
--- a/addons/account/report/account_invoice_report.py
+++ b/addons/account/report/account_invoice_report.py
@@ -45,7 +45,7 @@ class AccountInvoiceReport(models.Model):
user_currency_price_total = fields.Float(string="Total Without Tax", compute='_compute_amounts_in_user_currency', digits=0)
price_average = fields.Float(string='Average Price', readonly=True, group_operator="avg")
user_currency_price_average = fields.Float(string="Average Price", compute='_compute_amounts_in_user_currency', digits=0)
- currency_rate = fields.Float(string='Currency Rate', readonly=True)
+ currency_rate = fields.Float(string='Currency Rate', readonly=True, group_operator="avg")
nbr = fields.Integer(string='# of Lines', readonly=True) # TDE FIXME master: rename into nbr_lines
type = fields.Selection([
('out_invoice', 'Customer Invoice'),
diff --git a/addons/account/tests/test_tax.py b/addons/account/tests/test_tax.py
index d626f4e20e0..84aa784fad3 100644
--- a/addons/account/tests/test_tax.py
+++ b/addons/account/tests/test_tax.py
@@ -43,10 +43,46 @@ class TestTax(AccountTestUsers):
(4, self.percent_tax.id, 0)
]
})
+ self.group_tax_bis = self.tax_model.create({
+ 'name': "Group tax bis",
+ 'amount_type': 'group',
+ 'amount': 0,
+ 'sequence': 6,
+ 'children_tax_ids': [
+ (4, self.fixed_tax.id, 0),
+ (4, self.percent_tax.id, 0)
+ ]
+ })
+ self.group_of_group_tax = self.tax_model.create({
+ 'name': "Group of group tax",
+ 'amount_type': 'group',
+ 'amount': 0,
+ 'sequence': 7,
+ 'children_tax_ids': [
+ (4, self.group_tax.id, 0),
+ (4, self.group_tax_bis.id, 0)
+ ]
+ })
self.bank_journal = self.env['account.journal'].search([('type', '=', 'bank'), ('company_id', '=', self.account_manager.company_id.id)])[0]
self.bank_account = self.bank_journal.default_debit_account_id
self.expense_account = self.env['account.account'].search([('user_type_id.type', '=', 'payable')], limit=1) #Should be done by onchange later
+ def test_tax_group_of_group_tax(self):
+ self.fixed_tax.include_base_amount = True
+ self.group_tax.include_base_amount = True
+ self.group_of_group_tax.include_base_amount = True
+ res = self.group_of_group_tax.compute_all(200.0)
+ self.assertEquals(res['total_excluded'], 200.0)
+ # After calculation of first group
+ # base = 210
+ # total_included = 231
+ # Base of the first grouped is passed
+ # Base after the second group (220) is dropped.
+ # Base of the group of groups is passed out,
+ # so we obtain base as after first group
+ self.assertEquals(res['base'], 210.0)
+ self.assertEquals(res['total_included'], 263.0)
+
def test_tax_group(self):
res = self.group_tax.compute_all(200.0)
self.assertEquals(res['total_excluded'], 200.0)
diff --git a/addons/account/views/account_invoice_view.xml b/addons/account/views/account_invoice_view.xml
index 568bdead85d..e1da859f9d0 100644
--- a/addons/account/views/account_invoice_view.xml
+++ b/addons/account/views/account_invoice_view.xml
@@ -135,8 +135,8 @@
<field name="user_id"/>
<field name="date_due"/>
<field name="origin"/>
- <field name="amount_total_signed" string="Total"/>
- <field name="residual_signed" string="Amount Due"/>
+ <field name="amount_total_signed" string="Total" sum="Total"/>
+ <field name="residual_signed" string="Amount Due" sum="Amount Due"/>
<field name="currency_id" invisible="1"/>
<field name="company_currency_id" invisible="1"/>
<field name="state"/>
@@ -200,8 +200,8 @@
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
<field name="date_due"/>
<field name="origin"/>
- <field name="amount_total_signed" string="Total"/>
- <field name="residual_signed" string="To Pay"/>
+ <field name="amount_total_signed" string="Total" sum="Total"/>
+ <field name="residual_signed" string="To Pay" sum="To pay"/>
<field name="currency_id" invisible="1"/>
<field name="company_currency_id" invisible="1"/>
<field name="state"/>
diff --git a/addons/account/views/account_payment_view.xml b/addons/account/views/account_payment_view.xml
index 1f12802b74c..a14c4c73ffa 100644
--- a/addons/account/views/account_payment_view.xml
+++ b/addons/account/views/account_payment_view.xml
@@ -12,7 +12,7 @@
<field name="journal_id"/>
<field name="payment_method_id"/>
<field name="partner_id" string="Customer"/>
- <field name="amount"/>
+ <field name="amount" sum="Amount"/>
<field name="state"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="currency_id" invisible="1"/>
@@ -31,7 +31,7 @@
<field name="journal_id"/>
<field name="payment_method_id"/>
<field name="partner_id" string="Vendor"/>
- <field name="amount"/>
+ <field name="amount" sum="Amount"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="state"/>
<field name="currency_id" invisible="1"/>
@@ -149,7 +149,7 @@
</p>
</field>
</record>
-
+
<record id="action_account_payments_payable" model="ir.actions.act_window">
<field name="name">Payments</field>
<field name="res_model">account.payment</field>
diff --git a/addons/account/views/web_planner_data.xml b/addons/account/views/web_planner_data.xml
index de159a85cf0..b3569deb76b 100644
--- a/addons/account/views/web_planner_data.xml
+++ b/addons/account/views/web_planner_data.xml
@@ -409,7 +409,7 @@
<span class="fa fa-tasks fa-4x"></span>
<h3><strong>Revenue Recognition</strong></h3>
<p>Use in conjunction with contracts to calculate your monthly revenue for multi-month contracts.</p>
- <a t-att-href="prepare_backend_url('base.open_module_tree', 'form', 'account_asset')"
+ <a t-att-href="prepare_backend_url('base.open_module_tree', 'form', 'account_deferred_revenue')"
class="btn odoo_purple">
<span class="fa fa-arrow-circle-o-down"/> Install Now
</a>
diff --git a/addons/account_voucher/models/account_voucher.py b/addons/account_voucher/models/account_voucher.py
index d621d73dcfa..d677a6490e5 100644
--- a/addons/account_voucher/models/account_voucher.py
+++ b/addons/account_voucher/models/account_voucher.py
@@ -30,10 +30,10 @@ class AccountVoucher(models.Model):
name = fields.Char('Payment Reference',
readonly=True, states={'draft': [('readonly', False)]}, default='')
date = fields.Date("Bill Date", readonly=True,
- select=True, states={'draft': [('readonly', False)]},
+ index=True, states={'draft': [('readonly', False)]},
copy=False, default=fields.Date.context_today)
account_date = fields.Date("Accounting Date",
- readonly=True, select=True, states={'draft': [('readonly', False)]},
+ readonly=True, index=True, states={'draft': [('readonly', False)]},
help="Effective date for accounting entries", copy=False, default=fields.Date.context_today)
journal_id = fields.Many2one('account.journal', 'Journal',
required=True, readonly=True, states={'draft': [('readonly', False)]}, default=_default_journal)
diff --git a/addons/base_import/static/src/xml/base_import.xml b/addons/base_import/static/src/xml/base_import.xml
index dce2ab6fe36..0d23096f691 100644
--- a/addons/base_import/static/src/xml/base_import.xml
+++ b/addons/base_import/static/src/xml/base_import.xml
@@ -8,7 +8,7 @@
<input type="hidden" name="import_id"/>
<div class="oe_import_box col-sm-9">
<div class="col-sm-12">
- <p>Select a CSV or Excel file to import. <a href="https://www.odoo.com/documentation/user/10/general/base_import/import_faq.html" target="new" class="pull-right">Help</a></p>
+ <p>Select a CSV or Excel file to import. <a href="https://www.odoo.com/documentation/user/10.0/general/base_import/import_faq.html" target="new" class="pull-right">Help</a></p>
</div>
<div class="col-sm-10">
<div class="input-group">
diff --git a/addons/board/static/src/js/dashboard.js b/addons/board/static/src/js/dashboard.js
index 87ff74f826a..3575f36f655 100644
--- a/addons/board/static/src/js/dashboard.js
+++ b/addons/board/static/src/js/dashboard.js
@@ -139,7 +139,11 @@ var DashBoard = form_common.FormWidget.extend({
},
on_close_action: function(e) {
if (confirm(_t("Are you sure you want to remove this item ?"))) {
- $(e.currentTarget).parents('.oe_action:first').remove();
+ var $container = $(e.currentTarget).parents('.oe_action:first');
+ var am = _.findWhere(this.action_managers, { am_id: $container.data('am_id') });
+ am.destroy();
+ this.action_managers.splice(_.indexOf(this.action_managers, am), 1);
+ $container.remove();
this.do_save_dashboard();
}
},
@@ -223,7 +227,13 @@ var DashBoard = form_common.FormWidget.extend({
var am = new ActionManager(this),
// FIXME: ideally the dashboard view shall be refactored like kanban.
$action = $('#' + this.view.element_id + '_action_' + index);
- $action.parent().data('action_attrs', action_attrs);
+ var $action_container = $action.closest('.oe_action');
+ var am_id = _.uniqueId('action_manager_');
+ am.am_id = am_id;
+ $action_container.data({
+ action_attrs: action_attrs,
+ am_id: am_id,
+ });
this.action_managers.push(am);
am.appendTo($action).then(function () {
am.do_action(action).then(function () {
diff --git a/addons/crm/report/crm_opportunity_report.py b/addons/crm/report/crm_opportunity_report.py
index a44bb233592..5bf8abc1d8d 100644
--- a/addons/crm/report/crm_opportunity_report.py
+++ b/addons/crm/report/crm_opportunity_report.py
@@ -49,8 +49,9 @@ class OpportunityReport(models.Model):
medium_id = fields.Many2one('utm.medium', string='Medium', readonly=True)
def init(self):
+ tools.drop_view_if_exists(self._cr, 'crm_opportunity_report')
self._cr.execute("""
- CREATE OR REPLACE VIEW crm_opportunity_report AS (
+ CREATE VIEW crm_opportunity_report AS (
SELECT
c.id,
c.date_deadline,
diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py
index fa4c6498366..2756b0ebd42 100644
--- a/addons/hr_expense/models/hr_expense.py
+++ b/addons/hr_expense/models/hr_expense.py
@@ -171,79 +171,71 @@ class HrExpense(models.Model):
'''
main function that is called when trying to create the accounting entries related to an expense
'''
- journal_dict = {}
for expense in self:
- acc_date = expense.sheet_id.accounting_date or fields.Date.context_today(self)
- jrn = expense.sheet_id.bank_journal_id if expense.payment_mode == 'company_account' else expense.sheet_id.journal_id
- journal_dict.setdefault(jrn, self.env['hr.expense'])
- journal_dict[jrn] |= expense
-
- for journal, expense_list in journal_dict.items():
+ journal = expense.sheet_id.bank_journal_id if expense.payment_mode == 'company_account' else expense.sheet_id.journal_id
#create the move that will contain the accounting entries
- # reference if the same for every expense
- ref = len(expense_list.mapped('employee_id.address_home_id.ref')) == 1 and expense_list[0].employee_id.address_home_id.ref or False
+ acc_date = expense.sheet_id.accounting_date or expense.date
move = self.env['account.move'].create({
'journal_id': journal.id,
'company_id': self.env.user.company_id.id,
'date': acc_date,
- 'ref': ref,
+ 'ref': expense.sheet_id.name,
# force the name to the default value, to avoid an eventual 'default_name' in the context
# to set it to '' which cause no number to be given to the account.move when posted.
'name': '/',
})
- for expense in expense_list:
- company_currency = expense.company_id.currency_id
- diff_currency_p = expense.currency_id != company_currency
- #one account.move.line per expense (+taxes..)
- move_lines = expense._move_line_get()
-
- #create one more move line, a counterline for the total on payable account
- payment_id = False
- total, total_currency, move_lines = expense._compute_expense_totals(company_currency, move_lines, acc_date)
- if expense.payment_mode == 'company_account':
- if not expense.sheet_id.bank_journal_id.default_credit_account_id:
- raise UserError(_("No credit account found for the %s journal, please configure one.") % (expense.sheet_id.bank_journal_id.name))
- emp_account = expense.sheet_id.bank_journal_id.default_credit_account_id.id
- journal = expense.bank_journal_id
- #create payment
- payment_methods = (total < 0) and journal.outbound_payment_method_ids or journal.inbound_payment_method_ids
- journal_currency = journal.currency_id or journal.company_id.currency_id
- payment = self.env['account.payment'].create({
- 'payment_method_id': payment_methods and payment_methods[0].id or False,
- 'payment_type': total < 0 and 'outbound' or 'inbound',
- 'partner_id': expense.employee_id.address_home_id.commercial_partner_id.id,
- 'partner_type': 'supplier',
- 'journal_id': journal.id,
- 'payment_date': expense.date,
- 'state': 'reconciled',
- 'currency_id': diff_currency_p and expense.currency_id.id or journal_currency.id,
- 'amount': diff_currency_p and abs(total_currency) or abs(total),
- 'name': expense.name,
+ company_currency = expense.company_id.currency_id
+ diff_currency_p = expense.currency_id != company_currency
+ #one account.move.line per expense (+taxes..)
+ move_lines = expense._move_line_get()
+
+ #create one more move line, a counterline for the total on payable account
+ payment_id = False
+ total, total_currency, move_lines = expense._compute_expense_totals(company_currency, move_lines, acc_date)
+ if expense.payment_mode == 'company_account':
+ if not expense.sheet_id.bank_journal_id.default_credit_account_id:
+ raise UserError(_("No credit account found for the %s journal, please configure one.") % (expense.sheet_id.bank_journal_id.name))
+ emp_account = expense.sheet_id.bank_journal_id.default_credit_account_id.id
+ journal = expense.sheet_id.bank_journal_id
+ #create payment
+ payment_methods = (total < 0) and journal.outbound_payment_method_ids or journal.inbound_payment_method_ids
+ journal_currency = journal.currency_id or journal.company_id.currency_id
+ payment = self.env['account.payment'].create({
+ 'payment_method_id': payment_methods and payment_methods[0].id or False,
+ 'payment_type': total < 0 and 'outbound' or 'inbound',
+ 'partner_id': expense.employee_id.address_home_id.commercial_partner_id.id,
+ 'partner_type': 'supplier',
+ 'journal_id': journal.id,
+ 'payment_date': expense.date,
+ 'state': 'reconciled',
+ 'currency_id': diff_currency_p and expense.currency_id.id or journal_currency.id,
+ 'amount': diff_currency_p and abs(total_currency) or abs(total),
+ 'name': expense.name,
+ })
+ payment_id = payment.id
+ else:
+ if not expense.employee_id.address_home_id:
+ raise UserError(_("No Home Address found for the employee %s, please configure one.") % (expense.employee_id.name))
+ emp_account = expense.employee_id.address_home_id.property_account_payable_id.id
+
+ move_lines.append({
+ 'type': 'dest',
+ 'name': expense.employee_id.name,
+ 'price': total,
+ 'account_id': emp_account,
+ 'date_maturity': acc_date,
+ 'amount_currency': diff_currency_p and total_currency or False,
+ 'currency_id': diff_currency_p and expense.currency_id.id or False,
+ 'payment_id': payment_id,
})
- payment_id = payment.id
- else:
- if not expense.employee_id.address_home_id:
- raise UserError(_("No Home Address found for the employee %s, please configure one.") % (expense.employee_id.name))
- emp_account = expense.employee_id.address_home_id.property_account_payable_id.id
-
- move_lines.append({
- 'type': 'dest',
- 'name': expense.employee_id.name,
- 'price': total,
- 'account_id': emp_account,
- 'date_maturity': acc_date,
- 'amount_currency': diff_currency_p and total_currency or False,
- 'currency_id': diff_currency_p and expense.currency_id.id or False,
- 'payment_id': payment_id,
- })
-
- #convert eml into an osv-valid format
- lines = map(lambda x: (0, 0, expense._prepare_move_line(x)), move_lines)
- move.write({'line_ids': lines})
- expense.sheet_id.write({'account_move_id': move.id})
- if expense.payment_mode == 'company_account':
- expense.sheet_id.paid_expense_sheets()
+
+ #convert eml into an osv-valid format
+ lines = map(lambda x: (0, 0, expense._prepare_move_line(x)), move_lines)
+ move.write({'line_ids': lines})
+ expense.sheet_id.write({'account_move_id': move.id})
move.post()
+ if expense.payment_mode == 'company_account':
+ expense.sheet_id.paid_expense_sheets()
return True
@api.multi
@@ -526,3 +518,11 @@ class HrExpenseSheet(models.Model):
res['domain'] = [('res_model', '=', 'hr.expense'), ('res_id', 'in', self.expense_line_ids.ids)]
res['context'] = {'default_res_model': 'hr.expense.sheet', 'default_res_id': self.id}
return res
+
+ @api.multi
+ def action_open_journal_entries(self):
+ res = self.env['ir.actions.act_window'].for_xml_id('account', 'action_move_journal_line')
+ refs = [x.name for x in self]
+ res['domain'] = [('ref', 'in', refs)]
+ res['context'] = {}
+ return res
diff --git a/addons/hr_expense/views/hr_expense_views.xml b/addons/hr_expense/views/hr_expense_views.xml
index 7226b4e8d51..a9503f19df2 100644
--- a/addons/hr_expense/views/hr_expense_views.xml
+++ b/addons/hr_expense/views/hr_expense_views.xml
@@ -422,6 +422,13 @@
type="object">
<field name="attachment_number" widget="statinfo" string="Documents"/>
</button>
+ <button name="action_open_journal_entries"
+ class="oe_stat_button"
+ icon="fa-book"
+ string="Accounting Entries"
+ attrs="{'invisible': [('account_move_id', '=', False)]}"
+ type="object">
+ </button>
</div>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
@@ -444,7 +451,7 @@
<field name="journal_id" domain="[('type', '=', 'purchase')]" groups="account.group_account_user" options="{'no_open': True, 'no_create': True}" attrs="{'invisible': [('payment_mode', '!=', 'own_account')]}"/>
<field name="bank_journal_id" groups="account.group_account_user" options="{'no_open': True, 'no_create': True}" attrs="{'invisible': [('payment_mode', '!=', 'company_account')]}" domain="[('type', 'in', ['cash', 'bank'])]"/>
<field name="accounting_date" groups="account.group_account_user" attrs="{'invisible': [('state', 'not in', ['approve', 'post', 'done'])]}"/>
- <field name="account_move_id" attrs="{'invisible': [('state', 'not in', ['post', 'done'])]}" groups="account.group_account_user" readonly="1"/>
+ <field name="account_move_id" invisible="1"/>
</group>
</group>
<field name="expense_line_ids" nolabel="1" widget="many2many" domain="[('state', '=', 'draft')]" options="{'no_delete': True}">
diff --git a/addons/hr_timesheet_attendance/models/hr_timesheet_sheet.py b/addons/hr_timesheet_attendance/models/hr_timesheet_sheet.py
index acdd32820f4..965789a35ed 100644
--- a/addons/hr_timesheet_attendance/models/hr_timesheet_sheet.py
+++ b/addons/hr_timesheet_attendance/models/hr_timesheet_sheet.py
@@ -122,15 +122,7 @@ class hr_timesheet_sheet_sheet_day(models.Model):
sheet_id,
timezone,
SUM(total_timesheet) as total_timesheet,
- CASE WHEN SUM(orphan_attendances) != 0
- THEN (SUM(total_attendance) +
- CASE WHEN current_date <> name
- THEN 1440
- ELSE (EXTRACT(hour FROM current_time AT TIME ZONE 'UTC' AT TIME ZONE coalesce(timezone, 'UTC')) * 60) + EXTRACT(minute FROM current_time AT TIME ZONE 'UTC' AT TIME ZONE coalesce(timezone, 'UTC'))
- END
- )
- ELSE SUM(total_attendance)
- END /60 as total_attendance
+ SUM(total_attendance) /60 as total_attendance
FROM
((
select
@@ -139,7 +131,6 @@ class hr_timesheet_sheet_sheet_day(models.Model):
l.date::date as name,
s.id as sheet_id,
sum(l.unit_amount) as total_timesheet,
- 0 as orphan_attendances,
0.0 as total_attendance
from
account_analytic_line l
@@ -156,9 +147,12 @@ class hr_timesheet_sheet_sheet_day(models.Model):
(a.check_in AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))::date as name,
s.id as sheet_id,
0.0 as total_timesheet,
- 0 as orphan_attendances,
- SUM((EXTRACT(hour FROM (a.check_out AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))) * 60 + EXTRACT(minute FROM (a.check_out AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))))
- - (EXTRACT(hour FROM (a.check_in AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))) * 60 + EXTRACT(minute FROM (a.check_in AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))))) as total_attendance
+ SUM(DATE_PART('day', (a.check_out AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))
+ - (a.check_in AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC')) ) * 60 * 24
+ + DATE_PART('hour', (a.check_out AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))
+ - (a.check_in AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC')) ) * 60
+ + DATE_PART('minute', (a.check_out AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC'))
+ - (a.check_in AT TIME ZONE 'UTC' AT TIME ZONE coalesce(p.tz, 'UTC')) )) as total_attendance
from
hr_attendance a
LEFT JOIN hr_timesheet_sheet_sheet s
diff --git a/addons/l10n_in/data/account_tax_template_data.xml b/addons/l10n_in/data/account_tax_template_data.xml
index 9436a5f1eb5..48d06ad2f3f 100644
--- a/addons/l10n_in/data/account_tax_template_data.xml
+++ b/addons/l10n_in/data/account_tax_template_data.xml
@@ -484,7 +484,7 @@
<field name="account_id" ref="p11232"/>
<field name="refund_account_id" ref="p20011"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_26'), ref('st_tag_in_58')])]"/>
+ <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_33'), ref('st_tag_in_74')])]"/>
</record>
<!--output purchase tax-->
@@ -498,7 +498,7 @@
<field name="account_id" ref="p10050"/>
<field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_51'), ref('st_tag_in_15')])]"/>
+ <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_22'), ref('st_tag_in_67')])]"/>
</record>
<record id="output_tax_12" model="account.tax.template">
<field name="name">(standard)Output Purchase Tax @ 12%</field>
@@ -510,7 +510,7 @@
<field name="account_id" ref="p10050"/>
<field name="refund_account_id" ref="p2107"/>
<field name="chart_template_id" ref="indian_chart_template_standard"/>
- <field name="tag_ids" eval="[(6,0,[ref('st_tag_in_12'), ref('st_tag_in_48')])]"/>
+ <field name="tag_ids" eval="[(6, 0, [ref('st_tag_in_19'), ref('st_tag_in_64')])]"/>
</record>
<record id="output_tax_4" model="account.tax.template">
<field name="name">(standard)Output Purchase Tax @ 4%</field>
diff --git a/addons/l10n_nl/data/account_chart_template.xml b/addons/l10n_nl/data/account_chart_template.xml
index 86cc4b007b8..baf4e5b700d 100644
--- a/addons/l10n_nl/data/account_chart_template.xml
+++ b/addons/l10n_nl/data/account_chart_template.xml
@@ -1,18 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
- <data noupdate="1">
-
- <record id="action_client_l10n_nl_menu" model="ir.actions.client">
- <field name="name">Open Account Dashboard Menu</field>
- <field name="tag">reload</field>
- <field name="params" eval="{'menu_id': ref('account.menu_board_journal_1')}"/>
- </record>
- <record id="base.open_menu" model="ir.actions.todo">
- <field name="action_id" ref="action_client_l10n_nl_menu"/>
- <field name="state">open</field>
- </record>
-
- </data>
<data noupdate="0">
<!-- Chart Template -->
diff --git a/addons/l10n_nz/data/l10n_nz_chart_data.xml b/addons/l10n_nz/data/l10n_nz_chart_data.xml
index 81990d84f9f..d07bed10581 100644
--- a/addons/l10n_nz/data/l10n_nz_chart_data.xml
+++ b/addons/l10n_nz/data/l10n_nz_chart_data.xml
@@ -1,16 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
-<data noupdate="1">
- <record id="action_client_l10n_nz_menu" model="ir.actions.client">
- <field name="name">Open Account Dashboard Menu</field>
- <field name="tag">reload</field>
- <field name="params" eval="{'menu_id': ref('account.menu_board_journal_1')}"/>
- </record>
- <record id="base.open_menu" model="ir.actions.todo">
- <field name="action_id" ref="action_client_l10n_nz_menu"/>
- <field name="state">open</field>
- </record>
-</data>
<data noupdate="0">
diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py
index c2533edf6d4..f0fe2784447 100644
--- a/addons/mail/models/mail_channel.py
+++ b/addons/mail/models/mail_channel.py
@@ -66,8 +66,7 @@ class Channel(models.Model):
group_public_id = fields.Many2one('res.groups', string='Authorized Group',
default=lambda self: self.env.ref('base.group_user'))
group_ids = fields.Many2many(
- 'res.groups', rel='mail_channel_res_group_rel',
- id1='mail_channel_id', id2='groups_id', string='Auto Subscription',
+ 'res.groups', string='Auto Subscription',
help="Members of those groups will automatically added as followers. "
"Note that they will be able to manage their subscription manually "
"if necessary.")
diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js
index 0b06d300679..8291237a2de 100644
--- a/addons/mail/static/src/js/chatter.js
+++ b/addons/mail/static/src/js/chatter.js
@@ -9,6 +9,7 @@ var utils = require('mail.utils');
var config = require('web.config');
var core = require('web.core');
var form_common = require('web.form_common');
+var framework = require('web.framework');
var web_utils = require('web.utils');
var _t = core._t;
@@ -430,7 +431,14 @@ var Chatter = form_common.AbstractField.extend({
},
load_more_messages: function () {
- this.fetch_and_render_thread(this.msg_ids, {force_fetch: true});
+ var self = this;
+ var top_msg_id = this.$('.o_thread_message').first().data('messageId');
+ var top_msg_selector = '.o_thread_message[data-message-id="' + top_msg_id + '"]';
+ var offset = -framework.getPosition(document.querySelector(top_msg_selector)).top;
+ this.fetch_and_render_thread(this.msg_ids, {force_fetch: true}).then(function(){
+ offset += framework.getPosition(document.querySelector(top_msg_selector)).top;
+ self.thread.scroll_to({offset: offset});
+ });
},
/**
diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py
index 853bd6d56a4..39f190ece58 100644
--- a/addons/mrp/models/stock_move.py
+++ b/addons/mrp/models/stock_move.py
@@ -170,21 +170,31 @@ class StockMove(models.Model):
''' Creates an extra move if necessary depending on extra quantities than foreseen or extra moves'''
self.ensure_one()
quantity_to_split = 0
+ uom_qty_to_split = 0
extra_move = self.env['stock.move']
rounding = self.product_uom.rounding
link_procurement = False
+ # If more produced than the procurement linked, you should create an extra move
if self.procurement_id and self.production_id and float_compare(self.production_id.qty_produced, self.procurement_id.product_qty, precision_rounding=rounding) > 0:
done_moves_total = sum(self.production_id.move_finished_ids.filtered(lambda x: x.product_id == self.product_id and x.state == 'done').mapped('product_uom_qty'))
+ # If you depassed the quantity before, you don't need to split anymore, but adapt the quantities
if float_compare(done_moves_total, self.procurement_id.product_qty, precision_rounding=rounding) >= 0:
quantity_to_split = 0
- self.product_uom_qty = self.quantity_done #TODO: could change qty on move_dest_id also (in case of 2-step in/out)
+ if float_compare(self.product_uom_qty, self.quantity_done, precision_rounding=rounding) < 0:
+ self.product_uom_qty = self.quantity_done #TODO: could change qty on move_dest_id also (in case of 2-step in/out)
else:
quantity_to_split = done_moves_total + self.quantity_done - self.procurement_id.product_qty
+ uom_qty_to_split = self.product_uom_qty - (self.quantity_done - quantity_to_split)#self.product_uom_qty - (self.procurement_id.product_qty + done_moves_total)
+ if float_compare(uom_qty_to_split, quantity_to_split, precision_rounding=rounding) < 0:
+ uom_qty_to_split = quantity_to_split
+ self.product_uom_qty = self.quantity_done - quantity_to_split
+ # You split also simply when the quantity done is bigger than foreseen
elif float_compare(self.quantity_done, self.product_uom_qty, precision_rounding=rounding) > 0:
- quantity_to_split = self.quantity_done - self.product_uom_qty
+ quantity_to_split = self.quantity_done - self.product_uom_qty
+ uom_qty_to_split = quantity_to_split # + no need to change existing self.product_uom_qty
link_procurement = True
if quantity_to_split:
- extra_move = self.copy(default={'quantity_done': quantity_to_split, 'product_uom_qty': quantity_to_split, 'production_id': self.production_id.id,
+ extra_move = self.copy(default={'quantity_done': quantity_to_split, 'product_uom_qty': uom_qty_to_split, 'production_id': self.production_id.id,
'raw_material_production_id': self.raw_material_production_id.id,
'procurement_id': link_procurement and self.procurement_id.id or False})
extra_move.action_confirm()
@@ -203,8 +213,7 @@ class StockMove(models.Model):
else:
movelot.move_id = extra_move.id
else:
- self.quantity_done = self.product_uom_qty
- self.product_uom_qty = self.quantity_done - quantity_to_split
+ self.quantity_done -= quantity_to_split
return extra_move
@api.multi
diff --git a/addons/mrp/models/stock_warehouse.py b/addons/mrp/models/stock_warehouse.py
index 7bef9923ca6..e5df59e5130 100644
--- a/addons/mrp/models/stock_warehouse.py
+++ b/addons/mrp/models/stock_warehouse.py
@@ -118,8 +118,8 @@ class StockWarehouse(models.Model):
return routes
@api.multi
- def _handle_renaming(self, name, code):
- res = super(StockWarehouse, self)._handle_renaming(name, code)
+ def _update_name_and_code(self, name, code):
+ res = super(StockWarehouse, self)._update_name_and_code(name, code)
# change the manufacture procurement rule name
for warehouse in self:
if warehouse.manufacture_pull_id:
diff --git a/addons/mrp_repair/models/mrp_repair.py b/addons/mrp_repair/models/mrp_repair.py
index aa8b202077b..de6238e9df4 100644
--- a/addons/mrp_repair/models/mrp_repair.py
+++ b/addons/mrp_repair/models/mrp_repair.py
@@ -209,7 +209,7 @@ class Repair(models.Model):
to_confirm = self - before_repair
to_confirm_operations = to_confirm.mapped('operations')
for operation in to_confirm_operations:
- if operation.product_id.tracking != 'none' and not operation.lot_id :
+ if operation.product_id.tracking != 'none' and not operation.lot_id:
raise UserError(_("Serial number is required for operation line with product '%s'") % (operation.product_id.name))
to_confirm_operations.write({'state': 'confirmed'})
to_confirm.write({'state': 'confirmed'})
@@ -230,7 +230,7 @@ class Repair(models.Model):
if self.invoice_method == 'b4repair':
self.action_repair_ready()
elif self.invoice_method == 'after_repair':
- self.action_repair_done()
+ self.write({'state': 'done'})
return True
@api.multi
@@ -352,13 +352,13 @@ class Repair(models.Model):
"""
if self.filtered(lambda repair: repair.state != 'under_repair'):
raise UserError(_("Repair must be under repair in order to end reparation."))
- for order in self:
- vals = {'repaired': True, 'state': 'done'}
- if not order.invoiced and order.invoice_method == 'after_repair':
+ for repair in self:
+ repair.write({'repaired': True})
+ vals = {'state': 'done'}
+ vals['move_id'] = repair.action_repair_done().get(repair.id)
+ if not repair.invoiced and repair.invoice_method == 'after_repair':
vals['state'] = '2binvoiced'
- elif not order.invoiced and order.invoice_method == 'b4repair':
- vals['state'] = 'ready'
- order.write(vals)
+ repair.write(vals)
return True
@api.multi
@@ -368,9 +368,7 @@ class Repair(models.Model):
"""
if self.filtered(lambda repair: not repair.repaired):
- raise UserError(_("Repair must be repaired in order to finalize it."))
- if self.filtered(lambda repair: not repair.invoiced and repair.invoice_method != 'none'):
- raise UserError(_("Repair must be invoiced in order to finalize it."))
+ raise UserError(_("Repair must be repaired in order to make the product moves."))
res = {}
Move = self.env['stock.move']
for repair in self:
@@ -400,7 +398,6 @@ class Repair(models.Model):
})
moves |= move
moves.action_done()
- repair.write({'state': 'done', 'move_id': move.id})
res[repair.id] = move.id
return res
@@ -409,7 +406,6 @@ class RepairLine(models.Model):
_name = 'mrp.repair.line'
_description = 'Repair Line'
-
name = fields.Char('Description', required=True)
repair_id = fields.Many2one(
'mrp.repair', 'Repair Order Reference',
diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py
index 6fed4e34dd6..80cbb50eee6 100644
--- a/addons/payment/models/payment_acquirer.py
+++ b/addons/payment/models/payment_acquirer.py
@@ -656,8 +656,8 @@ class PaymentToken(models.Model):
values.update(getattr(self, custom_method_name)(values))
# remove all non-model fields used by (provider)_create method to avoid warning
fields_wl = set(self._fields.keys()) & set(values.keys())
- clean_vals = {field: values[field] for field in fields_wl}
- return super(PaymentToken, self).create(clean_vals)
+ values = {field: values[field] for field in fields_wl}
+ return super(PaymentToken, self).create(values)
@api.multi
@api.depends('name')
diff --git a/addons/payment_stripe/models/payment.py b/addons/payment_stripe/models/payment.py
index 3e9958a78f6..d7a0be07547 100644
--- a/addons/payment_stripe/models/payment.py
+++ b/addons/payment_stripe/models/payment.py
@@ -168,11 +168,11 @@ class PaymentTokenStripe(models.Model):
@api.model
def stripe_create(self, values):
- res = None
+ res = {}
payment_acquirer = self.env['payment.acquirer'].browse(values.get('acquirer_id'))
url_token = 'https://%s/tokens' % payment_acquirer._get_stripe_api_url()
url_customer = 'https://%s/customers' % payment_acquirer._get_stripe_api_url()
- if values['cc_number']:
+ if values.get('cc_number'):
payment_params = {
'card[number]': values['cc_number'].replace(' ', ''),
'card[exp_month]': str(values['cc_expiry'][:2]),
@@ -202,5 +202,5 @@ class PaymentTokenStripe(models.Model):
# pop credit card info to info sent to create
for field_name in ["cc_number", "cvc", "cc_holder_name", "cc_expiry", "cc_brand"]:
- values.pop(field_name)
+ values.pop(field_name, None)
return res
diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py
index ca3d71f665b..b57c4c3247c 100644
--- a/addons/point_of_sale/models/pos_order.py
+++ b/addons/point_of_sale/models/pos_order.py
@@ -214,8 +214,10 @@ class PosOrder(models.Model):
partner_id = ResPartner._find_accounting_partner(order.partner_id).id or False
if move is None:
# Create an entry for the sale
+ journal_id = self.env['ir.config_parameter'].sudo().get_param(
+ 'pos.closing.journal_id', default=order.sale_journal.id)
move = self._create_account_move(
- order.session_id.start_at, order.name, order.sale_journal.id, order.company_id.id)
+ order.session_id.start_at, order.name, int(journal_id), order.company_id.id)
def insert_data(data_type, values):
# if have_to_group_by:
@@ -540,7 +542,10 @@ class PosOrder(models.Model):
for order in self:
address = order.partner_id.address_get(['delivery']) or {}
picking_type = order.picking_type_id
- picking_id = False
+ return_pick_type = order.picking_type_id.return_picking_type_id or order.picking_type_id
+ order_picking = Picking
+ return_picking = Picking
+ moves = Move
location_id = order.location_id.id
if order.partner_id:
destination_id = order.partner_id.property_stock_customer.id
@@ -550,9 +555,10 @@ class PosOrder(models.Model):
destination_id = customerloc.id
else:
destination_id = picking_type.default_location_dest_id.id
+
if picking_type:
- pos_qty = all([x.qty >= 0 for x in order.lines])
- picking_id = Picking.create({
+ message = _("This transfer has been created from the point of sale session: <a href=# data-oe-model=pos.order data-oe-id=%d>%s</a>") % (order.id, order.name)
+ picking_vals = {
'origin': order.name,
'partner_id': address.get('delivery', False),
'date_done': order.date_order,
@@ -560,44 +566,72 @@ class PosOrder(models.Model):
'company_id': order.company_id.id,
'move_type': 'direct',
'note': order.note or "",
- 'location_id': location_id if pos_qty else destination_id,
- 'location_dest_id': destination_id if pos_qty else location_id,
- })
- message = _("This transfer has been created from the point of sale session: <a href=# data-oe-model=pos.order data-oe-id=%d>%s</a>") % (order.id, order.name)
- picking_id.message_post(body=message)
- order.write({'picking_id': picking_id.id})
+ 'location_id': location_id,
+ 'location_dest_id': destination_id,
+ }
+ pos_qty = any([x.qty >= 0 for x in order.lines])
+ if pos_qty:
+ order_picking = Picking.create(picking_vals.copy())
+ order_picking.message_post(body=message)
+ neg_qty = any([x.qty < 0 for x in order.lines])
+ if neg_qty:
+ return_vals = picking_vals.copy()
+ return_vals.update({
+ 'location_id': destination_id,
+ 'location_dest_id': return_pick_type != picking_type and return_pick_type.default_location_dest_id.id or location_id,
+ 'picking_type_id': return_pick_type.id
+ })
+ return_picking = Picking.create(return_vals)
+ return_picking.message_post(body=message)
for line in order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu']):
- Move += Move.create({
+ moves |= Move.create({
'name': line.name,
'product_uom': line.product_id.uom_id.id,
- 'picking_id': picking_id and picking_id.id or False,
- 'picking_type_id': picking_type.id,
+ 'picking_id': order_picking.id if line.qty >= 0 else return_picking.id,
+ 'picking_type_id': picking_type.id if line.qty >= 0 else return_pick_type.id,
'product_id': line.product_id.id,
'product_uom_qty': abs(line.qty),
'state': 'draft',
'location_id': location_id if line.qty >= 0 else destination_id,
- 'location_dest_id': destination_id if line.qty >= 0 else location_id,
+ 'location_dest_id': destination_id if line.qty >= 0 else return_pick_type != picking_type and return_pick_type.default_location_dest_id.id or location_id,
})
- if picking_id:
- picking_id.action_confirm()
- picking_id.force_assign()
- order.set_pack_operation_lot()
- picking_id.action_done()
- elif Move:
- Move.action_confirm()
- Move.force_assign()
- Move.action_done()
+
+ # prefer associating the regular order picking, not the return
+ order.write({'picking_id': order_picking.id or return_picking.id})
+
+ if return_picking:
+ order._force_picking_done(return_picking)
+ if order_picking:
+ order._force_picking_done(order_picking)
+
+ # when the pos.config has no picking_type_id set only the moves will be created
+ if moves and not return_picking and not order_picking:
+ moves.action_confirm()
+ moves.force_assign()
+ moves.action_done()
+
return True
- def set_pack_operation_lot(self):
+ def _force_picking_done(self, picking):
+ """Force picking in order to be set as done."""
+ self.ensure_one()
+ picking.action_confirm()
+ picking.force_assign()
+ self.set_pack_operation_lot(picking)
+ picking.action_done()
+
+ def set_pack_operation_lot(self, picking=None):
"""Set Serial/Lot number in pack operations to mark the pack operation done."""
StockProductionLot = self.env['stock.production.lot']
PosPackOperationLot = self.env['pos.pack.operation.lot']
+ if not picking:
+ picking = order.picking_id
+
for order in self:
- for pack_operation in order.picking_id.pack_operation_ids:
+ for pack_operation in picking.pack_operation_ids:
qty = 0
qty_done = 0
pack_lots = []
@@ -868,15 +902,15 @@ class ReportSaleDetails(models.AbstractModel):
'payments': payments,
'company_name': self.env.user.company_id.name,
'taxes': taxes.values(),
- 'products': [{
+ 'products': sorted([{
'product_id': product.id,
- 'product_name': product.name[:20],
+ 'product_name': product.name,
'code': product.default_code,
'quantity': qty,
'price_unit': price_unit,
'discount': discount,
'uom': product.uom_id.name
- } for (product, price_unit, discount), qty in products_sold.items()]
+ } for (product, price_unit, discount), qty in products_sold.items()], key=lambda l: l['product_name'])
}
@api.multi
diff --git a/addons/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py
index ea0f8391bfe..66dfb2b6659 100644
--- a/addons/point_of_sale/models/pos_session.py
+++ b/addons/point_of_sale/models/pos_session.py
@@ -18,7 +18,9 @@ class PosSession(models.Model):
for session in self:
company_id = session.config_id.journal_id.company_id.id
orders = session.order_ids.filtered(lambda order: order.state == 'paid')
- move = self.env['pos.order'].with_context(force_company=company_id)._create_account_move(session.start_at, session.name, session.config_id.journal_id.id, company_id)
+ journal_id = self.env['ir.config_parameter'].sudo().get_param(
+ 'pos.closing.journal_id', default=session.config_id.journal_id.id)
+ move = self.env['pos.order'].with_context(force_company=company_id)._create_account_move(session.start_at, session.name, int(journal_id), company_id)
orders.with_context(force_company=company_id)._create_account_move_line(session, move)
for order in session.order_ids.filtered(lambda o: o.state != 'done'):
if order.state not in ('paid', 'invoiced'):
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index 1b044274ce8..3245c9a409b 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -871,7 +871,7 @@
<div class='orderlines' line-ratio='0.6'>
<t t-foreach='products' t-as='line'>
<line>
- <left><t t-esc='line.product_name' /></left>
+ <left><t t-esc='line.product_name.substr(0,20)' /></left>
<right>
<value value-decimals='2' value-autoint='on'>
<t t-esc='line.quantity' />
diff --git a/addons/point_of_sale/tests/test_point_of_sale_flow.py b/addons/point_of_sale/tests/test_point_of_sale_flow.py
index e9d7bf68a7b..51e0bf5a69b 100644
--- a/addons/point_of_sale/tests/test_point_of_sale_flow.py
+++ b/addons/point_of_sale/tests/test_point_of_sale_flow.py
@@ -119,9 +119,208 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
# I test that the generated journal entry is attached to the PoS order
self.assertTrue(self.pos_order_pos0.account_move, "Journal entry has not been attached to Pos order.")
+ def test_order_to_picking(self):
+ """
+ In order to test the Point of Sale in module, I will do three orders from the sale to the payment,
+ invoicing + picking, but will only check the picking consistency in the end.
+
+ TODO: Check the negative picking after changing the picking relation to One2many (also for a mixed use case),
+ check the quantity, the locations and return picking logic
+ """
+ # I click on create a new session button
+ self.pos_config.open_session_cb()
+
+ # I create a PoS order with 2 units of PCSC234 at 450 EUR
+ # and 3 units of PCSC349 at 300 EUR.
+ self.pos_order_pos1 = self.PosOrder.create({
+ 'company_id': self.company_id,
+ 'pricelist_id': self.partner1.property_product_pricelist.id,
+ 'partner_id': self.partner1.id,
+ 'lines': [(0, 0, {
+ 'name': "OL/0001",
+ 'product_id': self.product3.id,
+ 'price_unit': 450,
+ 'discount': 0.0,
+ 'qty': 2.0,
+ }), (0, 0, {
+ 'name': "OL/0002",
+ 'product_id': self.product4.id,
+ 'price_unit': 300,
+ 'discount': 0.0,
+ 'qty': 3.0,
+ })]
+ })
+
+ # I click on the "Make Payment" wizard to pay the PoS order with the total amount (2*450 + 3*300 = 1800)
+ context_make_payment = {
+ "active_ids": [self.pos_order_pos1.id],
+ "active_id": self.pos_order_pos1.id
+ }
+ self.pos_make_payment_2 = self.PosMakePayment.with_context(context_make_payment).create({
+ 'amount': 1800
+ })
+
+ # I click on the validate button to register the payment.
+ context_payment = {'active_id': self.pos_order_pos1.id}
+ self.pos_make_payment_2.with_context(context_payment).check()
+
+ # I check that the order is marked as paid
+ self.assertEqual(
+ self.pos_order_pos1.state,
+ 'paid',
+ 'Order should be in paid state.'
+ )
+
+ # I generate the journal entries
+ self.pos_order_pos1._create_account_move_line()
+
+ # I test that the generated journal entry is attached to the PoS order
+ self.assertTrue(
+ self.pos_order_pos1.account_move,
+ "Journal entry has not been attached to Pos order."
+ )
+
+ # I test that the pickings are created as expected
+ # One picking attached and having all the positive move lines in the correct state
+ self.pos_order_pos1.create_picking()
+ self.assertEqual(
+ self.pos_order_pos1.picking_id.state,
+ 'done',
+ 'Picking should be in done state.'
+ )
+ self.assertEqual(
+ self.pos_order_pos1.picking_id.move_lines.mapped('state'),
+ ['done', 'done'],
+ 'Move Lines should be in done state.'
+ )
+
+ self.pos_order_pos2 = self.PosOrder.create({
+ 'company_id': self.company_id,
+ 'pricelist_id': self.partner1.property_product_pricelist.id,
+ 'partner_id': self.partner1.id,
+ 'lines': [(0, 0, {
+ 'name': "OL/0003",
+ 'product_id': self.product3.id,
+ 'price_unit': 450,
+ 'discount': 0.0,
+ 'qty': (-2.0),
+ }), (0, 0, {
+ 'name': "OL/0004",
+ 'product_id': self.product4.id,
+ 'price_unit': 300,
+ 'discount': 0.0,
+ 'qty': (-3.0),
+ })]
+ })
+
+ # I click on the "Make Payment" wizard to pay the PoS order with the total amount (-2*450 + -3*300 = -1800)
+ context_make_payment = {
+ "active_ids": [self.pos_order_pos2.id],
+ "active_id": self.pos_order_pos2.id
+ }
+ self.pos_make_payment_3 = self.PosMakePayment.with_context(context_make_payment).create({
+ 'amount': (-1800)
+ })
+
+ # I click on the validate button to register the payment.
+ context_payment = {'active_id': self.pos_order_pos2.id}
+ self.pos_make_payment_3.with_context(context_payment).check()
+
+ # I check that the order is marked as paid
+ self.assertEqual(
+ self.pos_order_pos2.state,
+ 'paid',
+ 'Order should be in paid state.'
+ )
+
+ # I generate the journal entries
+ self.pos_order_pos2._create_account_move_line()
+
+ # I test that the generated journal entry is attached to the PoS order
+ self.assertTrue(
+ self.pos_order_pos2.account_move,
+ "Journal entry has not been attached to PoS order."
+ )
+
+ # I test that the pickings are created as expected
+ # One picking attached and having all the positive move lines in the correct state
+ self.pos_order_pos2.create_picking()
+ self.assertEqual(
+ self.pos_order_pos2.picking_id.state,
+ 'done',
+ 'Picking should be in done state.'
+ )
+ self.assertEqual(
+ self.pos_order_pos2.picking_id.move_lines.mapped('state'),
+ ['done', 'done'],
+ 'Move Lines should be in done state.'
+ )
+
+ self.pos_order_pos3 = self.PosOrder.create({
+ 'company_id': self.company_id,
+ 'pricelist_id': self.partner1.property_product_pricelist.id,
+ 'partner_id': self.partner1.id,
+ 'lines': [(0, 0, {
+ 'name': "OL/0005",
+ 'product_id': self.product3.id,
+ 'price_unit': 450,
+ 'discount': 0.0,
+ 'qty': (-2.0),
+ }), (0, 0, {
+ 'name': "OL/0006",
+ 'product_id': self.product4.id,
+ 'price_unit': 300,
+ 'discount': 0.0,
+ 'qty': 3.0,
+ })]
+ })
+
+ # I click on the "Make Payment" wizard to pay the PoS order with the total amount (-2*450 + 3*300 = 0)
+ context_make_payment = {
+ "active_ids": [self.pos_order_pos3.id],
+ "active_id": self.pos_order_pos3.id
+ }
+ self.pos_make_payment_4 = self.PosMakePayment.with_context(context_make_payment).create({
+ 'amount': 0
+ })
+
+ # I click on the validate button to register the payment.
+ context_payment = {'active_id': self.pos_order_pos3.id}
+ self.pos_make_payment_4.with_context(context_payment).check()
+
+ # I check that the order is marked as paid
+ self.assertEqual(
+ self.pos_order_pos3.state,
+ 'paid',
+ 'Order should be in paid state.'
+ )
+
+ # I generate the journal entries
+ self.pos_order_pos3._create_account_move_line()
+
+ # I test that the generated journal entry is attached to the PoS order
+ self.assertTrue(
+ self.pos_order_pos3.account_move,
+ "Journal entry has not been attached to PoS order."
+ )
+
+ # I test that the pickings are created as expected
+ # One picking attached and having all the positive move lines in the correct state
+ self.pos_order_pos3.create_picking()
+ self.assertEqual(
+ self.pos_order_pos3.picking_id.state,
+ 'done',
+ 'Picking should be in done state.'
+ )
+ self.assertEqual(
+ self.pos_order_pos3.picking_id.move_lines.mapped('state'),
+ ['done'],
+ 'Move Lines should be in done state.'
+ )
+
def test_order_to_invoice(self):
- #I create a new PoS order with 2 units of PC1 at 450 EUR (Tax Incl) and 3 units of PCSC349 at 300 EUR. (Tax Excl)
+ # I create a new PoS order with 2 units of PC1 at 450 EUR (Tax Incl) and 3 units of PCSC349 at 300 EUR. (Tax Excl)
self.pos_order_pos1 = self.PosOrder.create({
'company_id': self.company_id,
'partner_id': self.partner1.id,
diff --git a/addons/product/models/product.py b/addons/product/models/product.py
index 10ec0e20208..e13747f6ca1 100644
--- a/addons/product/models/product.py
+++ b/addons/product/models/product.py
@@ -133,7 +133,7 @@ class ProductProduct(models.Model):
'Barcode', copy=False, oldname='ean13',
help="International Article Number used for product identification.")
attribute_value_ids = fields.Many2many(
- 'product.attribute.value', id1='prod_id', id2='att_id', string='Attributes', ondelete='restrict')
+ 'product.attribute.value', string='Attributes', ondelete='restrict')
# image: all image fields are base64 encoded and PIL-supported
image_variant = fields.Binary(
"Variant Image", attachment=True,
@@ -392,8 +392,7 @@ class ProductProduct(models.Model):
name = variant and "%s (%s)" % (product.name, variant) or product.name
sellers = []
if partner_ids:
- if variant:
- sellers = [x for x in product.seller_ids if (x.name.id in partner_ids) and (x.product_id == product)]
+ sellers = [x for x in product.seller_ids if (x.name.id in partner_ids) and (x.product_id == product)]
if not sellers:
sellers = [x for x in product.seller_ids if (x.name.id in partner_ids) and not x.product_id]
if sellers:
diff --git a/addons/product/models/product_attribute.py b/addons/product/models/product_attribute.py
index d90e070c05e..e215c4c537e 100644
--- a/addons/product/models/product_attribute.py
+++ b/addons/product/models/product_attribute.py
@@ -25,7 +25,7 @@ class ProductAttributevalue(models.Model):
name = fields.Char('Value', required=True, translate=True)
sequence = fields.Integer('Sequence', help="Determine the display order")
attribute_id = fields.Many2one('product.attribute', 'Attribute', ondelete='cascade', required=True)
- product_ids = fields.Many2many('product.product', id1='att_id', id2='prod_id', string='Variants', readonly=True)
+ product_ids = fields.Many2many('product.product', string='Variants', readonly=True)
price_extra = fields.Float(
'Attribute Price Extra', compute='_compute_price_extra', inverse='_set_price_extra',
default=0.0, digits=dp.get_precision('Product Price'),
@@ -93,7 +93,7 @@ class ProductAttributeLine(models.Model):
product_tmpl_id = fields.Many2one('product.template', 'Product Template', ondelete='cascade', required=True)
attribute_id = fields.Many2one('product.attribute', 'Attribute', ondelete='restrict', required=True)
- value_ids = fields.Many2many('product.attribute.value', id1='line_id', id2='val_id', string='Attribute Values')
+ value_ids = fields.Many2many('product.attribute.value', string='Attribute Values')
@api.constrains('value_ids', 'attribute_id')
def _check_valid_attribute(self):
diff --git a/addons/purchase/models/stock.py b/addons/purchase/models/stock.py
index aa2b67c4750..93b3f99a902 100644
--- a/addons/purchase/models/stock.py
+++ b/addons/purchase/models/stock.py
@@ -124,8 +124,8 @@ class StockWarehouse(models.Model):
return routes
@api.multi
- def _handle_renaming(self, name, code):
- res = super(StockWarehouse, self)._handle_renaming(name, code)
+ def _update_name_and_code(self, name, code):
+ res = super(StockWarehouse, self)._update_name_and_code(name, code)
warehouse = self[0]
#change the buy procurement rule name
if warehouse.buy_pull_id:
@@ -133,8 +133,8 @@ class StockWarehouse(models.Model):
return res
@api.multi
- def change_route(self):
- res = super(StockWarehouse, self).change_route()
+ def _update_routes(self):
+ res = super(StockWarehouse, self)._update_routes()
for warehouse in self:
if warehouse.in_type_id.default_location_dest_id != warehouse.buy_pull_id.location_id:
warehouse.buy_pull_id.write({'location_id': warehouse.in_type_id.default_location_dest_id.id})
diff --git a/addons/resource/models/resource.py b/addons/resource/models/resource.py
index 4059e6e02b1..9acafc84110 100644
--- a/addons/resource/models/resource.py
+++ b/addons/resource/models/resource.py
@@ -653,7 +653,7 @@ class ResourceCalendarAttendance(models.Model):
date_to = fields.Date(string='End Date')
hour_from = fields.Float(string='Work from', required=True, index=True, help="Start and End time of working.")
hour_to = fields.Float(string='Work to', required=True)
- calendar_id = fields.Many2one("resource.calendar", string="Resource's Calendar", required=True)
+ calendar_id = fields.Many2one("resource.calendar", string="Resource's Calendar", required=True, ondelete='cascade')
def hours_time_string(hours):
diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py
index 35aa4f067c6..0040778ee1f 100644
--- a/addons/sale/models/sale.py
+++ b/addons/sale/models/sale.py
@@ -175,7 +175,7 @@ class SaleOrder(models.Model):
return super(SaleOrder, self)._track_subtype(init_values)
@api.multi
- @api.onchange('partner_shipping_id')
+ @api.onchange('partner_shipping_id', 'partner_id')
def onchange_partner_shipping_id(self):
"""
Trigger the change of fiscal position when the shipping address is modified.
diff --git a/addons/sale/wizard/sale_make_invoice_advance.py b/addons/sale/wizard/sale_make_invoice_advance.py
index 84ab9490389..3e28cbb9575 100644
--- a/addons/sale/wizard/sale_make_invoice_advance.py
+++ b/addons/sale/wizard/sale_make_invoice_advance.py
@@ -67,9 +67,8 @@ class SaleAdvancePaymentInv(models.TransientModel):
if self.product_id.id:
account_id = self.product_id.property_account_income_id.id
if not account_id:
- prop = ir_property_obj.get('property_account_income_categ_id', 'product.category')
- prop_id = prop and prop.id or False
- account_id = order.fiscal_position_id.map_account(prop_id)
+ inc_acc = ir_property_obj.get('property_account_income_categ_id', 'product.category')
+ account_id = order.fiscal_position_id.map_account(inc_acc).id if inc_acc else False
if not account_id:
raise UserError(
_('There is no income account defined for this product: "%s". You may have to install a chart of account from Accounting app, settings menu.') %
diff --git a/addons/sale_timesheet/views/sale_order_views.xml b/addons/sale_timesheet/views/sale_order_views.xml
index 4a782672e23..8d86ee57894 100644
--- a/addons/sale_timesheet/views/sale_order_views.xml
+++ b/addons/sale_timesheet/views/sale_order_views.xml
@@ -20,7 +20,7 @@
<button type="object"
name="action_view_project_project"
class="oe_stat_button"
- icon="fa-tasks"
+ icon="fa-puzzle-piece"
string="Project"
attrs="{'invisible': [('project_project_id', '=', False)]}" groups="base.group_user">
</button>
diff --git a/addons/stock/models/procurement.py b/addons/stock/models/procurement.py
index a6ba9d5c004..bc06e4a1a12 100644
--- a/addons/stock/models/procurement.py
+++ b/addons/stock/models/procurement.py
@@ -10,6 +10,9 @@ from odoo import api, fields, models, registry, _
from odoo.osv import expression
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_compare, float_round
+import logging
+
+_logger = logging.getLogger(__name__)
class ProcurementGroup(models.Model):
_inherit = 'procurement.group'
@@ -172,8 +175,11 @@ class ProcurementOrder(models.Model):
'propagate': self.rule_id.propagate,
'priority': self.priority,
}
- # compatibility
- _run_move_create = _get_stock_move_values
+
+ def _run_move_create(self):
+ # FIXME - remove me in master/saas-14
+ _logger.warning("'_run_move_create' has been renamed into '_get_stock_move_values'... Overrides are ignored")
+ return self._get_stock_move_values()
@api.multi
def _run(self):
diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py
index fd0b041d94b..9335f304b4b 100644
--- a/addons/stock/models/stock_quant.py
+++ b/addons/stock/models/stock_quant.py
@@ -6,6 +6,10 @@ from odoo.tools.translate import _
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.exceptions import UserError
+import logging
+
+_logger = logging.getLogger(__name__)
+
class Quant(models.Model):
""" Quants are the smallest unit of stock physical instances """
@@ -261,8 +265,17 @@ class Quant(models.Model):
# create the quant as superuser, because we want to restrict the creation of quant manually: we should always use this method to create quants
return self.sudo().create(vals)
- # compatibility method
- _quant_create = _quant_create_from_move
+
+ @api.model
+ def _quant_create(self, qty, move, lot_id=False, owner_id=False,
+ src_package_id=False, dest_package_id=False,
+ force_location_from=False, force_location_to=False):
+ # FIXME - remove me in master/saas-14
+ _logger.warning("'_quant_create' has been renamed into '_quant_create_from_move'... Overrides are ignored")
+ return self._quant_create_from_move(
+ qty, move, lot_id=lot_id, owner_id=owner_id,
+ src_package_id=src_package_id, dest_package_id=dest_package_id,
+ force_location_from=force_location_from, force_location_to=force_location_to)
@api.multi
def _quant_update_from_move(self, move, location_dest_id, dest_package_id, lot_id=False, entire_pack=False):
@@ -275,8 +288,12 @@ class Quant(models.Model):
if not entire_pack:
vals.update({'package_id': dest_package_id})
self.write(vals)
- # compatibility method
- move_quants_write = _quant_update_from_move
+
+ @api.multi
+ def move_quants_write(self, move, location_dest_id, dest_package_id, lot_id=False, entire_pack=False):
+ # FIXME - remove me in master/saas-14
+ _logger.warning("'move_quants_write' has been renamed into '_quant_update_from_move'... Overrides are ignored")
+ return self._quant_update_from_move(move, location_dest_id, dest_package_id, lot_id=lot_id, entire_pack=entire_pack)
@api.one
def _quant_reconcile_negative(self, move):
diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py
index 900e7f18309..189618a7755 100644
--- a/addons/stock/models/stock_warehouse.py
+++ b/addons/stock/models/stock_warehouse.py
@@ -10,6 +10,10 @@ from odoo.addons import decimal_precision as dp
from odoo.exceptions import UserError, ValidationError
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
+import logging
+
+_logger = logging.getLogger(__name__)
+
class Warehouse(models.Model):
_name = "stock.warehouse"
@@ -452,7 +456,11 @@ class Warehouse(models.Model):
'product_categ_selectable': True,
'supplied_wh_id': self.id,
'supplier_wh_id': supplier_warehouse.id}
- _get_inter_wh_route = _get_inter_warehouse_route_values # compatibility
+
+ def _get_inter_wh_route(self, supplier_warehouse):
+ # FIXME - remove me in master/saas-14
+ _logger.warning("'_get_inter_wh_route' has been renamed into '_get_inter_warehouse_route_values'... Overrides are ignored")
+ return self._get_inter_warehouse_route_values(supplier_warehouse)
def _get_crossdock_route_values(self):
return {
@@ -463,8 +471,10 @@ class Warehouse(models.Model):
'active': self.delivery_steps != 'ship_only' and self.reception_steps != 'one_step',
'sequence': 20}
- def _get_crossdock_route(self, route_name): # compatibility
- return self._get_crossdock_route_values()
+ def _get_crossdock_route(self, route_name):
+ # FIXME - remove me in master/saas-14
+ _logger.warning("'_get_crossdock_route' has been renamed into '_get_crossdock_route_values'... Overrides are ignored")
+ return self._get_crossdock_route_values(route_name)
# Pull / Push tools
# ------------------------------------------------------------
@@ -503,9 +513,15 @@ class Warehouse(models.Model):
'procure_method': 'make_to_order',
'active': True}, name_suffix=_('MTO'))
return pull_rules_list
- _get_mto_pull_rule = _get_mto_pull_rules_values # compatibility
- def _get_push_pull_rules(self, active, values, new_route_id): # compatibility
+ def _get_mto_pull_rule(self, route_values):
+ # FIXME - remove me in master/saas-14
+ _logger.warning("'_get_mto_pull_rule' has been renamed into '_get_mto_pull_rules_values'... Overrides are ignored")
+ return self._get_mto_pull_rules_values(route_values)
+
+ def _get_push_pull_rules(self, active, values, new_route_id):
+ # FIXME - remove me in master/saas-14
+ _logger.warning("'_get_push_pull_rules' has been renamed into '_get_push_pull_rules_values'... Overrides are ignored")
return self._get_push_pull_rules_values(values, values={'active': active, 'route_id': new_route_id})
def _get_supply_pull_rules_values(self, route_values, values=None):
@@ -523,7 +539,11 @@ class Warehouse(models.Model):
if delivery_new and warehouse.delivery_steps != delivery_new and (warehouse.delivery_steps == 'ship_only' or delivery_new == 'ship_only'):
change_to_multiple = warehouse.delivery_steps == 'ship_only'
warehouse._check_delivery_resupply(output_loc, change_to_multiple)
- _check_resupply = _update_reception_delivery_resupply
+
+ def _check_resupply(self, reception_new, delivery_new):
+ # FIXME - remove me in master/saas-14
+ _logger.warning("'_check_resupply' has been renamed into '_update_reception_delivery_resupply'... Overrides are ignored")
+ return self._update_reception_delivery_resupply(reception_new, delivery_new)
def _check_delivery_resupply(self, new_location, change_to_multiple):
""" Check if the resupply routes from this warehouse follow the changes of number of delivery steps
@@ -567,7 +587,12 @@ class Warehouse(models.Model):
self._create_or_update_crossdock_route(routes_data)
self._create_or_update_mto_pull(routes_data)
return True
- change_route = _update_routes
+
+ @api.multi
+ def change_route(self):
+ # FIXME - remove me in master/saas-14
+ _logger.warning("'change_route' has been renamed into '_update_routes'... Overrides are ignored")
+ return self._update_routes()
@api.one
def _update_picking_type(self):
@@ -597,7 +622,12 @@ class Warehouse(models.Model):
warehouse.pack_type_id.sequence_id.write(sequence_data['pack_type_id'])
warehouse.pick_type_id.sequence_id.write(sequence_data['pick_type_id'])
warehouse.int_type_id.sequence_id.write(sequence_data['int_type_id'])
- _handle_renaming = _update_name_and_code
+
+ @api.multi
+ def _handle_renaming(self, new_name=False, new_code=False):
+ # FIXME - remove me in master/saas-14
+ _logger.warning("'_handle_renaming' has been renamed into '_update_name_and_code'... Overrides are ignored")
+ return self._update_name_and_code(new_name=new_name, new_code=new_code)
def _update_location_reception(self, new_reception_step):
switch_warehouses = self.filtered(lambda wh: wh.reception_steps != new_reception_step and not wh._location_used(wh.wh_input_stock_loc_id))
diff --git a/addons/stock_account/models/account_invoice.py b/addons/stock_account/models/account_invoice.py
index 5dba13166ae..115006e4c48 100644
--- a/addons/stock_account/models/account_invoice.py
+++ b/addons/stock_account/models/account_invoice.py
@@ -76,7 +76,7 @@ class AccountInvoiceLine(models.Model):
return self.product_id.standard_price
def _get_price(self, company_currency, price_unit):
- if self.invoice_id.currency_id.id != company_currency:
+ if self.invoice_id.currency_id.id != company_currency.id:
price = company_currency.with_context(date=self.invoice_id.date_invoice).compute(price_unit * self.quantity, self.invoice_id.currency_id)
else:
price = price_unit * self.quantity
diff --git a/addons/web/static/src/js/framework/data.js b/addons/web/static/src/js/framework/data.js
index a350dadb59e..c160ebaf539 100644
--- a/addons/web/static/src/js/framework/data.js
+++ b/addons/web/static/src/js/framework/data.js
@@ -793,18 +793,19 @@ var BufferedDataSet = DataSetStatic.extend({
var def = $.Deferred();
this.mutex.exec(function () {
var dirty = false;
- _.each(data, function (v, k) {
- if (!_.isEqual(v, cached.values[k])) {
+ // _.each is broken if a field "length" is present
+ for (var k in data) {
+ if (!_.isEqual(data[k], cached.values[k])) {
dirty = true;
- if (_.isEqual(v, cached.from_read[k])) { // clean changes
+ if (_.isEqual(data[k], cached.from_read[k])) { // clean changes
delete cached.changes[k];
} else {
- cached.changes[k] = v;
+ cached.changes[k] = data[k];
}
} else {
delete data[k];
}
- });
+ }
self._update_cache(id, options);
if (dirty) {
diff --git a/addons/web/static/src/js/views/list_view.js b/addons/web/static/src/js/views/list_view.js
index 54ee12f684a..cdf2ff18c63 100644
--- a/addons/web/static/src/js/views/list_view.js
+++ b/addons/web/static/src/js/views/list_view.js
@@ -470,11 +470,12 @@ var ListView = View.extend({
self.records.remove(record);
return;
}
- _.each(values, function (value, key) {
+ // _.each is broken if a field "length" is present
+ for (var key in values) {
if (fields[key] && fields[key].type === 'many2many')
record.set(key + '__display', false, {silent: true});
- record.set(key, value, {silent: true});
- });
+ record.set(key, values[key], {silent: true});
+ }
record.trigger('change', record);
/* When a record is reloaded, there is a rendering lag because of the addition/suppression of
diff --git a/addons/web/static/test/list-editable.js b/addons/web/static/test/list-editable.js
index be2acdb1365..4b88d9f06e5 100644
--- a/addons/web/static/test/list-editable.js
+++ b/addons/web/static/test/list-editable.js
@@ -196,6 +196,13 @@ odoo.define_section('list.edition', ['web.data', 'web.ListView', 'web.data_manag
mock.add('demo:onchange', function () {
return {};
});
+ mock.add('demo:fields_get', function () {
+ return {
+ a: {type: 'char', string: "A"},
+ b: {type: 'char', string: "B"},
+ c: {type: 'char', string: "C"}
+ };
+ });
}
test('newrecord', function (assert, data, ListView, data_manager) {
@@ -263,6 +270,13 @@ odoo.define_section('list.edition.events', ['web.data', 'web.ListView', 'web.dat
mock.add('demo:read', function () {
return [{ id: 1, a: 'foo', b: 'bar', c: 'baz' }];
});
+ mock.add('demo:fields_get', function () {
+ return {
+ a: {type: 'char', string: "A"},
+ b: {type: 'char', string: "B"},
+ c: {type: 'char', string: "C"}
+ };
+ });
}
test('edition events',function (assert, data, ListView, data_manager) {
@@ -345,6 +359,13 @@ odoo.define_section('list.edition.onwrite', ['web.data', 'web.ListView', 'web.da
mock.add('demo:default_get', function () { return {}; });
mock.add('demo:create', function () { return 1; });
mock.add('demo:on_write', function () { return [42]; });
+ mock.add('demo:fields_get', function () {
+ return {
+ a: {type: 'char', string: "A"},
+ b: {type: 'char', string: "B"},
+ c: {type: 'char', string: "C"}
+ };
+ });
var ds = new data.DataSetStatic(null, 'demo', null, []);
var fields_view = data_manager._postprocess_fvg({
diff --git a/addons/web/static/test/list.js b/addons/web/static/test/list.js
index 9d1d1b9ec7d..49adabba13d 100644
--- a/addons/web/static/test/list.js
+++ b/addons/web/static/test/list.js
@@ -2,7 +2,7 @@ odoo.define_section('list.buttons', ['web.ListView', 'web.data', 'web.data_manag
test('record-deletion', function (assert, ListView, data, data_manager) {
assert.expect(2);
-
+
mock.add('demo:read', function (args, kwargs) {
if (_.isEqual(args[0], [1, 2, 3])) {
return [
@@ -18,7 +18,9 @@ odoo.define_section('list.buttons', ['web.ListView', 'web.data', 'web.data_manag
throw new Error(JSON.stringify(_.toArray(arguments)));
});
mock.add('/web/dataset/call_button', function () { return false; });
-
+ mock.add('demo:fields_get', function() {
+ return {a: {type: 'char', string: "A"}};
+ });
var ds = new data.DataSetStatic(null, 'demo', null, [1, 2, 3]);
var fields_view = data_manager._postprocess_fvg({
diff --git a/addons/web/tests/test_js.py b/addons/web/tests/test_js.py
index c134074e789..8b138a56924 100644
--- a/addons/web/tests/test_js.py
+++ b/addons/web/tests/test_js.py
@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import unittest
import odoo.tests
-
class WebSuite(odoo.tests.HttpCase):
+ @unittest.skip('Memory leak in this test lead to phantomjs crash, making it unreliable')
def test_01_js(self):
self.phantom_js('/web/tests?mod=web',"","", login='admin')
diff --git a/addons/web_diagram/static/src/js/diagram.js b/addons/web_diagram/static/src/js/diagram.js
index 4192e30a185..56664176ebe 100644
--- a/addons/web_diagram/static/src/js/diagram.js
+++ b/addons/web_diagram/static/src/js/diagram.js
@@ -246,16 +246,13 @@ var DiagramView = View.extend({
self.dataset.read_index(_.keys(self.fields_view.fields)).then(self.on_diagram_loaded);
});
- var form_fields = [self.parent_field];
- var form_controller = pop.view_form;
-
- form_controller.on("load_record", self, function(){
- _.each(form_fields, function(fld) {
- if (!(fld in form_controller.fields)) { return; }
- var field = form_controller.fields[fld];
+ pop.opened().then(function(){
+ var form_controller = pop.view_form;
+ var field = form_controller.fields[self.parent_field];
+ if (field) {
field.$input.prop('disabled', true);
field.$dropdown.unbind();
- });
+ }
});
},
@@ -274,16 +271,13 @@ var DiagramView = View.extend({
}
}).open();
- var form_controller = pop.view_form;
- var form_fields = [this.parent_field];
-
- form_controller.on("load_record", self, function(){
- _.each(form_fields, function(fld) {
- if (!(fld in form_controller.fields)) { return; }
- var field = form_controller.fields[fld];
+ pop.opened().then(function(){
+ var form_controller = pop.view_form;
+ var field = form_controller.fields[self.parent_field];
+ if (field) {
field.set_value(self.id);
field.dirty = true;
- });
+ }
});
},
@@ -326,12 +320,14 @@ var DiagramView = View.extend({
}
});
- var form_controller = pop.view_form;
- form_controller.on("load_record", self, function(){
- form_controller.fields[self.connectors.attrs.source].set_value(node_source_id);
- form_controller.fields[self.connectors.attrs.source].dirty = true;
- form_controller.fields[self.connectors.attrs.destination].set_value(node_dest_id);
- form_controller.fields[self.connectors.attrs.destination].dirty = true;
+ pop.opened().then(function(){
+ var form_controller = pop.view_form;
+ var source_field = form_controller.fields[self.connectors.attrs.source];
+ var destination_field = form_controller.fields[self.connectors.attrs.destination];
+ source_field.set_value(node_source_id);
+ source_field.dirty = true;
+ destination_field.set_value(node_dest_id);
+ destination_field.dirty = true;
});
},
diff --git a/addons/web_editor/static/src/js/ace.js b/addons/web_editor/static/src/js/ace.js
index 56500383851..7bd6ec36ba2 100644
--- a/addons/web_editor/static/src/js/ace.js
+++ b/addons/web_editor/static/src/js/ace.js
@@ -106,13 +106,18 @@ var ViewEditor = Widget.extend({
storeEditorWidth();
}
}
- document.body.addEventListener('mouseup', stopResizing, true);
self.$('.ace_gutter').mouseup(stopResizing).mousedown(startResizing).click(stopResizing);
- $(document).mousemove(updateWidth);
+ $(document).on("mousemove.ViewEditor", updateWidth).on("mouseup.ViewEditor", stopResizing);
$('button[data-action=edit]').click(function () {
self.close();
});
resizeEditor(readEditorWidth());
+
+ return this._super.apply(this, arguments);
+ },
+ destroy: function () {
+ this._super.apply(this, arguments);
+ $(document).off(".ViewEditor");
},
// The 4 following methods are meant to be extended depending on the context
@@ -320,7 +325,10 @@ var ViewEditor = Widget.extend({
if (line) self.aceEditor.on('changeSession', onchangeSession);
this.$("#ace-view-list").val(session.id).change();
}
- return session.text.match(/\s+name=['"]([^'"]+)['"]/i)[1], "<b>Malformed XML document</b>:<br/>" + message;
+ return {
+ title: session.text.match(/\s+name=['"]([^'"]+)['"]/i)[1],
+ message: "<b>Malformed XML document</b>:<br/>" + message
+ };
},
});
diff --git a/addons/web_planner/models/web_planner.py b/addons/web_planner/models/web_planner.py
index 9f88ec4e708..3fa38c1dc7c 100644
--- a/addons/web_planner/models/web_planner.py
+++ b/addons/web_planner/models/web_planner.py
@@ -67,6 +67,8 @@ class Planner(models.Model):
module = self.env['ir.module.module'].sudo().search([('name', '=', module_name)], limit=1)
if module:
params['id'] = module.id
+ else:
+ return "#show_enterprise"
return "/web#%s" % (urlencode(params),)
@api.model
diff --git a/addons/web_planner/static/src/js/web_planner_common.js b/addons/web_planner/static/src/js/web_planner_common.js
index 93bcf2bc775..ad27739f2c5 100644
--- a/addons/web_planner/static/src/js/web_planner_common.js
+++ b/addons/web_planner/static/src/js/web_planner_common.js
@@ -49,6 +49,7 @@ var PlannerDialog = Widget.extend({
category_selector: 'div[menu-category-id]',
events: {
'click li a[href^="#"]:not([data-toggle="collapse"])': 'change_page',
+ 'click a[href^="#show_enterprise"]': 'show_enterprise',
'click button.mark_as_done': 'click_on_done',
'click a.btn-next': 'change_to_next_page',
'click .o_planner_close_block span': 'close_modal',
@@ -420,7 +421,40 @@ var PlannerDialog = Widget.extend({
destroy: function() {
this.$el.modal('hide');
return this._super.apply(this, arguments);
- }
+ },
+ show_enterprise: function(ev) {
+ ev.preventDefault();
+ var message = $(QWeb.render('EnterpriseUpgrade'));
+ var buttons = [
+ {
+ text: _t("Upgrade now"),
+ classes: 'btn-primary',
+ close: true,
+ click: this.confirm_upgrade,
+ },
+ {
+ text: _t("Cancel"),
+ close: true,
+ },
+ ];
+ var dialog = new Dialog(this, {
+ size: 'medium',
+ buttons: buttons,
+ $content: $('<div>', {
+ html: message,
+ }),
+ title: _t("Odoo Enterprise"),
+ }).open();
+
+ // force dialog to be hover the planner
+ dialog.$el.parents('.modal').css('z-index', 1052);
+ return dialog;
+ },
+ confirm_upgrade: function() {
+ new Model("res.users").call("search_count", [[["share", "=", false]]]).then(function(data) {
+ window.location = "https://www.odoo.com/odoo-enterprise/upgrade?utm_medium=community_upgrade&num_users=" + data;
+ });
+ },
});
return {
diff --git a/addons/website/static/src/js/website.ace.js b/addons/website/static/src/js/website.ace.js
index a89896939b7..630a8919a2f 100644
--- a/addons/website/static/src/js/website.ace.js
+++ b/addons/website/static/src/js/website.ace.js
@@ -43,7 +43,7 @@ var ViewEditor = AceCommon.ViewEditor.extend({
var args = {
key: $(document.documentElement).data('view-xmlid'),
full: true,
- bundles: !!$('script[src*=".assets_common"]').length
+ bundles: this.$('.js_include_bundles')[0].checked
};
return ajax
.jsonRpc('/website/customize_template_get', 'call', args)
@@ -65,8 +65,8 @@ var ViewEditor = AceCommon.ViewEditor.extend({
});
},
displayError: function () {
- var error_message = this._super.apply(this, arguments);
- website.error(error_message);
+ var error = this._super.apply(this, arguments);
+ website.error(error.title, error.message);
},
updateHash: function () {
window.location.hash = hash + "?view=" + this.selectedViewId();
diff --git a/addons/website/static/src/js/website.backendDashboard.js b/addons/website/static/src/js/website.backendDashboard.js
index fe6e9541ac5..5caad853fcf 100644
--- a/addons/website/static/src/js/website.backendDashboard.js
+++ b/addons/website/static/src/js/website.backendDashboard.js
@@ -259,7 +259,7 @@ var Dashboard = Widget.extend(ControlPanelMixin, {
// Check if the user is authenticated and has the right to make API calls
if (!gapi.analytics.auth.getAuthResponse()) {
this.display_unauthorized_message($analytics_components, 'not_connected');
- } else if (gapi.analytics.auth.getAuthResponse() && gapi.analytics.auth.getAuthResponse().scope.indexOf('https://www.googleapis.com/auth/analytics ') === -1) {
+ } else if (gapi.analytics.auth.getAuthResponse() && gapi.analytics.auth.getAuthResponse().scope.indexOf('https://www.googleapis.com/auth/analytics') === -1) {
this.display_unauthorized_message($analytics_components, 'no_right');
} else {
this.make_analytics_calls($analytics_components);
diff --git a/addons/website/static/src/js/website.js b/addons/website/static/src/js/website.js
index c765e335f7c..72a5934fcd6 100644
--- a/addons/website/static/src/js/website.js
+++ b/addons/website/static/src/js/website.js
@@ -135,9 +135,9 @@ odoo.define('website.website', function (require) {
var error = function (title, message, url) {
return new Dialog(null, {
- title: data.data ? data.data.arguments[0] : "",
+ title: title || "",
$content: $(qweb.render('website.error_dialog', {
- message: data.data ? data.data.arguments[1] : data.statusText,
+ message: message || "",
backend_url: url,
})),
}).open();
diff --git a/addons/website/static/src/less/website.ui.components.less b/addons/website/static/src/less/website.ui.components.less
index 5c2aa821e13..0d75a422219 100644
--- a/addons/website/static/src/less/website.ui.components.less
+++ b/addons/website/static/src/less/website.ui.components.less
@@ -242,7 +242,7 @@ body .modal {
}
// LOGIN FORM
-.oe_login_form, .oe_signup_form {
+.oe_login_form, .oe_signup_form, .oe_reset_password_form {
max-width: 300px;
position: relative;
margin: 50px auto;
diff --git a/addons/website_event_track/models/website_event_track.py b/addons/website_event_track/models/website_event_track.py
index 969bef0fa61..e14670f56e4 100644
--- a/addons/website_event_track/models/website_event_track.py
+++ b/addons/website_event_track/models/website_event_track.py
@@ -85,7 +85,8 @@ class Track(models.Model):
def _compute_website_url(self):
super(Track, self)._compute_website_url()
for track in self:
- track.website_url = '/event/%s/track/%s' % (slug(track.event_id), slug(track))
+ if not isinstance(track.id, models.NewId):
+ track.website_url = '/event/%s/track/%s' % (slug(track.event_id), slug(track))
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
diff --git a/addons/website_mail_channel/controllers/main.py b/addons/website_mail_channel/controllers/main.py
index 19c1cec172b..9a64504e309 100644
--- a/addons/website_mail_channel/controllers/main.py
+++ b/addons/website_mail_channel/controllers/main.py
@@ -15,7 +15,8 @@ class MailGroup(http.Controller):
def _get_archives(self, group_id):
MailMessage = request.env['mail.message']
groups = MailMessage._read_group_raw(
- [('model', '=', 'mail.channel'), ('res_id', '=', group_id)], ['subject', 'date'],
+ [('model', '=', 'mail.channel'), ('res_id', '=', group_id), ('message_type', '!=', 'notification')],
+ ['subject', 'date'],
groupby=["date"], orderby="date desc")
for group in groups:
(r, label) = group['date']
@@ -41,7 +42,7 @@ class MailGroup(http.Controller):
# compute statistics
month_date = datetime.today() - relativedelta.relativedelta(months=1)
messages = request.env['mail.message'].read_group(
- [('model', '=', 'mail.channel'), ('date', '>=', fields.Datetime.to_string(month_date))],
+ [('model', '=', 'mail.channel'), ('date', '>=', fields.Datetime.to_string(month_date)), ('message_type', '!=', 'notification')],
[], ['res_id'])
message_data = dict((message['res_id'], message['res_id_count']) for message in messages)
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index c3e998a7865..95a7e8f7225 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -1178,7 +1178,7 @@
<t t-if="mode[1] == 'billing'">
<div t-attf-class="form-group #{error.get('email') and 'has-error' or ''} col-md-6" id="div_email">
<label class="control-label" for="email">Email</label>
- <input type="tel" name="email" class="form-control" t-att-value="'email' in checkout and checkout['email']" />
+ <input type="email" name="email" class="form-control" t-att-value="'email' in checkout and checkout['email']" />
</div>
</t>
<div t-attf-class="form-group #{error.get('phone') and 'has-error' or ''} col-md-6" id="div_phone">