diff --git a/addons/account/account.py b/addons/account/account.py index cfba30856a9..15881100441 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -641,8 +641,7 @@ class account_account(osv.osv): return True def _check_allow_type_change(self, cr, uid, ids, new_type, context=None): - group1 = ['payable', 'receivable', 'other'] - group2 = ['consolidation','view'] + restricted_groups = ['consolidation','view'] line_obj = self.pool.get('account.move.line') for account in self.browse(cr, uid, ids, context=context): old_type = account.type @@ -650,14 +649,25 @@ class account_account(osv.osv): if line_obj.search(cr, uid, [('account_id', 'in', account_ids)]): #Check for 'Closed' type if old_type == 'closed' and new_type !='closed': - raise osv.except_osv(_('Warning!'), _("You cannot change the type of account from 'Closed' to any other type which contains journal items!")) - #Check for change From group1 to group2 and vice versa - if (old_type in group1 and new_type in group2) or (old_type in group2 and new_type in group1): - raise osv.except_osv(_('Warning!'), _("You cannot change the type of account from '%s' to '%s' type as it contains journal items!") % (old_type,new_type,)) + raise osv.except_osv(_('Warning !'), _("You cannot change the type of account from 'Closed' to any other type as it contains journal items!")) + # Forbid to change an account type for restricted_groups as it contains journal items (or if one of its children does) + if (new_type in restricted_groups): + raise osv.except_osv(_('Warning !'), _("You cannot change the type of account to '%s' type as it contains journal items!") % (new_type,)) + + return True + + # For legal reason (forbiden to modify journal entries which belongs to a closed fy or period), Forbid to modify + # the code of an account if journal entries have been already posted on this account. This cannot be simply + # 'configurable' since it can lead to a lack of confidence in OpenERP and this is what we want to change. + def _check_allow_code_change(self, cr, uid, ids, context=None): + line_obj = self.pool.get('account.move.line') + for account in self.browse(cr, uid, ids, context=context): + account_ids = self.search(cr, uid, [('id', 'child_of', [account.id])], context=context) + if line_obj.search(cr, uid, [('account_id', 'in', account_ids)], context=context): + raise osv.except_osv(_('Warning !'), _("You cannot change the code of account which contains journal items!")) return True def write(self, cr, uid, ids, vals, context=None): - if context is None: context = {} if not ids: @@ -677,6 +687,8 @@ class account_account(osv.osv): self._check_moves(cr, uid, ids, "write", context=context) if 'type' in vals.keys(): self._check_allow_type_change(cr, uid, ids, vals['type'], context=context) + if 'code' in vals.keys(): + self._check_allow_code_change(cr, uid, ids, context=context) return super(account_account, self).write(cr, uid, ids, vals, context=context) def unlink(self, cr, uid, ids, context=None): @@ -1470,6 +1482,11 @@ class account_move(osv.osv): raise osv.except_osv(_('User Error!'), _('You cannot delete a posted journal entry "%s".') % \ move['name']) + for line in move.line_id: + if line.invoice: + raise osv.except_osv(_('User Error!'), + _("Move cannot be deleted if linked to an invoice. (Invoice: %s - Move ID:%s)") % \ + (line.invoice.number,move.name)) line_ids = map(lambda x: x.id, move.line_id) context['journal_id'] = move.journal_id.id context['period_id'] = move.period_id.id @@ -1678,11 +1695,41 @@ class account_move_reconcile(osv.osv): 'line_id': fields.one2many('account.move.line', 'reconcile_id', 'Entry Lines'), 'line_partial_ids': fields.one2many('account.move.line', 'reconcile_partial_id', 'Partial Entry lines'), 'create_date': fields.date('Creation date', readonly=True), + 'opening_reconciliation': fields.boolean('Opening Entries Reconciliation', help="Is this reconciliation produced by the opening of a new fiscal year ?."), } _defaults = { 'name': lambda self,cr,uid,ctx=None: self.pool.get('ir.sequence').get(cr, uid, 'account.reconcile', context=ctx) or '/', } + + # You cannot unlink a reconciliation if it is a opening_reconciliation one, + # you should use the generate opening entries wizard for that + def unlink(self, cr, uid, ids, context=None): + for move_rec in self.browse(cr, uid, ids, context=context): + if move_rec.opening_reconciliation: + raise osv.except_osv(_('Error!'), _('You cannot unreconcile journal items if they has been generated by the \ + opening/closing fiscal year process.')) + return super(account_move_reconcile, self).unlink(cr, uid, ids, context=context) + + # Look in the line_id and line_partial_ids to ensure the partner is the same or empty + # on all lines. We allow that only for opening/closing period + def _check_same_partner(self, cr, uid, ids, context=None): + for reconcile in self.browse(cr, uid, ids, context=context): + move_lines = [] + if not reconcile.opening_reconciliation: + if reconcile.line_id: + first_partner = reconcile.line_id[0].partner_id.id + move_lines = reconcile.line_id + elif reconcile.line_partial_ids: + first_partner = reconcile.line_partial_ids[0].partner_id.id + move_lines = reconcile.line_partial_ids + if any([line.partner_id.id != first_partner for line in move_lines]): + return False + return True + _constraints = [ + (_check_same_partner, 'You can only reconcile journal items with the same partner.', ['line_id']), + ] + def reconcile_partial_check(self, cr, uid, ids, type='auto', context=None): total = 0.0 for rec in self.browse(cr, uid, ids, context=context): diff --git a/addons/account/account_bank_statement.py b/addons/account/account_bank_statement.py index 4422537007f..e7e3f740067 100644 --- a/addons/account/account_bank_statement.py +++ b/addons/account/account_bank_statement.py @@ -311,7 +311,7 @@ class account_bank_statement(osv.osv): 'statement_id': st_line.statement_id.id, 'journal_id': st_line.statement_id.journal_id.id, 'period_id': st_line.statement_id.period_id.id, - 'currency_id': cur_id, + 'currency_id': amount_currency and cur_id, 'amount_currency': amount_currency, 'analytic_account_id': analytic_id, } diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index 80a406bb0b7..6804d5116f1 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -1073,8 +1073,9 @@ class account_invoice(osv.osv): self.message_post(cr, uid, [inv_id], body=message, context=context) return True - def action_cancel(self, cr, uid, ids, *args): - context = {} # TODO: Use context from arguments + def action_cancel(self, cr, uid, ids, context=None): + if context is None: + context = {} account_move_obj = self.pool.get('account.move') invoices = self.read(cr, uid, ids, ['move_id', 'payment_ids']) move_ids = [] # ones that we will need to remove @@ -1244,12 +1245,15 @@ class account_invoice(osv.osv): ref = invoice.reference else: ref = self._convert_ref(cr, uid, invoice.number) + partner = invoice.partner_id + if partner.parent_id and not partner.is_company: + partner = partner.parent_id # Pay attention to the sign for both debit/credit AND amount_currency l1 = { 'debit': direction * pay_amount>0 and direction * pay_amount, 'credit': direction * pay_amount<0 and - direction * pay_amount, 'account_id': src_account_id, - 'partner_id': invoice.partner_id.id, + 'partner_id': partner.id, 'ref':ref, 'date': date, 'currency_id':currency_id, @@ -1260,7 +1264,7 @@ class account_invoice(osv.osv): 'debit': direction * pay_amount<0 and - direction * pay_amount, 'credit': direction * pay_amount>0 and direction * pay_amount, 'account_id': pay_account_id, - 'partner_id': invoice.partner_id.id, + 'partner_id': partner.id, 'ref':ref, 'date': date, 'currency_id':currency_id, diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index 00fad390282..3b2fe21d95b 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -620,12 +620,34 @@ class account_move_line(osv.osv): return False return True + def _check_currency_and_amount(self, cr, uid, ids, context=None): + for l in self.browse(cr, uid, ids, context=context): + if (l.amount_currency and not l.currency_id): + return False + return True + + def _check_currency_amount(self, cr, uid, ids, context=None): + for l in self.browse(cr, uid, ids, context=context): + if l.amount_currency: + if (l.amount_currency > 0.0 and l.credit > 0.0) or (l.amount_currency < 0.0 and l.debit > 0.0): + return False + return True + + def _check_currency_company(self, cr, uid, ids, context=None): + for l in self.browse(cr, uid, ids, context=context): + if l.currency_id.id == l.company_id.currency_id.id: + return False + return True + _constraints = [ (_check_no_view, 'You cannot create journal items on an account of type view.', ['account_id']), (_check_no_closed, 'You cannot create journal items on closed account.', ['account_id']), (_check_company_id, 'Account and Period must belong to the same company.', ['company_id']), (_check_date, 'The date of your Journal Entry is not in the defined period! You should change the date or remove this constraint from the journal.', ['date']), (_check_currency, 'The selected account of your Journal Entry forces to provide a secondary currency. You should remove the secondary currency on the account or select a multi-currency view on the journal.', ['currency_id']), + (_check_currency_and_amount, "You cannot create journal items with a secondary currency without recording both 'currency' and 'amount currency' field.", ['currency_id','amount_currency']), + (_check_currency_amount, 'The amount expressed in the secondary currency must be positif when journal item are debit and negatif when journal item are credit.', ['amount_currency']), + (_check_currency_company, "You cannot provide a secondary currency if it is the same than the company one." , ['currency_id']), ] #TODO: ONCHANGE_ACCOUNT_ID: set account_tax_id @@ -1111,7 +1133,7 @@ class account_move_line(osv.osv): 'has been confirmed.') % res[2]) return res - def _remove_move_reconcile(self, cr, uid, move_ids=None, context=None): + def _remove_move_reconcile(self, cr, uid, move_ids=None, opening_reconciliation=False, context=None): # Function remove move rencocile ids related with moves obj_move_line = self.pool.get('account.move.line') obj_move_rec = self.pool.get('account.move.reconcile') @@ -1126,6 +1148,8 @@ class account_move_line(osv.osv): unlink_ids += rec_ids unlink_ids += part_rec_ids if unlink_ids: + if opening_reconciliation: + obj_move_rec.write(cr, uid, unlink_ids, {'opening_reconciliation': False}) obj_move_rec.unlink(cr, uid, unlink_ids) return True diff --git a/addons/account/wizard/account_fiscalyear_close.py b/addons/account/wizard/account_fiscalyear_close.py index dbf815d5218..e11b4dcd34b 100644 --- a/addons/account/wizard/account_fiscalyear_close.py +++ b/addons/account/wizard/account_fiscalyear_close.py @@ -60,7 +60,7 @@ class account_fiscalyear_close(osv.osv_memory): cr.execute('select distinct(company_id) from account_move_line where id in %s',(tuple(ids),)) if len(cr.fetchall()) > 1: raise osv.except_osv(_('Warning!'), _('The entries to reconcile should belong to the same company.')) - r_id = self.pool.get('account.move.reconcile').create(cr, uid, {'type': 'auto'}) + r_id = self.pool.get('account.move.reconcile').create(cr, uid, {'type': 'auto', 'opening_reconciliation': True}) cr.execute('update account_move_line set reconcile_id = %s where id in %s',(r_id, tuple(ids),)) return r_id @@ -107,7 +107,7 @@ class account_fiscalyear_close(osv.osv_memory): ('journal_id', '=', new_journal.id), ('period_id', '=', period.id)]) if move_ids: move_line_ids = obj_acc_move_line.search(cr, uid, [('move_id', 'in', move_ids)]) - obj_acc_move_line._remove_move_reconcile(cr, uid, move_line_ids, context=context) + obj_acc_move_line._remove_move_reconcile(cr, uid, move_line_ids, opening_reconciliation=True, context=context) obj_acc_move_line.unlink(cr, uid, move_line_ids, context=context) obj_acc_move.unlink(cr, uid, move_ids, context=context) diff --git a/addons/account_payment/account_invoice.py b/addons/account_payment/account_invoice.py index 7138540fa3b..ec4feca9b81 100644 --- a/addons/account_payment/account_invoice.py +++ b/addons/account_payment/account_invoice.py @@ -20,12 +20,29 @@ ############################################################################## from datetime import datetime - +from tools.translate import _ from osv import fields, osv class Invoice(osv.osv): _inherit = 'account.invoice' + # Forbid to cancel an invoice if the related move lines have already been + # used in a payment order. The risk is that importing the payment line + # in the bank statement will result in a crash cause no more move will + # be found in the payment line + def action_cancel(self, cr, uid, ids, context=None): + payment_line_obj = self.pool.get('payment.line') + for inv in self.browse(cr, uid, ids, context=context): + pl_line_ids = [] + if inv.move_id and inv.move_id.line_id: + inv_mv_lines = [x.id for x in inv.move_id.line_id] + pl_line_ids = payment_line_obj.search(cr, uid, [('move_line_id','in',inv_mv_lines)], context=context) + if pl_line_ids: + pay_line = payment_line_obj.browse(cr, uid, pl_line_ids, context=context) + payment_order_name = ','.join(map(lambda x: x.order_id.reference, pay_line)) + raise osv.except_osv(_('Error!'), _("You cannot cancel an invoice which has already been imported in a payment order. Remove it from the following payment order : %s."%(payment_order_name))) + return super(Invoice, self).action_cancel(cr, uid, ids, context=context) + def _amount_to_pay(self, cursor, user, ids, name, args, context=None): '''Return the amount still to pay regarding all the payment orders''' if not ids: diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py index 32be8c34929..ffc7cf1769d 100644 --- a/addons/account_voucher/account_voucher.py +++ b/addons/account_voucher/account_voucher.py @@ -1549,6 +1549,15 @@ class account_bank_statement(osv.osv): return move_line_obj.write(cr, uid, [x.id for x in v.move_ids], {'statement_id': st_line.statement_id.id}, context=context) return super(account_bank_statement, self).create_move_from_st_line(cr, uid, st_line.id, company_currency_id, next_number, context=context) + def write(self, cr, uid, ids, vals, context=None): + # Restrict to modify the journal if we already have some voucher of reconciliation created/generated. + # Because the voucher keeps in memory the journal it was created with. + for bk_st in self.browse(cr, uid, ids, context=context): + if vals.get('journal_id') and bk_st.line_ids: + if any([x.voucher_id and True or False for x in bk_st.line_ids]): + raise osv.except_osv(_('Unable to change journal !'), _('You can not change the journal as you already reconciled some statement lines!')) + return super(account_bank_statement, self).write(cr, uid, ids, vals, context=context) + account_bank_statement() class account_bank_statement_line(osv.osv): diff --git a/addons/account_voucher/test/case5_suppl_usd_usd.yml b/addons/account_voucher/test/case5_suppl_usd_usd.yml index 3cae9667d84..4caeb19c847 100644 --- a/addons/account_voucher/test/case5_suppl_usd_usd.yml +++ b/addons/account_voucher/test/case5_suppl_usd_usd.yml @@ -4,10 +4,10 @@ - I create a cash account with currency USD - - !record {model: account.account, id: account_cash_usd_id}: + !record {model: account.account, id: account_cash_usd_id2}: currency_id: base.USD name: "cash account in usd" - code: "Xcash usd" + code: "Xcash usd2" type: 'liquidity' user_type: "account.data_account_type_cash" @@ -56,8 +56,8 @@ type: bank analytic_journal_id: account.sit sequence_id: account.sequence_bank_journal - default_debit_account_id: account_cash_usd_id - default_credit_account_id: account_cash_usd_id + default_debit_account_id: account_cash_usd_id2 + default_credit_account_id: account_cash_usd_id2 currency: base.USD company_id: base.main_company view_id: account.account_journal_bank_view diff --git a/addons/membership/membership.py b/addons/membership/membership.py index 12e1c89a04a..4ffd067d73e 100644 --- a/addons/membership/membership.py +++ b/addons/membership/membership.py @@ -483,16 +483,16 @@ class Invoice(osv.osv): '''Invoice''' _inherit = 'account.invoice' - def action_cancel(self, cr, uid, ids, *args): + def action_cancel(self, cr, uid, ids, context=None): '''Create a 'date_cancel' on the membership_line object''' member_line_obj = self.pool.get('membership.membership_line') today = time.strftime('%Y-%m-%d') - for invoice in self.browse(cr, uid, ids): + for invoice in self.browse(cr, uid, ids, context=context): mlines = member_line_obj.search(cr, uid, [('account_invoice_line', 'in', [l.id for l in invoice.invoice_line])]) member_line_obj.write(cr, uid, mlines, {'date_cancel': today}) - return super(Invoice, self).action_cancel(cr, uid, ids) + return super(Invoice, self).action_cancel(cr, uid, ids, context=context) Invoice() diff --git a/addons/purchase/report/request_quotation.rml b/addons/purchase/report/request_quotation.rml index 495c8790e7b..c1500c48710 100644 --- a/addons/purchase/report/request_quotation.rml +++ b/addons/purchase/report/request_quotation.rml @@ -149,11 +149,6 @@ [[ (order_line.product_uom and order_line.product_uom.name) or '' ]] - - - [[ format(order_line.notes or removeParentNode('tr')) ]] - -