diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 3b9267deb9c..1ee32068a00 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -795,7 +795,7 @@ class AccountBankStatementLine(models.Model): or self.statement_id.journal_id.default_debit_account_id.id, 'credit': amount < 0 and -amount or 0.0, 'debit': amount > 0 and amount or 0.0, - 'statement_id': self.statement_id.id, + 'statement_line_id': self.id, 'currency_id': statement_currency != company_currency and statement_currency.id or (st_line_currency != company_currency and st_line_currency.id or False), 'amount_currency': amount_currency, } diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 29557781875..8a39428c162 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -504,9 +504,9 @@ class AccountInvoice(models.Model): else: account_id = pay_account.id payment_term_id = p.property_supplier_payment_term_id.id - addr = self.partner_id.address_get(['delivery']) - fiscal_position = self.env['account.fiscal.position'].get_fiscal_position(self.partner_id.id, delivery_id=addr['delivery']) + delivery_partner_id = self.get_delivery_partner_id() + fiscal_position = self.env['account.fiscal.position'].get_fiscal_position(self.partner_id.id, delivery_id=delivery_partner_id) # If partner has no warning, check its company if p.invoice_warn == 'no-message' and p.parent_id: @@ -534,6 +534,10 @@ class AccountInvoice(models.Model): return {'domain': {'partner_bank_id': [('id', 'in', bank_ids.ids)]}} return {} + @api.multi + def get_delivery_partner_id(self): + self.ensure_one() + return self.partner_id.address_get(['delivery'])['delivery'] @api.onchange('journal_id') def _onchange_journal_id(self): @@ -1050,7 +1054,7 @@ class AccountInvoice(models.Model): values['invoice_line_ids'] = self._refund_cleanup_lines(invoice.invoice_line_ids) - tax_lines = filter(lambda l: l.manual, invoice.tax_line_ids) + tax_lines = invoice.tax_line_ids values['tax_line_ids'] = self._refund_cleanup_lines(tax_lines) if journal_id: diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py index d14b95be8e5..b945a8e3584 100644 --- a/addons/account/models/account_journal_dashboard.py +++ b/addons/account/models/account_journal_dashboard.py @@ -304,11 +304,13 @@ class account_journal(models.Model): 'default_type': invoice_type, 'type': invoice_type }) + [action] = self.env.ref('account.%s' % action_name).read() action['context'] = ctx action['domain'] = self._context.get('use_domain', []) - action['views'] = False - action['view_id'] = False + if action_name in ['action_bank_statement_tree', 'action_view_bank_statement_tree']: + action['views'] = False + action['view_id'] = False return action @api.multi diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 4be1ac19bd7..8bac68560cc 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -8,6 +8,7 @@ from odoo.exceptions import RedirectWarning, UserError, ValidationError from odoo.tools.misc import formatLang from odoo.tools import float_is_zero, float_compare from odoo.tools.safe_eval import safe_eval +import odoo.addons.decimal_precision as dp from lxml import etree #---------------------------------------------------------- @@ -244,9 +245,9 @@ class AccountMove(models.Model): journal_id=journal_id) reversed_moves |= reversed_move #unreconcile all lines reversed - aml = ac_move.line_ids.filtered(lambda x: x.account_id.reconcile) + aml = ac_move.line_ids.filtered(lambda x: x.account_id.reconcile or x.account_id.internal_type == 'liquidity') aml.remove_move_reconcile() - #reconcile together the reconciliable aml and their newly created counterpart + #reconcile together the reconciliable and the liquidity aml and their newly created counterpart for account in [x.account_id for x in aml]: to_rec = aml.filtered(lambda y: y.account_id == account) to_rec |= reversed_move.line_ids.filtered(lambda y: y.account_id == account) @@ -370,7 +371,7 @@ class AccountMoveLine(models.Model): self.counterpart = ",".join(counterpart) name = fields.Char(string="Label") - quantity = fields.Float(digits=(16, 2), + quantity = fields.Float(digits=dp.get_precision('Product Unit of Measure'), help="The optional quantity expressed by this line, eg: number of product sold. The quantity is not a legal requirement but is very useful for some reports.") product_uom_id = fields.Many2one('product.uom', string='Unit of Measure') product_id = fields.Many2one('product.product', string='Product') @@ -811,6 +812,11 @@ class AccountMoveLine(models.Model): def _get_pair_to_reconcile(self): #field is either 'amount_residual' or 'amount_residual_currency' (if the reconciled account has a secondary currency set) field = self[0].account_id.currency_id and 'amount_residual_currency' or 'amount_residual' + #reconciliation on bank accounts are special cases as we don't want to set them as reconciliable + #but we still want to reconcile entries that are reversed together in order to clear those lines + #in the bank reconciliation report. + if not self[0].account_id.reconcile and self[0].account_id.internal_type == 'liquidity': + field = 'balance' rounding = self[0].company_id.currency_id.rounding if self[0].currency_id and all([x.amount_currency and x.currency_id == self[0].currency_id for x in self]): #or if all lines share the same currency @@ -906,7 +912,7 @@ class AccountMoveLine(models.Model): raise UserError(_('To reconcile the entries company should be the same for all entries!')) if len(set(all_accounts)) > 1: raise UserError(_('Entries are not of the same account!')) - if not all_accounts[0].reconcile: + if not (all_accounts[0].reconcile or all_accounts[0].internal_type == 'liquidity'): raise UserError(_('The account %s (%s) is not marked as reconciliable !') % (all_accounts[0].name, all_accounts[0].code)) if len(partners) > 1: raise UserError(_('The partner has to be the same on all lines for receivable and payable accounts!')) @@ -1500,9 +1506,9 @@ class AccountPartialReconcile(models.Model): for rec in self: if not rec.company_id.currency_exchange_journal_id: raise UserError(_("You should configure the 'Exchange Rate Journal' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates.")) - if not self.company_id.income_currency_exchange_account_id.id: + if not rec.company_id.income_currency_exchange_account_id.id: raise UserError(_("You should configure the 'Gain Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates.")) - if not self.company_id.expense_currency_exchange_account_id.id: + if not rec.company_id.expense_currency_exchange_account_id.id: raise UserError(_("You should configure the 'Loss Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates.")) move = rec.env['account.move'].create( @@ -1552,6 +1558,8 @@ class AccountPartialReconcile(models.Model): #amount is the current cash_basis amount minus the one before the reconciliation amount = line.balance * percentage_after - line.balance * percentage_before rounded_amt = line.company_id.currency_id.round(amount) + if float_is_zero(rounded_amt, precision_rounding=line.company_id.currency_id.rounding): + continue if line.tax_line_id and line.tax_line_id.use_cash_basis: if not newly_created_move: newly_created_move = self._create_tax_basis_move() diff --git a/addons/account/report/account_partner_ledger.py b/addons/account/report/account_partner_ledger.py index c01d22cac04..ba2503d41a9 100644 --- a/addons/account/report/account_partner_ledger.py +++ b/addons/account/report/account_partner_ledger.py @@ -11,6 +11,7 @@ class ReportPartnerLedger(models.AbstractModel): def _lines(self, data, partner): full_account = [] + currency = self.env['res.currency'] query_get_data = self.env['account.move.line'].with_context(data['form'].get('used_context', {}))._query_get() reconcile_clause = "" if data['form']['reconciled'] else ' AND "account_move_line".reconciled = false ' params = [partner.id, tuple(data['computed']['move_state']), tuple(data['computed']['account_ids'])] + query_get_data[2] @@ -40,6 +41,7 @@ class ReportPartnerLedger(models.AbstractModel): ) sum += r['debit'] - r['credit'] r['progress'] = sum + r['currency_id'] = currency.browse(r.get('currency_id')) full_account.append(r) return full_account diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml index 25a153a54d1..e92b0b73443 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -621,7 +621,7 @@ - + form diff --git a/addons/account/views/report_invoice.xml b/addons/account/views/report_invoice.xml index 07973558ab0..ade9b2738d7 100644 --- a/addons/account/views/report_invoice.xml +++ b/addons/account/views/report_invoice.xml @@ -125,18 +125,23 @@ -
+ +
+ + diff --git a/addons/account/views/report_partnerledger.xml b/addons/account/views/report_partnerledger.xml index 0259e5167e8..5294736086c 100644 --- a/addons/account/views/report_partnerledger.xml +++ b/addons/account/views/report_partnerledger.xml @@ -36,7 +36,7 @@ - + @@ -77,8 +77,10 @@ - diff --git a/addons/account/wizard/account_invoice_refund.py b/addons/account/wizard/account_invoice_refund.py index 8be8405b3f7..2d987861b4f 100644 --- a/addons/account/wizard/account_invoice_refund.py +++ b/addons/account/wizard/account_invoice_refund.py @@ -58,7 +58,6 @@ class AccountInvoiceRefund(models.TransientModel): date = form.date or False description = form.description or inv.name refund = inv.refund(form.date_invoice, date, description, inv.journal_id.id) - refund.compute_taxes() created_inv.append(refund.id) if mode in ('cancel', 'modify'): diff --git a/addons/account_asset/models/account_asset.py b/addons/account_asset/models/account_asset.py index 6f1251ccaca..83639b5f3a5 100644 --- a/addons/account_asset/models/account_asset.py +++ b/addons/account_asset/models/account_asset.py @@ -471,15 +471,14 @@ class AccountAssetDepreciationLine(models.Model): @api.multi def create_move(self, post_move=True): created_moves = self.env['account.move'] + prec = self.env['decimal.precision'].precision_get('Account') for line in self: category_id = line.asset_id.category_id depreciation_date = self.env.context.get('depreciation_date') or line.depreciation_date or fields.Date.context_today(self) company_currency = line.asset_id.company_id.currency_id current_currency = line.asset_id.currency_id amount = 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, len(line.asset_id.depreciation_line_ids)) - prec = self.env['decimal.precision'].precision_get('Account') move_line_1 = { 'name': asset_name, 'account_id': category_id.account_depreciation_id.id, @@ -489,7 +488,7 @@ class AccountAssetDepreciationLine(models.Model): 'partner_id': line.asset_id.partner_id.id, 'analytic_account_id': category_id.account_analytic_id.id if category_id.type == 'sale' else False, 'currency_id': company_currency != current_currency and current_currency.id or False, - 'amount_currency': company_currency != current_currency and - sign * line.amount or 0.0, + 'amount_currency': company_currency != current_currency and - 1.0 * line.amount or 0.0, } move_line_2 = { 'name': asset_name, @@ -500,7 +499,7 @@ class AccountAssetDepreciationLine(models.Model): 'partner_id': line.asset_id.partner_id.id, 'analytic_account_id': category_id.account_analytic_id.id if category_id.type == 'purchase' else False, 'currency_id': company_currency != current_currency and current_currency.id or False, - 'amount_currency': company_currency != current_currency and sign * line.amount or 0.0, + 'amount_currency': company_currency != current_currency and line.amount or 0.0, } move_vals = { 'ref': line.asset_id.code, diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index bcdef2b7b66..2d127588aa6 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -894,7 +894,7 @@ class Lead(models.Model): 'nb_opportunities': 0, } - opportunities = self.search([('type', '=', 'opportunity'), ('user_id', '=', self._uid)]) + opportunities = self.search([('type', '=', 'opportunity'), ('user_id', '=', self._uid), ('activity_date_deadline', '!=', False)]) for opp in opportunities: # Expected closing diff --git a/addons/event/models/event.py b/addons/event/models/event.py index 6ae4b4c3ee3..4e7a144eac3 100644 --- a/addons/event/models/event.py +++ b/addons/event/models/event.py @@ -30,7 +30,7 @@ class EventType(models.Model): 'interval_unit': 'days', 'interval_type': 'before_event', 'template_id': self.env.ref('event.event_reminder') - })] + })] if self.user_has_groups('event.group_email_scheduling') else [] name = fields.Char('Event Category', required=True, translate=True) # registration @@ -239,7 +239,7 @@ class EventEvent(models.Model): if self.event_type_id.use_reply_to: self.reply_to = self.event_type_id.default_reply_to - if self.event_type_id.use_reply_to: + if self.event_type_id.use_hashtag: self.twitter_hashtag = self.event_type_id.default_hashtag if self.event_type_id.use_timezone: diff --git a/addons/event/models/event_mail.py b/addons/event/models/event_mail.py index 9c462c9abe7..4db265d0091 100644 --- a/addons/event/models/event_mail.py +++ b/addons/event/models/event_mail.py @@ -77,7 +77,7 @@ class EventMailScheduler(models.Model): if self.interval_type in ['before_event', 'after_event']: self.done = self.mail_sent else: - self.done = len(self.mail_registration_ids) == len(self.event_id.registration_ids) and all(filter(lambda line: line.mail_sent, self.mail_registration_ids)) + self.done = len(self.mail_registration_ids) == len(self.event_id.registration_ids) and all(mail.mail_sent for mail in self.mail_registration_ids) @api.one @api.depends('event_id.state', 'event_id.date_begin', 'interval_type', 'interval_unit', 'interval_nbr') diff --git a/addons/event/views/event_views.xml b/addons/event/views/event_views.xml index 28ba24ada70..05995070924 100644 --- a/addons/event/views/event_views.xml +++ b/addons/event/views/event_views.xml @@ -172,6 +172,10 @@ Attendeestree,form,calendar,graph{'search_default_event_id': active_id, 'default_event_id': active_id, 'search_default_expected': True} + +

