diff --git a/MANIFEST.in b/MANIFEST.in index eea6b83adeb..78401965c48 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -10,6 +10,8 @@ recursive-include openerp *.jpg recursive-include openerp *.js recursive-include openerp *.less recursive-include openerp *.md +recursive-include openerp *.mp3 +recursive-include openerp *.ogg recursive-include openerp *.png recursive-include openerp *.po recursive-include openerp *.rml diff --git a/addons/account/__openerp__.py b/addons/account/__openerp__.py index 0411532ab27..07d737f1358 100644 --- a/addons/account/__openerp__.py +++ b/addons/account/__openerp__.py @@ -57,6 +57,7 @@ You could use this simplified accounting in case you work with an (external) acc "static/src/xml/account_reconciliation.xml", "static/src/xml/account_payment.xml", "static/src/xml/account_report_backend.xml", + 'test/analytic_hierarchy.yml', ], 'installable': True, 'application': True, diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index a613adafbdf..a37f23628d7 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -738,7 +738,9 @@ class AccountInvoice(models.Model): ctx['company_id'] = inv.company_id.id ctx['dont_create_taxes'] = True ctx['invoice_id'] = inv.move_name and inv or False - move = account_move.with_context(ctx).create(move_vals) + ctx_nolang = ctx.copy() + ctx_nolang.pop('lang', None) + move = account_move.with_context(ctx_nolang).create(move_vals) # make the invoice point to that move vals = { 'move_id': move.id, diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 8f3ff680217..4d4d9afffde 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -177,13 +177,15 @@ class AccountMove(models.Model): def assert_balanced(self): if not self.ids: return True + prec = self.env['decimal.precision'].precision_get('Account') + self._cr.execute("""\ SELECT move_id FROM account_move_line WHERE move_id in %s GROUP BY move_id - HAVING abs(sum(debit) - sum(credit)) > 0.00001 - """, (tuple(self.ids),)) + HAVING abs(sum(debit) - sum(credit)) > %s + """, (tuple(self.ids), 10 ** (-max(5, prec)))) if len(self._cr.fetchall()) != 0: raise UserError(_("Cannot create unbalanced journal entry.")) return True @@ -1086,7 +1088,7 @@ class AccountMoveLine(models.Model): 'journal_id': self.journal_id.analytic_journal_id.id, 'ref': self.ref, 'move_id': self.id, - 'user_id': self._uid, + 'user_id': self.invoice_id.user_id.id or self._uid, } @api.model diff --git a/addons/account/test/analytic_hierarchy.yml b/addons/account/test/analytic_hierarchy.yml new file mode 100644 index 00000000000..117d6506b21 --- /dev/null +++ b/addons/account/test/analytic_hierarchy.yml @@ -0,0 +1,56 @@ +- + We create a simple hierarchy like this + Toplevel view account + ├ Real analytic account 1 + └ Real analytic account 2 +- + !record {model: account.analytic.account, id: analytic_account_view1}: + name: 'Toplevel view account' + type: 'view' +- + !record {model: account.analytic.account, id: analytic_account1}: + name: 'Real analytic account 1' + type: 'normal' + parent_id: analytic_account_view1 +- + !record {model: account.analytic.account, id: analytic_account2}: + name: 'Real analytic account 2' + type: 'normal' + parent_id: analytic_account_view1 +- + We add analytic lines in real accounts and journal +- + !record {model: account.analytic.journal, id: analytic_journal1}: + name: 'Analytic journal' +- + !record {model: account.analytic.line, id: analytic_line1}: + name: 'Analytic line 1' + account_id: analytic_account1 + journal_id: analytic_journal1 + general_account_id: account.a_expense +- + !record {model: account.analytic.line, id: analytic_line2}: + name: 'Analytic line 2' + account_id: analytic_account2 + journal_id: analytic_journal1 + general_account_id: account.a_expense +- + Now we copy the toplevel account +- + !function {model: account.analytic.account, name: copy, id: analytic_account_view1}: + - eval: "ref('analytic_account_view1')" +- + !python {model: account.analytic.account}: | + # the toplevel account has been copied with the (copy) suffix + accounts = self.search(cr, uid, [('name', 'like', '%Toplevel view account%')]) + assert len(accounts) == 2, 'The toplevel account has not been copied' + accounts = self.search(cr, uid, [('name', '=', 'Toplevel view account')]) + assert len(accounts) == 1, 'The toplevel account copy has a bad name' + # the sub-accounts and sub-accounts have been copied without the (copy) suffix + accounts = self.search(cr, uid, [('name', '=', 'Real analytic account 1')]) + assert len(accounts) == 2, 'The sub-account has not been copied' + accounts = self.search(cr, uid, [('name', '=', 'Real analytic account 2')]) + assert len(accounts) == 2, 'The sub-account has not been copied' + # the analytic lines should not be duplicated + lines = self.pool.get('account.analytic.line').search(cr, uid, [('name', 'like', '%Analytic line %')]) + assert len(lines) == 2, "The analytic lines shouldn't have been copied" diff --git a/addons/account/tests/account_test_users.py b/addons/account/tests/account_test_users.py index 3fa284bc7de..3c2af90c37d 100644 --- a/addons/account/tests/account_test_users.py +++ b/addons/account/tests/account_test_users.py @@ -19,19 +19,17 @@ class AccountTestUsers(AccountingTestCase): self.account_type_model = self.env['account.account.type'] self.currency_euro = self.env.ref('base.EUR') - self.account_user = self.res_user_model.create(dict( + self.account_user = self.res_user_model.with_context({'no_reset_password': True}).create(dict( name="Accountant", company_id=self.main_company.id, login="acc", - password="acc", email="accountuser@yourcompany.com", groups_id=[(6, 0, [res_users_account_user.id, partner_manager.id])] )) - self.account_manager = self.res_user_model.create(dict( + self.account_manager = self.res_user_model.with_context({'no_reset_password': True}).create(dict( name="Adviser", company_id=self.main_company.id, login="fm", - password="fm", email="accountmanager@yourcompany.com", groups_id=[(6, 0, [res_users_account_manager.id, partner_manager.id])] )) diff --git a/addons/account_analytic_analysis/account_analytic_analysis.py b/addons/account_analytic_analysis/account_analytic_analysis.py index 821d250d76c..367d5ae28af 100644 --- a/addons/account_analytic_analysis/account_analytic_analysis.py +++ b/addons/account_analytic_analysis/account_analytic_analysis.py @@ -684,6 +684,28 @@ class account_analytic_account(osv.osv): } return invoice + def _prepare_invoice_line(self, cr, uid, line, fiscal_position, context=None): + fpos_obj = self.pool.get('account.fiscal.position') + res = line.product_id + account_id = res.property_account_income_id.id + if not account_id: + account_id = res.categ_id.property_account_income_categ_id.id + account_id = fpos_obj.map_account(cr, uid, fiscal_position, account_id) + + taxes = res.taxes_id or False + tax_id = fpos_obj.map_tax(cr, uid, fiscal_position, taxes) + values = { + 'name': line.name, + 'account_id': account_id, + 'account_analytic_id': line.analytic_account_id.id, + 'price_unit': line.price_unit or 0.0, + 'quantity': line.quantity, + 'uos_id': line.uom_id.id or False, + 'product_id': line.product_id.id or False, + 'invoice_line_tax_ids': [(6, 0, tax_id)], + } + return values + def _prepare_invoice_lines(self, cr, uid, contract, fiscal_position_id, context=None): fpos_obj = self.pool.get('account.fiscal.position') fiscal_position = None @@ -691,26 +713,8 @@ class account_analytic_account(osv.osv): fiscal_position = fpos_obj.browse(cr, uid, fiscal_position_id, context=context) invoice_lines = [] for line in contract.recurring_invoice_line_ids: - - res = line.product_id - account_id = res.property_account_income_id.id - if not account_id: - account_id = res.categ_id.property_account_income_categ_id.id - account_id = fpos_obj.map_account(cr, uid, fiscal_position, account_id) - - taxes = res.taxes_id or False - tax_id = fpos_obj.map_tax(cr, uid, fiscal_position, taxes) - - invoice_lines.append((0, 0, { - 'name': line.name, - 'account_id': account_id, - 'account_analytic_id': contract.id, - 'price_unit': line.price_unit or 0.0, - 'quantity': line.quantity, - 'uos_id': line.uom_id.id or False, - 'product_id': line.product_id.id or False, - 'invoice_line_tax_ids': [(6, 0, tax_id)], - })) + values = self._prepare_invoice_line(cr, uid, line, fiscal_position, context=context) + invoice_lines.append((0, 0, values)) return invoice_lines def _prepare_invoice(self, cr, uid, contract, context=None): diff --git a/addons/account_budget/account_budget.py b/addons/account_budget/account_budget.py index 9f5e1f0d54a..5c1522c29a1 100644 --- a/addons/account_budget/account_budget.py +++ b/addons/account_budget/account_budget.py @@ -99,10 +99,12 @@ class crossovered_budget_lines(osv.osv): result = 0.0 if context is None: context = {} + account_obj = self.pool.get('account.account') for line in self.browse(cr, uid, ids, context=context): acc_ids = [x.id for x in line.general_budget_id.account_ids] if not acc_ids: raise UserError(_("The Budget '%s' has no accounts!") % ustr(line.general_budget_id.name)) + acc_ids = account_obj._get_children_and_consol(cr, uid, acc_ids, context=context) date_to = line.date_to date_from = line.date_from if line.analytic_account_id.id: diff --git a/addons/account_voucher/test/account_voucher_users.yml b/addons/account_voucher/test/account_voucher_users.yml index cb822ae4cef..4126ef874bc 100644 --- a/addons/account_voucher/test/account_voucher_users.yml +++ b/addons/account_voucher/test/account_voucher_users.yml @@ -5,7 +5,6 @@ company_id: base.main_company name: Voucher Accountant login: vacc - password: acc email: accountant@yourcompany.com - I added groups to Accountant for account voucher. @@ -21,7 +20,6 @@ company_id: base.main_company name: Financial Manager for voucher login: fmv - password: fmv email: finmanager@yourcompany.com - I added groups to Financial Manager for account voucher. diff --git a/addons/analytic/models/analytic.py b/addons/analytic/models/analytic.py index e6136b60fec..411476b1823 100644 --- a/addons/analytic/models/analytic.py +++ b/addons/analytic/models/analytic.py @@ -168,9 +168,9 @@ class account_analytic_account(osv.osv): 'template_id': fields.many2one('account.analytic.account', 'Template of Contract'), 'description': fields.text('Description'), 'parent_id': fields.many2one('account.analytic.account', 'Parent Analytic Account', select=2), - 'child_ids': fields.one2many('account.analytic.account', 'parent_id', 'Child Accounts'), + 'child_ids': fields.one2many('account.analytic.account', 'parent_id', 'Child Accounts', copy=True), 'child_complete_ids': fields.function(_child_compute, relation='account.analytic.account', string="Account Hierarchy", type='many2many'), - 'line_ids': fields.one2many('account.analytic.line', 'account_id', 'Analytic Entries'), + 'line_ids': fields.one2many('account.analytic.line', 'account_id', 'Analytic Entries', copy=False), 'balance': fields.function(_debit_credit_bal_qtty, type='float', string='Balance', multi='debit_credit_bal_qtty', digits=0), 'debit': fields.function(_debit_credit_bal_qtty, type='float', string='Debit', multi='debit_credit_bal_qtty', digits=0), 'credit': fields.function(_debit_credit_bal_qtty, type='float', string='Credit', multi='debit_credit_bal_qtty', digits=0), @@ -263,6 +263,8 @@ class account_analytic_account(osv.osv): raise UserError(_("Quick account creation disallowed.")) def copy(self, cr, uid, id, default=None, context=None): + """ executed only on the toplevel copied object of the hierarchy. + Subobject are actually copied with copy_data""" if not default: default = {} analytic = self.browse(cr, uid, id, context=context) @@ -299,9 +301,12 @@ class account_analytic_account(osv.osv): dom = [] for name2 in name.split('/'): name = name2.strip() - account_ids = self.search(cr, uid, dom + [('name', operator, name)] + args, limit=limit, context=context) + account_ids = self.search(cr, uid, dom + [('name', operator, name)], limit=limit, context=context) if not account_ids: break dom = [('parent_id','in',account_ids)] + if account_ids and args: + # final filtering according to domain (args) + account_ids = self.search(cr, uid, [('id', 'in', account_ids)] + args, limit=limit, context=context) else: account_ids = self.search(cr, uid, args, limit=limit, context=context) return self.name_get(cr, uid, account_ids, context=context) diff --git a/addons/auth_ldap/users_ldap.py b/addons/auth_ldap/users_ldap.py index e85a4a8985a..0c685aa8a98 100644 --- a/addons/auth_ldap/users_ldap.py +++ b/addons/auth_ldap/users_ldap.py @@ -123,7 +123,8 @@ class CompanyLDAP(osv.osv): try: conn = self.connect(conf) ldap_password = conf['ldap_password'] or '' - conn.simple_bind_s(conf['ldap_binddn'] or '', ldap_password.encode('utf-8')) + ldap_binddn = conf['ldap_binddn'] or '' + conn.simple_bind_s(ldap_binddn.encode('utf-8'), ldap_password.encode('utf-8')) results = conn.search_st(conf['ldap_base'], ldap.SCOPE_SUBTREE, filter, retrieve_attributes, timeout=60) conn.unbind() diff --git a/addons/auth_oauth/res_users.py b/addons/auth_oauth/res_users.py index d2aed236f75..f2c6be389f1 100644 --- a/addons/auth_oauth/res_users.py +++ b/addons/auth_oauth/res_users.py @@ -46,6 +46,20 @@ class res_users(osv.Model): validation.update(data) return validation + def _generate_signup_values(self, cr, uid, provider, validation, params, context=None): + oauth_uid = validation['user_id'] + email = validation.get('email', 'provider_%s_user_%s' % (provider, oauth_uid)) + name = validation.get('name', email) + return { + 'name': name, + 'login': email, + 'email': email, + 'oauth_provider_id': provider, + 'oauth_uid': oauth_uid, + 'oauth_access_token': params['access_token'], + 'active': True, + } + def _auth_oauth_signin(self, cr, uid, provider, validation, params, context=None): """ retrieve and sign in the user corresponding to provider and validated access token :param provider: oauth provider id (int) @@ -70,18 +84,7 @@ class res_users(osv.Model): return None state = simplejson.loads(params['state']) token = state.get('t') - oauth_uid = validation['user_id'] - email = validation.get('email', 'provider_%s_user_%s' % (provider, oauth_uid)) - name = validation.get('name', email) - values = { - 'name': name, - 'login': email, - 'email': email, - 'oauth_provider_id': provider, - 'oauth_uid': oauth_uid, - 'oauth_access_token': params['access_token'], - 'active': True, - } + values = self._generate_signup_values(cr, uid, provider, validation, params, context=context) try: _, login, _ = self.signup(cr, uid, values, token, context=context) return login diff --git a/addons/auth_signup/auth_signup_data.xml b/addons/auth_signup/auth_signup_data.xml index 5f5a05740ac..51a4dbfc676 100644 --- a/addons/auth_signup/auth_signup_data.xml +++ b/addons/auth_signup/auth_signup_data.xml @@ -6,8 +6,6 @@ Template User portaltemplate - - diff --git a/addons/auth_signup/res_users.py b/addons/auth_signup/res_users.py index de4714008b4..043a78fed58 100644 --- a/addons/auth_signup/res_users.py +++ b/addons/auth_signup/res_users.py @@ -247,7 +247,7 @@ class res_users(osv.Model): if not user_ids: user_ids = self.search(cr, uid, [('email', '=', login)], context=context) if len(user_ids) != 1: - raise Exception('Reset password: invalid username or email') + raise Exception(_('Reset password: invalid username or email')) return self.action_reset_password(cr, uid, user_ids, context=context) def action_reset_password(self, cr, uid, ids, context=None): diff --git a/addons/barcodes/static/src/js/barcode_parser.js b/addons/barcodes/static/src/js/barcode_parser.js index 00462f36a51..1f804a40fea 100644 --- a/addons/barcodes/static/src/js/barcode_parser.js +++ b/addons/barcodes/static/src/js/barcode_parser.js @@ -118,7 +118,7 @@ var BarcodeParser = Class.extend({ // - value: the numerical value encoded in the barcode (0 if no value encoded) // - base_code: the barcode in which numerical content is replaced by 0's // - match: boolean - match_pattern: function (barcode, pattern){ + match_pattern: function (barcode, pattern, encoding){ var match = { value: 0, base_code: barcode, @@ -156,6 +156,16 @@ var BarcodeParser = Class.extend({ .replace("\{", "{") .replace("\}","}") .replace("\.","."); + + var base_code = match.base_code.split('') + if (encoding === 'ean13') { + base_code[12] = '' + this.ean_checksum(match.base_code); + } else if (encoding === 'ean8') { + base_code[7] = '' + this.ean8_checksum(match.base_code); + } else if (encoding === 'upca') { + base_code[11] = '' + this.ean_checksum('0' + match.base_code); + } + match.base_code = base_code.join('') } if (base_pattern[0] !== '^') { @@ -208,7 +218,7 @@ var BarcodeParser = Class.extend({ continue; } - var match = this.match_pattern(cur_barcode,rules[i].pattern); + var match = this.match_pattern(cur_barcode, rules[i].pattern, rule.encoding); if (match.match) { if(rules[i].type === 'alias') { barcode = rules[i].alias; diff --git a/addons/base_action_rule/base_action_rule.py b/addons/base_action_rule/base_action_rule.py index 11dc5ab5a74..e84b9cc9d3e 100644 --- a/addons/base_action_rule/base_action_rule.py +++ b/addons/base_action_rule/base_action_rule.py @@ -2,7 +2,8 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from collections import defaultdict -from datetime import datetime, timedelta +from datetime import datetime +from dateutil.relativedelta import relativedelta import time import logging @@ -16,11 +17,11 @@ from openerp.tools.safe_eval import safe_eval as eval _logger = logging.getLogger(__name__) DATE_RANGE_FUNCTION = { - 'minutes': lambda interval: timedelta(minutes=interval), - 'hour': lambda interval: timedelta(hours=interval), - 'day': lambda interval: timedelta(days=interval), - 'month': lambda interval: timedelta(months=interval), - False: lambda interval: timedelta(0), + 'minutes': lambda interval: relativedelta(minutes=interval), + 'hour': lambda interval: relativedelta(hours=interval), + 'day': lambda interval: relativedelta(days=interval), + 'month': lambda interval: relativedelta(months=interval), + False: lambda interval: relativedelta(0), } def get_datetime(date_str): diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index 8005eed0ece..c09770126fd 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -96,7 +96,7 @@ class calendar_attendee(osv.Model): 'email': fields.char('Email', help="Email of Invited Person"), 'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"), 'access_token': fields.char('Invitation Token'), - 'event_id': fields.many2one('calendar.event', 'Meeting linked'), + 'event_id': fields.many2one('calendar.event', 'Meeting linked', ondelete='cascade'), } _defaults = { 'state': 'needsAction', @@ -1674,12 +1674,6 @@ class calendar_event(osv.Model): virtual_id = context.get('virtual_id', True) context.update({'virtual_id': False}) res = super(calendar_event, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby, lazy=lazy) - for result in res: - #remove the count, since the value is not consistent with the result of the search when expand the group - for groupname in groupby: - if result.get(groupname + "_count"): - del result[groupname + "_count"] - result.get('__context', {}).update({'virtual_id': virtual_id}) return res def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'): diff --git a/addons/crm/base_partner_merge_view.xml b/addons/crm/base_partner_merge_view.xml index bbbf27ee444..db580a6835d 100644 --- a/addons/crm/base_partner_merge_view.xml +++ b/addons/crm/base_partner_merge_view.xml @@ -86,6 +86,9 @@ type='object' attrs="{'invisible': [('state', 'in', ('option', 'finished' ))]}" /> + diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index a90cac606bd..37642848e9e 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -490,6 +490,12 @@ class mrp_production(osv.osv): result[prod.id]['cycle_total'] += wc.cycle return result + def _get_workcenter_line(self, cr, uid, ids, context=None): + result = {} + for line in self.pool['mrp.production.workcenter.line'].browse(cr, uid, ids, context=context): + result[line.production_id.id] = True + return result.keys() + def _src_id_default(self, cr, uid, ids, context=None): try: location_model, location_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_stock') @@ -588,8 +594,14 @@ class mrp_production(osv.osv): "If the stock is available then the status is set to 'Ready to Produce.\n" "When the production gets started then the status is set to 'In Production.\n" "When the production is over, the status is set to 'Done'."), - 'hour_total': fields.function(_production_calc, type='float', string='Total Hours', multi='workorder', store=True), - 'cycle_total': fields.function(_production_calc, type='float', string='Total Cycles', multi='workorder', store=True), + 'hour_total': fields.function(_production_calc, type='float', string='Total Hours', multi='workorder', store={ + _name: (lambda self, cr, uid, ids, c={}: ids, ['workcenter_lines'], 40), + 'mrp.production.workcenter.line': (_get_workcenter_line, ['hour', 'cycle'], 40), + }), + 'cycle_total': fields.function(_production_calc, type='float', string='Total Cycles', multi='workorder', store={ + _name: (lambda self, cr, uid, ids, c={}: ids, ['workcenter_lines'], 40), + 'mrp.production.workcenter.line': (_get_workcenter_line, ['hour', 'cycle'], 40), + }), 'user_id': fields.many2one('res.users', 'Responsible'), 'company_id': fields.many2one('res.company', 'Company', required=True), 'ready_production': fields.function(_moves_assigned, type='boolean', string="Ready for production", store={'stock.move': (_mrp_from_move, ['state'], 10)}), diff --git a/addons/mrp/stock.py b/addons/mrp/stock.py index 18b6d772192..d9b19908988 100644 --- a/addons/mrp/stock.py +++ b/addons/mrp/stock.py @@ -14,7 +14,7 @@ class StockMove(osv.osv): _inherit = 'stock.move' _columns = { - 'production_id': fields.many2one('mrp.production', 'Production Order for Produced Products', select=True), + 'production_id': fields.many2one('mrp.production', 'Production Order for Produced Products', select=True, copy=False), 'raw_material_production_id': fields.many2one('mrp.production', 'Production Order for Raw Materials', select=True), 'consumed_for': fields.many2one('stock.move', 'Consumed for', help='Technical field used to make the traceability of produced products'), } @@ -156,6 +156,8 @@ class StockMove(osv.osv): quantity_rest_uom = move.product_uom_qty - self.pool.get("product.uom")._compute_qty_obj(cr, uid, move.product_id.uom_id, product_qty, move.product_uom) if float_compare(quantity_rest_uom, 0, precision_rounding=move.product_uom.rounding) != 0: new_mov = self.split(cr, uid, move, quantity_rest, context=context) + if move.production_id: + self.write(cr, uid, [new_mov], {'production_id': move.production_id.id}, context=context) res.append(new_mov) vals = {'restrict_lot_id': restrict_lot_id, 'restrict_partner_id': restrict_partner_id, @@ -187,9 +189,9 @@ class StockMove(osv.osv): production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])]) for prod_id in production_ids: production_obj.signal_workflow(cr, uid, [prod_id], 'button_produce') - for new_move in new_moves: - production_obj.write(cr, uid, production_ids, {'move_lines': [(4, new_move)]}) - res.append(new_move) + if move.production_id.id: + self.write(cr, uid, new_moves, {'production_id': move.production_id.id}, context=context) + res.append(new_moves) return res def write(self, cr, uid, ids, vals, context=None): diff --git a/addons/mrp/test/mrp_users.yml b/addons/mrp/test/mrp_users.yml index 8f89207a47d..414610feb19 100644 --- a/addons/mrp/test/mrp_users.yml +++ b/addons/mrp/test/mrp_users.yml @@ -5,7 +5,6 @@ company_id: base.main_company name: MRP Manager login: mam - password: mam email: mrp_manager@yourcompany.com - I added groups for MRP Manager. @@ -21,11 +20,10 @@ company_id: base.main_company name: MRP User login: mau - password: mau email: mrp_user@yourcompany.com - I added groups for MRP User. - !record {model: res.users, id: res_users_mrp_user}: groups_id: - - mrp.group_mrp_user \ No newline at end of file + - mrp.group_mrp_user diff --git a/addons/mrp/wizard/stock_move.py b/addons/mrp/wizard/stock_move.py index 1c6a0686ff9..35880b39f29 100644 --- a/addons/mrp/wizard/stock_move.py +++ b/addons/mrp/wizard/stock_move.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from openerp.osv import fields, osv +from openerp.tools import float_compare from openerp.tools.translate import _ import openerp.addons.decimal_precision as dp @@ -40,12 +41,26 @@ class stock_move_consume(osv.osv_memory): context = {} move_obj = self.pool.get('stock.move') uom_obj = self.pool.get('product.uom') + production_obj = self.pool.get('mrp.production') move_ids = context['active_ids'] + move = move_obj.browse(cr, uid, move_ids[0], context=context) + production_id = move.raw_material_production_id.id + production = production_obj.browse(cr, uid, production_id, context=context) + precision = self.pool['decimal.precision'].precision_get(cr, uid, 'Product Unit of Measure') + for data in self.browse(cr, uid, ids, context=context): - if move_ids and move_ids[0]: - move = move_obj.browse(cr, uid, move_ids[0], context=context) qty = uom_obj._compute_qty(cr, uid, data['product_uom'].id, data.product_qty, data.product_id.uom_id.id) - move_obj.action_consume(cr, uid, move_ids, - qty, data.location_id.id, restrict_lot_id=data.restrict_lot_id.id, - context=context) - return {'type': 'ir.actions.act_window_close'} + remaining_qty = move.product_qty - qty + #check for product quantity is less than previously planned + if float_compare(remaining_qty, 0, precision_digits=precision) >= 0: + move_obj.action_consume(cr, uid, move_ids, qty, data.location_id.id, restrict_lot_id=data.restrict_lot_id.id, context=context) + else: + consumed_qty = min(move.product_qty, qty) + new_moves = move_obj.action_consume(cr, uid, move_ids, consumed_qty, data.location_id.id, restrict_lot_id=data.restrict_lot_id.id, context=context) + #consumed more in wizard than previously planned + extra_more_qty = qty - consumed_qty + #create new line for a remaining qty of the product + extra_move_id = production_obj._make_consume_line_from_data(cr, uid, production, data.product_id, data.product_id.uom_id.id, extra_more_qty, False, 0, context=context) + move_obj.write(cr, uid, [extra_move_id], {'restrict_lot_id': data.restrict_lot_id.id}, context=context) + move_obj.action_done(cr, uid, [extra_move_id], context=context) + return {'type': 'ir.actions.act_window_close'} \ No newline at end of file diff --git a/addons/mrp_operations/mrp_operations.py b/addons/mrp_operations/mrp_operations.py index 14d06306617..fc5f29c414e 100644 --- a/addons/mrp_operations/mrp_operations.py +++ b/addons/mrp_operations/mrp_operations.py @@ -278,7 +278,8 @@ class mrp_production(osv.osv): if l.state in ('done','cancel','draft'): continue todo += l.move_dest_id_lines - if l.production_id and (l.production_id.date_finished > dt): + date_end = l.production_id.date_finished + if date_end and datetime.strptime(date_end, '%Y-%m-%d %H:%M:%S') > dt: if l.production_id.state not in ('done','cancel'): for wc in l.production_id.workcenter_lines: i = self.pool.get('resource.calendar').interval_min_get( diff --git a/addons/mrp_operations/test/workcenter_operations.yml b/addons/mrp_operations/test/workcenter_operations.yml index a595fe7f624..0432d4ad8e1 100644 --- a/addons/mrp_operations/test/workcenter_operations.yml +++ b/addons/mrp_operations/test/workcenter_operations.yml @@ -5,7 +5,6 @@ company_id: base.main_company name: MRP User login: maou - password: maou email: mrp_operation_user@yourcompany.com - I added groups for MRP User. diff --git a/addons/mrp_repair/mrp_repair.py b/addons/mrp_repair/mrp_repair.py index 0c894beef83..a47eb3eeac8 100644 --- a/addons/mrp_repair/mrp_repair.py +++ b/addons/mrp_repair/mrp_repair.py @@ -268,7 +268,7 @@ class mrp_repair(osv.osv): else: self.write(cr, uid, [o.id], {'state': 'confirmed'}) for line in o.operations: - if line.product_id.tracking != 'none': + if line.product_id.tracking != 'none' and not line.lot_id: raise UserError(_("Serial number is required for operation line with product '%s'") % (line.product_id.name)) mrp_line_obj.write(cr, uid, [l.id for l in o.operations], {'state': 'confirmed'}) return True diff --git a/addons/mrp_repair/test/mrp_repair_users.yml b/addons/mrp_repair/test/mrp_repair_users.yml index fe8a2f5babe..ba39b09ab0d 100644 --- a/addons/mrp_repair/test/mrp_repair_users.yml +++ b/addons/mrp_repair/test/mrp_repair_users.yml @@ -5,7 +5,6 @@ company_id: base.main_company name: MRP Manager login: marm - password: marm email: mrp_repair_manager@yourcompany.com - I added groups for MRP Repair Manager. @@ -20,11 +19,10 @@ company_id: base.main_company name: MRP User login: maru - password: maru email: mrp_repair_user@yourcompany.com - I added groups for MRP Repair User. - !record {model: res.users, id: res_mrp_repair_user}: groups_id: - - mrp.group_mrp_user \ No newline at end of file + - mrp.group_mrp_user diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py index 7a822be624f..0aa4710caaf 100644 --- a/addons/payment/models/payment_acquirer.py +++ b/addons/payment/models/payment_acquirer.py @@ -13,7 +13,7 @@ def _partner_format_address(address1=False, address2=False): def _partner_split_name(partner_name): - return [' '.join(partner_name.split()[-1:]), ' '.join(partner_name.split()[:-1])] + return [' '.join(partner_name.split()[:-1]), ' '.join(partner_name.split()[-1:])] class ValidationError(ValueError): diff --git a/addons/payment/views/res_partner_view.xml b/addons/payment/views/res_partner_view.xml index 193018e74e1..73c7e22fa04 100644 --- a/addons/payment/views/res_partner_view.xml +++ b/addons/payment/views/res_partner_view.xml @@ -9,8 +9,8 @@ - - + + diff --git a/addons/payment_authorize/tests/test_authorize.py b/addons/payment_authorize/tests/test_authorize.py index f49aefaee40..eda6c4d3a5c 100644 --- a/addons/payment_authorize/tests/test_authorize.py +++ b/addons/payment_authorize/tests/test_authorize.py @@ -62,8 +62,8 @@ class AuthorizeForm(AuthorizeCommon): 'return_url': None, 'x_currency_code': 'USD', 'x_invoice_num': 'SO004', - 'x_first_name': 'Buyer', - 'x_last_name': 'Norbert', + 'x_first_name': 'Norbert', + 'x_last_name': 'Buyer', 'x_address': 'Huge Street 2/543', 'x_city': 'Sin City', 'x_zip': '1000', diff --git a/addons/payment_ogone/controllers/main.py b/addons/payment_ogone/controllers/main.py index 42947d7a4b9..6443e85f426 100644 --- a/addons/payment_ogone/controllers/main.py +++ b/addons/payment_ogone/controllers/main.py @@ -31,7 +31,7 @@ class OgoneController(http.Controller): @http.route(['/payment/ogone/s2s/create_json'], type='json', auth='public') def ogone_s2s_create_json(self, **kwargs): - data = kwargs['params'] + data = kwargs acquirer_id = int(data.get('acquirer_id')) acquirer = request.env['payment.acquirer'].browse(acquirer_id) new_id = acquirer.s2s_process(data) diff --git a/addons/payment_ogone/models/ogone.py b/addons/payment_ogone/models/ogone.py index f6610544113..aafe2e1f438 100644 --- a/addons/payment_ogone/models/ogone.py +++ b/addons/payment_ogone/models/ogone.py @@ -17,7 +17,7 @@ from openerp.addons.payment_ogone.controllers.main import OgoneController from openerp.addons.payment_ogone.data import ogone from openerp.osv import osv, fields from openerp.tools import float_round, DEFAULT_SERVER_DATE_FORMAT -from openerp.tools.float_utils import float_compare +from openerp.tools.float_utils import float_compare, float_repr from openerp.tools.safe_eval import safe_eval _logger = logging.getLogger(__name__) @@ -148,12 +148,11 @@ class PaymentAcquirerOgone(osv.Model): def ogone_form_generate_values(self, cr, uid, id, partner_values, tx_values, context=None): base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url') acquirer = self.browse(cr, uid, id, context=context) - ogone_tx_values = dict(tx_values) temp_ogone_tx_values = { 'PSPID': acquirer.ogone_pspid, 'ORDERID': tx_values['reference'], - 'AMOUNT': '%d' % int(float_round(tx_values['amount'], 2) * 100), + 'AMOUNT': float_repr(float_round(tx_values['amount'], 2) * 100, 0), 'CURRENCY': tx_values['currency'] and tx_values['currency'].name or '', 'LANGUAGE': partner_values['lang'], 'CN': partner_values['name'], @@ -289,9 +288,11 @@ class PaymentTxOgone(osv.Model): if status in self._ogone_valid_tx_status: tx.write({ 'state': 'done', - 'date_validate': data['TRXDATE'], + 'date_validate': datetime.datetime.strptime(data['TRXDATE'],'%m/%d/%y').strftime(DEFAULT_SERVER_DATE_FORMAT), 'acquirer_reference': data['PAYID'], }) + if tx.s2s_cb_eval: + safe_eval(tx.s2s_cb_eval, {'self': tx}) return True elif status in self._ogone_cancel_tx_status: tx.write({ @@ -305,9 +306,9 @@ class PaymentTxOgone(osv.Model): }) else: error = 'Ogone: feedback error: %(error_str)s\n\n%(error_code)s: %(error_msg)s' % { - 'error_str': data.get('NCERROR'), - 'error_code': data.get('NCERRORPLUS'), - 'error_msg': ogone.OGONE_ERROR_MAP.get(data.get('NCERRORPLUS')), + 'error_str': data.get('NCERRORPLUS'), + 'error_code': data.get('NCERROR'), + 'error_msg': ogone.OGONE_ERROR_MAP.get(data.get('NCERROR')), } _logger.info(error) tx.write({ @@ -406,9 +407,9 @@ class PaymentTxOgone(osv.Model): return self.ogone_s2s_validate(tx, tree, tries - 1) else: error = 'Ogone: feedback error: %(error_str)s\n\n%(error_code)s: %(error_msg)s' % { - 'error_str': tree.get('NCERROR'), - 'error_code': tree.get('NCERRORPLUS'), - 'error_msg': ogone.OGONE_ERROR_MAP.get(tree.get('NCERRORPLUS')), + 'error_str': tree.get('NCERRORPLUS'), + 'error_code': tree.get('NCERROR'), + 'error_msg': ogone.OGONE_ERROR_MAP.get(tree.get('NCERROR')), } _logger.info(error) tx.write({ diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 4e92debba17..0945eff0ed0 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -604,26 +604,37 @@ class pos_order(osv.osv): order['pos_session_id'] = rescue_session_id session = self.pool['pos.session'].browse(cr, uid, rescue_session_id, context=context) order_id = self.create(cr, uid, self._order_fields(cr, uid, order, context=context),context) - + journal_ids = set() for payments in order['statement_ids']: self.add_payment(cr, uid, order_id, self._payment_fields(cr, uid, payments[2], context=context), context=context) + journal_ids.add(payments[2]['journal_id']) if session.sequence_number <= order['sequence_number']: session.write({'sequence_number': order['sequence_number'] + 1}) session.refresh() if not float_is_zero(order['amount_return'], self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')): - cash_journal = session.cash_journal_id + cash_journal = session.cash_journal_id.id if not cash_journal: - cash_journal_ids = filter(lambda st: st.journal_id.type=='cash', session.statement_ids) - if not len(cash_journal_ids): - raise UserError(_("No cash statement found for this session. Unable to record returned cash.")) - cash_journal = cash_journal_ids[0].journal_id + # Select for change one of the cash journals used in this payment + cash_journal_ids = self.pool['account.journal'].search(cr, uid, [ + ('type', '=', 'cash'), + ('id', 'in', list(journal_ids)), + ], limit=1, context=context) + if not cash_journal_ids: + # If none, select for change one of the cash journals of the POS + # This is used for example when a customer pays by credit card + # an amount higher than total amount of the order and gets cash back + cash_journal_ids = [statement.journal_id.id for statement in session.statement_ids + if statement.journal_id.type == 'cash'] + if not cash_journal_ids: + raise UserError(_("No cash statement found for this session. Unable to record returned cash.")) + cash_journal = cash_journal_ids[0] self.add_payment(cr, uid, order_id, { 'amount': -order['amount_return'], 'payment_date': time.strftime('%Y-%m-%d %H:%M:%S'), 'payment_name': _('return'), - 'journal': cash_journal.id, + 'journal': cash_journal, }, context=context) return order_id @@ -703,7 +714,8 @@ class pos_order(osv.osv): val1 += self._amount_line_tax(cr, uid, line, context=context) val2 += line.price_subtotal res[order.id]['amount_tax'] = cur_obj.round(cr, uid, cur, val1) - res[order.id]['amount_total'] = cur_obj.round(cr, uid, cur, val1+val2) + amount_untaxed = cur_obj.round(cr, uid, cur, val2) + res[order.id]['amount_total'] = res[order.id]['amount_tax'] + amount_untaxed return res _columns = { @@ -804,6 +816,8 @@ class pos_order(osv.osv): move_obj = self.pool.get('stock.move') for order in self.browse(cr, uid, ids, context=context): + if all(t == 'service' for t in order.lines.mapped('product_id.type')): + continue addr = order.partner_id and partner_obj.address_get(cr, uid, [order.partner_id.id], ['delivery']) or {} picking_type = order.picking_type_id picking_id = False @@ -878,9 +892,14 @@ class pos_order(osv.osv): statement_line_obj = self.pool.get('account.bank.statement.line') property_obj = self.pool.get('ir.property') order = self.browse(cr, uid, order_id, context=context) + date = data.get('payment_date', time.strftime('%Y-%m-%d')) + if len(date) > 10: + timestamp = datetime.strptime(date, tools.DEFAULT_SERVER_DATETIME_FORMAT) + ts = fields.datetime.context_timestamp(cr, uid, timestamp, context) + date = ts.strftime(tools.DEFAULT_SERVER_DATE_FORMAT) args = { 'amount': data['amount'], - 'date': data.get('payment_date', time.strftime('%Y-%m-%d')), + 'date': date, 'name': order.name + ': ' + (data.get('payment_name', '') or ''), 'partner_id': order.partner_id and self.pool.get("res.partner")._find_accounting_partner(order.partner_id).id or False, } diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js index 9a8df01feae..1160fcf9807 100644 --- a/addons/point_of_sale/static/src/js/db.js +++ b/addons/point_of_sale/static/src/js/db.js @@ -382,7 +382,7 @@ var PosDB = core.Class.extend({ search_product_in_category: function(category_id, query){ try { query = query.replace(/[\[\]\(\)\+\*\?\.\-\!\&\^\$\|\~\_\{\}\:\,\\\/]/g,'.'); - query = query.replace(' ','.+'); + query = query.replace(/ /g,'.+'); var re = RegExp("([0-9]+):.*?"+query,"gi"); }catch(e){ return []; diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 45b7db18481..4a51acc3d36 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -287,7 +287,7 @@ exports.PosModel = Backbone.Model.extend({ loaded: function(self, pricelists){ self.pricelist = pricelists[0]; }, },{ model: 'res.currency', - fields: ['symbol','position','rounding','accuracy'], + fields: ['name','symbol','position','rounding','accuracy'], ids: function(self){ return [self.pricelist.currency_id[0]]; }, loaded: function(self, currencies){ self.currency = currencies[0]; @@ -1826,9 +1826,7 @@ exports.Order = Backbone.Model.extend({ }), 0), this.pos.currency.rounding); }, get_total_with_tax: function() { - return round_pr(this.orderlines.reduce((function(sum, orderLine) { - return sum + orderLine.get_price_with_tax(); - }), 0), this.pos.currency.rounding); + return this.get_total_without_tax() + this.get_total_tax(); }, get_total_without_tax: function() { return round_pr(this.orderlines.reduce((function(sum, orderLine) { diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index 192d70cbeba..0b9a2168d28 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -1508,7 +1508,9 @@ var PaymentScreenWidget = ScreenWidget.extend({ if (event.type === "keypress") { if (event.keyCode === 13) { // Enter self.validate_order(); - } else if (event.keyCode === 46) { // Period + } else if ( event.keyCode === 190 || // Dot + event.keyCode === 110 || // Decimal point (numpad) + event.keyCode === 188 ) { // Comma key = '.'; } else if (event.keyCode >= 48 && event.keyCode <= 57) { // Numbers key = '' + (event.keyCode - 48); diff --git a/addons/point_of_sale/views/report_receipt.xml b/addons/point_of_sale/views/report_receipt.xml index c17d8add90f..468e37b7965 100644 --- a/addons/point_of_sale/views/report_receipt.xml +++ b/addons/point_of_sale/views/report_receipt.xml @@ -31,7 +31,7 @@ - + diff --git a/addons/procurement/procurement.py b/addons/procurement/procurement.py index a2014b5fa0f..89ce89cc6bb 100644 --- a/addons/procurement/procurement.py +++ b/addons/procurement/procurement.py @@ -63,7 +63,7 @@ class procurement_rule(osv.osv): return [] _columns = { - 'name': fields.char('Name', required=True, + 'name': fields.char('Name', required=True, translate=True, help="This field will fill the packing origin and the name of its moves"), 'active': fields.boolean('Active', help="If unchecked, it will allow you to hide the rule without removing it."), 'group_propagation_option': fields.selection([('none', 'Leave Empty'), ('propagate', 'Propagate'), ('fixed', 'Fixed')], string="Propagation of Procurement Group"), diff --git a/addons/procurement_jit/procurement_jit.py b/addons/procurement_jit/procurement_jit.py index a554c91267b..43ae4a65abd 100644 --- a/addons/procurement_jit/procurement_jit.py +++ b/addons/procurement_jit/procurement_jit.py @@ -15,12 +15,20 @@ class procurement_order(osv.osv): self.check(cr, uid, [procurement_id], context=context) return procurement_id - def run(self, cr, uid, ids, context=None): + def run(self, cr, uid, ids, autocommit=False, context=None): context = dict(context or {}, procurement_autorun_defer=True) - res = super(procurement_order, self).run(cr, uid, ids, context=context) + res = super(procurement_order, self).run(cr, uid, ids, autocommit=autocommit, context=context) procurement_ids = self.search(cr, uid, [('move_dest_id.procurement_id', 'in', ids)], order='id', context=context) if procurement_ids: - return self.run(cr, uid, procurement_ids, context=context) + return self.run(cr, uid, procurement_ids, autocommit=autocommit, context=context) + return res + +class stock_move(osv.osv): + _inherit = "stock.move" + + def _create_procurements(self, cr, uid, moves, context=None): + res = super(stock_move, self)._create_procurements(cr, uid, moves, context=dict(context or {}, procurement_autorun_defer=True)) + self.pool['procurement.order'].run(cr, uid, res, context=context) return res diff --git a/addons/product/product.py b/addons/product/product.py index f8de6f269a4..c23366ee78b 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -697,7 +697,7 @@ class product_template(osv.osv): # unlink or inactive product for variant_id in map(int,variants_inactive): try: - with cr.savepoint(): + with cr.savepoint(), tools.mute_logger('openerp.sql_db'): product_obj.unlink(cr, uid, [variant_id], context=ctx) #We catch all kind of exception to be sure that the operation doesn't fail. except (psycopg2.Error, except_orm): @@ -931,12 +931,12 @@ class product_product(osv.osv): def _set_image_variant(self, cr, uid, id, name, value, args, context=None): image = tools.image_resize_image_big(value) - res = self.write(cr, uid, [id], {'image_variant': image}, context=context) + product = self.browse(cr, uid, id, context=context) - if not product.product_tmpl_id.image: - product.write({'image_variant': None}) - product.product_tmpl_id.write({'image': image}) - return res + if product.product_tmpl_id.image: + product.image_variant = image + else: + product.product_tmpl_id.image = image def _get_price_extra(self, cr, uid, ids, name, args, context=None): result = dict.fromkeys(ids, False) diff --git a/addons/product_extended/product_extended.py b/addons/product_extended/product_extended.py index ce31ef2c035..9b3defeec5b 100644 --- a/addons/product_extended/product_extended.py +++ b/addons/product_extended/product_extended.py @@ -56,11 +56,10 @@ class product_template(osv.osv): uom_obj = self.pool.get("product.uom") tmpl_obj = self.pool.get('product.template') for sbom in bom.bom_line_ids: - my_qty = sbom.product_qty + my_qty = sbom.product_qty / sbom.product_efficiency if not sbom.attribute_value_ids: - bom_product = sbom.product_id # No attribute_value_ids means the bom line is not variant specific - price += uom_obj._compute_price(cr, uid, bom_product.uom_po_id and bom_product.uom_po_id.id or bom_product.uom_id.id, bom_product.standard_price, sbom.product_uom.id) * my_qty + price += uom_obj._compute_price(cr, uid, sbom.product_id.uom_id.id, sbom.product_id.standard_price, sbom.product_uom.id) * my_qty if bom.routing_id: for wline in bom.routing_id.workcenter_lines: diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml index a665e5d9dda..cd50e74cac7 100644 --- a/addons/project_issue/project_issue_view.xml +++ b/addons/project_issue/project_issue_view.xml @@ -248,7 +248,7 @@ project.issue form Issues - kanban,tree,form,calendar,graph + kanban,tree,form,calendar,graph,pivot { 'search_default_project_id': [active_id], 'default_project_id': active_id, diff --git a/addons/project_issue/test/issue_users.yml b/addons/project_issue/test/issue_users.yml index 7ec9d22c7e2..a0ad327b86b 100644 --- a/addons/project_issue/test/issue_users.yml +++ b/addons/project_issue/test/issue_users.yml @@ -5,7 +5,6 @@ company_id: base.main_company name: Project Manager login: prim - password: prim email: issuemanager@yourcompany.com - I added groups for Project manager. @@ -20,11 +19,10 @@ company_id: base.main_company name: Project User login: priu - password: priu email: issueuser@yourcompany.com - I added groups for Project user. - !record {model: res.users, id: res_users_project_issue_user}: groups_id: - - project.group_project_user \ No newline at end of file + - project.group_project_user diff --git a/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml b/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml index 5d442f34ba4..5a38fbe61dd 100644 --- a/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml +++ b/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml @@ -5,7 +5,6 @@ company_id: base.main_company name: HR Manager login: hr - password: hr groups_id: - base.group_hr_manager - diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index f7c1153b4ba..182a60576cc 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -860,6 +860,7 @@ class purchase_order(osv.osv): } picking_id = self.pool.get('stock.picking').create(cr, uid, picking_vals, context=context) self._create_stock_moves(cr, uid, order, order.order_line, picking_id, context=context) + return picking_id def picking_done(self, cr, uid, ids, context=None): self.write(cr, uid, ids, {'shipped':1,'state':'approved'}, context=context) @@ -1372,7 +1373,11 @@ class procurement_order(osv.osv): names_dict[id] = name for procurement in procurements: taxes_ids = procurement.product_id.supplier_taxes_id - taxes = acc_pos_obj.map_tax(cr, uid, partner.property_account_position_id, taxes_ids) + # It is necessary to have the appropriate fiscal position to get the right tax mapping + fp = acc_pos_obj.get_fiscal_position(cr, uid, None, partner.id, context=context) + if fp: + fp = acc_pos_obj.browse(cr, uid, fp, context=context) + taxes = acc_pos_obj.map_tax(cr, uid, fp, taxes_ids) name = names_dict[procurement.product_id.id] if procurement.product_id.description_purchase: name += '\n' + procurement.product_id.description_purchase @@ -1493,6 +1498,7 @@ class procurement_order(osv.osv): po_line_obj = self.pool.get('purchase.order.line') seq_obj = self.pool.get('ir.sequence') uom_obj = self.pool.get('product.uom') + acc_pos_obj = self.pool.get('account.fiscal.position') add_purchase_procs, create_purchase_procs = self._get_grouping_dicts(cr, uid, ids, context=context) procs_done = [] @@ -1580,6 +1586,7 @@ class procurement_order(osv.osv): name = seq_obj.next_by_code(cr, uid, 'purchase.order') or _('PO: %s') % procurement.name gpo = procurement.rule_id.group_propagation_option group = (gpo == 'fixed' and procurement.rule_id.group_id.id) or (gpo == 'propagate' and procurement.group_id.id) or False + fp = acc_pos_obj.get_fiscal_position(cr, uid, None, partner.id, context=context) po_vals = { 'name': name, 'origin': procurement.origin, @@ -1589,7 +1596,7 @@ class procurement_order(osv.osv): 'pricelist_id': partner.property_product_pricelist_purchase.id, 'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT), 'company_id': procurement.company_id.id, - 'fiscal_position_id': partner.property_account_position_id.id, + 'fiscal_position_id': fp, 'payment_term_id': partner.property_supplier_payment_term_id.id, 'dest_address_id': procurement.partner_dest_id.id, 'group_id': group, @@ -1719,7 +1726,8 @@ class account_invoice(osv.Model): not all(picking.invoice_state in ['invoiced'] for picking in order.picking_ids)): shipped = False for po_line in order.order_line: - if all(line.invoice_id.state not in ['draft', 'cancel'] for line in po_line.invoice_lines): + if (po_line.invoice_lines and + all(line.invoice_id.state not in ['draft', 'cancel'] for line in po_line.invoice_lines)): invoiced.append(po_line.id) if invoiced and shipped: self.pool['purchase.order.line'].write(cr, user_id, invoiced, {'invoiced': True}) diff --git a/addons/purchase/report/purchase_report.py b/addons/purchase/report/purchase_report.py index d1af33f4cc8..721d1b061ba 100644 --- a/addons/purchase/report/purchase_report.py +++ b/addons/purchase/report/purchase_report.py @@ -52,6 +52,15 @@ class purchase_report(osv.osv): tools.sql.drop_view_if_exists(cr, 'purchase_report') cr.execute(""" create or replace view purchase_report as ( + WITH currency_rate (currency_id, rate, date_start, date_end) AS ( + SELECT r.currency_id, r.rate, r.name AS date_start, + (SELECT name FROM res_currency_rate r2 + WHERE r2.name > r.name AND + r2.currency_id = r.currency_id + ORDER BY r2.name ASC + LIMIT 1) AS date_end + FROM res_currency_rate r + ) select min(l.id) as id, s.date_order as date, @@ -75,10 +84,10 @@ class purchase_report(osv.osv): extract(epoch from age(s.date_approve,s.date_order))/(24*60*60)::decimal(16,2) as delay, extract(epoch from age(l.date_planned,s.date_order))/(24*60*60)::decimal(16,2) as delay_pass, count(*) as nbr, - sum(l.price_unit*l.product_qty)::decimal(16,2) as price_total, - avg(100.0 * (l.price_unit*l.product_qty) / NULLIF(ip.value_float*l.product_qty/u.factor*u2.factor, 0.0))::decimal(16,2) as negociation, + sum(l.price_unit*cr.rate*l.product_qty)::decimal(16,2) as price_total, + avg(100.0 * (l.price_unit*cr.rate*l.product_qty) / NULLIF(ip.value_float*l.product_qty/u.factor*u2.factor, 0.0))::decimal(16,2) as negociation, sum(ip.value_float*l.product_qty/u.factor*u2.factor)::decimal(16,2) as price_standard, - (sum(l.product_qty*l.price_unit)/NULLIF(sum(l.product_qty/u.factor*u2.factor),0.0))::decimal(16,2) as price_average, + (sum(l.product_qty*cr.rate*l.price_unit)/NULLIF(sum(l.product_qty/u.factor*u2.factor),0.0))::decimal(16,2) as price_average, partner.country_id as country_id, partner.commercial_partner_id as commercial_partner_id, analytic_account.id as account_analytic_id @@ -92,6 +101,9 @@ class purchase_report(osv.osv): left join product_uom u2 on (u2.id=t.uom_id) left join stock_picking_type spt on (spt.id=s.picking_type_id) left join account_analytic_account analytic_account on (l.account_analytic_id = analytic_account.id) + join currency_rate cr on (cr.currency_id = s.currency_id and + cr.date_start <= coalesce(s.date_order, now()) and + (cr.date_end is null or cr.date_end > coalesce(s.date_order, now()))) group by s.company_id, s.create_uid, diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py index 1c6762c7709..f18ff4574ec 100644 --- a/addons/purchase/stock.py +++ b/addons/purchase/stock.py @@ -76,9 +76,12 @@ class stock_move(osv.osv): return invoice_line_id def _get_master_data(self, cr, uid, move, company, context=None): - if context.get('inv_type') in ('in_invoice', 'in_refund') and move.purchase_line_id: + if context.get('inv_type') == 'in_invoice' and move.purchase_line_id: purchase_order = move.purchase_line_id.order_id return purchase_order.partner_id, purchase_order.create_uid.id, purchase_order.currency_id.id + if context.get('inv_type') == 'in_refund' and move.origin_returned_move_id.purchase_line_id: + purchase_order = move.origin_returned_move_id.purchase_line_id.order_id + return purchase_order.partner_id, purchase_order.create_uid.id, purchase_order.currency_id.id elif context.get('inv_type') in ('in_invoice', 'in_refund') and move.picking_id: # In case of an extra move, it is better to use the data from the original moves for purchase_move in move.picking_id.move_lines: @@ -96,10 +99,14 @@ class stock_move(osv.osv): def _get_invoice_line_vals(self, cr, uid, move, partner, inv_type, context=None): res = super(stock_move, self)._get_invoice_line_vals(cr, uid, move, partner, inv_type, context=context) - if inv_type in ('in_invoice', 'in_refund') and move.purchase_line_id: + if inv_type == 'in_invoice' and move.purchase_line_id: purchase_line = move.purchase_line_id res['invoice_line_tax_ids'] = [(6, 0, [x.id for x in purchase_line.taxes_id])] res['price_unit'] = purchase_line.price_unit + elif inv_type == 'in_refund' and move.origin_returned_move_id.purchase_line_id: + purchase_line = move.origin_returned_move_id.purchase_line_id + res['invoice_line_tax_id'] = [(6, 0, [x.id for x in purchase_line.taxes_id])] + res['price_unit'] = purchase_line.price_unit return res def _get_moves_taxes(self, cr, uid, moves, inv_type, context=None): @@ -203,7 +210,7 @@ class stock_warehouse(osv.osv): _columns = { 'buy_to_resupply': fields.boolean('Purchase to resupply this warehouse', help="When products are bought, they can be delivered to this warehouse"), - 'buy_pull_id': fields.many2one('procurement.rule', 'BUY rule'), + 'buy_pull_id': fields.many2one('procurement.rule', 'Buy rule'), } _defaults = { 'buy_to_resupply': True, diff --git a/addons/purchase/stock_view.xml b/addons/purchase/stock_view.xml index 1021550734f..8300cb6b892 100644 --- a/addons/purchase/stock_view.xml +++ b/addons/purchase/stock_view.xml @@ -18,7 +18,7 @@ - + diff --git a/addons/purchase/test/ui/purchase_users.yml b/addons/purchase/test/ui/purchase_users.yml index e15998589d0..872af41a6d8 100644 --- a/addons/purchase/test/ui/purchase_users.yml +++ b/addons/purchase/test/ui/purchase_users.yml @@ -5,7 +5,6 @@ company_id: base.main_company name: Purchase Manager login: pm - password: pm email: purchasemanager@yourcompany.com - I added groups for Purchase manager. @@ -20,7 +19,6 @@ company_id: base.main_company name: Purchase User login: pu - password: pu email: purchaseuser@yourcompany.com - I added groups for Purchase user. diff --git a/addons/purchase_requisition/test/purchase_requisition_users.yml b/addons/purchase_requisition/test/purchase_requisition_users.yml index 4e66d70a25c..61f9c0952cb 100644 --- a/addons/purchase_requisition/test/purchase_requisition_users.yml +++ b/addons/purchase_requisition/test/purchase_requisition_users.yml @@ -5,7 +5,6 @@ company_id: base.main_company name: Purchase requisition Manager login: prm - password: prm email: requisition_manager@yourcompany.com - I added groups for Purchase Requisition Manager. @@ -20,11 +19,10 @@ company_id: base.main_company name: Purchase requisition User login: pru - password: pru email: requisition_user@yourcompany.com - I added groups for Purchase Requisition User. - !record {model: res.users, id: res_users_purchase_requisition_user}: groups_id: - - purchase_requisition.group_purchase_requisition_user \ No newline at end of file + - purchase_requisition.group_purchase_requisition_user diff --git a/addons/report/models/report.py b/addons/report/models/report.py index 4e037626073..97e5e28fbc3 100644 --- a/addons/report/models/report.py +++ b/addons/report/models/report.py @@ -330,6 +330,8 @@ class Report(osv.Model): :returns: Content of the pdf as a string """ command_args = [] + if wkhtmltopdf_state == "ok": + command_args.extend(['--viewport-size', '1024x768']) # Passing the cookie to wkhtmltopdf in order to resolve internal links. try: diff --git a/addons/report/static/src/css/report.css b/addons/report/static/src/css/report.css index e95e7a9742d..2bc83bf7f61 100644 --- a/addons/report/static/src/css/report.css +++ b/addons/report/static/src/css/report.css @@ -5,6 +5,9 @@ body { .border-black td { border-top: 1px solid black !important; } +.table-condensed > tbody > tr { + page-break-inside: avoid !important; +} .table-condensed > thead > tr > th { border-bottom: 1px solid black !important; } diff --git a/addons/sale/report/sale_report.py b/addons/sale/report/sale_report.py index 2f2220d3f5e..97169fb66dc 100644 --- a/addons/sale/report/sale_report.py +++ b/addons/sale/report/sale_report.py @@ -47,11 +47,20 @@ class sale_report(osv.osv): def _select(self): select_str = """ + WITH currency_rate (currency_id, rate, date_start, date_end) AS ( + SELECT r.currency_id, r.rate, r.name AS date_start, + (SELECT name FROM res_currency_rate r2 + WHERE r2.name > r.name AND + r2.currency_id = r.currency_id + ORDER BY r2.name ASC + LIMIT 1) AS date_end + FROM res_currency_rate r + ) SELECT min(l.id) as id, l.product_id as product_id, t.uom_id as product_uom, sum(l.product_uom_qty / u.factor * u2.factor) as product_uom_qty, - sum(l.product_uom_qty * l.price_unit * (100.0-l.discount) / 100.0) as price_total, + sum(l.product_uom_qty * cr.rate * l.price_unit * (100.0-l.discount) / 100.0) as price_total, count(*) as nbr, s.date_order as date, s.date_confirm as date_confirm, @@ -81,6 +90,10 @@ class sale_report(osv.osv): left join product_template t on (p.product_tmpl_id=t.id) left join product_uom u on (u.id=l.product_uom) left join product_uom u2 on (u2.id=t.uom_id) + left join product_pricelist pp on (s.pricelist_id = pp.id) + join currency_rate cr on (cr.currency_id = pp.currency_id and + cr.date_start <= coalesce(s.date_order, now()) and + (cr.date_end is null or cr.date_end > coalesce(s.date_order, now()))) """ return from_str diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 4b82003fcf3..8c78faf9505 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -218,7 +218,7 @@ class sale_order(osv.osv): def _track_subtype(self, cr, uid, ids, init_values, context=None): record = self.browse(cr, uid, ids[0], context=context) - if 'state' in init_values and record.state == 'manual': + if 'state' in init_values and record.state in ['manual', 'progress']: return 'sale.mt_order_confirmed' elif 'state' in init_values and record.state == 'sent': return 'sale.mt_order_sent' @@ -524,8 +524,12 @@ class sale_order(osv.osv): return True def action_button_confirm(self, cr, uid, ids, context=None): + if not context: + context = {} assert len(ids) == 1, 'This option should only be used for a single id at a time.' self.signal_workflow(cr, uid, ids, 'order_confirm') + if context.get('send_email'): + self.force_quotation_send(cr, uid, ids, context=context) return True def action_wait(self, cr, uid, ids, context=None): @@ -575,6 +579,29 @@ class sale_order(osv.osv): 'context': ctx, } + def force_quotation_send(self, cr, uid, ids, context=None): + for order_id in ids: + email_act = self.action_quotation_send(cr, uid, [order_id], context=context) + if email_act and email_act.get('context'): + composer_obj = self.pool['mail.compose.message'] + composer_values = {} + email_ctx = email_act['context'] + template_values = [ + email_ctx.get('default_template_id'), + email_ctx.get('default_composition_mode'), + email_ctx.get('default_model'), + email_ctx.get('default_res_id'), + ] + composer_values.update(composer_obj.onchange_template_id(cr, uid, None, *template_values, context=context).get('value', {})) + if not composer_values.get('email_from'): + composer_values['email_from'] = self.browse(cr, uid, order_id, context=context).company_id.email + for key in ['attachment_ids', 'partner_ids']: + if composer_values.get(key): + composer_values[key] = [(6, 0, composer_values[key])] + composer_id = composer_obj.create(cr, uid, composer_values, context=email_ctx) + composer_obj.send_mail(cr, uid, [composer_id], context=email_ctx) + return True + def action_done(self, cr, uid, ids, context=None): for order in self.browse(cr, uid, ids, context=context): self.pool.get('sale.order.line').write(cr, uid, [line.id for line in order.order_line if line.state != 'cancel'], {'state': 'done'}, context=context) @@ -1069,13 +1096,17 @@ class sale_order_line(osv.osv): uom2 = product_obj.uom_id if pricelist and partner_id: + ctx = dict( + context, + uom=uom or result.get('product_uom'), + date=date_order, + ) price = self.pool['product.pricelist'].price_get(cr, uid, [pricelist], - product, qty or 1.0, partner_id, { - 'uom': uom or result.get('product_uom'), - 'date': date_order, - })[pricelist] + product, qty or 1.0, partner_id, ctx)[pricelist] else: price = Product.price_get(cr, uid, [product], ptype='list_price', context=ctx_product)[product] or False + if context.get('uom_qty_change', False): + result, domain = {}, {} result.update({'price_unit': price}) return {'value': result, 'domain': domain} diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index badf5d310dc..8a328570080 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -193,7 +193,7 @@