diff --git a/.tx/config b/.tx/config index 43ce1968270..29678c84409 100644 --- a/.tx/config +++ b/.tx/config @@ -77,6 +77,11 @@ file_filter = addons/anonymization/i18n/.po source_file = addons/anonymization/i18n/anonymization.pot source_lang = en +[odoo-master.association] +file_filter = addons/association/i18n/.po +source_file = addons/association/i18n/association.pot +source_lang = en + [odoo-master.auth_crypt] file_filter = addons/auth_crypt/i18n/.po source_file = addons/auth_crypt/i18n/auth_crypt.pot @@ -317,6 +322,7 @@ file_filter = addons/l10n_multilang/i18n/.po source_file = addons/l10n_multilang/i18n/l10n_multilang.pot source_lang = en + [odoo-master.link_tracker] file_filter = addons/link_tracker/i18n/.po source_file = addons/link_tracker/i18n/link_tracker.pot @@ -462,6 +468,11 @@ file_filter = addons/pos_cache/i18n/.po source_file = addons/pos_cache/i18n/pos_cache.pot source_lang = en +[odoo-master.pos_data_drinks] +file_filter = addons/pos_data_drinks/i18n/.po +source_file = addons/pos_data_drinks/i18n/pos_data_drinks.pot +source_lang = en + [odoo-master.pos_discount] file_filter = addons/pos_discount/i18n/.po source_file = addons/pos_discount/i18n/pos_discount.pot @@ -517,11 +528,6 @@ file_filter = addons/product_margin/i18n/.po source_file = addons/product_margin/i18n/product_margin.pot source_lang = en -[odoo-master.product_visible_discount] -file_filter = addons/product_visible_discount/i18n/.po -source_file = addons/product_visible_discount/i18n/product_visible_discount.pot -source_lang = en - [odoo-master.project] file_filter = addons/project/i18n/.po source_file = addons/project/i18n/project.pot @@ -592,11 +598,6 @@ file_filter = addons/sale_crm/i18n/.po source_file = addons/sale_crm/i18n/sale_crm.pot source_lang = en -[odoo-master.sale_expense] -file_filter = addons/sale_expense/i18n/.po -source_file = addons/sale_expense/i18n/sale_expense.pot -source_lang = en - [odoo-master.sale_margin] file_filter = addons/sale_margin/i18n/.po source_file = addons/sale_margin/i18n/sale_margin.pot @@ -677,16 +678,6 @@ file_filter = addons/survey_crm/i18n/.po source_file = addons/survey_crm/i18n/survey_crm.pot source_lang = en -[odoo-master.theme_bootswatch] -file_filter = addons/theme_bootswatch/i18n/.po -source_file = addons/theme_bootswatch/i18n/theme_bootswatch.pot -source_lang = en - -[odoo-master.theme_default] -file_filter = addons/theme_default/i18n/.po -source_file = addons/theme_default/i18n/theme_default.pot -source_lang = en - [odoo-master.utm] file_filter = addons/utm/i18n/.po source_file = addons/utm/i18n/utm.pot @@ -802,11 +793,6 @@ file_filter = addons/website_gengo/i18n/.po source_file = addons/website_gengo/i18n/website_gengo.pot source_lang = en -[odoo-master.website_google_map] -file_filter = addons/website_google_map/i18n/.po -source_file = addons/website_google_map/i18n/website_google_map.pot -source_lang = en - [odoo-master.website_hr] file_filter = addons/website_hr/i18n/.po source_file = addons/website_hr/i18n/website_hr.pot diff --git a/addons/account/data/invoice_action_data.xml b/addons/account/data/invoice_action_data.xml index 8a9f4bcb139..96fa2342f06 100644 --- a/addons/account/data/invoice_action_data.xml +++ b/addons/account/data/invoice_action_data.xml @@ -63,7 +63,7 @@ from ${object.company_id.name}. - +
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 2512fe8ac30..439daf7bf33 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -1343,9 +1343,10 @@ class AccountInvoiceTax(models.Model): base = 0.0 for line in tax.invoice_id.invoice_line_ids: if tax.tax_id in line.invoice_line_tax_ids: - base += line.price_subtotal - # To add include base amount taxes - base += sum((line.invoice_line_tax_ids.filtered(lambda t: t.include_base_amount) - tax.tax_id).mapped('amount')) + price_unit = line.price_unit * (1 - (line.discount or 0.0) / 100.0) + base += (line.invoice_line_tax_ids - tax.tax_id).compute_all( + price_unit, line.invoice_id.currency_id, line.quantity, line.product_id, line.invoice_id.partner_id + )['base'] tax.base = base invoice_id = fields.Many2one('account.invoice', string='Invoice', ondelete='cascade', index=True) diff --git a/addons/account/views/account_menuitem.xml b/addons/account/views/account_menuitem.xml index 256af22af5e..99b203bac02 100644 --- a/addons/account/views/account_menuitem.xml +++ b/addons/account/views/account_menuitem.xml @@ -13,22 +13,22 @@ + + + + + - - - - - - - - + + + + + + + + - - - - - 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 @@ - + diff --git a/addons/account/views/res_config_view.xml b/addons/account/views/res_config_view.xml index 5840947cf1f..a7fa7728efa 100644 --- a/addons/account/views/res_config_view.xml +++ b/addons/account/views/res_config_view.xml @@ -269,7 +269,7 @@
- (*) This configuration value is related to the company you're logged in. + (*) This configuration is related to the company you're logged into.
diff --git a/addons/account_asset/models/account_asset.py b/addons/account_asset/models/account_asset.py index c02b559e685..7c5585b174b 100644 --- a/addons/account_asset/models/account_asset.py +++ b/addons/account_asset/models/account_asset.py @@ -7,7 +7,7 @@ from dateutil.relativedelta import relativedelta from odoo import api, fields, models, _ from odoo.exceptions import UserError, ValidationError from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF -from openerp.tools import float_compare +from odoo.tools import float_compare class AccountAssetCategory(models.Model): @@ -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/base_setup/views/res_config_views.xml b/addons/base_setup/views/res_config_views.xml index 80f70b36f5e..0e2b3f7274b 100644 --- a/addons/base_setup/views/res_config_views.xml +++ b/addons/base_setup/views/res_config_views.xml @@ -149,7 +149,7 @@
- (*) This configuration value is related to the company you're logged in. + (*) This configuration is related to the company you're logged into.
diff --git a/addons/crm/data/web_planner_data.xml b/addons/crm/data/web_planner_data.xml index 40da0112141..994979c907c 100644 --- a/addons/crm/data/web_planner_data.xml +++ b/addons/crm/data/web_planner_data.xml @@ -474,13 +474,11 @@ Create manually
- Recommended if <100 products + Recommended if <50 items
@@ -490,51 +488,46 @@ Import a file
- Recommended if >100 products + Recommended if >50 items