+ Click to add a new attendee. +

@@ -231,7 +235,11 @@ - + @@ -589,7 +597,7 @@ - + diff --git a/addons/fleet/models/fleet.py b/addons/fleet/models/fleet.py index 755eb5c17b4..3dfd4538990 100644 --- a/addons/fleet/models/fleet.py +++ b/addons/fleet/models/fleet.py @@ -213,11 +213,11 @@ class FleetVehicle(models.Model): LogContract = self.env['fleet.vehicle.log.contract'] Cost = self.env['fleet.vehicle.cost'] for record in self: - record.odometer_count = Odometer.search_count([('vehicle_id', '=', self.id)]) - record.fuel_logs_count = LogFuel.search_count([('vehicle_id', '=', self.id)]) - record.service_count = LogService.search_count([('vehicle_id', '=', self.id)]) - record.contract_count = LogContract.search_count([('vehicle_id', '=', self.id)]) - record.cost_count = Cost.search_count([('vehicle_id', '=', self.id), ('parent_id', '=', False)]) + record.odometer_count = Odometer.search_count([('vehicle_id', '=', record.id)]) + record.fuel_logs_count = LogFuel.search_count([('vehicle_id', '=', record.id)]) + record.service_count = LogService.search_count([('vehicle_id', '=', record.id)]) + record.contract_count = LogContract.search_count([('vehicle_id', '=', record.id)]) + record.cost_count = Cost.search_count([('vehicle_id', '=', record.id), ('parent_id', '=', False)]) @api.depends('log_contracts') def _compute_contract_reminder(self): diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index 89d5215af00..f3a10cfb417 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -172,19 +172,24 @@ class HrExpense(models.Model): ''' main function that is called when trying to create the accounting entries related to an expense ''' + move_group_by_sheet = {} for expense in self: journal = expense.sheet_id.bank_journal_id if expense.payment_mode == 'company_account' else expense.sheet_id.journal_id #create the move that will contain the accounting entries acc_date = expense.sheet_id.accounting_date or expense.date - move = self.env['account.move'].create({ - 'journal_id': journal.id, - 'company_id': self.env.user.company_id.id, - 'date': acc_date, - 'ref': expense.sheet_id.name, - # force the name to the default value, to avoid an eventual 'default_name' in the context - # to set it to '' which cause no number to be given to the account.move when posted. - 'name': '/', - }) + if not expense.sheet_id.id in move_group_by_sheet: + move = self.env['account.move'].create({ + 'journal_id': journal.id, + 'company_id': self.env.user.company_id.id, + 'date': acc_date, + 'ref': expense.sheet_id.name, + # force the name to the default value, to avoid an eventual 'default_name' in the context + # to set it to '' which cause no number to be given to the account.move when posted. + 'name': '/', + }) + move_group_by_sheet[expense.sheet_id.id] = move + else: + move = move_group_by_sheet[expense.sheet_id.id] company_currency = expense.company_id.currency_id diff_currency_p = expense.currency_id != company_currency #one account.move.line per expense (+taxes..) @@ -235,38 +240,46 @@ class HrExpense(models.Model): lines = map(lambda x: (0, 0, expense._prepare_move_line(x)), move_lines) move.with_context(dont_create_taxes=True).write({'line_ids': lines}) expense.sheet_id.write({'account_move_id': move.id}) - move.post() if expense.payment_mode == 'company_account': expense.sheet_id.paid_expense_sheets() + for move in move_group_by_sheet.values(): + move.post() return True + @api.multi + def _prepare_move_line_value(self): + self.ensure_one() + if self.account_id: + account = self.account_id + elif self.product_id: + account = self.product_id.product_tmpl_id._get_product_accounts()['expense'] + if not account: + raise UserError( + _("No Expense account found for the product %s (or for its category), please configure one.") % (self.product_id.name)) + else: + account = self.env['ir.property'].with_context(force_company=self.company_id.id).get('property_account_expense_categ_id', 'product.category') + if not account: + raise UserError( + _('Please configure Default Expense account for Product expense: `property_account_expense_categ_id`.')) + aml_name = self.employee_id.name + ': ' + self.name.split('\n')[0][:64] + move_line = { + 'type': 'src', + 'name': aml_name, + 'price_unit': self.unit_amount, + 'quantity': self.quantity, + 'price': self.total_amount, + 'account_id': account.id, + 'product_id': self.product_id.id, + 'uom_id': self.product_uom_id.id, + 'analytic_account_id': self.analytic_account_id.id, + } + return move_line + @api.multi def _move_line_get(self): account_move = [] for expense in self: - if expense.account_id: - account = expense.account_id - elif expense.product_id: - account = expense.product_id.product_tmpl_id._get_product_accounts()['expense'] - if not account: - raise UserError(_("No Expense account found for the product %s (or for its category), please configure one.") % (expense.product_id.name)) - else: - account = self.env['ir.property'].with_context(force_company=expense.company_id.id).get('property_account_expense_categ_id', 'product.category') - if not account: - raise UserError(_('Please configure Default Expense account for Product expense: `property_account_expense_categ_id`.')) - - aml_name = expense.employee_id.name + ': ' + expense.name.split('\n')[0][:64] - move_line = { - 'type': 'src', - 'name': aml_name, - 'price_unit': expense.unit_amount, - 'quantity': expense.quantity, - 'price': expense.total_amount, - 'account_id': account.id, - 'product_id': expense.product_id.id, - 'uom_id': expense.product_uom_id.id, - 'analytic_account_id': expense.analytic_account_id.id, - } + move_line = expense._prepare_move_line_value() account_move.append(move_line) # Calculate tax lines and adjust base line diff --git a/addons/hr_timesheet_attendance/models/hr_timesheet_sheet.py b/addons/hr_timesheet_attendance/models/hr_timesheet_sheet.py index edb11786749..49adf3fed7f 100644 --- a/addons/hr_timesheet_attendance/models/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_attendance/models/hr_timesheet_sheet.py @@ -10,7 +10,7 @@ class HrTimesheetSheet(models.Model): _inherit = "hr_timesheet_sheet.sheet" attendances_ids = fields.One2many('hr.attendance', 'sheet_id', 'Attendances') - total_attendance = fields.Integer(string='Total Attendance', compute='_compute_total') + total_attendance = fields.Float(string='Total Attendance', compute='_compute_total') total_timesheet = fields.Float(string='Total Timesheet', compute="_compute_total") total_difference = fields.Float(string='Difference', compute="_compute_total") period_ids = fields.One2many('hr_timesheet_sheet.sheet.day', 'sheet_id', string='Period', readonly=True) diff --git a/addons/l10n_at/data/l10n_at_chart_data.xml b/addons/l10n_at/data/l10n_at_chart_data.xml index 8bc33d795cf..b280c11c0e3 100644 --- a/addons/l10n_at/data/l10n_at_chart_data.xml +++ b/addons/l10n_at/data/l10n_at_chart_data.xml @@ -779,7 +779,7 @@ 2500 - + Vorsteuer diff --git a/addons/l10n_cr/data/l10n_cr_state_data.xml b/addons/l10n_cr/data/l10n_cr_state_data.xml index 585012600b2..3c523f44634 100644 --- a/addons/l10n_cr/data/l10n_cr_state_data.xml +++ b/addons/l10n_cr/data/l10n_cr_state_data.xml @@ -43,55 +43,55 @@ Resource: res.partner.title Update partner titles --> - + Corporation Corp. - + Limited Company Ltd. - + Sociedad Anónima Laboral S.A.L. - + Asociation Asoc. - + Government Gov. - + Educational Institution Edu. - + Independant Professional Ind. Prof. - + Doctora Dra. - + Msc. Msc. - + MBA MBA - + Licenciado Lic. - + Licenciada Licda. - + Ingeniero/a Ing. diff --git a/addons/l10n_in_schedule6/__init__.py b/addons/l10n_in_schedule6/__init__.py new file mode 100644 index 00000000000..67dee8c60db --- /dev/null +++ b/addons/l10n_in_schedule6/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. diff --git a/addons/l10n_in_schedule6/__openerp__.py b/addons/l10n_in_schedule6/__openerp__.py new file mode 100644 index 00000000000..20b803525d1 --- /dev/null +++ b/addons/l10n_in_schedule6/__openerp__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Indian - Schedule VI Accounting', + 'version': '2.0', + 'description': """ +Indian Accounting: Chart of Account. +==================================== + +Indian accounting chart and localization. + +Schedule VI chart of account for indian accounting. + +Note: The Schedule VI has been revised by MCA and is applicable for all Balance Sheet made after +31st March, 2011. The Format has done away with earlier two options of format of Balance +Sheet, now only Vertical format has been permitted Which is Supported By OpenERP. + """, + 'category': 'Localization', + 'depends': [ + 'account', + ], + 'demo': [], + 'data': [ + 'l10n_in_schedule6_chart.xml', + 'l10n_in_schedule6_tax_template.xml', + 'account_chart_template.yml', + ], + 'auto_install': False, + 'installable': True, +} diff --git a/addons/l10n_in_schedule6/account_chart_template.yml b/addons/l10n_in_schedule6/account_chart_template.yml new file mode 100644 index 00000000000..e536be4873d --- /dev/null +++ b/addons/l10n_in_schedule6/account_chart_template.yml @@ -0,0 +1,3 @@ +- + !python {model: account.chart.template, id: indian_chart_template_schedule6}: | + self[0].try_loading_for_current_company() diff --git a/addons/l10n_in_schedule6/l10n_in_schedule6_chart.xml b/addons/l10n_in_schedule6/l10n_in_schedule6_chart.xml new file mode 100644 index 00000000000..5552803bd7a --- /dev/null +++ b/addons/l10n_in_schedule6/l10n_in_schedule6_chart.xml @@ -0,0 +1,1433 @@ + + + + + + 1206 + Transfer Account + + + + + Indian Chart of Accounts - Schedule VI + 12142 + 12141 + 6 + + + + + + + + + + + Authorised Share Capital + 11101 + + + + + + Issued, Subscribed And Paid Up Capital + 11102 + + + + + + Capital Reserve + 11110 + + + + + + Capital Redemption Reserve + 11111 + + + + + + Securities Premium Reserve + 11112 + + + + + + Debenture Redeemption Reserve + 11113 + + + + + + Revaluation Reserve + 11114 + + + + + + Shares Option Outstanding Account + 11115 + + + + + + Other Reserve + 11116 + + + + + + Surplus (Profit and Loss Account) + 11117 + + + + + + Money Received Against Share Warrants + 1112 + + + + + + + + Share Application Money Pending Allotment + 1121 + + + + + + + Bank Loan (Long Term) + 113011 + + + + + + From Others + 113021 + + + + + + Deferred Tax Liabilities (Net) + 1131 + + + + + + Other Long Term Liabilities + 1132 + + + + + + Long Term Provisions + 1133 + + + + + + + + Bank Loan (Short Term) + 114011 + + + + + + Loans And Advances From Related Parties + 11402 + + + + + + Depsoits + 11403 + + + + + + Others + 11404 + + + + + + + Sundry Creditors For Material/Supplies + 11411 + + + + + + Sundry Creditors For Services + 11412 + + + + + + + Excise Duty Payable + 1142111 + + + + + + Education Cess Payable On Excise Duty + 1142112 + + + + + + Higher Education Cess Payable On Excise Duty + 1142113 + + + + + + Service Tax Payable + 1142121 + + + + + + Education Cess Payable On Service Tax + 1142122 + + + + + + Higher Education Cess Payable On Service Tax + 1142123 + + + + + + TDS Paybale + 114213 + + + + + + Central Sales Tax Payable + 114214 + + + + + + VAT Payable + 114215 + + + + + + Out Standing Cheques for Clearance + 11422 + + + + + + Advance From Customers + 11423 + + + + + + + EPF Payable + 114301 + + + + + + Unpaid Remuneration + 114302 + + + + + + Electricity And Power Charges Payable + 114311 + + + + + + Employee Providend Fund Payable + 114312 + + + + + + Internet Expenses Payable + 114313 + + + + + + Mobile Expenses Payable + 114314 + + + + + + Secueity Expenses Payable + 114315 + + + + + + Telephone Expenses Payable + 114316 + + + + + + Wages Payable + 114317 + + + + + + Provision For Taxation + 114318 + + + + + + Provision For Expenses + 114319 + + + + + + + + + Land + 120101 + + + + + + Buildings + 120102 + + + + + + Plant And Equipments + 120103 + + + + + + Furnitures And Fixtures + 120104 + + + + + + Vehicles + 120105 + + + + + + Computer/Laptops + 120106 + + + + + + Office Equipments + 120107 + + + + + + + Goodwill + 120111 + + + + + + + Building Under Construction + 120120 + + + + + + + Intangible Assets Under Development + 12013 + + + + + + + Investment In Property + 12020 + + + + + + Investment In Equity Instrument + 12021 + + + + + + Investment In Preference Shares + 12022 + + + + + + Investment In Government Or Trust Securities + 12023 + + + + + + Investment In Debentures And Bonds + 12024 + + + + + + Investment In Mutual Fund + 12025 + + + + + + Investment In Partnership Firm + 12026 + + + + + + Gold + 120270 + + + + + + Fixed Deposit With Banks-Earmarked + 120271 + + + + + + + Deferred Tax Assets (Net) + 1203 + + + + + + + Secured, Considered Good + 120411 + + + + + + Unsecured, Considered Good + 120412 + + + + + + Doubtful + 120413 + + + + + + Earnest Money Deposit + 1204211 + + + + + + Other Deposit + 1204212 + + + + + + Unsecured, Considered Good + 120422 + + + + + + Doubtful + 120423 + + + + + + Loans And Advances To Related Parties + 12043 + + + + + + Other Loans And Advances + 12044 + + + + + + + Secured, Considered Good + 120511 + + + + + + Unsecured, Considered Good + 120512 + + + + + + Doubtful + 120513 + + + + + + Preliminery Expense + 120521 + + + + + + Software + 120522 + + + + + + Others + 12053 + + + + + + + + Investment In Equity + 12110 + + + + + + Investment In Preference Shares + 12111 + + + + + + Investment In Government Securities + 12112 + + + + + + Investment In Debentures And Bonds + 12113 + + + + + + Investment In Mutual Fund + 12114 + + + + + + Investment In Partnership Firm + 12115 + + + + + + In Short Term Fixed Deposits + 12116 + + + + + + + Raw Material + 12120 + + + + + + Work-In-Progress + 12121 + + + + + + Finished Goods + 12122 + + + + + + Stock-In-Trade + 12123 + + + + + + Stores And Spares + 12124 + + + + + + Loose Tools + 12125 + + + + + + Other With Job Worker + 12126 + + + + + + Goods-In-Transit + 12127 + + + + + + + Secured, Considered Good + 121311 + + + + + + Unsecured, Considered Good + 121312 + + + + + + Doubtful + 121313 + + + + + + Secured, Considered Good + 121321 + + + + + + Unsecured, Considered Good + 121322 + + + + + + Doubtful + 121323 + + + + + + Sundry Debtors + 12133 + + + + + + + + Advance To Associate Concerns + 1215111 + + + + + + Unsecured Recoverable in Cash or in Kind or for Value to be Received + 1215121 + + + + + + Doubtful + 121513 + + + + + + Advance To Suppliers + 121521 + + + + + + Advance Income Tax/Refund Due + 121522 + + + + + + Balance With Revenue Authorities (Indirect Taxes) + 121523 + + + + + + Prepaid Expenses + 121524 + + + + + + + Sales Tax Receivable + 121610 + + + + + + VAT Receivable + 121611 + + + + + + Excise Duty Receivable + 1216121 + + + + + + Education Cess Receivable On Excise Duty + 1216122 + + + + + + Higher Education Cess Receivable On Excise Duty + 1216123 + + + + + + Service Tax Receivable + 1216131 + + + + + + Education Cess Receivable On Service Tax + 1216132 + + + + + + Higher Education Cess Receivable On Service Tax + 1216133 + + + + + + TDS Receivable + 121614 + + + + + + Preliminary Expense + 121621 + + + + + + + + Central Sales(Exclusive of Excise Duty) + 2011 + + + + + + Export Sales + 2012 + + + + + + Sales of Services + 2013 + + + + + + Job Work Receipts + 2014 + + + + + + Product Sales + 2015 + + + + + + Other Operating Income + 2016 + + + + + + + Interest + 2021 + + + + + + Other Financial Services + 2022 + + + + + + Other Non Operating Income + 2023 + + + + + + + Opening Stock + 2101 + + + + + + Raw Material + 21021 + + + + + + Stores And Consumables + 21022 + + + + + + Closing Stock + 2103 + + + + + + + Purchase of Stock-In-Trade + 2111 + + + + + + + Changes In Inventories of Finished Goods, Work-In-Progress And Stock-In-Trade + 212 + + + + + + + Salaries, Bonus, PF And ESIC + 2131 + + + + + + Directors Remuneration + 2132 + + + + + + Directors Setting Fees + 2133 + + + + + + Staff Welfare Expense + 2134 + + + + + + Commision To M.D + 2135 + + + + + + Commision To Joint M.D + 2136 + + + + + + + Interest On Cash Credit Facality + 2141 + + + + + + Interest On Car Loan + 2142 + + + + + + Interest On Other Loans + 2143 + + + + + + Bank Charges + 2144 + + + + + + Bank Gurantee Charges + 2145 + + + + + + + Depreciation + 2151 + + + + + + Preliminary Expenses + 2152 + + + + + + + Jobwork Charges + 21601 + + + + + + Stores and Spares + 21602 + + + + + + Workshop Expense + 21603 + + + + + + Centeral Sales Tax + 21604 + + + + + + Machinery Repair and Maintenance + 21605 + + + + + + Freight Forwarding and Octroi Expenses (Local) + 21606 + + + + + + Electrical Repairing Expense + 21607 + + + + + + Material Testing Expense + 21608 + + + + + + Power and Fuel Charges + 21609 + + + + + + Packing Material Expense + 216091 + + + + + + Measuring Expense + 216092 + + + + + + Processing Labour Charges + 216093 + + + + + + Car Expenses (Including Insurance) + 21611 + + + + + + Travelling Expenses (Including Foreign Travelling) + 21612 + + + + + + Repair And Maintenance Building + 21613 + + + + + + Computer Maintenace + 21614 + + + + + + General Expenses + 21615 + + + + + + Internet And Website Expenses + 21616 + + + + + + Insurance Expenses + 21617 + + + + + + Telephone Expense + 21618 + + + + + + Labour Welfare Expenses + 21619 + + + + + + Legal Expenses + 216191 + + + + + + Postage And Telegram + 216192 + + + + + + Professional Charges + 216193 + + + + + + Security Expesnes + 216194 + + + + + + Stationery Expenses + 216195 + + + + + + Auditors Remuneration + 216196 + + + + + + Rent Rates And Taxes + 216197 + + + + + + Tender Fee And Testing Charges + 216198 + + + + + + Interest On Delayed Payment of Taxes + 216199 + + + + + + Excise/ Service Tax Expenses + 2161991 + + + + + + Advertisement And Publicity + 2161992 + + + + + + Donation + 2161993 + + + + + + Export Expenses + 2161994 + + + + + + Internal Audit Fee + 2161995 + + + + + + Conveyance Expense + 2161996 + + + + + + Packing and Forwarding Expense + 2161997 + + + + + + Annual Service Fees + 2161998 + + + + + + Loan Processing Fees + 2161999 + + + + + + Office Cleaning Expense + 21619991 + + + + + + Prelimenary Expense W/Off + 21619992 + + + + + + Software Expense W/Off + 21619993 + + + + + + Custom Duty + 21619994 + + + + + + Stamping Charge + 21619995 + + + + + + Post and Courrier Charges + 21619996 + + + + + + + + + Foreign Exchange Profit + 2017 + + + + + + Foreign Exchange Loss + 2170 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/addons/l10n_in_schedule6/l10n_in_schedule6_tax_template.xml b/addons/l10n_in_schedule6/l10n_in_schedule6_tax_template.xml new file mode 100644 index 00000000000..4b98a7efafa --- /dev/null +++ b/addons/l10n_in_schedule6/l10n_in_schedule6_tax_template.xml @@ -0,0 +1,842 @@ + + + + + + + Tax to Pay + taxes + + + + C Forms + taxes + + + + C Forms Tax Collected on Sale + taxes + + + + C Forms Tax Paid on Purchase + taxes + + + + Excise Duty + taxes + + + + Excise Duty 12.36% + taxes + + + + Excise Duty Collected on Sales + taxes + + + + Education Cess 2% Excise Duty Collected on Sales + taxes + + + + Higher Education Cess 1% Excise Duty Collected on Sales + taxes + + + + Excise Duty Paid on Purchase + taxes + + + + Education Cess 2% Excise Duty Paid on Purchase + taxes + + + + Higher Education Cess 1% Excise Duty Paid on Purchase + taxes + + + + Sales Tax + taxes + + + + Sales Tax 4% + taxes + + + + Sales Tax 4% Collected on Sale + taxes + + + + Sales Tax 4% Paid on Purchase + taxes + + + + Sales Tax 12% + taxes + + + + Sales Tax 12% Collected on Sale + taxes + + + + Sales Tax 12% Paid on Purchase + taxes + + + + Sales Tax 15% + taxes + + + + Sales Tax 15% Collected on Sale + taxes + + + + Sales Tax 15% Paid on Purchase + taxes + + + + Service Tax + taxes + + + + Service Tax 12.36% + taxes + + + + Service Tax Collected on Sales of Services + taxes + + + + Education Cess 2% Service Tax Collected on Sales of Services + taxes + + + + Higher Education Cess 1% Service Tax Collected on Sales of Services + taxes + + + + Service Tax Paid on Purchase of Services + taxes + + + + Education Cess 2% Service Tax Paid on Purchase of Services + taxes + + + + Higher Education Cess 1% Service Tax Paid on Purchase of Services + taxes + + + + VAT + taxes + + + + VAT 4% + taxes + + + + VAT 4% Collected on Sale + taxes + + + + VAT 4% Paid on Purchase + taxes + + + + VAT 8% + taxes + + + + VAT 8% Collected on Sale + taxes + + + + VAT 8% Paid on Purchase + taxes + + + + VAT 10% + taxes + + + + VAT 10% Collected on Sale + taxes + + + + VAT 10% Paid on Purchase + taxes + + + + VAT 12.5% + taxes + + + + VAT 12.5% Collected on Sale + taxes + + + + VAT 12.5% Paid on Purchase + taxes + + + + Additional VAT 1% + taxes + + + + Additional VAT 1% Collected on Sale + taxes + + + + Additional VAT 1% Paid on Purchase + taxes + + + + Additional VAT 2.5% + taxes + + + + Additional VAT 2.5% Collected on Sale + taxes + + + + Additional VAT 2.5% Paid on Purchase + taxes + + + + Tax Bases + taxes + + + + Base C Forms + taxes + + + + Base of Sale + taxes + + + + Base of Purchase + taxes + + + + Base Excise Duty + taxes + + + + Base Excise Duty 12.36% + taxes + + + + Base of Sale + taxes + + + + Base of Purchase + taxes + + + + Base Sales Tax + taxes + + + + Base Sales Tax 4% + taxes + + + + Base of Sale + taxes + + + + Base of Purchase + taxes + + + + Base Sales Tax 12% + taxes + + + + Base of Sale + taxes + + + + Base of Purchase + taxes + + + + Base Sales Tax 15% + taxes + + + + Base of Sale + taxes + + + + Base of Purchase + taxes + + + + Base Service Tax + taxes + + + + Base Service Tax 12.36% + taxes + + + + Base of Sale + taxes + + + + Base of Purchase + taxes + + + + Base VAT + taxes + + + + Base VAT 4% + taxes + + + + Base of Sale + taxes + + + + Base of Purchase + taxes + + + + Base VAT 8% + taxes + + + + Base of Sale + taxes + + + + Base of Purchase + taxes + + + + Base VAT 10% + taxes + + + + Base of Sale + taxes + + + + Base of Purchase + taxes + + + + Base VAT 12.5% + taxes + + + + Base of Sale + taxes + + + + Base of Purchase + taxes + + + + Base Additional VAT 1% + taxes + + + + Base of Sale + taxes + + + + Base of Purchase + taxes + + + + Base Additional VAT 2.5% + taxes + + + + Base of Sale + taxes + + + + Base of Purchase + taxes + + + + + (schedule6)Input Sales Tax @ 15% + (schedule6)Input Sales Tax @ 15% + sale + percent + 15 + 10 + + + + + + (schedule6)Input Sales Tax @ 12% + (schedule6)Input Sales Tax @ 12% + sale + percent + 12 + 10 + + + + + + (schedule6)Input Sales Tax @ 4% + (schedule6)Input Sales Tax @ 4% + sale + percent + 4 + 10 + + + + + + (schedule6)Additional Input VAT @ 1% + (schedule6)Additional Input VAT @ 1% + sale + percent + 1 + 15 + + + + + + (schedule6)Input VAT @ 12.5% + (schedule6)Input VAT @ 12.5% + sale + percent + 12.5 + 10 + + + + + + (schedule6)Additional Input VAT @ 2.5% + (schedule6)Additional Input VAT @ 2.5% + sale + percent + 2.5 + 15 + + + + + + (schedule6)Input VAT @ 8% + (schedule6)Input VAT @ 8% + sale + percent + 8 + 10 + + + + + + (schedule6)Input VAT @ 10% + (schedule6)Input VAT @ 10% + sale + percent + 10 + 10 + + + + + + (schedule6)Input Tax 12% + (schedule6)Input Tax 12% + none + percent + 12 + 1 + + + + + + (schedule6)Input Education Cess @ 2% On Excise Duty + (schedule6)Input Education Cess @ 2% On Excise Duty + none + percent + 0.24 + 2 + + + + + + (schedule6)Input Higher Education Cess @ 1% On Excise Duty + (schedule6)Input Higher Education Cess @ 1% On Excise Duty + none + percent + 0.1224 + 3 + + + + + + (schedule6)Input Excise Duty @ 12.36% + (schedule6)Input Excise Duty @ 12.36% + sale + group + 0 + 1 + + + + + + + (schedule6)Input Service Tax 12% + (schedule6)Input Service Tax 12% + none + percent + 12 + 13 + + + + + + (schedule6)Input Education Cess @ 2% On Service Tax + (schedule6)Input Education Cess @ 2% On Service Tax + none + percent + 0.24 + 14 + + + + + + (schedule6)Input Higher Education Cess @ 1% On Service Tax + (schedule6)Input Higher Education Cess @ 1% On Service Tax + none + percent + 0.1224 + 15 + + + + + + (schedule6)Input Service Tax @ 12.36% + (schedule6)Input Service Tax @ 12.36% + sale + group + 12 + True + 8 + + + + + + + (schedule6)Input VAT @ 4% + (schedule6)Input VAT @ 4% + sale + percent + 4 + 10 + + + + + + + + (schedule6)Output Purchase Tax @ 15% + (schedule6)Output Purchase Tax @ 15% + purchase + percent + 15 + 10 + + + + + + (schedule6)Output Purchase Tax @ 12% + (schedule6)Output Purchase Tax @ 12% + purchase + percent + 12 + 10 + + + + + + (schedule6)Output Purchase Tax @ 4% + (schedule6)Output Purchase Tax @ 4% + purchase + percent + 4 + 10 + + + + + + (schedule6)Additional Output VAT @ 1% + (schedule6)Additional Output VAT @ 1% + purchase + percent + 1 + 15 + + + + + + (schedule6)Output VAT @ 12.5% + (schedule6)Output VAT @ 12.5% + purchase + percent + 12.5 + 10 + + + + + + (schedule6)Additional Output VAT @ 2.5% + (schedule6)Additional Output VAT @ 2.5% + purchase + percent + 2.5 + 15 + + + + + + (schedule6)Output VAT @ 8% + (schedule6)Output VAT @ 8% + purchase + percent + 8 + 10 + + + + + + (schedule6)Output VAT @ 10% + (schedule6)Output VAT @ 10% + purchase + percent + 10 + 10 + + + + + + (schedule6)Output Tax 12% + (schedule6)Output Tax 12% + none + percent + 12 + 2 + + + + + + (schedule6)Output Education Cess @ 2% Excise Duty + (schedule6)Output Education Cess @ 2% Excise Duty + none + percent + 0.24 + 3 + + + + + + (schedule6)Output Higher Education Cess @ 1% Excise Duty + (schedule6)Output Higher Education Cess @ 1% Excise Duty + none + percent + 0.1224 + 4 + + + + + + (schedule6)Output Excise Duty @ 12.36% + (schedule6)Output Excise Duty @ 12.36% + purchase + group + 12 + True + 1 + + + + + + + (schedule6)Output Service Tax 12% + (schedule6)Output Service Tax 12% + none + percent + 12 + 9 + + + + + + (schedule6)Output Education Cess @ 2% On Service Tax + (schedule6)Output Education Cess @ 2% On Service Tax + none + percent + 0.24 + 10 + + + + + + (schedule6)Output Higher Education Cess @ 1% On Service Tax + (schedule6)Output Higher Education Cess @ 1% On Service Tax + none + percent + 0.1224 + 11 + + + + + + (schedule6)Output Service Tax @ 12.36% + (schedule6)Output Service Tax @ 12.36% + purchase + True + group + 12 + 8 + + + + + + + (schedule6)Output VAT @ 4% + (schedule6)Output VAT @ 4% + purchase + percent + 4 + 10 + + + + + + + diff --git a/addons/l10n_in_schedule6/static/description/icon.png b/addons/l10n_in_schedule6/static/description/icon.png new file mode 100644 index 00000000000..ddaf38f8b6d Binary files /dev/null and b/addons/l10n_in_schedule6/static/description/icon.png differ diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index c3d41ace630..e7e61b61f3c 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -1696,9 +1696,10 @@ class MailThread(models.AbstractModel): return self._message_post_process_attachments(attachments, attachment_ids, {'model': attach_model, 'res_id': attach_res_id}) def _message_post_process_attachments(self, attachments, attachment_ids, message_data): - IrAttachment, parameter_attachments = self.env['ir.attachment'], self.env['ir.attachment'] + IrAttachment = self.env['ir.attachment'] m2m_attachment_ids = [] cid_mapping = {} + fname_mapping = {} if attachment_ids: filtered_attachment_ids = self.env['ir.attachment'].sudo().search([ ('res_model', '=', 'mail.compose.message'), @@ -1714,9 +1715,7 @@ class MailThread(models.AbstractModel): name, content = attachment elif len(attachment) == 3: name, content, info = attachment - if info and info.get('cid'): - cid = info['cid'] - cid_mapping[cid] = name + cid = info and info.get('cid') else: continue if isinstance(content, unicode): @@ -1724,13 +1723,16 @@ class MailThread(models.AbstractModel): data_attach = { 'name': name, 'datas': base64.b64encode(str(content)), - 'datas_fname': cid or name, + 'datas_fname': name, 'description': name, 'res_model': message_data['model'], 'res_id': message_data['res_id'], } - parameter_attachments |= IrAttachment.create(data_attach) - m2m_attachment_ids += [(4, attach.id) for attach in parameter_attachments] + new_attachment = IrAttachment.create(data_attach) + m2m_attachment_ids.append((4, new_attachment.id)) + if cid: + cid_mapping[cid] = new_attachment + fname_mapping[name] = new_attachment if cid_mapping and message_data.get('body'): root = lxml.html.fromstring(tools.ustr(message_data['body'])) @@ -1738,12 +1740,11 @@ class MailThread(models.AbstractModel): for node in root.iter('img'): if node.get('src', '').startswith('cid:'): cid = node.get('src').split('cid:')[1] - fname = cid_mapping.get(cid, node.get('data-filename', '')) - attachment = parameter_attachments.filtered(lambda attachment: attachment.datas_fname == cid) + attachment = cid_mapping.get(cid) if not attachment: - attachment = parameter_attachments.filtered(lambda attachment: attachment.datas_fname == fname) + attachment = fname_mapping.get(node.get('data-filename'), '') if attachment: - node.set('src', '/web/image/%s' % attachment.ids[0]) + node.set('src', '/web/image/%s' % attachment.id) postprocessed = True if postprocessed: body = lxml.html.tostring(root, pretty_print=False, encoding='UTF-8') diff --git a/addons/maintenance/views/maintenance_views.xml b/addons/maintenance/views/maintenance_views.xml index a2636caf408..6154745ba35 100644 --- a/addons/maintenance/views/maintenance_views.xml +++ b/addons/maintenance/views/maintenance_views.xml @@ -312,7 +312,7 @@ - + diff --git a/addons/mass_mailing/models/mail_mail.py b/addons/mass_mailing/models/mail_mail.py index 8dae482529f..3a69530db1a 100644 --- a/addons/mass_mailing/models/mail_mail.py +++ b/addons/mass_mailing/models/mail_mail.py @@ -7,8 +7,7 @@ import werkzeug.urls from odoo import api, fields, models, tools - -URL_REGEX = r'(\bhref=[\'"]([^\'"]+)[\'"])' +from openerp.addons.link_tracker.models.link_tracker import URL_REGEX class MailMail(models.Model): diff --git a/addons/mrp/models/product.py b/addons/mrp/models/product.py index 7ecc6274636..25ef11da9a3 100644 --- a/addons/mrp/models/product.py +++ b/addons/mrp/models/product.py @@ -44,7 +44,12 @@ class ProductProduct(models.Model): read_group_res = self.env['mrp.bom'].read_group([('product_id', 'in', self.ids)], ['product_id'], ['product_id']) mapped_data = dict([(data['product_id'][0], data['product_id_count']) for data in read_group_res]) for product in self: - product.bom_count = mapped_data.get(product.id, 0) + if product.product_tmpl_id.product_variant_count == 1: + bom_count = mapped_data.get(product.id, product.product_tmpl_id.bom_count) + else: + bom_count = mapped_data.get(product.id, 0) + product.bom_count = bom_count + def _compute_mo_count(self): read_group_res = self.env['mrp.production'].read_group([('product_id', 'in', self.ids)], ['product_id'], ['product_id']) diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 150f9cd562e..ad1a50ad738 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -33,7 +33,7 @@ class StockMoveLots(models.Model): def _check_lot_id(self): if self.move_id.product_id.tracking == 'serial': lots = set([]) - for move_lot in self.move_id.move_lot_ids.filtered(lambda r: not r.lot_produced_id): + for move_lot in self.move_id.active_move_lot_ids.filtered(lambda r: not r.lot_produced_id and r.lot_id): if move_lot.lot_id in lots: raise exceptions.UserError(_('You cannot use the same serial number in two different lines.')) if float_compare(move_lot.quantity_done, 1.0, precision_rounding=move_lot.product_id.uom_id.rounding) == 1: @@ -59,6 +59,15 @@ class StockMoveLots(models.Model): self.quantity_done = self.quantity_done - 1 return self.move_id.split_move_lot() + @api.multi + def write(self, vals): + if 'lot_id' in vals: + for movelot in self: + movelot.move_id.production_id.move_raw_ids.mapped('move_lot_ids')\ + .filtered(lambda r: r.done_wo and not r.done_move and r.lot_produced_id == movelot.lot_id)\ + .write({'lot_produced_id': vals['lot_id']}) + return super(StockMoveLots, self).write(vals) + class StockMove(models.Model): _inherit = 'stock.move' diff --git a/addons/mrp_repair/models/mrp_repair.py b/addons/mrp_repair/models/mrp_repair.py index dbcb70a9fee..8de88aeabf2 100644 --- a/addons/mrp_repair/models/mrp_repair.py +++ b/addons/mrp_repair/models/mrp_repair.py @@ -520,7 +520,10 @@ class RepairLine(models.Model): if not self.product_id or not self.product_uom_qty: return if self.product_id: - self.name = self.product_id.display_name + if partner: + self.name = self.product_id.with_context(lang=partner.lang).display_name + else: + self.name = self.product_id.display_name self.product_uom = self.product_id.uom_id.id if self.type != 'remove': if partner and self.product_id: diff --git a/addons/mrp_repair/report/mrp_repair_reports.xml b/addons/mrp_repair/report/mrp_repair_reports.xml index db73449155d..9963f87d614 100644 --- a/addons/mrp_repair/report/mrp_repair_reports.xml +++ b/addons/mrp_repair/report/mrp_repair_reports.xml @@ -5,7 +5,7 @@ string="Quotation / Order" id="action_report_mrp_repair_order" model="mrp.repair" - name="mrp_repair.report_mrprepairorder" + name="mrp_repair.report_mrprepairorder2" file="mrp_repair.report_mrprepairorder" report_type="qweb-pdf" /> diff --git a/addons/mrp_repair/report/mrp_repair_templates_repair_order.xml b/addons/mrp_repair/report/mrp_repair_templates_repair_order.xml index f02b84104bc..433df24c6e1 100644 --- a/addons/mrp_repair/report/mrp_repair_templates_repair_order.xml +++ b/addons/mrp_repair/report/mrp_repair_templates_repair_order.xml @@ -2,9 +2,9 @@ + + diff --git a/addons/payment_authorize/models/authorize_request.py b/addons/payment_authorize/models/authorize_request.py index 28cba8db301..6eef3ae80cc 100644 --- a/addons/payment_authorize/models/authorize_request.py +++ b/addons/payment_authorize/models/authorize_request.py @@ -150,7 +150,7 @@ class AuthorizeAPI(): etree.SubElement(root, "transId").text = transaction_id customer = etree.SubElement(root, "customer") etree.SubElement(customer, "merchantCustomerId").text = 'ODOO-%s-%s' % (partner.id, uuid4().hex[:8]) - etree.SubElement(customer, "email").text = partner.email + etree.SubElement(customer, "email").text = partner.email or '' response = self._authorize_request(root) res = dict() res['profile_id'] = response.find('customerProfileId').text diff --git a/addons/payment_paypal/controllers/main.py b/addons/payment_paypal/controllers/main.py index bdf35742bec..2fefb1417ef 100644 --- a/addons/payment_paypal/controllers/main.py +++ b/addons/payment_paypal/controllers/main.py @@ -32,16 +32,22 @@ class PaypalController(http.Controller): :param response str: text response, structured in the following way: STATUS\nkey1=value1\nkey2=value2...\n + or STATUS\nError message...\n :rtype tuple(str, dict) :return: tuple containing the STATUS str and the key/value pairs parsed as a dict """ lines = filter(None, response.split('\n')) status = lines.pop(0) - pdt_post = dict(line.split('=', 1) for line in lines) - # html unescape - for post in pdt_post: - pdt_post[post] = urllib.unquote_plus(pdt_post[post]).decode('utf8') + + pdt_post = {} + for line in lines: + split = line.split('=', 1) + if len(split) == 2: + pdt_post[split[0]] = urllib.unquote_plus(split[1]).decode('utf8') + else: + _logger.warning('Paypal: error processing pdt response: %s', line) + return status, pdt_post def paypal_validate_data(self, **post): diff --git a/addons/point_of_sale/controllers/main.py b/addons/point_of_sale/controllers/main.py index 3c110494630..cd8a1e8e493 100644 --- a/addons/point_of_sale/controllers/main.py +++ b/addons/point_of_sale/controllers/main.py @@ -14,7 +14,10 @@ class PosController(http.Controller): @http.route('/pos/web', type='http', auth='user') def pos_web(self, debug=False, **k): # if user not logged in, log him in - pos_sessions = request.env['pos.session'].search([('state', '=', 'opened'), ('user_id', '=', request.session.uid)]) + pos_sessions = request.env['pos.session'].search([ + ('state', '=', 'opened'), + ('user_id', '=', request.session.uid), + ('name', 'not like', '(RESCUE FOR')]) if not pos_sessions: return werkzeug.utils.redirect('/web#action=point_of_sale.action_client_pos_menu') pos_sessions.login() diff --git a/addons/point_of_sale/models/pos_config.py b/addons/point_of_sale/models/pos_config.py index f079fd02cbd..fc5c4753a3d 100644 --- a/addons/point_of_sale/models/pos_config.py +++ b/addons/point_of_sale/models/pos_config.py @@ -31,7 +31,7 @@ class PosConfig(models.Model): def _default_sale_journal(self): journal = self.env.ref('point_of_sale.pos_sale_journal', raise_if_not_found=False) - if journal and journal.company_id == self.env.user.company_id: + if journal and journal.sudo().company_id == self.env.user.company_id: return journal return self._default_invoice_journal() @@ -146,9 +146,12 @@ class PosConfig(models.Model): @api.depends('session_ids') def _compute_current_session(self): for pos_config in self: - session = pos_config.session_ids.filtered(lambda r: r.user_id.id == self.env.uid and not r.state == 'closed') - pos_config.current_session_id = session - pos_config.current_session_state = session.state + session = pos_config.session_ids.filtered(lambda r: r.user_id.id == self.env.uid and \ + not r.state == 'closed' and \ + '(RESCUE FOR' not in r.name) + # sessions ordered by id desc + pos_config.current_session_id = session and session[0].id or False + pos_config.current_session_state = session and session[0].state or False @api.depends('session_ids') def _compute_last_session(self): @@ -168,7 +171,8 @@ class PosConfig(models.Model): @api.depends('session_ids') def _compute_current_session_user(self): for pos_config in self: - pos_config.pos_session_username = pos_config.session_ids.filtered(lambda s: s.state == 'opened').user_id.name + session = pos_config.session_ids.filtered(lambda s: s.state == 'opened' and '(RESCUE FOR' not in s.name) + pos_config.pos_session_username = session and session[0].user_id.name or False @api.constrains('company_id', 'stock_location_id') def _check_company_location(self): @@ -216,7 +220,7 @@ class PosConfig(models.Model): @api.model def create(self, values): - IrSequence = self.env['ir.sequence'] + IrSequence = self.env['ir.sequence'].sudo() val = { 'name': _('POS Order %s') % values['name'], 'padding': 4, diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index 78c128d9eb6..c958e853f75 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -66,11 +66,18 @@ class PosOrder(models.Model): closed_session.id, order['name'], order['amount_total']) + rescue_session = PosSession.search([ + ('name', 'like', '(RESCUE FOR %(session)s)' % {'session': closed_session.name}), + ('state', 'not in', ('closed', 'closing_control')), + ], limit=1) + if rescue_session: + _logger.warning('reusing recovery session %s for saving order %s', rescue_session.name, order['name']) + return rescue_session + _logger.warning('attempting to create recovery session for saving order %s', order['name']) new_session = PosSession.create({ 'config_id': closed_session.config_id.id, - 'name': _('(RESCUE FOR %(session)s)') % {'session': closed_session.name}, - 'rescue': True, # avoid conflict with live sessions + 'name': '(RESCUE FOR %(session)s)' % {'session': closed_session.name}, }) # bypass opening_control (necessary when using cash control) new_session.action_pos_session_open() @@ -347,7 +354,12 @@ class PosOrder(models.Model): name = fields.Char(string='Order Ref', required=True, readonly=True, copy=False, default='/') company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, default=lambda self: self.env.user.company_id) date_order = fields.Datetime(string='Order Date', readonly=True, index=True, default=fields.Datetime.now) - user_id = fields.Many2one('res.users', string='Salesman', help="Person who uses the cash register. It can be a reliever, a student or an interim employee.", default=lambda self: self.env.uid) + user_id = fields.Many2one( + comodel_name='res.users', string='Salesman', + help="Person who uses the cash register. It can be a reliever, a student or an interim employee.", + default=lambda self: self.env.uid, + states={'done': [('readonly', True)], 'invoiced': [('readonly', True)]}, + ) amount_tax = fields.Float(compute='_compute_amount_all', string='Taxes', digits=0) amount_total = fields.Float(compute='_compute_amount_all', string='Total', digits=0) amount_paid = fields.Float(compute='_compute_amount_all', string='Paid', states={'draft': [('readonly', False)]}, readonly=True, digits=0) @@ -372,12 +384,22 @@ class PosOrder(models.Model): account_move = fields.Many2one('account.move', string='Journal Entry', readonly=True, copy=False) picking_id = fields.Many2one('stock.picking', string='Picking', readonly=True, copy=False) picking_type_id = fields.Many2one('stock.picking.type', related='session_id.config_id.picking_type_id', string="Operation Type") - location_id = fields.Many2one('stock.location', related='session_id.config_id.stock_location_id', string="Location", store=True) + location_id = fields.Many2one( + comodel_name='stock.location', + related='session_id.config_id.stock_location_id', + string="Location", store=True, + readonly=True, + ) note = fields.Text(string='Internal Notes') nb_print = fields.Integer(string='Number of Print', readonly=True, copy=False, default=0) pos_reference = fields.Char(string='Receipt Ref', readonly=True, copy=False) sale_journal = fields.Many2one('account.journal', related='session_id.config_id.journal_id', string='Sales Journal', store=True, readonly=True) - fiscal_position_id = fields.Many2one('account.fiscal.position', string='Fiscal Position', default=lambda self: self._default_session().config_id.default_fiscal_position_id) + fiscal_position_id = fields.Many2one( + comodel_name='account.fiscal.position', string='Fiscal Position', + default=lambda self: self._default_session().config_id.default_fiscal_position_id, + readonly=True, + states={'draft': [('readonly', False)]}, + ) @api.depends('statement_ids', 'lines.price_subtotal_incl', 'lines.discount') def _compute_amount_all(self): @@ -553,6 +575,8 @@ class PosOrder(models.Model): Move = self.env['stock.move'] StockWarehouse = self.env['stock.warehouse'] for order in self: + if not order.lines.filtered(lambda l: l.product_id.type in ['product', 'consu']): + continue address = order.partner_id.address_get(['delivery']) or {} picking_type = order.picking_type_id return_pick_type = order.picking_type_id.return_picking_type_id or order.picking_type_id @@ -582,7 +606,7 @@ class PosOrder(models.Model): 'location_id': location_id, 'location_dest_id': destination_id, } - pos_qty = any([x.qty >= 0 for x in order.lines]) + pos_qty = any([x.qty > 0 for x in order.lines]) if pos_qty: order_picking = Picking.create(picking_vals.copy()) order_picking.message_post(body=message) @@ -620,8 +644,8 @@ class PosOrder(models.Model): # when the pos.config has no picking_type_id set only the moves will be created if moves and not return_picking and not order_picking: - moves.action_confirm() - moves.force_assign() + moves.action_assign() + moves.filtered(lambda m: m.state in ['confirmed', 'waiting']).force_assign() moves.filtered(lambda m: m.product_id.tracking == 'none').action_done() return True @@ -629,7 +653,7 @@ class PosOrder(models.Model): def _force_picking_done(self, picking): """Force picking in order to be set as done.""" self.ensure_one() - picking.action_confirm() + picking.action_assign() picking.force_assign() wrong_lots = self.set_pack_operation_lot(picking) if not wrong_lots: @@ -766,9 +790,9 @@ class PosOrderLine(models.Model): def _order_line_fields(self, line, session_id=None): if line and 'name' not in line[2]: - if session_id: + session = self.env['pos.session'].browse(session_id).exists() if session_id else None + if session and session.config_id.sequence_line_id: # set name based on the sequence specified on the config - session = self.env['pos.session'].browse(session_id) line[2]['name'] = session.config_id.sequence_line_id._next() else: # fallback on any pos.order.line sequence diff --git a/addons/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py index e0377442925..77ac79a64b1 100644 --- a/addons/point_of_sale/models/pos_session.py +++ b/addons/point_of_sale/models/pos_session.py @@ -130,12 +130,20 @@ class PosSession(models.Model): @api.constrains('user_id', 'state') def _check_unicity(self): # open if there is no session in 'opening_control', 'opened', 'closing_control' for one user - if self.search_count([('state', 'not in', ('closed', 'closing_control')), ('user_id', '=', self.user_id.id)]) > 1: + if self.search_count([ + ('state', 'not in', ('closed', 'closing_control')), + ('user_id', '=', self.user_id.id), + ('name', 'not like', 'RESCUE FOR'), + ]) > 1: raise ValidationError(_("You cannot create two active sessions with the same responsible!")) @api.constrains('config_id') def _check_pos_config(self): - if self.search_count([('state', '!=', 'closed'), ('config_id', '=', self.config_id.id)]) > 1: + if self.search_count([ + ('state', '!=', 'closed'), + ('config_id', '=', self.config_id.id), + ('name', 'not like', 'RESCUE FOR'), + ]) > 1: raise ValidationError(_("You cannot create two active sessions related to the same point of sale!")) @api.model diff --git a/addons/point_of_sale/report/pos_order_report.py b/addons/point_of_sale/report/pos_order_report.py index a3f7ecc95a1..c493bf852d6 100644 --- a/addons/point_of_sale/report/pos_order_report.py +++ b/addons/point_of_sale/report/pos_order_report.py @@ -12,7 +12,7 @@ class PosOrderReport(models.Model): date = fields.Datetime(string='Date Order', readonly=True) order_id = fields.Many2one('pos.order', string='Order', readonly=True) - partner_id = fields.Many2one('res.partner', string='Partner', readonly=True) + partner_id = fields.Many2one('res.partner', string='Customer', readonly=True) product_id = fields.Many2one('product.product', string='Product', readonly=True) product_tmpl_id = fields.Many2one('product.template', string='Product Template', readonly=True) state = fields.Selection( diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js index 2e7d9200642..fe16f0bfa48 100644 --- a/addons/point_of_sale/static/src/js/chrome.js +++ b/addons/point_of_sale/static/src/js/chrome.js @@ -11,6 +11,7 @@ var CrashManager = require('web.CrashManager'); var _t = core._t; +var _lt = core._lt; var QWeb = core.qweb; /* -------- The Order Selector -------- */ @@ -821,7 +822,7 @@ var Chrome = PosBaseWidget.extend({ 'widget': HeaderButtonWidget, 'append': '.pos-rightheader', 'args': { - label: _t('Close'), + label: _lt('Close'), action: function(){ var self = this; if (!this.confirmed) { diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 28a05268d96..f5c995a612e 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1437,7 +1437,9 @@ exports.Orderline = Backbone.Model.extend({ this.trigger('change',this); }, get_unit_price: function(){ - return round_di(this.price || 0, this.pos.dp['Product Price']) + var digits = this.pos.dp['Product Price']; + // round and truncate to mimic _sybmbol_set behavior + return parseFloat(round_di(this.price || 0, digits).toFixed(digits)); }, get_unit_display_price: function(){ if (this.pos.config.iface_tax_included) { diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index 7929a25048f..71dd3b98957 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -1360,6 +1360,16 @@ var ClientListScreenWidget = ScreenWidget.extend({ contents.append($(QWeb.render('ClientDetailsEdit',{widget:this,partner:partner}))); this.toggle_save_button(); + // Browsers attempt to scroll invisible input elements + // into view (eg. when hidden behind keyboard). They don't + // seem to take into account that some elements are not + // scrollable. + contents.find('input').blur(function() { + setTimeout(function() { + self.$('.window').scrollTop(0); + }, 0); + }); + contents.find('.image-uploader').on('change',function(event){ self.load_image_file(event.target.files[0],function(res){ if (res) { @@ -1409,7 +1419,9 @@ var ReceiptScreenWidget = ScreenWidget.extend({ this.render_change(); this.render_receipt(); - + this.handle_auto_print(); + }, + handle_auto_print: function() { if (this.should_auto_print()) { this.print(); if (this.should_close_immediately()){ @@ -1418,7 +1430,6 @@ var ReceiptScreenWidget = ScreenWidget.extend({ } else { this.lock_screen(false); } - }, should_auto_print: function() { return this.pos.config.iface_print_auto && !this.pos.get_order()._printed; diff --git a/addons/point_of_sale/tests/common.py b/addons/point_of_sale/tests/common.py index ebf971d2ec3..4c5597979c6 100644 --- a/addons/point_of_sale/tests/common.py +++ b/addons/point_of_sale/tests/common.py @@ -10,12 +10,16 @@ class TestPointOfSaleCommon(common.TransactionCase): self.AccountBankStatementLine = self.env['account.bank.statement.line'] self.PosMakePayment = self.env['pos.make.payment'] self.PosOrder = self.env['pos.order'] + self.PosSession = self.env['pos.session'] self.company_id = self.ref('base.main_company') self.product3 = self.env.ref('product.product_product_3') self.product4 = self.env.ref('product.product_product_4') self.partner1 = self.env.ref('base.res_partner_1') self.partner4 = self.env.ref('base.res_partner_4') self.pos_config = self.env.ref('point_of_sale.pos_config_main') + self.carotte = self.env.ref('point_of_sale.carotte') + self.courgette = self.env.ref('point_of_sale.courgette') + self.onions = self.env.ref('point_of_sale.Onions') # create a new session self.pos_order_session0 = self.env['pos.session'].create({ diff --git a/addons/point_of_sale/tests/test_point_of_sale_flow.py b/addons/point_of_sale/tests/test_point_of_sale_flow.py index 42b129d1ffd..2ad3682f826 100644 --- a/addons/point_of_sale/tests/test_point_of_sale_flow.py +++ b/addons/point_of_sale/tests/test_point_of_sale_flow.py @@ -4,8 +4,8 @@ import time import os import odoo -from odoo import tools, report as odoo_report -from odoo.tools import float_compare, test_reports +from odoo import fields, tools, report as odoo_report +from odoo.tools import float_compare, mute_logger, test_reports from odoo.addons.point_of_sale.tests.common import TestPointOfSaleCommon @@ -451,3 +451,138 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): # I confirm the bank statement using Confirm button self.AccountBankStatement.button_confirm_bank() + + def test_create_from_ui(self): + """ + Simulation of sales coming from the interface, even after closing the session + """ + # I click on create a new session button + self.pos_config.open_session_cb() + + current_session = self.pos_config.current_session_id + num_starting_orders = len(current_session.order_ids) + + carrot_order = {'data': + {'amount_paid': 0.9, + 'amount_return': 0, + 'amount_tax': 0, + 'amount_total': 0.9, + 'creation_date': fields.Datetime.now(), + 'fiscal_position_id': False, + 'lines': [[0, + 0, + {'discount': 0, + 'id': 42, + 'pack_lot_ids': [], + 'price_unit': 0.9, + 'product_id': self.carotte.id, + 'qty': 1, + 'tax_ids': []}]], + 'name': 'Order 00042-003-0014', + 'partner_id': False, + 'pos_session_id': current_session.id, + 'sequence_number': 2, + 'statement_ids': [[0, + 0, + {'account_id': self.env.user.partner_id.property_account_receivable_id.id, + 'amount': 0.9, + 'journal_id': self.pos_config.journal_ids[0].id, + 'name': fields.Datetime.now(), + 'statement_id': current_session.statement_ids[0].id}]], + 'uid': '00042-003-0014', + 'user_id': self.env.uid}, + 'id': '00042-003-0014', + 'to_invoice': False} + + zucchini_order = {'data': + {'amount_paid': 1.2, + 'amount_return': 0, + 'amount_tax': 0, + 'amount_total': 1.1, + 'creation_date': fields.Datetime.now(), + 'fiscal_position_id': False, + 'lines': [[0, + 0, + {'discount': 0, + 'id': 3, + 'pack_lot_ids': [], + 'price_unit': 1.2, + 'product_id': self.courgette.id, + 'qty': 1, + 'tax_ids': []}]], + 'name': 'Order 00043-003-0014', + 'partner_id': False, + 'pos_session_id': current_session.id, + 'sequence_number': self.pos_config.journal_id.id, + 'statement_ids': [[0, + 0, + {'account_id': self.env.user.partner_id.property_account_receivable_id.id, + 'amount': 1.2, + 'journal_id': self.pos_config.journal_ids[0].id, + 'name': fields.Datetime.now(), + 'statement_id': current_session.statement_ids[0].id}]], + 'uid': '00043-003-0014', + 'user_id': self.env.uid}, + 'id': '00043-003-0014', + 'to_invoice': False} + + onions_order = {'data': + {'amount_paid': 1.28, + 'amount_return': 0, + 'amount_tax': 0, + 'amount_total': 1.28, + 'creation_date': fields.Datetime.now(), + 'fiscal_position_id': False, + 'lines': [[0, + 0, + {'discount': 0, + 'id': 3, + 'pack_lot_ids': [], + 'price_unit': 1.28, + 'product_id': self.onions.id, + 'qty': 1, + 'tax_ids': [[6, False, self.onions.taxes_id.ids]]}]], + 'name': 'Order 00044-003-0014', + 'partner_id': False, + 'pos_session_id': current_session.id, + 'sequence_number': self.pos_config.journal_id.id, + 'statement_ids': [[0, + 0, + {'account_id': self.env.user.partner_id.property_account_receivable_id.id, + 'amount': 1.28, + 'journal_id': self.pos_config.journal_ids[0].id, + 'name': fields.Datetime.now(), + 'statement_id': current_session.statement_ids[0].id}]], + 'uid': '00044-003-0014', + 'user_id': self.env.uid}, + 'id': '00044-003-0014', + 'to_invoice': False} + + + # I create an order on an open session + self.PosOrder.create_from_ui([carrot_order]) + self.assertEqual(num_starting_orders + 1, len(current_session.order_ids), "Submitted order not encoded") + + # I resubmit the same order + self.PosOrder.create_from_ui([carrot_order]) + self.assertEqual(num_starting_orders + 1, len(current_session.order_ids), "Resubmitted order was not skipped") + + # I close the session + current_session.action_pos_session_closing_control() + self.assertEqual(current_session.state, 'closed', "Session was not properly closed") + self.assertFalse(self.pos_config.current_session_id, "Current session not properly recomputed") + + # I keep selling after the session is closed + with mute_logger('odoo.addons.point_of_sale.models.pos_order'): + self.PosOrder.create_from_ui([zucchini_order, onions_order]) + rescue_session = self.PosSession.search([ + ('config_id', '=', self.pos_config.id), + ('state', '=', 'opened'), + ]) + self.assertEqual(len(rescue_session), 1, "One (and only one) rescue session should be created for orphan orders") + self.assertIn("(RESCUE FOR %s)" % current_session.name, rescue_session.name, "Rescue session is not linked to the previous one") + self.assertEqual(len(rescue_session.order_ids), 2, "Rescue session does not contain both orders") + + # I close the rescue session + rescue_session.action_pos_session_closing_control() + self.assertEqual(rescue_session.state, 'closed', "Rescue session was not properly closed") diff --git a/addons/point_of_sale/views/pos_session_view.xml b/addons/point_of_sale/views/pos_session_view.xml index 1a377c34db8..052a9c9a822 100644 --- a/addons/point_of_sale/views/pos_session_view.xml +++ b/addons/point_of_sale/views/pos_session_view.xml @@ -157,7 +157,7 @@ - + diff --git a/addons/project/models/project.py b/addons/project/models/project.py index 4064e001f8f..ed029b1a4f3 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -270,7 +270,7 @@ class Project(models.Model): def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None, force=True): """ Subscribe to all existing active tasks when subscribing to a project """ res = super(Project, self).message_subscribe(partner_ids=partner_ids, channel_ids=channel_ids, subtype_ids=subtype_ids, force=force) - if not subtype_ids or any(subtype.parent_id.model == 'project.task' for subtype in self.env['mail.message.subtype'].browse(subtype_ids)): + if not subtype_ids or any(subtype.parent_id.res_model == 'project.task' for subtype in self.env['mail.message.subtype'].browse(subtype_ids)): for partner_id in partner_ids or []: self.mapped('tasks').filtered(lambda task: not task.stage_id.fold and partner_id not in task.message_partner_ids.ids).message_subscribe( partner_ids=[partner_id], channel_ids=None, subtype_ids=None, force=False) diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 87559c4913c..237bca65182 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -553,13 +553,34 @@ class PurchaseOrderLine(models.Model): line = super(PurchaseOrderLine, self).create(values) if line.order_id.state == 'purchase': line.order_id._create_picking() + msg = _("Extra line with %s ") % (line.product_id.display_name,) + line.order_id.message_post(body=msg) return line @api.multi def write(self, values): + orders = False + if 'product_qty' in values: + changed_lines = self.filtered(lambda x: x.order_id.state == 'purchase') + if changed_lines: + orders = changed_lines.mapped('order_id') + for order in orders: + order_lines = changed_lines.filtered(lambda x: x.order_id == order) + msg = "" + if any([values['product_qty'] < x.product_qty for x in order_lines]): + msg += "" + _('The ordered quantity has been decreased. Do not forget to take it into account on your bills and receipts.') + '
' + msg += "
    " + for line in order_lines: + msg += "
  • %s:" % (line.product_id.display_name,) + msg += "
    " + _("Ordered Quantity") + ": %s -> %s
    " % (line.product_qty, float(values['product_qty']),) + if line.product_id.type in ('product', 'consu'): + msg += _("Received Quantity") + ": %s
    " % (line.qty_received,) + msg += _("Billed Quantity") + ": %s
  • " % (line.qty_invoiced,) + msg += "
