diff options
| author | Christophe Simonis <chs@odoo.com> | 2016-09-09 18:13:31 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2016-09-09 18:13:31 +0200 |
| commit | 298e2032ea3a8ef7aac3a606e2cbc420760fd4c9 (patch) | |
| tree | 0832a96998574fcb45148480bf22207a9bfef439 | |
| parent | 75f60f69416625713819c810d2da33db4dc0b06c (diff) | |
| parent | 9f28139297af937990a79d4a3026ba25bb9be6fe (diff) | |
[MERGE] forward port branch saas-12 up to 9f28139
44 files changed, 560 insertions, 99 deletions
diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml index ac3a3cb2114..19fbd7fe0aa 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -283,7 +283,7 @@ <field name="code"/> <field name="refund_sequence" attrs="{'invisible': [('type', 'not in', ['sale', 'purchase'])]}" groups="base.group_no_one"/> <field name="sequence_id" required="0" attrs="{'readonly': 1}" groups="base.group_no_one"/> - <field name="refund_sequence_id" attrs="{'required': [('refund_sequence', '=', True)], 'invisible': ['|',('type', 'not in', ['sale', 'purchase']), ('refund_sequence', '!=', True)]}" required="0" groups="base.group_no_one"/> + <field name="refund_sequence_id" attrs="{'invisible': ['|',('type', 'not in', ['sale', 'purchase']), ('refund_sequence', '!=', True)], 'required': [('type', 'in', ['sale', 'purchase']), ('refund_sequence', '=', True)]}" groups="base.group_no_one"/> </group> <group> <field name="default_debit_account_id" domain="[('deprecated', '=', False)]" /> diff --git a/addons/account_asset/models/account_asset.py b/addons/account_asset/models/account_asset.py index 3656fce7ff3..7c5585b174b 100644 --- a/addons/account_asset/models/account_asset.py +++ b/addons/account_asset/models/account_asset.py @@ -455,7 +455,7 @@ class AccountAssetDepreciationLine(models.Model): current_currency = line.asset_id.currency_id amount = current_currency.compute(line.amount, company_currency) sign = (category_id.journal_id.type == 'purchase' or category_id.journal_id.type == 'sale' and 1) or -1 - asset_name = line.asset_id.name + ' (%s/%s)' % (line.sequence, line.asset_id.method_number) + asset_name = line.asset_id.name + ' (%s/%s)' % (line.sequence, len(line.asset_id.depreciation_line_ids)) prec = self.env['decimal.precision'].precision_get('Account') move_line_1 = { 'name': asset_name, diff --git a/addons/account_asset/models/account_invoice.py b/addons/account_asset/models/account_invoice.py index 7f4782dfb25..9eb4cca484b 100644 --- a/addons/account_asset/models/account_invoice.py +++ b/addons/account_asset/models/account_invoice.py @@ -51,15 +51,15 @@ class AccountInvoiceLine(models.Model): @api.one def asset_create(self): - if self.asset_category_id and self.asset_category_id.method_number > 1: + if self.asset_category_id: vals = { 'name': self.name, 'code': self.invoice_id.number or False, 'category_id': self.asset_category_id.id, - 'value': self.price_subtotal, + 'value': self.price_subtotal_signed, 'partner_id': self.invoice_id.partner_id.id, 'company_id': self.invoice_id.company_id.id, - 'currency_id': self.invoice_id.currency_id.id, + 'currency_id': self.invoice_id.company_currency_id.id, 'date': self.asset_start_date or self.invoice_id.date_invoice, 'invoice_id': self.invoice_id.id, } diff --git a/addons/account_tax_cash_basis/models/account_partial_reconcile.py b/addons/account_tax_cash_basis/models/account_partial_reconcile.py index 8cf22009c9e..86b06389516 100644 --- a/addons/account_tax_cash_basis/models/account_partial_reconcile.py +++ b/addons/account_tax_cash_basis/models/account_partial_reconcile.py @@ -8,19 +8,22 @@ from odoo.exceptions import UserError class AccountPartialReconcileCashBasis(models.Model): _inherit = 'account.partial.reconcile' - def create_tax_cash_basis_entry(self, value_before_reconciliation): + def _get_tax_cash_basis_lines(self, value_before_reconciliation): # Search in account_move if we have any taxes account move lines tax_group = {} total_by_cash_basis_account = {} line_to_create = [] + move_date = self.debit_move_id.date for move in (self.debit_move_id.move_id, self.credit_move_id.move_id): + if move_date < move.date: + move_date = move.date for line in move.line_ids: #TOCHECK: normal and cash basis taxes shoudn't be mixed together (on the same invoice line for example) as it will #create reporting issues. Not sure of the behavior to implement in that case, though. + # amount to write is the current cash_basis amount minus the one before the reconciliation + matched_percentage = value_before_reconciliation[move.id] + amount = (line.credit_cash_basis - line.debit_cash_basis) - (line.credit - line.debit) * matched_percentage if not line.tax_exigible: - # amount to write is the current cash_basis amount minus the one before the reconciliation - matched_percentage = value_before_reconciliation[move.id] - amount = (line.credit_cash_basis - line.debit_cash_basis) - (line.credit - line.debit) * matched_percentage if line.tax_line_id and line.tax_line_id.use_cash_basis: # group by line account acc = line.account_id.id @@ -28,7 +31,6 @@ class AccountPartialReconcileCashBasis(models.Model): tax_group[acc] += amount else: tax_group[acc] = amount - # Group by cash basis account and tax acc = line.tax_line_id.cash_basis_account.id key = (acc, line.tax_line_id.id) @@ -36,8 +38,8 @@ class AccountPartialReconcileCashBasis(models.Model): total_by_cash_basis_account[key] += amount else: total_by_cash_basis_account[key] = amount - for tax in line.tax_ids: - if tax.use_cash_basis: + if any([tax.use_cash_basis for tax in line.tax_ids]): + for tax in line.tax_ids: line_to_create.append((0, 0, { 'name': '/', 'debit': line.debit_cash_basis - line.debit * matched_percentage, @@ -45,13 +47,14 @@ class AccountPartialReconcileCashBasis(models.Model): 'account_id': line.account_id.id, 'tax_ids': [(6, 0, [tax.id])], 'tax_exigible': True, - })) + })) line_to_create.append((0, 0, { 'name': '/', 'credit': line.debit_cash_basis - line.debit * matched_percentage, 'debit': line.credit_cash_basis - line.credit * matched_percentage, 'account_id': line.account_id.id, - })) + 'tax_exigible': True, + })) for k, v in tax_group.items(): line_to_create.append((0, 0, { @@ -59,7 +62,8 @@ class AccountPartialReconcileCashBasis(models.Model): 'debit': v if v > 0 else 0.0, 'credit': abs(v) if v < 0 else 0.0, 'account_id': k, - })) + 'tax_exigible': True, + })) # Create counterpart vals for key, v in total_by_cash_basis_account.items(): @@ -71,19 +75,29 @@ class AccountPartialReconcileCashBasis(models.Model): 'account_id': k, 'tax_line_id': tax_id, 'tax_exigible': True, - })) + })) + return line_to_create, move_date - # Create move + def create_tax_cash_basis_entry(self, value_before_reconciliation): + line_to_create, move_date = self._get_tax_cash_basis_lines(value_before_reconciliation) if len(line_to_create) > 0: # Check if company_journal for cash basis is set if not, raise exception if not self.company_id.tax_cash_basis_journal_id: - raise UserError(_('There is no tax cash basis journal defined ' \ - 'for this company: "%s" \nConfigure it in Accounting/Configuration/Settings') % \ - (self.company_id.name)) - move = self.env['account.move'].with_context(dont_create_taxes=True).create({ + raise UserError(_('There is no tax cash basis journal defined ' + 'for this company: "%s" \nConfigure it in Accounting/Configuration/Settings') % + (self.company_id.name)) + move_vals = { 'journal_id': self.company_id.tax_cash_basis_journal_id.id, 'line_ids': line_to_create, - 'tax_cash_basis_rec_id': self.id}) + 'tax_cash_basis_rec_id': self.id + } + # The move date should be the maximum date between payment and invoice (in case + # of payment in advance). However, we should make sure the move date is not + # recorded after the period lock date as the tax statement for this period is + # probably already sent to the estate. + if move_date > self.company_id.period_lock_date: + move_vals['date'] = move_date + move = self.env['account.move'].with_context(dont_create_taxes=True).create(move_vals) # post move move.post() diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index 5d1394d2996..c3733ba3f50 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -17,18 +17,18 @@ class HrExpense(models.Model): _description = "Expense" _order = "date desc, id desc" - name = fields.Char(string='Expense Description', readonly=True, required=True, states={'draft': [('readonly', False)]}) - date = fields.Date(readonly=True, states={'draft': [('readonly', False)]}, default=fields.Date.context_today, string="Date") - employee_id = fields.Many2one('hr.employee', string="Employee", required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)) - product_id = fields.Many2one('product.product', string='Product', readonly=True, states={'draft': [('readonly', False)]}, domain=[('can_be_expensed', '=', True)], required=True) - product_uom_id = fields.Many2one('product.uom', string='Unit of Measure', required=True, readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env['product.uom'].search([], limit=1, order='id')) - unit_amount = fields.Float(string='Unit Price', readonly=True, required=True, states={'draft': [('readonly', False)]}, digits=dp.get_precision('Product Price')) - quantity = fields.Float(required=True, readonly=True, states={'draft': [('readonly', False)]}, digits=dp.get_precision('Product Unit of Measure'), default=1) + name = fields.Char(string='Expense Description', readonly=True, required=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}) + date = fields.Date(readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=fields.Date.context_today, string="Date") + employee_id = fields.Many2one('hr.employee', string="Employee", required=True, readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env['hr.employee'].search([('user_id', '=', self.env.uid)], limit=1)) + product_id = fields.Many2one('product.product', string='Product', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, domain=[('can_be_expensed', '=', True)], required=True) + product_uom_id = fields.Many2one('product.uom', string='Unit of Measure', required=True, readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env['product.uom'].search([], limit=1, order='id')) + unit_amount = fields.Float(string='Unit Price', readonly=True, required=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, digits=dp.get_precision('Product Price')) + quantity = fields.Float(required=True, readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, digits=dp.get_precision('Product Unit of Measure'), default=1) tax_ids = fields.Many2many('account.tax', 'expense_tax', 'expense_id', 'tax_id', string='Taxes', states={'done': [('readonly', True)], 'post': [('readonly', True)]}) untaxed_amount = fields.Float(string='Subtotal', store=True, compute='_compute_amount', digits=dp.get_precision('Account')) total_amount = fields.Float(string='Total', store=True, compute='_compute_amount', digits=dp.get_precision('Account')) - company_id = fields.Many2one('res.company', string='Company', readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env.user.company_id) - currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, states={'draft': [('readonly', False)]}, default=lambda self: self.env.user.company_id.currency_id) + company_id = fields.Many2one('res.company', string='Company', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.user.company_id) + currency_id = fields.Many2one('res.currency', string='Currency', readonly=True, states={'draft': [('readonly', False)], 'refused': [('readonly', False)]}, default=lambda self: self.env.user.company_id.currency_id) analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, oldname='analytic_account') account_id = fields.Many2one('account.account', string='Account', states={'post': [('readonly', True)], 'done': [('readonly', True)]}, default=lambda self: self.env['ir.property'].get('property_account_expense_categ_id', 'product.category')) description = fields.Text() diff --git a/addons/hr_timesheet/project_timesheet.py b/addons/hr_timesheet/project_timesheet.py index f82ca0cb1f3..69f7e82beec 100644 --- a/addons/hr_timesheet/project_timesheet.py +++ b/addons/hr_timesheet/project_timesheet.py @@ -55,7 +55,7 @@ class Task(models.Model): parent_id = fields.Many2one('project.task', string='Parent Task') child_ids = fields.One2many('project.task', 'parent_id', string="Sub-tasks") - subtask_project_id = fields.Many2one('project.project', related="project_id.subtask_project_id", string='Sub-task Project') + subtask_project_id = fields.Many2one('project.project', related="project_id.subtask_project_id", string='Sub-task Project', readonly=True) subtask_count = fields.Integer(compute='_get_subtask_count', type='integer', string="Sub-task count") _constraints = [(models.BaseModel._check_recursion, 'Circular references are not permitted between tasks and sub-tasks', ['parent_id'])] diff --git a/addons/link_tracker/controller/main.py b/addons/link_tracker/controller/main.py index 83c35f71300..c03cbf56813 100644 --- a/addons/link_tracker/controller/main.py +++ b/addons/link_tracker/controller/main.py @@ -10,6 +10,7 @@ from odoo.http import request class LinkTracker(http.Controller): @http.route('/r/<string:code>', type='http', auth='none', website=True) def full_url_redirect(self, code, **post): - request.env['link.tracker.click'].add_click(code, request.httprequest.remote_addr, request.session['geoip'].get('country_code'), stat_id=False) + country_code = request.session.geoip and request.session.geoip.get('country_code') or False + request.env['link.tracker.click'].add_click(code, request.httprequest.remote_addr, country_code, stat_id=False) redirect_url = request.env['link.tracker'].get_url_from_code(code) return werkzeug.utils.redirect(redirect_url or '', 301) diff --git a/addons/mail/static/src/js/chat_manager.js b/addons/mail/static/src/js/chat_manager.js index dbadbb47bb9..90fbb6ed801 100644 --- a/addons/mail/static/src/js/chat_manager.js +++ b/addons/mail/static/src/js/chat_manager.js @@ -672,7 +672,7 @@ var chat_manager = { // post a message in a channel or execute a command return ChannelModel.call(data.command ? 'execute_command' : 'message_post', [options.channel_id], _.extend(msg, { message_type: 'comment', - content_subtype: 'plaintext', + content_subtype: 'html', subtype: 'mail.mt_comment', command: data.command, })); diff --git a/addons/note/views/note_views.xml b/addons/note/views/note_views.xml index 244cb15bd2f..1bbb796eb70 100644 --- a/addons/note/views/note_views.xml +++ b/addons/note/views/note_views.xml @@ -74,7 +74,7 @@ <field name="open"/> <field name="memo"/> <field name="date_done"/> - <field name="message_follower_ids"/> + <field name="message_partner_ids"/> <field name="tag_ids"/> <templates> <t t-name="kanban-box"> @@ -101,9 +101,9 @@ <!-- title --> <field name="name"/> </span> - <t t-if="record.message_follower_ids.raw_value.length > 1"> + <t t-if="record.message_partner_ids.raw_value.length > 1"> <div class="clearfix"></div> - <t t-foreach="record.message_follower_ids.raw_value" t-as="follower"> + <t t-foreach="record.message_partner_ids.raw_value" t-as="follower"> <img t-att-src="kanban_image('res.partner', 'image_small', follower)" width="24" height="24" class="oe_kanban_avatar pull-right" t-att-data-member_id="follower"/> </t> <div class="clearfix"></div> diff --git a/addons/payment_adyen/models/payment.py b/addons/payment_adyen/models/payment.py index edf19953bab..75faabeaba4 100644 --- a/addons/payment_adyen/models/payment.py +++ b/addons/payment_adyen/models/payment.py @@ -20,9 +20,9 @@ class AcquirerAdyen(models.Model): _inherit = 'payment.acquirer' provider = fields.Selection(selection_add=[('adyen', 'Adyen')]) - adyen_merchant_account = fields.Char('Merchant Account', required_if_provider='adyen') - adyen_skin_code = fields.Char('Skin Code', required_if_provider='adyen') - adyen_skin_hmac_key = fields.Char('Skin HMAC Key', required_if_provider='adyen') + adyen_merchant_account = fields.Char('Merchant Account', required_if_provider='adyen', groups='base.group_user') + adyen_skin_code = fields.Char('Skin Code', required_if_provider='adyen', groups='base.group_user') + adyen_skin_hmac_key = fields.Char('Skin HMAC Key', required_if_provider='adyen', groups='base.group_user') @api.model def _get_adyen_urls(self, environment): diff --git a/addons/payment_authorize/models/payment.py b/addons/payment_authorize/models/payment.py index db112af777d..1ff84577889 100644 --- a/addons/payment_authorize/models/payment.py +++ b/addons/payment_authorize/models/payment.py @@ -21,8 +21,8 @@ class PaymentAcquirerAuthorize(models.Model): _inherit = 'payment.acquirer' provider = fields.Selection(selection_add=[('authorize', 'Authorize.Net')]) - authorize_login = fields.Char(string='API Login Id', required_if_provider='authorize') - authorize_transaction_key = fields.Char(string='API Transaction Key', required_if_provider='authorize') + authorize_login = fields.Char(string='API Login Id', required_if_provider='authorize', groups='base.group_user') + authorize_transaction_key = fields.Char(string='API Transaction Key', required_if_provider='authorize', groups='base.group_user') def _get_feature_support(self): """Get advanced feature support by provider. diff --git a/addons/payment_buckaroo/models/payment.py b/addons/payment_buckaroo/models/payment.py index 82b532c9e15..ed633aa92ad 100644 --- a/addons/payment_buckaroo/models/payment.py +++ b/addons/payment_buckaroo/models/payment.py @@ -26,8 +26,8 @@ class AcquirerBuckaroo(models.Model): _inherit = 'payment.acquirer' provider = fields.Selection(selection_add=[('buckaroo', 'Buckaroo')]) - brq_websitekey = fields.Char('WebsiteKey', required_if_provider='buckaroo') - brq_secretkey = fields.Char('SecretKey', required_if_provider='buckaroo') + brq_websitekey = fields.Char('WebsiteKey', required_if_provider='buckaroo', groups='base.group_user') + brq_secretkey = fields.Char('SecretKey', required_if_provider='buckaroo', groups='base.group_user') def _get_buckaroo_urls(self, environment): """ Buckaroo URLs diff --git a/addons/payment_ogone/models/payment.py b/addons/payment_ogone/models/payment.py index 2282c5fede3..f6f5362d27d 100644 --- a/addons/payment_ogone/models/payment.py +++ b/addons/payment_ogone/models/payment.py @@ -27,11 +27,11 @@ class PaymentAcquirerOgone(models.Model): _inherit = 'payment.acquirer' provider = fields.Selection(selection_add=[('ogone', 'Ogone')]) - ogone_pspid = fields.Char('PSPID', required_if_provider='ogone') - ogone_userid = fields.Char('API User ID', required_if_provider='ogone') - ogone_password = fields.Char('API User Password', required_if_provider='ogone') - ogone_shakey_in = fields.Char('SHA Key IN', size=32, required_if_provider='ogone') - ogone_shakey_out = fields.Char('SHA Key OUT', size=32, required_if_provider='ogone') + ogone_pspid = fields.Char('PSPID', required_if_provider='ogone', groups='base.group_user') + ogone_userid = fields.Char('API User ID', required_if_provider='ogone', groups='base.group_user') + ogone_password = fields.Char('API User Password', required_if_provider='ogone', groups='base.group_user') + ogone_shakey_in = fields.Char('SHA Key IN', size=32, required_if_provider='ogone', groups='base.group_user') + ogone_shakey_out = fields.Char('SHA Key OUT', size=32, required_if_provider='ogone', groups='base.group_user') ogone_alias_usage = fields.Char('Alias Usage', default="Allow saving my payment data", help="If you want to use Ogone Aliases, this default " "Alias Usage will be presented to the customer as the " diff --git a/addons/payment_paypal/models/payment.py b/addons/payment_paypal/models/payment.py index 624331a7647..6aaff94a631 100644 --- a/addons/payment_paypal/models/payment.py +++ b/addons/payment_paypal/models/payment.py @@ -17,17 +17,17 @@ class AcquirerPaypal(models.Model): _inherit = 'payment.acquirer' provider = fields.Selection(selection_add=[('paypal', 'Paypal')]) - paypal_email_account = fields.Char('Paypal Email ID', required_if_provider='paypal') + paypal_email_account = fields.Char('Paypal Email ID', required_if_provider='paypal', groups='base.group_user') paypal_seller_account = fields.Char( - 'Paypal Merchant ID', + 'Paypal Merchant ID', groups='base.group_user', help='The Merchant ID is used to ensure communications coming from Paypal are valid and secured.') - paypal_use_ipn = fields.Boolean('Use IPN', default=True, help='Paypal Instant Payment Notification') + paypal_use_ipn = fields.Boolean('Use IPN', default=True, help='Paypal Instant Payment Notification', groups='base.group_user') # Server 2 server paypal_api_enabled = fields.Boolean('Use Rest API', default=False) - paypal_api_username = fields.Char('Rest API Username') - paypal_api_password = fields.Char('Rest API Password') - paypal_api_access_token = fields.Char('Access Token') - paypal_api_access_token_validity = fields.Datetime('Access Token Validity') + paypal_api_username = fields.Char('Rest API Username', groups='base.group_user') + paypal_api_password = fields.Char('Rest API Password', groups='base.group_user') + paypal_api_access_token = fields.Char('Access Token', groups='base.group_user') + paypal_api_access_token_validity = fields.Datetime('Access Token Validity', groups='base.group_user') # Default paypal fees fees_dom_fixed = fields.Float(default=0.35) fees_dom_var = fields.Float(default=3.4) diff --git a/addons/payment_sips/controllers/main.py b/addons/payment_sips/controllers/main.py index 41ad8508210..c5b21fbf517 100644 --- a/addons/payment_sips/controllers/main.py +++ b/addons/payment_sips/controllers/main.py @@ -26,7 +26,7 @@ class SipsController(http.Controller): def sips_validate_data(self, **post): sips = request.env['payment.acquirer'].search([('provider', '=', 'sips')], limit=1) - security = sips._sips_generate_shasign(post) + security = sips.sudo()._sips_generate_shasign(post) if security == post['Seal']: _logger.debug('Sips: validated data') return request.env['payment.transaction'].sudo().form_feedback(post, 'sips') diff --git a/addons/payment_sips/models/payment.py b/addons/payment_sips/models/payment.py index 7ce02a9d147..e9cd4d40e6b 100644 --- a/addons/payment_sips/models/payment.py +++ b/addons/payment_sips/models/payment.py @@ -37,8 +37,8 @@ class AcquirerSips(models.Model): _inherit = 'payment.acquirer' provider = fields.Selection(selection_add=[('sips', 'Sips')]) - sips_merchant_id = fields.Char('SIPS API User Password', required_if_provider='sips') - sips_secret = fields.Char('SIPS Secret', size=64, required_if_provider='sips') + sips_merchant_id = fields.Char('SIPS API User Password', required_if_provider='sips', groups='base.group_user') + sips_secret = fields.Char('SIPS Secret', size=64, required_if_provider='sips', groups='base.group_user') def _get_sips_urls(self, environment): """ Worldline SIPS URLS """ diff --git a/addons/point_of_sale/__manifest__.py b/addons/point_of_sale/__manifest__.py index fdb4b75965f..d85d1e68700 100644 --- a/addons/point_of_sale/__manifest__.py +++ b/addons/point_of_sale/__manifest__.py @@ -57,6 +57,7 @@ Main Features 'views/res_config_view.xml', 'views/report_statement.xml', 'views/report_userlabel.xml', + 'views/report_saledetails.xml', 'views/point_of_sale.xml', 'views/point_of_sale_dashboard.xml', ], diff --git a/addons/point_of_sale/controllers/main.py b/addons/point_of_sale/controllers/main.py index f4af63e4c61..3c110494630 100644 --- a/addons/point_of_sale/controllers/main.py +++ b/addons/point_of_sale/controllers/main.py @@ -22,3 +22,10 @@ class PosController(http.Controller): 'session_info': json.dumps(request.env['ir.http'].session_info()) } return request.render('point_of_sale.index', qcontext=context) + + @http.route('/pos/sale_details_report', type='http', auth='user') + def print_sale_details(self, date_start=False, date_stop=False, **kw): + r = request.env['report.point_of_sale.report_saledetails'] + pdf = request.env['report'].with_context(date_start=date_start, date_stop=date_stop).get_pdf(r, 'point_of_sale.report_saledetails') + pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))] + return request.make_response(pdf, headers=pdfhttpheaders) diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index 8bca47f7b5a..95afee9b93d 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import logging +from datetime import timedelta from functools import partial import psycopg2 @@ -8,7 +9,7 @@ import psycopg2 from odoo import api, fields, models, tools, _ from odoo.tools import float_is_zero from odoo.exceptions import UserError - +from odoo.http import request import odoo.addons.decimal_precision as dp _logger = logging.getLogger(__name__) @@ -781,3 +782,104 @@ class PosOrderLineLot(models.Model): order_id = fields.Many2one('pos.order', related="pos_order_line_id.order_id") lot_name = fields.Char('Lot Name') product_id = fields.Many2one('product.product', related='pos_order_line_id.product_id') + + +class ReportSaleDetails(models.AbstractModel): + + _name = 'report.point_of_sale.report_saledetails' + + + @api.model + def get_sale_details(self, date_start=False, date_stop=False, company=False): + """ Serialise the orders of the day information + + params: date_start, date_stop string representing the day of order + """ + if not company: + company = self.env.user.company_id + + today = fields.Datetime.from_string(fields.Date.context_today(self)) + if date_start: + date_start = fields.Datetime.from_string(date_start) + else: + # start by default today 00:00:00 + date_start = today + + if date_stop: + # set time to 23:59:59 + date_stop = fields.Datetime.from_string(date_stop) + timedelta(days=1, seconds=-1) + else: + # stop by default today 23:59:59 + date_stop = today + timedelta(days=1, seconds=-1) + + # avoid a date_stop smaller than date_start + date_stop = max(date_stop, date_start) + + date_start = fields.Datetime.to_string(date_start) + date_stop = fields.Datetime.to_string(date_stop) + orders = self.env['pos.order'].search([('date_order', '>=', date_start), ('date_order', '<=', date_stop), + ('state', 'in', ['paid','invoiced','done']), + ('company_id', '=', company.id)]) + user_currency = self.env.user.company_id.currency_id + + total = 0.0 + products_sold = {} + taxes = {} + for order in orders: + if user_currency != order.pricelist_id.currency_id: + total += order.pricelist_id.currency_id.compute(order.amount_total, user_currency) + else: + total += order.amount_total + currency = order.session_id.currency_id + + for line in order.lines: + key = (line.product_id, line.price_unit, line.discount) + products_sold.setdefault(key, 0.0) + products_sold[key] += line.qty + + if line.tax_ids_after_fiscal_position: + line_taxes = line.tax_ids_after_fiscal_position.compute_all(line.price_unit * (1-(line.discount or 0.0)/100.0), currency, line.qty, product=line.product_id, partner=line.order_id.partner_id or False) + for tax in line_taxes['taxes']: + taxes.setdefault(tax['id'], {'name': tax['name'], 'total':0.0}) + taxes[tax['id']]['total'] += tax['amount'] + + st_line_ids = self.env["account.bank.statement.line"].search([('pos_statement_id', 'in', orders.ids)]).ids + if st_line_ids: + self.env.cr.execute(""" + SELECT aj.name, sum(amount) total + FROM account_bank_statement_line AS absl, + account_bank_statement AS abs, + account_journal AS aj + WHERE absl.statement_id = abs.id + AND abs.journal_id = aj.id + AND absl.id IN %s + GROUP BY aj.name + """, (tuple(st_line_ids),)) + payments = self.env.cr.dictfetchall() + else: + payments = [] + + return { + 'total_paid': user_currency.round(total), + 'payments': payments, + 'company_name': self.env.user.company_id.name, + 'taxes': taxes.values(), + 'products': [{ + 'product_id': product.id, + 'product_name': product.name[:20], + '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()] + } + + @api.multi + def render_html(self, data=None): + company = request.env.user.company_id + date_start = self.env.context.get('date_start', False) + date_stop = self.env.context.get('date_stop', False) + data = dict(data or {}, date_start=date_start, date_stop=date_stop) + data.update(self.get_sale_details(date_start, date_stop, company)) + return self.env['report'].render('point_of_sale.report_saledetails', data)
\ No newline at end of file diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js index 926fe23eee7..f7bf1f78010 100644 --- a/addons/point_of_sale/static/src/js/chrome.js +++ b/addons/point_of_sale/static/src/js/chrome.js @@ -406,6 +406,23 @@ var ProxyStatusWidget = StatusWidget.extend({ }, }); + +/* --------- The Sale Details --------- */ + +// Generates a report to print the sales of the +// day on a ticket + +var SaleDetailsButton = PosBaseWidget.extend({ + template: 'SaleDetailsButton', + start: function(){ + var self = this; + this.$el.click(function(){ + self.pos.proxy.print_sale_details(); + }); + }, +}); + + /*--------------------------------------*\ | THE CHROME | \*======================================*/ @@ -663,6 +680,11 @@ var Chrome = PosBaseWidget.extend({ 'widget': OrderSelectorWidget, 'replace': '.placeholder-OrderSelectorWidget', },{ + 'name': 'sale_details', + 'widget': SaleDetailsButton, + 'append': '.pos-rightheader', + 'condition': function(){ return this.pos.config.use_proxy; }, + },{ 'name': 'proxy_status', 'widget': ProxyStatusWidget, 'append': '.pos-rightheader', @@ -769,6 +791,7 @@ return { HeaderButtonWidget: HeaderButtonWidget, OrderSelectorWidget: OrderSelectorWidget, ProxyStatusWidget: ProxyStatusWidget, + SaleDetailsButton: SaleDetailsButton, StatusWidget: StatusWidget, SynchNotificationWidget: SynchNotificationWidget, UsernameWidget: UsernameWidget, diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index 76daa045c10..3a5e0ba45a0 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -2,8 +2,10 @@ odoo.define('point_of_sale.devices', function (require) { "use strict"; var core = require('web.core'); +var Model = require('web.DataModel'); var Session = require('web.Session'); +var QWeb = core.qweb; var _t = core._t; // the JobQueue schedules a sequence of 'jobs'. each job is @@ -434,6 +436,23 @@ var ProxyDevice = core.Class.extend(core.mixins.PropertiesMixin,{ send_printing_job(); }, + print_sale_details: function() { + var self = this; + new Model('report.point_of_sale.report_saledetails').call('get_sale_details').then(function(result){ + var env = { + company: self.pos.company, + pos: self.pos, + products: result.products, + payments: result.payments, + taxes: result.taxes, + total_paid: result.total_paid, + date: (new Date()).toLocaleString(), + }; + var report = QWeb.render('SaleDetailsReport', env); + self.print_receipt(report); + }) + }, + // asks the proxy to log some information, as with the debug.log you can provide several arguments. log: function(){ return this.message('log',{'arguments': _.toArray(arguments)}); diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 7f54a6ddd78..1282d35d3a9 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1405,13 +1405,14 @@ exports.Orderline = Backbone.Model.extend({ }, compute_all: function(taxes, price_unit, quantity, currency_rounding) { var self = this; - var total_excluded = round_pr(price_unit * quantity, currency_rounding); - var total_included = total_excluded; - var base = total_excluded; var list_taxes = []; + var currency_rounding_bak = currency_rounding; if (this.pos.company.tax_calculation_rounding_method == "round_globally"){ currency_rounding = currency_rounding * 0.00001; } + var total_excluded = round_pr(price_unit * quantity, currency_rounding); + var total_included = total_excluded; + var base = total_excluded; _(taxes).each(function(tax) { tax = self._map_tax_fiscal_position(tax); if (tax.amount_type === 'group'){ @@ -1445,7 +1446,11 @@ exports.Orderline = Backbone.Model.extend({ } } }); - return {taxes: list_taxes, total_excluded: total_excluded, total_included: total_included}; + return { + taxes: list_taxes, + total_excluded: round_pr(total_excluded, currency_rounding_bak), + total_included: round_pr(total_included, currency_rounding_bak) + }; }, get_all_prices: function(){ var price_unit = this.get_unit_price() * (1.0 - (this.get_discount() / 100.0)); diff --git a/addons/point_of_sale/static/src/js/report.js b/addons/point_of_sale/static/src/js/report.js new file mode 100644 index 00000000000..783ace7f655 --- /dev/null +++ b/addons/point_of_sale/static/src/js/report.js @@ -0,0 +1,63 @@ +odoo.define('point_of_sale.report', function (require) { +"use strict"; + +var chat_manager = require('mail.chat_manager'); +var composer = require('mail.composer'); +var ChatThread = require('mail.ChatThread'); +var utils = require('mail.utils'); + +var config = require('web.config'); +var core = require('web.core'); +var data = require('web.data'); +var Dialog = require('web.Dialog'); +var framework = require('web.framework'); +var Model = require('web.Model'); + +var pyeval = require('web.pyeval'); +var SearchView = require('web.SearchView'); + +var ActionManager = require('web.ActionManager'); +var ControlPanelMixin = require('web.ControlPanelMixin'); +var datepicker = require('web.datepicker'); +var Widget = require('web.Widget'); + +var QWeb = core.qweb; +var _t = core._t; + +// TODO replace in v10 with proper wizard +var PosDetailsWidget = Widget.extend(ControlPanelMixin, { + template: 'WizardSaleDetailsReport', + events: { + "click .js_generate": "renderReport", + }, + + renderElement: function() { + this._super(); + + var current_date = new moment(); + this.dp_start = new datepicker.DateWidget(this); + this.dp_start.insertBefore(this.$el.find('.js_date_start')); + this.dp_start.set_value(current_date.format('YYYY-MM-DD')); + + this.dp_stop = new datepicker.DateWidget(this); + this.dp_stop.insertAfter(this.$el.find('.js_date_stop')); + this.dp_stop.set_value(current_date.format('YYYY-MM-DD')); + + }, + + renderReport: function() { + var start = this.dp_start.get_value(); + var stop = this.dp_stop.get_value(); + var url = '/pos/sale_details_report?date_start='+start+'&date_stop='+stop; + window.open(url, '_blank'); + + }, + +}); + + +core.action_registry.add('report_pos_details', PosDetailsWidget); + +return PosDetailsWidget; + +}); 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 4a529dd1bf5..5f7f4285d95 100644 --- a/addons/point_of_sale/static/src/js/widget_base.js +++ b/addons/point_of_sale/static/src/js/widget_base.js @@ -38,7 +38,7 @@ var PosBaseWidget = Widget.extend({ var currency = (this.pos && this.pos.currency) ? this.pos.currency : {symbol:'$', position: 'after', rounding: 0.01, decimals: 2}; var decimals = currency.decimals; - if (precision && (typeof this.pos.dp[precision]) !== undefined) { + if (precision && this.pos.dp[precision] !== undefined) { decimals = this.pos.dp[precision]; } diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 1d8e57eb76a..d7d54123a8c 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -84,6 +84,14 @@ </div> </t> + <t t-name="SaleDetailsButton"> + <div class="oe_status"> + <div class="js_connected oe_icon"> + <i class='fa fa-fw fa-print'></i> + </div> + </div> + </t> + <t t-name="HeaderButtonWidget"> <div class="header-button"> <t t-esc="widget.label" /> @@ -846,6 +854,114 @@ </receipt> </t> + <t t-name="SaleDetailsReport"> + <receipt align='center' width='40' value-thousands-separator='' > + <t t-if='pos.company_logo_base64'> + <img t-att-src='pos.company_logo_base64' /> + <br/> + </t> + <t t-if='!pos.company_logo_base64'> + <h1><t t-esc='company.name' /></h1> + <br/> + </t> + <br /><br /> + + <!-- Orderlines --> + + <div class='orderlines' line-ratio='0.6'> + <t t-foreach='products' t-as='line'> + <line> + <left><t t-esc='line.product_name' /></left> + <right> + <value value-decimals='2' value-autoint='on'> + <t t-esc='line.quantity' /> + </value> + <t t-if='line.uom !== "Unit(s)"'> + <t t-esc='line.uom' /> + </t> + </right> + <right> + <value><t t-esc='line.price_unit' /></value> + </right> + </line> + <t t-if='line.discount !== 0'> + <line indent='1'><left>Discount: <t t-esc='line.discount' />%</left></line> + </t> + </t> + </div> + + <br/> + <line><left>------------------------</left></line> + <br/> + + <line> + <left>Payments:</left> + </line> + <line t-foreach='payments' t-as='payment'> + <left><t t-esc='payment.name' /></left> + <right> + <t t-esc='payment.total'/> + </right> + </line> + + <br/> + <line><left>------------------------</left></line> + <br/> + + <line> + <left>Taxes:</left> + </line> + <line t-foreach='taxes' t-as='taxe'> + <left><t t-esc='taxe.name' /></left> + <right> + <t t-esc='taxe.total'/> + </right> + </line> + + <br/> + <line><left>------------------------</left></line> + <br/> + + <line> + <left>Total:</left> + <right> + <t t-esc='total_paid'/> + </right> + </line> + + <br/> + <div font='b'> + <div><t t-esc='date' /></div> + </div> + + </receipt> + </t> + + + <t t-name="WizardSaleDetailsReport"> + <div class="o_form_view"><div class="oe_form_sheetbg o_form_sheet_bg"><div class="oe_form_sheet oe_form_sheet_width o_form_sheet"> + <div class="pos_details_dates"> + <h3>Select Dates for the report</h3> + <table class="table-condensed"> + <tr> + <td><strong>Start date</strong></td> + <td><div class="js_date_start"></div></td> + </tr> + <tr> + <td><strong>Stop date</strong></td> + <td><div class="js_date_stop"></div></td> + </tr> + </table> + <p> + <button class="js_generate">Generate report</button> + </p> + </div> + + + </div></div></div> + </t> + + <t t-name="PopupWidget"> <div class="modal-dialog"> <div class="popup popup-alert"> diff --git a/addons/point_of_sale/views/point_of_sale_report.xml b/addons/point_of_sale/views/point_of_sale_report.xml index f9787c1b4c7..94ad5b1e811 100644 --- a/addons/point_of_sale/views/point_of_sale_report.xml +++ b/addons/point_of_sale/views/point_of_sale_report.xml @@ -17,5 +17,11 @@ <field name="report_name">point_of_sale.report_invoice</field> </record> + <record id="sale_details_report" model="ir.actions.report.xml"> + <field name="name">Sale Details</field> + <field name="model">report.point_of_sale.details</field> + <field name="report_type">qweb-pdf</field> + <field name="report_name">point_of_sale.report_saledetails</field> + </record> </odoo> diff --git a/addons/point_of_sale/views/point_of_sale_template.xml b/addons/point_of_sale/views/point_of_sale_template.xml index 673c6e3e943..a30340fa4d7 100644 --- a/addons/point_of_sale/views/point_of_sale_template.xml +++ b/addons/point_of_sale/views/point_of_sale_template.xml @@ -4,6 +4,7 @@ <xpath expr="." position="inside"> <link rel="stylesheet" href="/point_of_sale/static/src/less/pos_dashboard.less"/> <script type="text/javascript" src="/point_of_sale/static/src/js/tour.js"></script> + <script type="text/javascript" src="/point_of_sale/static/src/js/report.js"></script> </xpath> </template> </odoo> diff --git a/addons/point_of_sale/views/pos_order_report_view.xml b/addons/point_of_sale/views/pos_order_report_view.xml index ce5aa141a4f..148b4e2b0bf 100644 --- a/addons/point_of_sale/views/pos_order_report_view.xml +++ b/addons/point_of_sale/views/pos_order_report_view.xml @@ -68,5 +68,11 @@ <field name="context">{'group_by_no_leaf':1,'group_by':[]}</field> </record> + <record id="action_report_pos_details" model="ir.actions.client"> + <field name="name">Sale Details</field> + <field name="tag">report_pos_details</field> + </record> + <menuitem id="menu_report_pos_order_all" name="Orders" action="action_report_pos_order_all" parent="menu_point_rep" sequence="3"/> + <menuitem id="menu_report_order_details" name="Sale Details" action="action_report_pos_details" parent="menu_point_rep" sequence="4"/> </odoo> diff --git a/addons/point_of_sale/views/report_saledetails.xml b/addons/point_of_sale/views/report_saledetails.xml new file mode 100644 index 00000000000..888eab47217 --- /dev/null +++ b/addons/point_of_sale/views/report_saledetails.xml @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="utf-8"?> +<openerp> +<data> +<template id="report_saledetails"> + <t t-call="report.html_container"> + <t t-call="report.internal_layout"> + <div class="page"> + + <div class="text-center"> + <h2>Sale Details</h2> + + <strong><t t-esc="date_start"/> - <t t-esc="date_stop"/></strong> + </div> + + <!-- Orderlines --> + <h3>Products</h3> + <table class="table table-condensed"> + <thead><tr> + <th>Product</th> + <th>Quantity</th> + <th>Price Unit</th> + </tr></thead> + <tbody> + <tr t-foreach='products' t-as='line'> + <td><t t-esc="line['product_name']" /></td> + <td> + <t t-esc="line['quantity']" /> + <t t-if='line["uom"] != "Unit(s)"'> + <t t-esc='line["uom"]' /> + </t> + </td> + <td> + <t t-esc='line["price_unit"]' /> + <t t-if='line["discount"] != 0'> + Disc: <t t-esc='line["discount"]' />% + </t> + </td> + </tr> + </tbody> + </table> + + <br/> + + <h3>Payments</h3> + <table class="table table-condensed"> + <thead><tr> + <th>Name</th> + <th>Total</th> + </tr></thead> + <tbody> + <tr t-foreach='payments' t-as='payment'> + <td><t t-esc="payment['name']" /></td> + <td><t t-esc="payment['total']" /></td> + </tr> + </tbody> + </table> + + <br/> + + <h3>Taxes</h3> + <table class="table table-condensed"> + <thead><tr> + <th>Name</th> + <th>Total</th> + </tr></thead> + <tbody> + <tr t-foreach='taxes' t-as='tax'> + <td><t t-esc="tax['name']" /></td> + <td><t t-esc="tax['total']" /></td> + </tr> + </tbody> + </table> + + <br/> + <br/> + + <strong>Total: <t t-esc='total_paid'/></strong> + + </div> + </t> + </t> +</template> +</data> +</openerp> diff --git a/addons/product/views/product_views.xml b/addons/product/views/product_views.xml index bfd6b2a42d9..0eb5745c5f4 100644 --- a/addons/product/views/product_views.xml +++ b/addons/product/views/product_views.xml @@ -122,6 +122,7 @@ <field name="res_model">product.template</field> <field name="view_mode">kanban,tree,form</field> <field name="view_type">form</field> + <field name="context">{}</field> <field name="help" type="html"> <p class="oe_view_nocontent_create"> Click to define a new product. diff --git a/addons/rating/models/rating.py b/addons/rating/models/rating.py index e92960c881d..0ac59c861da 100644 --- a/addons/rating/models/rating.py +++ b/addons/rating/models/rating.py @@ -80,9 +80,10 @@ class RatingMixin(models.AbstractModel): def rating_get_access_token(self, partner=None): if not partner: partner = self.rating_get_partner_id() + rated_partner = self.rating_get_rated_partner_id() ratings = self.rating_ids.filtered(lambda x: x.partner_id.id == partner.id and not x.consumed) if not ratings: - rating = self.env['rating.rating'].create({'partner_id': partner.id, 'res_model': self._name, 'res_id': self.id}) + rating = self.env['rating.rating'].create({'partner_id': partner.id, 'rated_partner_id': rated_partner.id, 'res_model': self._name, 'res_id': self.id}) else: rating = ratings[0] return rating.access_token diff --git a/addons/rating/views/rating_template.xml b/addons/rating/views/rating_template.xml index 89a4dad050d..b0af8bab7cd 100644 --- a/addons/rating/views/rating_template.xml +++ b/addons/rating/views/rating_template.xml @@ -134,7 +134,7 @@ </p> <form class="form-horizontal" t-attf-action="/rating/#{token}/#{rate}/submit_feedback" method="post"> <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/> - <textarea class="form-control" name="feedback" rows="8" t-esc="rating.feedback"></textarea> + <textarea class="form-control" name="feedback" rows="8" t-att-value="rating.feedback"></textarea> <button type="submit" class="btn btn-primary" style="margin-top:8px;">Send Feedback</button> </form> </div> diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py index a2df8cb74c6..2b3192dc0cf 100644 --- a/addons/stock/models/product.py +++ b/addons/stock/models/product.py @@ -518,7 +518,7 @@ class ProductTemplate(models.Model): action = self.env.ref('stock.act_product_stock_move_open').read()[0] if self: action['context'] = {'default_product_id': products.ids[0]} - action['domain'] = [('product_id.product_tmpl_id', 'in', products.ids)] + action['domain'] = [('product_id.product_tmpl_id', 'in', self.ids)] return action diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py index 6b7141aabd7..e2cdafe0ce1 100644 --- a/addons/stock/models/stock_picking.py +++ b/addons/stock/models/stock_picking.py @@ -918,6 +918,7 @@ class Picking(models.Model): moves.action_confirm() return moves + @api.model def _prepare_values_extra_move(self, op, product, remaining_qty): """ Creates an extra move when there is no corresponding original move to be copied diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 39ac40a35c2..08042763369 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -99,7 +99,7 @@ def ensure_db(redirect='/web/database/selector'): # If the db is taken out of a query parameter, it will be checked against # `http.db_filter()` in order to ensure it's legit and thus avoid db # forgering that could lead to xss attacks. - db = request.params.get('db') + db = request.params.get('db') and request.params.get('db').strip() # Ensure db is legit if db and db not in http.db_filter([db]): diff --git a/addons/web/static/src/js/framework/data.js b/addons/web/static/src/js/framework/data.js index 78bc5f39bfd..a350dadb59e 100644 --- a/addons/web/static/src/js/framework/data.js +++ b/addons/web/static/src/js/framework/data.js @@ -710,6 +710,13 @@ var DataSetSearch = DataSet.extend({ this._length -= (before - this.ids.length); } }, + add_ids: function(ids, at) { + var before = this.ids.length; + this._super(ids, at); + if(this._length){ + this._length += (this.ids.length - before); + } + }, unlink: function(ids, callback, error_callback) { var self = this; return this._super(ids).done(function() { diff --git a/addons/web_editor/static/src/js/snippets.options.js b/addons/web_editor/static/src/js/snippets.options.js index 4e74aba2fe3..b4b96beabca 100644 --- a/addons/web_editor/static/src/js/snippets.options.js +++ b/addons/web_editor/static/src/js/snippets.options.js @@ -354,6 +354,7 @@ odoo.define('web_editor.snippets.options', function (require) { this.$target.off(".background-option") .on("background-color-event.background-option", (function (e, type) { e.stopPropagation(); + if (e.currentTarget !== e.target) return; this.$el.find("li:first > a").trigger(type); }).bind(this)); return res; diff --git a/addons/web_kanban/static/src/js/kanban_column.js b/addons/web_kanban/static/src/js/kanban_column.js index 0094ad01743..f848c266944 100644 --- a/addons/web_kanban/static/src/js/kanban_column.js +++ b/addons/web_kanban/static/src/js/kanban_column.js @@ -116,6 +116,7 @@ var KanbanColumn = Widget.extend({ } else { // adding record to this column self.records.push(record); + self.dataset.add_ids([record.id]); record.setParent(self); ui.item.addClass('o_updating'); self.trigger_up('kanban_column_add_record', {record: record}); diff --git a/addons/web_kanban/static/src/js/kanban_view.js b/addons/web_kanban/static/src/js/kanban_view.js index a4e41ef6305..5de3c617758 100644 --- a/addons/web_kanban/static/src/js/kanban_view.js +++ b/addons/web_kanban/static/src/js/kanban_view.js @@ -557,9 +557,7 @@ var KanbanView = View.extend({ active_model: this.model, }); } - this.do_execute_action(event.data, this.dataset, event.target.id).then(function () { - self.reload_record(event.target); - }); + this.do_execute_action(event.data, this.dataset, event.target.id, _.bind(self.reload_record, this, event.target)); }, /* diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js index fdbf4e2282a..808c3b43614 100644 --- a/addons/website/static/src/js/website.snippets.editor.js +++ b/addons/website/static/src/js/website.snippets.editor.js @@ -163,33 +163,29 @@ options.registry.slider = options.Class.extend({ return $clone; }, remove_slide: function (type) { - if(type !== "click") return; - - if (this.remove_process) { - return; - } + if (type !== "click" || this.remove_process) return; var self = this; - var cycle = this.$inner.find('.item').length - 1; - var index = this.$inner.find('.item.active').index(); + + var $items = this.$inner.find('.item'); + var cycle = $items.length - 1; + var $active = $items.filter('.active'); + var index = $active.index(); if (cycle > 0) { this.remove_process = true; - var $el = this.$inner.find('.item.active'); - self.$target.on('slid.bs.carousel', function (event) { - $el.remove(); + this.$target.on('slid.bs.carousel.slide_removal', function (event) { + $active.remove(); self.$indicators.find("li:last").remove(); - self.$target.off('slid.bs.carousel'); + self.$target.off('slid.bs.carousel.slide_removal'); self.rebind_event(); self.remove_process = false; if (cycle === 1) { - self.on_remove_slide(event); + self.$target.find('.carousel-control, .carousel-indicators').addClass("hidden"); } }); - setTimeout(function () { - self.$target.carousel( index > 0 ? --index : cycle ); - }, 500); - } else { - this.$target.find('.carousel-control, .carousel-indicators').addClass("hidden"); + _.defer(function () { + self.$target.carousel(index > 0 ? --index : cycle); + }); } }, interval : function (type, value) { @@ -217,18 +213,24 @@ options.registry.carousel = options.registry.slider.extend({ }, start : function () { var self = this; - this._super(); + this._super.apply(this, arguments); // set background and prepare to clean for save this.$target.on('slid.bs.carousel', function () { - if(self.editor && self.editor.styles.background) { - self.editor.styles.background.$target = self.$target.find(".item.active"); self.editor.styles.background_position.$target = self.editor.styles.background.$target; - self.editor.styles.background.set_active(); self.editor.styles.background_position.set_active(); self.editor.styles.background.$target.trigger("snippet-option-change", [self.editor.styles.background]); - } self.$target.carousel("pause"); + if (!self.editor) return; + + _.each(["background", "background_position", "colorpicker"], function (opt_name) { + var s_option = self.editor.styles[opt_name]; + if (!s_option) return; + + s_option.$target = self.$target.find(".item.active"); + s_option.set_active(); + s_option.$target.trigger("snippet-option-change", [s_option]); + }); }); this.$target.trigger('slid.bs.carousel'); }, diff --git a/addons/website_project/models/project.py b/addons/website_project/models/project.py index d9a0ae1d0a4..e44a9f5f131 100644 --- a/addons/website_project/models/project.py +++ b/addons/website_project/models/project.py @@ -26,7 +26,7 @@ class Project(models.Model): continue if recipient.user_ids and all(recipient.user_ids.mapped('share')): group_data['user'] |= recipient - done_ids.add(recipient.id) + done_ids.add(recipient.id) return super(Project, self)._notification_group_recipients(message, recipients, done_ids, group_data) @@ -53,6 +53,6 @@ class Task(models.Model): continue if recipient.user_ids and all(recipient.user_ids.mapped('share')): group_data['user'] |= recipient - done_ids.add(recipient.id) + done_ids.add(recipient.id) return super(Task, self)._notification_group_recipients(message, recipients, done_ids, group_data) diff --git a/addons/website_project_issue/models/project_issue.py b/addons/website_project_issue/models/project_issue.py index 31bafc1854f..521b70c4398 100644 --- a/addons/website_project_issue/models/project_issue.py +++ b/addons/website_project_issue/models/project_issue.py @@ -31,7 +31,8 @@ class Issue(models.Model): continue if recipient.user_ids and group_project_user_id in recipient.user_ids[0].groups_id.ids: group_data['group_project_user'] |= recipient + done_ids.add(recipient.id) elif recipient.user_ids and all(recipient.user_ids.mapped('share')): group_data['user'] |= recipient - done_ids.add(recipient.id) + done_ids.add(recipient.id) return super(Issue, self)._notification_group_recipients(message, recipients, done_ids, group_data) diff --git a/addons/website_sale/models/account_invoice.py b/addons/website_sale/models/account_invoice.py index 83fc8f1393a..8011fab497f 100644 --- a/addons/website_sale/models/account_invoice.py +++ b/addons/website_sale/models/account_invoice.py @@ -15,7 +15,7 @@ class AccountInvoice(models.Model): continue if recipient.user_ids and all(recipient.user_ids.mapped('share')): group_data['user'] |= recipient - done_ids.add(recipient.id) + done_ids.add(recipient.id) return super(AccountInvoice, self)._notification_group_recipients(message, recipients, done_ids, group_data) @api.multi diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py index 8804286b314..6be5fcd1cf0 100644 --- a/addons/website_sale/models/sale_order.py +++ b/addons/website_sale/models/sale_order.py @@ -166,7 +166,7 @@ class SaleOrder(models.Model): continue if recipient.user_ids and all(recipient.user_ids.mapped('share')): group_data['user'] |= recipient - done_ids.add(recipient.id) + done_ids.add(recipient.id) return super(SaleOrder, self)._notification_group_recipients(message, recipients, done_ids, group_data) |
