diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py index a5ae75cefa9..bb0dd9b291a 100644 --- a/addons/account_voucher/account_voucher.py +++ b/addons/account_voucher/account_voucher.py @@ -651,13 +651,303 @@ class account_voucher(osv.osv): res['account_id'] = account_id return {'value':res} + def _sel_context(self, cr, uid, voucher_id,context=None): + """ + Select the context to use accordingly if it needs to be multicurrency or not. + + :param voucher_id: Id of the actual voucher + :return: The returned context will be the same as given in parameter if the voucher currency is the same + than the company currency, otherwise it's a copy of the parameter with an extra key 'date' containing + the date of the voucher. + :rtype: dict + """ + company_currency = self._get_company_currency(cr, uid, voucher_id, context) + current_currency = self._get_current_currency(cr, uid, voucher_id, context) + if current_currency <> company_currency: + context_multi_currency = context.copy() + voucher_brw = self.pool.get('account.voucher').browse(cr, uid, voucher_id, context) + context_multi_currency.update({'date': voucher_brw.date}) + return context_multi_currency + return context + + def first_move_line_get(self, cr, uid, voucher_id, move_id, company_currency, current_currency, context=None): + ''' + Return a dict to be use to create the first account move line of given voucher. + + :param voucher_id: Id of voucher what we are creating account_move. + :param move_id: Id of account move where this line will be added. + :param company_currency: id of currency of the company to which the voucher belong + :param current_currency: id of currency of the voucher + :return: mapping between fieldname and value of account move line to create + :rtype: dict + ''' + move_line_obj = self.pool.get('account.move.line') + currency_obj = self.pool.get('res.currency') + voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context) + debit = credit = 0.0 + # TODO: is there any other alternative then the voucher type ?? + # ANSWER: We can have payment and receipt "In Advance". + # TODO: Make this logic available. + # -for sale, purchase we have but for the payment and receipt we do not have as based on the bank/cash journal we can not know its payment or receipt + if voucher_brw.type in ('purchase', 'payment'): + credit = currency_obj.compute(cr, uid, current_currency, company_currency, voucher_brw.amount, context) + elif voucher_brw.type in ('sale', 'receipt'): + debit = currency_obj.compute(cr, uid, current_currency, company_currency, voucher_brw.amount, context) + if debit < 0: credit = -debit; debit = 0.0 + if credit < 0: debit = -credit; credit = 0.0 + sign = debit - credit < 0 and -1 or 1 + #set the first line of the voucher + move_line = { + 'name': voucher_brw.name or '/', + 'debit': debit, + 'credit': credit, + 'account_id': voucher_brw.account_id.id, + 'move_id': move_id, + 'journal_id': voucher_brw.journal_id.id, + 'period_id': voucher_brw.period_id.id, + 'partner_id': voucher_brw.partner_id.id, + 'currency_id': company_currency <> current_currency and current_currency or False, + 'amount_currency': company_currency <> current_currency and sign * voucher_brw.amount or 0.0, + 'date': voucher_brw.date, + 'date_maturity': voucher_brw.date_due + } + return move_line + + def account_move_get(self, cr, uid, voucher_id, context=None): + ''' + This method prepare the creation of the account move related to the given voucher. + + :param voucher_id: Id of voucher for which we are creating account_move. + :return: mapping between fieldname and value of account move to create + :rtype: dict + ''' + move_obj = self.pool.get('account.move') + seq_obj = self.pool.get('ir.sequence') + voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context) + if voucher_brw.number: + name = voucher_brw.number + elif voucher_brw.journal_id.sequence_id: + name = seq_obj.next_by_id(cr, uid, voucher_brw.journal_id.sequence_id.id) + else: + raise osv.except_osv(_('Error !'), + _('Please define a sequence on the journal !')) + if not voucher_brw.reference: + ref = name.replace('/','') + else: + ref = voucher_brw.reference + + move = { + 'name': name, + 'journal_id': voucher_brw.journal_id.id, + 'narration': voucher_brw.narration, + 'date': voucher_brw.date, + 'ref': ref, + 'period_id': voucher_brw.period_id and voucher_brw.period_id.id or False + } + return move + + def _get_exchange_lines(self, cr, uid, line, move_id, amount_residual, company_currency, current_currency, context=None): + ''' + Prepare the two lines due to currency rate difference. + + :param line: browse record of the voucher.line for which we want to create currency rate difference accounting entries + :param move_id: Account move wher the move lines will be. + :param amount_residual: Amount to be posted. + :param company_currency: id of currency of the company to which the voucher belong + :param current_currency: id of currency of the voucher + :return: the account move line and its counterpart to create, depicted as mapping between fieldname and value + :rtype: tuple of dict + ''' + if not line.voucher_id.exchange_acc_id.id: + raise osv.except_osv(_('Error!'), _('You must provide an account for the exchange difference.')) + + move_line = { + 'journal_id': line.voucher_id.journal_id.id, + 'period_id': line.voucher_id.period_id.id, + 'name': _('change')+': '+(line.name or '/'), + 'account_id': line.account_id.id, + 'move_id': move_id, + 'partner_id': line.voucher_id.partner_id.id, + 'currency_id': company_currency <> current_currency and current_currency or False, + 'amount_currency': 0.0, + 'quantity': 1, + 'credit': amount_residual > 0 and amount_residual or 0.0, + 'debit': amount_residual < 0 and -amount_residual or 0.0, + 'date': line.voucher_id.date, + } + move_line_counterpart = { + 'journal_id': line.voucher_id.journal_id.id, + 'period_id': line.voucher_id.period_id.id, + 'name': _('change')+': '+(line.name or '/'), + 'account_id': line.voucher_id.exchange_acc_id.id, + 'move_id': move_id, + 'amount_currency': 0.0, + 'partner_id': line.voucher_id.partner_id.id, + 'currency_id': company_currency <> current_currency and current_currency or False, + 'quantity': 1, + 'debit': amount_residual > 0 and amount_residual or 0.0, + 'credit': amount_residual < 0 and -amount_residual or 0.0, + 'date': line.voucher_id.date, + } + return (move_line, move_line_counterpart) + + def voucher_move_line_create(self, cr, uid, voucher_id, line_total, move_id, company_currency, current_currency, context=None): + ''' + Create one account move line, on the given account move, per voucher line where amount is not 0.0. + It returns Tuple with tot_line what is total of difference between debit and credit and + a list of lists with ids to be reconciled with this format (total_deb_cred,list_of_lists). + + :param voucher_id: Voucher id what we are working with + :param line_total: Amount of the first line, which correspond to the amount we should totally split among all voucher lines. + :param move_id: Account move wher those lines will be joined. + :param company_currency: id of currency of the company to which the voucher belong + :param current_currency: id of currency of the voucher + :return: Tuple build as (remaining amount not allocated on voucher lines, list of account_move_line created in this method) + :rtype: tuple(int, list of int) + ''' + move_line_obj = self.pool.get('account.move.line') + currency_obj = self.pool.get('res.currency') + tot_line = line_total + rec_lst_ids = [] + + voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context) + for line in voucher_brw.line_ids: + #create one move line per voucher line where amount is not 0.0 + if not line.amount: + continue + #we check if the voucher line is fully paid or not and create a move line to balance the payment and initial invoice if needed + if line.amount == line.amount_unreconciled: + amount = currency_obj.compute(cr, uid, current_currency, company_currency, line.untax_amount or line.amount, context) + amount_residual = line.move_line_id.amount_residual - amount #residual amount in company currency + else: + amount = currency_obj.compute(cr, uid, current_currency, company_currency, line.untax_amount or line.amount, context) + amount_residual = 0.0 + move_line = { + 'journal_id': voucher_brw.journal_id.id, + 'period_id': voucher_brw.period_id.id, + 'name': line.name or '/', + 'account_id': line.account_id.id, + 'move_id': move_id, + 'partner_id': voucher_brw.partner_id.id, + 'currency_id': company_currency <> current_currency and current_currency or False, + 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False, + 'quantity': 1, + 'credit': 0.0, + 'debit': 0.0, + 'date': voucher_brw.date + } + if amount < 0: + amount = -amount + if line.type == 'dr': + line.type = 'cr' + else: + line.type = 'dr' + + if (line.type=='dr'): + tot_line += amount + move_line['debit'] = amount + else: + tot_line -= amount + move_line['credit'] = amount + + if voucher_brw.tax_id and voucher_brw.type in ('sale', 'purchase'): + move_line.update({ + 'account_tax_id': voucher_brw.tax_id.id, + }) + + if move_line.get('account_tax_id', False): + tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0] + if not (tax_data.base_code_id and tax_data.tax_code_id): + raise osv.except_osv(_('No Account Base Code and Account Tax Code!'),_("You have to configure account base code and account tax code on the '%s' tax!") % (tax_data.name)) + + sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1 + move_line['amount_currency'] = company_currency <> current_currency and sign * line.amount or False + voucher_line = move_line_obj.create(cr, uid, move_line) + rec_ids = [voucher_line, line.move_line_id.id] + + if amount_residual: + # Change difference entry + exch_lines = self._get_exchange_lines(cr, uid, line, move_id, + amount_residual ,context) + new_id = move_line_obj.create(cr, uid, exch_lines[0],context) + move_line_obj.create(cr, uid, exch_lines[1], context) + rec_ids.append(new_id) + + if line.move_line_id.id: + rec_lst_ids.append(rec_ids) + + return (tot_line, rec_lst_ids) + + def writeoff_move_line_get(self, cr, uid, voucher_id, line_total, move_id, name, company_currency, current_currency, context=None): + ''' + Set a dict to be use to create the writeoff move line. + + :param voucher_id: Id of voucher what we are creating account_move. + :param line_total: Amount remaining to be allocated on lines. + :param move_id: Id of account move where this line will be added. + :param name: Description of account move line. + :param company_currency: id of currency of the company to which the voucher belong + :param current_currency: id of currency of the voucher + :return: mapping between fieldname and value of account move line to create + :rtype: dict + ''' + move_line_obj = self.pool.get('account.move.line') + currency_obj = self.pool.get('res.currency') + move_line = {} + + voucher_brw = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context) + current_currency_obj = voucher_brw.currency_id or voucher_brw.journal_id.company_id.currency_id + + if not currency_obj.is_zero(cr, uid, current_currency_obj, line_total): + diff = line_total + account_id = False + write_off_name = '' + if voucher_brw.payment_option == 'with_writeoff': + account_id = voucher_brw.writeoff_acc_id.id + write_off_name = voucher_brw.comment + elif voucher_brw.type in ('sale', 'receipt'): + account_id = voucher_brw.partner_id.property_account_receivable.id + else: + account_id = voucher_brw.partner_id.property_account_payable.id + move_line = { + 'name': write_off_name or name, + 'account_id': account_id, + 'move_id': move_id, + 'partner_id': voucher_brw.partner_id.id, + 'date': voucher_brw.date, + 'credit': diff > 0 and diff or 0.0, + 'debit': diff < 0 and -diff or 0.0, + 'amount_currency': company_currency <> current_currency and voucher_brw.writeoff_amount or False, + 'currency_id': company_currency <> current_currency and current_currency or False, + } + + return move_line + + def _get_company_currency(self, cr, uid, voucher_id, context=None): + ''' + Get the currency of the actual company. + + :param voucher_id: Id of the voucher what i want to obtain company currency. + :return: currency id of the company of the voucher + :rtype: int + ''' + return self.pool.get('account.voucher').browse(cr,uid,voucher_id,context).journal_id.company_id.currency_id.id + + def _get_current_currency(self, cr, uid, voucher_id, context=None): + ''' + Get the currency of the voucher. + + :param voucher_id: Id of the voucher what i want to obtain current currency. + :return: currency id of the voucher + :rtype: int + ''' + voucher = self.pool.get('account.voucher').browse(cr,uid,voucher_id,context) + return voucher.currency_id.id or self._get_company_currency(cr,uid,voucher.id,context) + def action_move_line_create(self, cr, uid, ids, context=None): - def _get_payment_term_lines(term_id, amount): - term_pool = self.pool.get('account.payment.term') - if term_id and amount: - terms = term_pool.compute(cr, uid, term_id, amount) - return terms - return False + ''' + Confirm the vouchers given in ids and create the journal entries for each of them + ''' if context is None: context = {} move_pool = self.pool.get('account.move') @@ -665,198 +955,44 @@ class account_voucher(osv.osv): currency_pool = self.pool.get('res.currency') tax_obj = self.pool.get('account.tax') seq_obj = self.pool.get('ir.sequence') - for inv in self.browse(cr, uid, ids, context=context): - if inv.move_id: + for voucher in self.browse(cr, uid, ids, context=context): + if voucher.move_id: continue - context_multi_currency = context.copy() - context_multi_currency.update({'date': inv.date}) - - if inv.number: - name = inv.number - elif inv.journal_id.sequence_id: - name = seq_obj.next_by_id(cr, uid, inv.journal_id.sequence_id.id) - else: - raise osv.except_osv(_('Error !'), _('Please define a sequence on the journal !')) - if not inv.reference: - ref = name.replace('/','') - else: - ref = inv.reference - - move = { - 'name': name, - 'journal_id': inv.journal_id.id, - 'narration': inv.narration, - 'date': inv.date, - 'ref': ref, - 'period_id': inv.period_id and inv.period_id.id or False - } - move_id = move_pool.create(cr, uid, move) - - #create the first line manually - company_currency = inv.journal_id.company_id.currency_id.id - current_currency = inv.currency_id.id or company_currency - current_currency_obj = inv.currency_id or inv.journal_id.company_id.currency_id - debit = 0.0 - credit = 0.0 - # TODO: is there any other alternative then the voucher type ?? - # -for sale, purchase we have but for the payment and receipt we do not have as based on the bank/cash journal we can not know its payment or receipt - if inv.type in ('purchase', 'payment'): - credit = currency_pool.compute(cr, uid, current_currency, company_currency, inv.amount, context=context_multi_currency) - elif inv.type in ('sale', 'receipt'): - debit = currency_pool.compute(cr, uid, current_currency, company_currency, inv.amount, context=context_multi_currency) - if debit < 0: - credit = -debit - debit = 0.0 - if credit < 0: - debit = -credit - credit = 0.0 - sign = debit - credit < 0 and -1 or 1 - #create the first line of the voucher, the payment made - move_line = { - 'name': inv.name or '/', - 'debit': debit, - 'credit': credit, - 'account_id': inv.account_id.id, - 'move_id': move_id, - 'journal_id': inv.journal_id.id, - 'period_id': inv.period_id.id, - 'partner_id': inv.partner_id.id, - 'currency_id': company_currency <> current_currency and current_currency or False, - 'amount_currency': company_currency <> current_currency and sign * inv.amount or 0.0, - 'date': inv.date, - 'date_maturity': inv.date_due - } - move_line_pool.create(cr, uid, move_line) + company_currency = self._get_company_currency(cr, uid, voucher.id, context) + current_currency = self._get_current_currency(cr, uid, voucher.id, context) + context = self._sel_context(cr, uid, voucher.id, context) + #Create the account move record. + move_id = move_pool.create(cr, uid, self.account_move_get(cr, uid, voucher.id, context=context), context=context) + # Get the name of the account_move just created + name = move_pool.browse(cr, uid, move_id, context=context).name + #Create the first line of the voucher + move_line_id = move_line_pool.create(cr, uid, self.first_move_line_get(cr,uid,voucher.id, move_id, company_currency, current_currency, context), context) + move_line_brw = move_line_pool.browse(cr, uid, move_line_id, context=context) + line_total = move_line_brw.debit - move_line_brw.credit rec_list_ids = [] - line_total = debit - credit - if inv.type == 'sale': - line_total = line_total - currency_pool.compute(cr, uid, current_currency, company_currency, inv.tax_amount, context=context_multi_currency) - elif inv.type == 'purchase': - line_total = line_total + currency_pool.compute(cr, uid, current_currency, company_currency, inv.tax_amount, context=context_multi_currency) + if voucher.type == 'sale': + line_total = line_total - currency_pool.compute(cr, uid, current_currency, company_currency, voucher.tax_amount, context=context) + elif voucher.type == 'purchase': + line_total = line_total + currency_pool.compute(cr, uid, current_currency, company_currency, voucher.tax_amount, context=context) + #create one move line per voucher line where amount is not 0.0 + line_total, rec_list_ids = self.voucher_move_line_create(cr, uid, voucher.id, line_total, move_id, company_currency, current_currency, context) - for line in inv.line_ids: - #create one move line per voucher line where amount is not 0.0 - if not line.amount: - continue - #we check if the voucher line is fully paid or not and create a move line to balance the payment and initial invoice if needed - if line.amount == line.amount_unreconciled: - amount = currency_pool.compute(cr, uid, current_currency, company_currency, line.untax_amount or line.amount, context=context_multi_currency) - amount_residual = line.move_line_id.amount_residual - amount #residual amount in company currency - else: - amount = currency_pool.compute(cr, uid, current_currency, company_currency, line.untax_amount or line.amount, context=context_multi_currency) - amount_residual = 0.0 - move_line = { - 'journal_id': inv.journal_id.id, - 'period_id': inv.period_id.id, - 'name': line.name or '/', - 'account_id': line.account_id.id, - 'move_id': move_id, - 'partner_id': inv.partner_id.id, - 'currency_id': company_currency <> current_currency and current_currency or False, - 'analytic_account_id': line.account_analytic_id and line.account_analytic_id.id or False, - 'quantity': 1, - 'credit': 0.0, - 'debit': 0.0, - 'date': inv.date - } - if amount < 0: - amount = -amount - if line.type == 'dr': - line.type = 'cr' - else: - line.type = 'dr' - if (line.type=='dr'): - line_total += amount - move_line['debit'] = amount - else: - line_total -= amount - move_line['credit'] = amount - - if inv.tax_id and inv.type in ('sale', 'purchase'): - move_line.update({ - 'account_tax_id': inv.tax_id.id, - }) - if move_line.get('account_tax_id', False): - tax_data = tax_obj.browse(cr, uid, [move_line['account_tax_id']], context=context)[0] - if not (tax_data.base_code_id and tax_data.tax_code_id): - raise osv.except_osv(_('No Account Base Code and Account Tax Code!'),_("You have to configure account base code and account tax code on the '%s' tax!") % (tax_data.name)) - sign = (move_line['debit'] - move_line['credit']) < 0 and -1 or 1 - move_line['amount_currency'] = company_currency <> current_currency and sign * line.amount or False - voucher_line = move_line_pool.create(cr, uid, move_line) - rec_ids = [voucher_line, line.move_line_id.id] - - # Change difference entry - if amount_residual: - if not inv.exchange_acc_id.id: - raise osv.except_osv(_('Error!'), _('You must provide an account for the exchange difference.')) - move_line = { - 'journal_id': inv.journal_id.id, - 'period_id': inv.period_id.id, - 'name': _('change')+': '+(line.name or '/'), - 'account_id': line.account_id.id, - 'move_id': move_id, - 'partner_id': inv.partner_id.id, - 'currency_id': company_currency <> current_currency and current_currency or False, - 'amount_currency': 0.0, - 'quantity': 1, - 'credit': amount_residual > 0 and amount_residual or 0.0, - 'debit': amount_residual < 0 and -amount_residual or 0.0, - 'date': inv.date - } - new_id = move_line_pool.create(cr, uid, move_line) - move_line = { - 'journal_id': inv.journal_id.id, - 'period_id': inv.period_id.id, - 'name': _('change')+': '+(line.name or '/'), - 'account_id': inv.exchange_acc_id.id, - 'move_id': move_id, - 'amount_currency': 0.0, - 'partner_id': inv.partner_id.id, - 'currency_id': company_currency <> current_currency and current_currency or False, - 'quantity': 1, - 'debit': amount_residual > 0 and amount_residual or 0.0, - 'credit': amount_residual < 0 and -amount_residual or 0.0, - 'date': inv.date - } - move_line_pool.create(cr, uid, move_line) - rec_ids.append(new_id) - - if line.move_line_id.id: - rec_list_ids.append(rec_ids) - - if not currency_pool.is_zero(cr, uid, current_currency_obj, line_total): - diff = line_total - account_id = False - write_off_name = '' - if inv.payment_option == 'with_writeoff': - account_id = inv.writeoff_acc_id.id - write_off_name = inv.comment - elif inv.type in ('sale', 'receipt'): - account_id = inv.partner_id.property_account_receivable.id - else: - account_id = inv.partner_id.property_account_payable.id - move_line = { - 'name': write_off_name or name, - 'account_id': account_id, - 'move_id': move_id, - 'partner_id': inv.partner_id.id, - 'date': inv.date, - 'credit': diff > 0 and diff or 0.0, - 'debit': diff < 0 and -diff or 0.0, - 'amount_currency': company_currency <> current_currency and inv.writeoff_amount or False, - 'currency_id': company_currency <> current_currency and current_currency or False, - } - move_line_pool.create(cr, uid, move_line) - self.write(cr, uid, [inv.id], { + #create the writeoff line if needed + ml_writeoff = self.writeoff_move_line_get(cr, uid, voucher.id, line_total, move_id, name, company_currency, current_currency, context) + if ml_writeoff: + ml_writeoff_id = move_line_pool.create(cr, uid, ml_writeoff, context) + #We post the voucher. + self.write(cr, uid, [voucher.id], { 'move_id': move_id, 'state': 'posted', 'number': name, }) - if inv.journal_id.entry_posted: + if voucher.journal_id.entry_posted: move_pool.post(cr, uid, [move_id], context={}) + #We automatically reconcile the account move lines. for rec_ids in rec_list_ids: if len(rec_ids) >= 2: - move_line_pool.reconcile_partial(cr, uid, rec_ids, writeoff_acc_id=inv.exchange_acc_id.id, writeoff_period_id=inv.period_id.id, writeoff_journal_id=inv.journal_id.id) + move_line_pool.reconcile_partial(cr, uid, rec_ids, writeoff_acc_id=voucher.exchange_acc_id.id, writeoff_period_id=voucher.period_id.id, writeoff_journal_id=voucher.journal_id.id) return True def copy(self, cr, uid, id, default={}, context=None):