" + order.message_post(body=msg) result = super(PurchaseOrderLine, self).write(values) - orders = self.filtered(lambda x: x.order_id.state == 'purchase').mapped('order_id') - orders._create_picking() + if orders: + orders._create_picking() return result name = fields.Text(string='Description', required=True) @@ -959,6 +980,12 @@ class ProcurementOrder(models.Model): 'group_id': group } + def _make_po_select_supplier(self, suppliers): + """ Method intended to be overridden by customized modules to implement any logic in the + selection of supplier. + """ + return suppliers[0] + @api.multi def make_po(self): cache = {} @@ -968,7 +995,7 @@ class ProcurementOrder(models.Model): if not suppliers: procurement.message_post(body=_('No vendor associated to product %s. Please set one to fix this procurement.') % (procurement.product_id.name)) continue - supplier = suppliers[0] + supplier = procurement._make_po_select_supplier(suppliers) partner = supplier.name gpo = procurement.rule_id.group_propagation_option diff --git a/addons/report/models/report.py b/addons/report/models/report.py index 03d3947d0c2..4c72a9d5058 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -481,8 +481,11 @@ class Report(models.Model): out, err = process.communicate() if process.returncode not in [0, 1]: - raise UserError(_('Wkhtmltopdf failed (error code: %s). ' - 'Message: %s') % (str(process.returncode), err)) + if process.returncode == -11: + message = _('Wkhtmltopdf failed (error code: %s). Memory limit too low or maximum file number of subprocess reached. Message : %s') + else: + message = _('Wkhtmltopdf failed (error code: %s). Message: %s') + raise UserError(message % (str(process.returncode), err[-1000:])) # Save the pdf in attachment if marked if reporthtml[0] is not False: diff --git a/addons/report/models/res_company.py b/addons/report/models/res_company.py index f8cd4bd5236..85683e6edc1 100644 --- a/addons/report/models/res_company.py +++ b/addons/report/models/res_company.py @@ -6,7 +6,7 @@ from odoo import api, fields, models class ResCompany(models.Model): _inherit = 'res.company' - paperformat_id = fields.Many2one('report.paperformat', 'Paper format') + paperformat_id = fields.Many2one('report.paperformat', 'Paper format', default=lambda self: self.env.ref('report.paperformat_euro', raise_if_not_found=False)) external_report_layout = fields.Selection([ ('background', 'Background'), ('boxed', 'Boxed'), diff --git a/addons/sale/models/account_invoice.py b/addons/sale/models/account_invoice.py index a389b5f9691..713308aa7a1 100644 --- a/addons/sale/models/account_invoice.py +++ b/addons/sale/models/account_invoice.py @@ -84,6 +84,10 @@ class AccountInvoice(models.Model): return report_pages + @api.multi + def get_delivery_partner_id(self): + self.ensure_one() + return self.partner_shipping_id.id or super(AccountInvoice, self).get_delivery_partner_id() class AccountInvoiceLine(models.Model): _inherit = 'account.invoice.line' diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 22d4c0747d1..a30cb13aa78 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -61,7 +61,9 @@ class SaleOrder(models.Model): for inv in invoice_ids: refund_ids += refund_ids.search([('type', '=', 'out_refund'), ('origin', '=', inv.number), ('origin', '!=', False), ('journal_id', '=', inv.journal_id.id)]) - line_invoice_status = [line.invoice_status for line in order.order_line] + # Ignore the status of the deposit product + deposit_product_id = self.env['sale.advance.payment.inv']._default_product_id() + line_invoice_status = [line.invoice_status for line in order.order_line if line.product_id != deposit_product_id] if order.state not in ('sale', 'done'): invoice_status = 'no' @@ -511,11 +513,11 @@ class SaleOrder(models.Model): group = tax.tax_group_id res.setdefault(group, 0.0) amount = tax.compute_all(line.price_reduce + base_tax, quantity=line.product_uom_qty, - product=line.product_id, partner=line.order_partner_id)['taxes'][0]['amount'] + product=line.product_id, partner=self.partner_shipping_id)['taxes'][0]['amount'] res[group] += amount if tax.include_base_amount: base_tax += tax.compute_all(line.price_reduce + base_tax, quantity=1, product=line.product_id, - partner=line.order_partner_id)['taxes'][0]['amount'] + partner=self.partner_shipping_id)['taxes'][0]['amount'] res = sorted(res.items(), key=lambda l: l[0].sequence) res = map(lambda l: (l[0].name, l[1]), res) return res @@ -562,7 +564,7 @@ class SaleOrderLine(models.Model): """ for line in self: price = line.price_unit * (1 - (line.discount or 0.0) / 100.0) - taxes = line.tax_id.compute_all(price, line.order_id.currency_id, line.product_uom_qty, product=line.product_id, partner=line.order_id.partner_id) + taxes = line.tax_id.compute_all(price, line.order_id.currency_id, line.product_uom_qty, product=line.product_id, partner=line.order_id.partner_shipping_id) line.update({ 'price_tax': sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])), 'price_total': taxes['total_included'], @@ -628,7 +630,7 @@ class SaleOrderLine(models.Model): fpos = line.order_id.fiscal_position_id or line.order_id.partner_id.property_account_position_id # If company_id is set, always filter taxes by the company taxes = line.product_id.taxes_id.filtered(lambda r: not line.company_id or r.company_id == line.company_id) - line.tax_id = fpos.map_tax(taxes, line.product_id, line.order_id.partner_id) if fpos else taxes + line.tax_id = fpos.map_tax(taxes, line.product_id, line.order_id.partner_shipping_id) if fpos else taxes @api.multi def _prepare_order_line_procurement(self, group_id=False): @@ -692,16 +694,37 @@ class SaleOrderLine(models.Model): line = super(SaleOrderLine, self).create(values) if line.state == 'sale': line._action_procurement_create() + msg = _("Extra line with %s ") % (line.product_id.display_name,) + line.order_id.message_post(body=msg) return line @api.multi def write(self, values): lines = False + changed_lines = False if 'product_uom_qty' in values: precision = self.env['decimal.precision'].precision_get('Product Unit of Measure') lines = self.filtered( lambda r: r.state == 'sale' and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) == -1) + changed_lines = self.filtered( + lambda r: r.state == 'sale' and float_compare(r.product_uom_qty, values['product_uom_qty'], precision_digits=precision) != 0) + if changed_lines: + orders = self.mapped('order_id') + for order in orders: + order_lines = changed_lines.filtered(lambda x: x.order_id == order) + msg = "" + if any([values['product_uom_qty'] < x.product_uom_qty for x in order_lines]): + msg += "" + _('The ordered quantity has been decreased. Do not forget to take it into account on your invoices and delivery orders.') + '' + msg += "
    " + for line in order_lines: + msg += "
  • %s:" % (line.product_id.display_name,) + msg += "
    " + _("Ordered Quantity") + ": %s -> %s
    " % (line.product_uom_qty, float(values['product_uom_qty']),) + if line.product_id.type in ('consu', 'product'): + msg += _("Delivered Quantity") + ": %s
    " % (line.qty_delivered,) + msg += _("Invoiced Quantity") + ": %s
    " % (line.qty_invoiced,) + msg += "