- We can handle the whole import process - for you: simply send your Odoo project - manager a CSV file containing all your - products. -

- If you want to do it yourself: + Use our import templates to get your catalog ready in no time!

-
- +
+
+
+ For complex imports (>5000 items, product variants, pictures, etc.): + +
  • Online users: we do it for you! Ask Our Experts
  • +
  • Community & Enterprise users: use Odoo API to import through scripts
  • +
    +
    +
    - Prices + Pricing & Discounts

    - In Odoo, there are 3 different ways to manage your products prices.
    + In Odoo, there are 3 different ways to manage your prices.

    - Unique price + Unique Price (default)

    - Each product can only have one price. (but you can change it when doing a quotation) + Each product has only one price.
    - Note: your product needs to be marked as 'Can be Sold' to have a Sale Price.

    @@ -543,23 +536,13 @@
    - Price per Customer + Specific Price per Audience
    -

    - You may have a different sale price for different - groups of customers: -

    -
      -
    • Consumers
    • -
    • Retailers
    • -
    • Company XYZ
    • -
    -

    - Change the Sale Price option in - Sales Settings -

    +
  • Basic, Premium
  • +
  • Consumers, Retailers
  • +
  • USD, EUR, GBP
  • @@ -572,28 +555,24 @@

    - Prices can be computed using advanced formulas.
    - Example: 15% discount when buying more than 3 t-shirts. -

    -

    - You can also combine several rules into a sequence.
    - Example: additional 10% discount during summer sales -

    -

    - Change the Sale Price option in - Sales Settings + Create and combine rules:
    +

  • e.g. 15% discount when buying more than 3 t-shirts
  • +
  • additional 10% during summer sales
  • - - + + + +
    diff --git a/addons/crm/models/crm_stage.py b/addons/crm/models/crm_stage.py index 9b8769e9e08..e786df9c706 100644 --- a/addons/crm/models/crm_stage.py +++ b/addons/crm/models/crm_stage.py @@ -38,7 +38,7 @@ class Stage(models.Model): on_change = fields.Boolean('Change Probability Automatically', help="Setting this stage will change the probability automatically on the opportunity.") requirements = fields.Text('Requirements', help="Enter here the internal requirements for this stage (ex: Offer sent to customer). It will appear as a tooltip over the stage's name.") team_id = fields.Many2one('crm.team', string='Team', ondelete='set null', - help='Specific team that uses this stage. Other teams will not ne able to see or use this stage.') + help='Specific team that uses this stage. Other teams will not be able to see or use this stage.') legend_priority = fields.Text('Priority Management Explanation', translate=True, help='Explanation text to help users using the star and priority mechanism on stages or issues that are in this stage.') fold = fields.Boolean('Folded in Pipeline', diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index 5d1394d2996..9b4367f2992 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() @@ -280,7 +280,7 @@ class HrExpense(models.Model): if help_message: alias_record = self.env.ref('hr_expense.mail_alias_expense') if alias_record and alias_record.alias_domain and alias_record.alias_name: - link = "%(email)s" % { + link = "%(email)s" % { 'email': '%s@%s' % (alias_record.alias_name, alias_record.alias_domain) } return '

    %s
    %s

    %s' % ( diff --git a/addons/hr_holidays/models/hr_holidays.py b/addons/hr_holidays/models/hr_holidays.py index f73d535a55d..fea6698288c 100644 --- a/addons/hr_holidays/models/hr_holidays.py +++ b/addons/hr_holidays/models/hr_holidays.py @@ -278,7 +278,7 @@ class Holidays(models.Model): if employee_id: employee = self.env['hr.employee'].browse(employee_id) - resource = self.env['resource.resource'].sudo().search([('user_id', '=', employee.user_id.id)], limit=1) + resource = employee.resource_id if resource and resource.calendar_id: hours = resource.calendar_id.get_working_hours(from_dt, to_dt, resource_id=resource.id, compute_leaves=True) uom_hour = resource.calendar_id.uom_id 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/l10n_be/i18n_extra/l10n_be_multilang.pot b/addons/l10n_be/i18n_extra/l10n_be_multilang.pot index 67bcb242a2e..60f978896d1 100644 --- a/addons/l10n_be/i18n_extra/l10n_be_multilang.pot +++ b/addons/l10n_be/i18n_extra/l10n_be_multilang.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 9.0\n" +"Project-Id-Version: Odoo Server 10.0c\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-18 14:42+0000\n" "PO-Revision-Date: 2015-09-18 14:42+0000\n" diff --git a/addons/l10n_ca/i18n_extra/l10n_ca.pot b/addons/l10n_ca/i18n_extra/l10n_ca.pot index 8cf28f26e0e..9ab9406e233 100644 --- a/addons/l10n_ca/i18n_extra/l10n_ca.pot +++ b/addons/l10n_ca/i18n_extra/l10n_ca.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 9.0\n" +"Project-Id-Version: Odoo Server 10.0c\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-11-03 22:23+0000\n" "PO-Revision-Date: 2015-11-03 22:23+0000\n" diff --git a/addons/l10n_ch/i18n_extra/l10n_ch.pot b/addons/l10n_ch/i18n_extra/l10n_ch.pot index c70880218e4..290e4d81ca1 100644 --- a/addons/l10n_ch/i18n_extra/l10n_ch.pot +++ b/addons/l10n_ch/i18n_extra/l10n_ch.pot @@ -4,7 +4,7 @@ # msgid "" msgstr "" -"Project-Id-Version: Odoo Server 9.0\n" +"Project-Id-Version: Odoo Server 10.0c\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-09-18 08:20+0000\n" "PO-Revision-Date: 2015-09-18 08:20+0000\n" 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/', 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/models/mail_thread.py b/addons/mail/models/mail_thread.py index cd1b7be7aa7..d75011b92d7 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -2024,13 +2024,6 @@ class MailThread(models.AbstractModel): auto_delete=True, auto_delete_message=True, subtype_id=self.env.ref('mail.mt_note').id) - messages = self.env['mail.message'].sudo().search([ - ('model', '=', self._name), - ('res_id', '=', record.id), - ('subtype_id', '!=', False), - ('subtype_id.internal', '=', False)], limit=1) - if messages: - messages.write({'needaction_partner_ids': [(4, pid) for pid in partner_ids]}) @api.multi def message_auto_subscribe(self, updated_fields, values=None): diff --git a/addons/mail/models/update.py b/addons/mail/models/update.py index 86becd9d679..a5717353a79 100644 --- a/addons/mail/models/update.py +++ b/addons/mail/models/update.py @@ -26,8 +26,8 @@ class PublisherWarrantyContract(AbstractModel): Users = self.env['res.users'] IrParamSudo = self.env['ir.config_parameter'].sudo() - dbuuid = IrParamSudo('database.uuid') - db_create_date = IrParamSudo('database.create_date') + dbuuid = IrParamSudo.get_param('database.uuid') + db_create_date = IrParamSudo.get_param('database.create_date') limit_date = datetime.datetime.now() limit_date = limit_date - datetime.timedelta(15) limit_date_str = limit_date.strftime(misc.DEFAULT_SERVER_DATETIME_FORMAT) @@ -42,9 +42,9 @@ class PublisherWarrantyContract(AbstractModel): domain = [('application', '=', True), ('state', 'in', ['installed', 'to upgrade', 'to remove'])] apps = self.env['ir.module.module'].sudo().search_read(domain, ['name']) - enterprise_code = IrParamSudo('database.enterprise_code') + enterprise_code = IrParamSudo.get_param('database.enterprise_code') - web_base_url = IrParamSudo('web.base.url') + web_base_url = IrParamSudo.get_param('web.base.url') msg = { "dbuuid": dbuuid, "nbr_users": nbr_users, @@ -60,7 +60,7 @@ class PublisherWarrantyContract(AbstractModel): "enterprise_code": enterprise_code, } if user.partner_id.company_id: - company_id = user.partner_id.company_id.id + company_id = user.partner_id.company_id msg.update(company_id.read(["name", "email", "phone"])[0]) return msg @@ -101,7 +101,7 @@ class PublisherWarrantyContract(AbstractModel): raise UserError(_("Error during communication with the publisher warranty server.")) # old behavior based on res.log; now on mail.message, that is not necessarily installed user = self.env['res.users'].sudo().browse(SUPERUSER_ID) - poster = self.env.sudo().ref('mail.channel_all_employees') + poster = self.sudo().env.ref('mail.channel_all_employees') if not (poster and poster.exists()): if not user.exists(): return True 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/mail/static/src/js/client_action.js b/addons/mail/static/src/js/client_action.js index 7de0b1ac83b..adf2d5714f6 100644 --- a/addons/mail/static/src/js/client_action.js +++ b/addons/mail/static/src/js/client_action.js @@ -72,7 +72,7 @@ var PartnerInviteDialog = Dialog.extend({ var data = this.$input.select2('data'); if(data.length >= 1){ var ChannelModel = new Model('mail.channel'); - return ChannelModel.call('channel_invite', [], {ids : [this.channel_id], partner_ids: _.pluck(data, 'id')}) + return ChannelModel.call('channel_invite', [this.channel_id], {partner_ids: _.pluck(data, 'id')}) .then(function(){ var names = _.escape(_.pluck(data, 'text').join(', ')); var notification = _.str.sprintf(_t('You added %s to the conversation.'), names); diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 373dcf7d135..acaf1825348 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -466,8 +466,9 @@ class MailComposer(models.TransientModel): bodies = self.render_template(self.body, self.model, res_ids, post_process=True) emails_from = self.render_template(self.email_from, self.model, res_ids) replies_to = self.render_template(self.reply_to, self.model, res_ids) - - default_recipients = self.env['mail.thread'].message_get_default_recipients(res_model=self.model, res_ids=res_ids) + default_recipients = {} + if not self.partner_ids: + default_recipients = self.env['mail.thread'].message_get_default_recipients(res_model=self.model, res_ids=res_ids) results = dict.fromkeys(res_ids, False) for res_id in res_ids: diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index 93a5a01530e..b6bef12b6c4 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -7,7 +7,7 @@ import random from odoo import api, fields, models, _ from odoo.exceptions import UserError from odoo.tools.safe_eval import safe_eval -from openerp.tools.translate import html_translate +from odoo.tools.translate import html_translate class MassMailingTag(models.Model): 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 @@ - + @@ -101,9 +101,9 @@
    - +
    - +
    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/controllers/main.py b/addons/payment_paypal/controllers/main.py index a74ab8e1d29..180c028edd1 100644 --- a/addons/payment_paypal/controllers/main.py +++ b/addons/payment_paypal/controllers/main.py @@ -62,7 +62,7 @@ class PaypalController(http.Controller): self.paypal_validate_data(**post) return '' - @http.route('/payment/paypal/dpn', type='http', auth="none", methods=['POST'], csrf=False) + @http.route('/payment/paypal/dpn', type='http', auth="none", methods=['POST', 'GET'], csrf=False) def paypal_dpn(self, **post): """ Paypal DPN """ _logger.info('Beginning Paypal DPN form_feedback with post data %s', pprint.pformat(post)) # debug 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_config.py b/addons/point_of_sale/models/pos_config.py index a68cbf05334..ad25692e1e3 100644 --- a/addons/point_of_sale/models/pos_config.py +++ b/addons/point_of_sale/models/pos_config.py @@ -71,7 +71,7 @@ class PosConfig(models.Model): iface_invoicing = fields.Boolean(string='Invoicing', help='Enables invoice generation from the Point of Sale', default=True) iface_big_scrollbars = fields.Boolean('Large Scrollbars', help='For imprecise industrial touchscreens') iface_print_auto = fields.Boolean(string='Automatic Receipt Printing', default=False, - help='The receipt will automatically be p-rinted at the end of each order') + help='The receipt will automatically be printed at the end of each order') iface_print_skip_screen = fields.Boolean(string='Skip Receipt Screen', default=True, help='The receipt screen will be skipped if the receipt can be printed automatically.') iface_precompute_cash = fields.Boolean(string='Prefill Cash Payment', 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/description/index.html b/addons/point_of_sale/static/description/index.html index 56d377abe38..df254603ebf 100644 --- a/addons/point_of_sale/static/description/index.html +++ b/addons/point_of_sale/static/description/index.html @@ -32,7 +32,7 @@

    An interface designed for productivity

    -

    The Point of Sales that keeps everything where you need it

    +

    The point of sale that keeps everything where you need it

    @@ -80,7 +80,7 @@ While an Internet connection is required to start the Point of Sale, it will stay operational even after complete disconnection. Because it's extremely flexible, you can configure Odoo POS to meet your precise needs.

    - No installation and no specific hardware is required to use Odoo POS. Our solution for point of sales is compatible with desktop PCs, iPads and other tablet computers, laptops and industrial POS machines. + No installation and no specific hardware is required to use Odoo POS. Our solution for points of sale is compatible with desktop PCs, iPads and other tablet computers, laptops and industrial POS machines.

    @@ -128,7 +128,7 @@

    - Odoo Point of Sales is more than an ordinary POS. + Odoo Point of Sale is more than an ordinary POS.
    Our app provides extra features to deliver awesome customer service.

    @@ -145,7 +145,7 @@

    - Give your shopper a positive experience by improving your services. Handle refunds, track warranties, follow customer claims, plan order deliveries, etc... in one interface. Bring your sales at the next level. Odoo POS can produce customer invoices in just a few clicks and synchronize with Odoo Accounting. Your accounting will be easier and more accurate. Our Point of Sales helps you to control sales and cash in real time but also to keep your whole operations under control. + Give your shopper a positive experience by improving your services. Handle refunds, track warranties, follow customer claims, plan order deliveries, etc... in one interface. Bring your sales at the next level. Odoo POS can produce customer invoices in just a few clicks and synchronize with Odoo Accounting. Your accounting will be easier and more accurate. Our Point of Sale helps you to control sales and cash in real time but also to keep your whole operations under control.

    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 | \*======================================*/ @@ -662,6 +679,11 @@ var Chrome = PosBaseWidget.extend({ 'name': 'order_selector', '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, @@ -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 @@ + +
    +
    + +
    +
    +
    +
    @@ -846,6 +854,114 @@ + + + + +
    +
    + +

    +
    +
    +

    + + + +
    + + + + + + + + + + + + + + + + + Discount: % + + +
    + +
    + ------------------------ +
    + + + Payments: + + + + + + + + +
    + ------------------------ +
    + + + Taxes: + + + + + + + + +
    + ------------------------ +
    + + + Total: + + + + + +
    +
    +
    +
    + +
    +
    + + + +
    +
    +

    Select Dates for the report

    +
    + + + + + + + + +
    Start date
    Stop date
    +

    + +

    + + + + + + +