" + order.message_post(body=msg) result = super(SaleOrderLine, self).write(values) if lines: lines._action_procurement_create() @@ -825,10 +848,16 @@ class SaleOrderLine(models.Model): @api.multi def _get_display_price(self, product): # TO DO: move me in master/saas-16 on sale.order - if self.order_id.pricelist_id.discount_policy == 'without_discount': + if self.order_id.pricelist_id.discount_policy == 'with_discount': + return product.with_context(pricelist=self.order_id.pricelist_id.id).price + price, rule_id = self.order_id.pricelist_id.get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id) + pricelist_item = self.env['product.pricelist.item'].browse(rule_id) + if (pricelist_item.base == 'pricelist' and pricelist_item.base_pricelist_id.discount_policy == 'with_discount'): + price, rule_id = pricelist_item.base_pricelist_id.get_product_price_rule(self.product_id, self.product_uom_qty or 1.0, self.order_id.partner_id) + return price + else: from_currency = self.order_id.company_id.currency_id return from_currency.compute(product.lst_price, self.order_id.pricelist_id.currency_id) - return product.with_context(pricelist=self.order_id.pricelist_id.id).price @api.multi @api.onchange('product_id') @@ -968,7 +997,7 @@ class SaleOrderLine(models.Model): new_list_price, currency_id = self.with_context(context_partner)._get_real_price_currency(self.product_id, rule_id, self.product_uom_qty, self.product_uom, self.order_id.pricelist_id.id) new_list_price = self.env['account.tax']._fix_tax_included_price(new_list_price, self.product_id.taxes_id, self.tax_id) - if price != 0 and new_list_price != 0: + if new_list_price != 0: if self.product_id.company_id and self.order_id.pricelist_id.currency_id != self.product_id.company_id.currency_id: # new_list_price is in company's currency while price in pricelist currency new_list_price = self.env['res.currency'].browse(currency_id).with_context(context_partner).compute(new_list_price, self.order_id.pricelist_id.currency_id) diff --git a/addons/sale/models/sale_analytic.py b/addons/sale/models/sale_analytic.py index acf3fd48682..e937292d818 100644 --- a/addons/sale/models/sale_analytic.py +++ b/addons/sale/models/sale_analytic.py @@ -12,7 +12,19 @@ class SaleOrderLine(models.Model): lines = {} if not domain: # To filter on analyic lines linked to an expense - domain = [('so_line', 'in', self.ids), ('amount', '<=', 0.0)] + expense_type_id = self.env.ref('account.data_account_type_expenses', raise_if_not_found=False) + expense_type_id = expense_type_id and expense_type_id.id + domain = [ + ('so_line', 'in', self.ids), + '|', + ('amount', '<', 0), + '&', + ('amount', '=', 0), + '|', + ('move_id', '=', False), + ('move_id.account_id.user_type_id', '=', expense_type_id) + ] + data = self.env['account.analytic.line'].read_group( domain, ['so_line', 'unit_amount', 'product_uom_id'], ['product_uom_id', 'so_line'], lazy=False diff --git a/addons/sale_timesheet/models/sale_order.py b/addons/sale_timesheet/models/sale_order.py index 4e5eee48591..0254c3dbe0e 100644 --- a/addons/sale_timesheet/models/sale_order.py +++ b/addons/sale_timesheet/models/sale_order.py @@ -142,5 +142,18 @@ class SaleOrderLine(models.Model): def _compute_analytic(self, domain=None): if not domain: # To filter on analyic lines linked to an expense - domain = [('so_line', 'in', self.ids), '|', ('amount', '<=', 0.0), ('project_id', '!=', False)] + expense_type_id = self.env.ref('account.data_account_type_expenses', raise_if_not_found=False) + expense_type_id = expense_type_id and expense_type_id.id + domain = [ + ('so_line', 'in', self.ids), + '|', + '|', + ('amount', '<', 0.0), + ('project_id', '!=', False), + '&', + ('amount', '=', 0), + '|', + ('move_id', '=', False), + ('move_id.account_id.user_type_id', '=', expense_type_id) + ] return super(SaleOrderLine, self)._compute_analytic(domain=domain) diff --git a/addons/sales_team/data/sales_team_data.xml b/addons/sales_team/data/sales_team_data.xml index 4f2397c5731..2af8a2f7992 100644 --- a/addons/sales_team/data/sales_team_data.xml +++ b/addons/sales_team/data/sales_team_data.xml @@ -4,10 +4,12 @@ Sales + Website + diff --git a/addons/sales_team/models/crm_team.py b/addons/sales_team/models/crm_team.py index a05520d422b..c4cb8b74301 100644 --- a/addons/sales_team/models/crm_team.py +++ b/addons/sales_team/models/crm_team.py @@ -24,9 +24,11 @@ class CrmTeam(models.Model): def _get_default_team_id(self, user_id=None): if not user_id: user_id = self.env.uid - team_id = self.env['crm.team'].sudo().search( - ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], - limit=1) + company_id = self.sudo(user_id).company_id.id + team_id = self.env['crm.team'].sudo().search([ + '|', ('user_id', '=', user_id), ('member_ids', '=', user_id), + '|', ('company_id', '=', False), ('company_id', 'child_of', [company_id]) + ], limit=1) if not team_id and 'default_team_id' in self.env.context: team_id = self.env['crm.team'].browse(self.env.context.get('default_team_id')) if not team_id: diff --git a/addons/sales_team/static/src/xml/sales_team_dashboard.xml b/addons/sales_team/static/src/xml/sales_team_dashboard.xml index 6075486c3fe..3f00b3ae4ae 100644 --- a/addons/sales_team/static/src/xml/sales_team_dashboard.xml +++ b/addons/sales_team/static/src/xml/sales_team_dashboard.xml @@ -84,7 +84,7 @@
@@ -98,7 +99,8 @@
- + +
@@ -213,7 +215,10 @@

- Proposed by + Proposed by + + +

diff --git a/addons/website_event_track/views/event_track_views.xml b/addons/website_event_track/views/event_track_views.xml index 19912ab98ea..492649f37d4 100644 --- a/addons/website_event_track/views/event_track_views.xml +++ b/addons/website_event_track/views/event_track_views.xml @@ -121,6 +121,7 @@

+
TaxBase Amount
+ + Debit Credit BalanceCurrencyCurrency
- + + + +
- + exp. closing diff --git a/addons/stock/models/procurement.py b/addons/stock/models/procurement.py index 93581c71f2f..b3edca53c0f 100644 --- a/addons/stock/models/procurement.py +++ b/addons/stock/models/procurement.py @@ -275,23 +275,27 @@ class ProcurementOrder(models.Model): @api.model def _procure_orderpoint_confirm(self, use_new_cursor=False, company_id=False): """ Create procurements based on orderpoints. - :param bool use_new_cursor: if set, use a dedicated cursor and auto-commit after processing each procurement. + :param bool use_new_cursor: if set, use a dedicated cursor and auto-commit after processing + 1000 orderpoints. This is appropriate for batch jobs only. """ - if use_new_cursor: - cr = registry(self._cr.dbname).cursor() - self = self.with_env(self.env(cr=cr)) OrderPoint = self.env['stock.warehouse.orderpoint'] - Procurement = self.env['procurement.order'] - ProcurementAutorundefer = Procurement.with_context(procurement_autorun_defer=True) - procurement_list = [] - orderpoints_noprefetch = OrderPoint.with_context(prefetch_fields=False).search( - company_id and [('company_id', '=', company_id)] or [], - order=self._procurement_from_orderpoint_get_order()) + domain = [('company_id', '=', company_id)] if company_id else [] + domain += [('product_id.active', '=', True)] + orderpoints_noprefetch = OrderPoint.with_context(prefetch_fields=False).search(domain, + order=self._procurement_from_orderpoint_get_order()).ids while orderpoints_noprefetch: - orderpoints = OrderPoint.browse(orderpoints_noprefetch[:1000].ids) + if use_new_cursor: + cr = registry(self._cr.dbname).cursor() + self = self.with_env(self.env(cr=cr)) + OrderPoint = self.env['stock.warehouse.orderpoint'] + Procurement = self.env['procurement.order'] + ProcurementAutorundefer = Procurement.with_context(procurement_autorun_defer=True) + procurement_list = [] + + orderpoints = OrderPoint.browse(orderpoints_noprefetch[:1000]) orderpoints_noprefetch = orderpoints_noprefetch[1000:] # Calculate groups that can be executed together @@ -341,7 +345,7 @@ class ProcurementOrder(models.Model): except OperationalError: if use_new_cursor: - orderpoints_noprefetch += orderpoint.id + orderpoints_noprefetch += [orderpoint.id] cr.rollback() continue else: @@ -365,8 +369,6 @@ class ProcurementOrder(models.Model): if use_new_cursor: cr.commit() + cr.close() - if use_new_cursor: - cr.commit() - cr.close() return {} diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 020a25ac276..591842e4225 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -349,7 +349,7 @@ class StockMove(models.Model): raise UserError(_('Cannot unreserve a done move')) self.quants_unreserve() if not self.env.context.get('no_state_change'): - waiting = self.filtered(lambda move: move.get_ancestors()) + waiting = self.filtered(lambda move: move.procure_method == 'make_to_order' or move.get_ancestors()) waiting.write({'state': 'waiting'}) (self - waiting).write({'state': 'confirmed'}) @@ -408,15 +408,8 @@ class StockMove(models.Model): type (moves should already have them identical). Otherwise, create a new picking to assign them to. """ Picking = self.env['stock.picking'] - - # If this method is called in batch by a write on a one2many and - # at some point had to create a picking, some next iterations could - # try to find back the created picking. As we look for it by searching - # on some computed fields, we have to force a recompute, else the - # record won't be found. - self.recompute() - for move in self: + recompute = False picking = Picking.search([ ('group_id', '=', move.group_id.id), ('location_id', '=', move.location_id.id), @@ -425,8 +418,17 @@ class StockMove(models.Model): ('printed', '=', False), ('state', 'in', ['draft', 'confirmed', 'waiting', 'partially_available', 'assigned'])], limit=1) if not picking: + recompute = True picking = Picking.create(move._get_new_picking_values()) move.write({'picking_id': picking.id}) + + # If this method is called in batch by a write on a one2many and + # at some point had to create a picking, some next iterations could + # try to find back the created picking. As we look for it by searching + # on some computed fields, we have to force a recompute, else the + # record won't be found. + if recompute: + move.recompute() return True _picking_assign = assign_picking @@ -687,7 +689,7 @@ class StockMove(models.Model): if len(reserved_quant_ids) == 0 and move.partially_available: vals['partially_available'] = False if move.state == 'assigned': - if move.find_move_ancestors(): + if move.procure_method == 'make_to_order' or move.find_move_ancestors(): vals['state'] = 'waiting' else: vals['state'] = 'confirmed' diff --git a/addons/stock/report/report_deliveryslip.xml b/addons/stock/report/report_deliveryslip.xml index c36ba994f7d..014869345c2 100644 --- a/addons/stock/report/report_deliveryslip.xml +++ b/addons/stock/report/report_deliveryslip.xml @@ -19,6 +19,9 @@
+
+ t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/> +

diff --git a/addons/stock/tests/test_stock_flow.py b/addons/stock/tests/test_stock_flow.py index e87d1c3b79c..d8dd43b0794 100644 --- a/addons/stock/tests/test_stock_flow.py +++ b/addons/stock/tests/test_stock_flow.py @@ -1726,4 +1726,69 @@ class TestStockFlow(TestStockCommon): # force assign on the delivery order, it should be assigned picking_out.force_assign() - self.assertEquals(picking_out.state, "assigned") \ No newline at end of file + self.assertEquals(picking_out.state, "assigned") + + def test_74_move_state_waiting_mto(self): + """ This test will check that when a move is unreserved, it state change to 'waiting' if + it has ancestors or is has a 'procure_method' equal to 'make_to_order' else the state + changes to 'confirmed'. + """ + picking_out = self.PickingObj.create({ + 'partner_id': self.partner_agrolite_id, + 'picking_type_id': self.picking_type_out, + 'location_id': self.stock_location, + 'location_dest_id': self.customer_location}) + move_mto_alone = self.MoveObj.create({ + 'name': self.productA.name, + 'product_id': self.productA.id, + 'product_uom_qty': 2, + 'product_uom': self.productA.uom_id.id, + 'picking_id': picking_out.id, + 'location_id': self.stock_location, + 'location_dest_id': self.customer_location, + 'procure_method':'make_to_order'}) + move_with_ancestors = self.MoveObj.create({ + 'name': self.productA.name, + 'product_id': self.productA.id, + 'product_uom_qty': 2, + 'product_uom': self.productA.uom_id.id, + 'picking_id': picking_out.id, + 'location_id': self.stock_location, + 'location_dest_id': self.customer_location}) + the_ancestor = self.MoveObj.create({ + 'name': self.productA.name, + 'product_id': self.productA.id, + 'product_uom_qty': 2, + 'product_uom': self.productA.uom_id.id, + 'picking_id': picking_out.id, + 'location_id': self.stock_location, + 'location_dest_id': self.customer_location, + 'move_dest_id': move_with_ancestors.id}) + other_move = self.MoveObj.create({ + 'name': self.productA.name, + 'product_id': self.productA.id, + 'product_uom_qty': 2, + 'product_uom': self.productA.uom_id.id, + 'picking_id': picking_out.id, + 'location_id': self.stock_location, + 'location_dest_id': self.customer_location}) + + move_mto_alone.action_confirm() + move_with_ancestors.action_confirm() + other_move.action_confirm() + + move_mto_alone.do_unreserve() + move_with_ancestors.do_unreserve() + other_move.do_unreserve() + + self.assertEquals(move_mto_alone.state, "waiting") + self.assertEquals(move_with_ancestors.state, "waiting") + self.assertEquals(other_move.state, "confirmed") + + move_mto_alone.recalculate_move_state() + move_with_ancestors.recalculate_move_state() + other_move.recalculate_move_state() + + self.assertEquals(move_mto_alone.state, "waiting") + self.assertEquals(move_with_ancestors.state, "waiting") + self.assertEquals(other_move.state, "confirmed") diff --git a/addons/stock/views/stock_warehouse_views.xml b/addons/stock/views/stock_warehouse_views.xml index 62d1e3d748c..7a8caea9e5a 100644 --- a/addons/stock/views/stock_warehouse_views.xml +++ b/addons/stock/views/stock_warehouse_views.xml @@ -98,6 +98,7 @@ stock.warehouse + diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py index 4706bbc6b45..9fec58c20ab 100644 --- a/addons/stock_account/models/stock.py +++ b/addons/stock_account/models/stock.py @@ -188,7 +188,7 @@ class StockMove(models.Model): tmpl_dict = defaultdict(lambda: 0.0) # adapt standard price on incomming moves if the product cost_method is 'average' std_price_update = {} - for move in self.filtered(lambda move: move.location_id.usage == 'supplier' and move.product_id.cost_method == 'average'): + for move in self.filtered(lambda move: move.location_id.usage in ('supplier', 'production') and move.product_id.cost_method == 'average'): product_tot_qty_available = move.product_id.qty_available + tmpl_dict[move.product_id.id] # if the incoming move is for a purchase order with foreign currency, need to call this to get the same value that the quant will use. diff --git a/addons/stock_landed_costs/models/stock_landed_cost.py b/addons/stock_landed_costs/models/stock_landed_cost.py index 2427a8ca0ca..12134cc0f0f 100644 --- a/addons/stock_landed_costs/models/stock_landed_cost.py +++ b/addons/stock_landed_costs/models/stock_landed_cost.py @@ -129,6 +129,7 @@ class LandedCost(models.Model): if quant.location_id.usage != 'internal': qty_out += quant.qty line._create_accounting_entries(move, qty_out) + move.assert_balanced() cost.write({'state': 'done', 'account_move_id': move.id}) move.post() return True @@ -326,7 +327,7 @@ class AdjustmentLines(models.Model): Generate the account.move.line values to track the landed cost. Afterwards, for the goods that are already out of stock, we should create the out moves """ - AccountMoveLine = self.env['account.move.line'].with_context(check_move_validity=False) + AccountMoveLine = self.env['account.move.line'].with_context(check_move_validity=False, recompute=False) base_line = { 'name': self.name, @@ -389,5 +390,4 @@ class AdjustmentLines(models.Model): AccountMoveLine.create(debit_line) AccountMoveLine.create(credit_line) - move.assert_balanced() return True diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 621fea372f9..8bc312eee98 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -665,6 +665,8 @@ class Database(http.Controller): @http.route('/web/database/create', type='http', auth="none", methods=['POST'], csrf=False) def create(self, master_pwd, name, lang, password, **post): try: + if not re.match('^[a-zA-Z][a-zA-Z0-9_]+$', name): + raise Exception(_('Invalid database name. Only alphanumerical characters and underscore are allowed.')) # country code could be = "False" which is actually True in python country_code = post.get('country_code') or False dispatch_rpc('db', 'create_database', [master_pwd, name, bool(post.get('demo')), lang, password, post['login'], country_code]) @@ -677,6 +679,8 @@ class Database(http.Controller): @http.route('/web/database/duplicate', type='http', auth="none", methods=['POST'], csrf=False) def duplicate(self, master_pwd, name, new_name): try: + if not re.match('^[a-zA-Z][a-zA-Z0-9_]+$', new_name): + raise Exception(_('Invalid database name. Only alphanumerical characters and underscore are allowed.')) dispatch_rpc('db', 'duplicate_database', [master_pwd, name, new_name]) return http.local_redirect('/web/database/manager') except Exception as e: diff --git a/addons/web/static/src/js/views/kanban/kanban_view.js b/addons/web/static/src/js/views/kanban/kanban_view.js index 4716e7a3bc3..2edb44abf7d 100644 --- a/addons/web/static/src/js/views/kanban/kanban_view.js +++ b/addons/web/static/src/js/views/kanban/kanban_view.js @@ -25,6 +25,13 @@ var KanbanView = BasicView.extend({ this._super.apply(this, arguments); var arch = viewInfo.arch; + + this.loadParams.limit = this.loadParams.limit || 40; + this.loadParams.openGroupByDefault = true; + this.loadParams.type = 'list'; + + this.loadParams.groupBy = arch.attrs.default_group_by ? [arch.attrs.default_group_by] : (params.groupBy || []); + var activeActions = this.controllerParams.activeActions; activeActions = _.extend(activeActions, { group_edit: arch.attrs.group_edit ? JSON.parse(arch.attrs.group_edit) : true, @@ -35,7 +42,7 @@ var KanbanView = BasicView.extend({ editable: activeActions.group_edit, deletable: activeActions.group_delete, group_creatable: true, - quick_create: params.isQuickCreateEnabled || this._isQuickCreateEnabled(arch), + quick_create: params.isQuickCreateEnabled || this._isQuickCreateEnabled(viewInfo), }; this.rendererParams.record_options = { editable: activeActions.edit, @@ -47,19 +54,17 @@ var KanbanView = BasicView.extend({ this.controllerParams.readOnlyMode = false; this.controllerParams.hasButtons = true; - - this.loadParams.limit = this.loadParams.limit || 40; - this.loadParams.openGroupByDefault = true; - this.loadParams.type = 'list'; - - this.loadParams.groupBy = arch.attrs.default_group_by ? [arch.attrs.default_group_by] : (params.groupBy || []); }, - _isQuickCreateEnabled: function (arch) { + _isQuickCreateEnabled: function (viewInfo) { + var groupBy = this.loadParams.groupBy[0]; + if(groupBy !== undefined && !_.contains(['char', 'boolean', 'many2one'], viewInfo.fields[groupBy].type)) { + return false; + } if (!this.controllerParams.activeActions.create) { return false; } - if (arch.attrs.quick_create !== undefined) { - return JSON.parse(arch.attrs.quick_create); + if (viewInfo.arch.attrs.quick_create !== undefined) { + return JSON.parse(viewInfo.arch.attrs.quick_create); } return true; } diff --git a/addons/web/static/src/js/widgets/data_export.js b/addons/web/static/src/js/widgets/data_export.js index f8c622bf5e8..44a815cf82d 100644 --- a/addons/web/static/src/js/widgets/data_export.js +++ b/addons/web/static/src/js/widgets/data_export.js @@ -207,9 +207,15 @@ var DataExport = Dialog.extend({ this.records = {}; this.dataset = dataset; this.exports = new data.DataSetSearch(this, 'ir.exports', this.dataset.get_context()); - + this.row_index = 0; this.row_index_level = 0; + + // The default for the ".modal_content" element is "max-height: 100%;" + // but we want it to always expand to "height: 100%;" for this modal. + // This can be achieved thanks to LESS modification without touching + // the ".modal-content" rules... but not with Internet explorer (11). + this.$modal.find(".modal-content").css("height", "100%"); }, start: function() { var self = this; @@ -265,7 +271,7 @@ var DataExport = Dialog.extend({ function do_setup_export_formats(formats) { var $fmts = self.$('.o_export_format'); - + _.each(formats, function(format, i) { var $radio = $('', {type: 'radio', value: format.tag, name: 'o_export_format_name'}); var $label = $('
+
diff --git a/addons/website_event_track/models/event.py b/addons/website_event_track/models/event.py index d1088c1d500..3acd8e8279f 100644 --- a/addons/website_event_track/models/event.py +++ b/addons/website_event_track/models/event.py @@ -11,6 +11,12 @@ class EventType(models.Model): website_track = fields.Boolean('Tracks on Website') website_track_proposal = fields.Boolean('Tracks Proposals on Website') + @api.onchange('website_menu') + def _onchange_website_menu(self): + if not self.website_menu: + self.website_track = False + self.website_track_proposal = False + class Event(models.Model): _inherit = "event.event" @@ -64,10 +70,16 @@ class Event(models.Model): @api.onchange('event_type_id') def _onchange_type(self): super(Event, self)._onchange_type() - if self.event_type_id: + if self.event_type_id and self.website_menu: self.website_track = self.event_type_id.website_track self.website_track_proposal = self.event_type_id.website_track_proposal + @api.onchange('website_menu') + def _onchange_website_menu(self): + if not self.website_menu: + self.website_track = False + self.website_track_proposal = False + def _get_menu_entries(self): self.ensure_one() res = super(Event, self)._get_menu_entries() diff --git a/addons/website_event_track/views/event_track_templates.xml b/addons/website_event_track/views/event_track_templates.xml index 9e6e86b0c63..c8282180215 100644 --- a/addons/website_event_track/views/event_track_templates.xml +++ b/addons/website_event_track/views/event_track_templates.xml @@ -83,7 +83,8 @@ unpublished
- + +