You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

496 lines
26 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import datetime
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError, MissingError, UserError
class AccountPayment(models.Model):
"""收付款单:原生收付款单扩展"""
_name = 'account.payment'
_inherit = ['account.payment']
cash_flow_id = fields.Many2one('account.cash.flow', string='现金流量项目')
account_id_payment = fields.Many2one('account.account', string="业务伙伴科目")
cash_bool = fields.Boolean(related='journal_id.default_account_id.cash_bool', string="现金流量")
# 付款单确认操作,确认操作时,添加 凭证 审核人 ,审核日期 过账人,过账日期
def action_post(self):
""" 继承修改原生方法,此处因为原生客户支付,确认,没有将凭证上的 过账人等信息写入,此方法添加信息 """
res = super(AccountPayment, self).action_post()
self.move_id.write({
'fr_approved_uid': self.env.user.id,
'fr_approved_date': datetime.datetime.now(),
'fr_posted_uid': self.env.user.id,
'fr_posted_date': datetime.datetime.now(),
})
return res
# 付款单,重置为草稿时, 不允许 将非当前期间的关联凭证,重置为草稿
def action_draft(self):
""" 继承修改原生方法在account_payment 将已过账的凭证找出,禁止重置为草稿 """
res = super(AccountPayment, self).action_draft()
# if self.move_id.fr_period_state != 'ongoing':
# raise UserError(_("当前单据对应的凭证的会计期间已关闭。不允许进行重置操作。"))
return res
@api.onchange('payment_type', 'partner_id')
def onchange_payment_type(self):
"""筛选相应的业务伙伴科目"""
"""
account_account_type_receivable = self.env.ref('account.data_account_type_receivable').id
if self.payment_type == 'inbound':
# account_id = self.env['account.account'].search([('user_type_id', '=', account_account_type_receivable)])
account_id = self.env['account.account'].search([('account', '=', account_account_type_receivable)])
return {'domain': {'account_id_payment': [('id', 'in', account_id.ids)]}}
account_account_type_payable = self.env.ref('account.data_account_type_payable').id
if self.payment_type == 'outbound':
account_id = self.env['account.account'].search([
'|', ('account_type', '=', account_account_type_payable), ('account_type.name', '=', '流动负债')])
# '|', ('user_type_id', '=', account_account_type_payable), ('user_type_id.name', '=', '流动负债')])
return {'domain': {'account_id_payment': [('id', 'in', account_id.ids)]}}
"""
pass
@api.onchange('partner_id')
def onchange_partner_id_ledger(self):
if self.partner_id:
if self.payment_type == 'inbound':
self.account_id_payment = self.partner_id.property_account_receivable_id.id
if self.payment_type == 'outbound':
self.account_id_payment = self.partner_id.property_account_payable_id.id
@api.depends('payment_type', 'partner_type', 'partner_id')
def _compute_destination_account_id(self):
"""继承并修复原生方法中存在的BUG"""
# if self.invoice_ids:
# self.destination_account_id = self.invoice_ids[0].account_id.id
#####################此处在进行系统编号重排时,会报多列操作。需要循环操作####################
# if self.payment_type == 'transfer':
# if not self.company_id.transfer_account_id.id:
# raise UserError('在会计设置中没有转账帐户。请定义一个来确认这个转账。')
# self.destination_account_id = self.company_id.transfer_account_id.id
# elif self.partner_id:
# if self.partner_type == 'customer':
# self.destination_account_id = self.partner_id.property_account_receivable_id.id
# else:
# self.destination_account_id = self.partner_id.property_account_payable_id.id
######################################################################################
for payment in self:
if payment.payment_type == 'transfer':
if not payment.company_id.transfer_account_id.id:
raise UserError('在会计设置中没有转账帐户。请定义一个来确认这个转账。')
payment.destination_account_id = payment.company_id.transfer_account_id.id
elif payment.partner_id:
if payment.partner_type == 'customer':
payment.destination_account_id = payment.partner_id.property_account_receivable_id.id
else:
payment.destination_account_id = payment.partner_id.property_account_payable_id.id
# elif self.partner_type == 'customer':
# default_account = self.env['ir.property'].get('property_account_receivable_id', 'res.partner')
# self.destination_account_id = default_account
# elif self.partner_type == 'supplier':
# default_account = self.env['ir.property'].get('property_account_payable_id', 'res.partner')
# self.destination_account_id = default_account
def _get_liquidity_move_line_vals(self, amount):
"""日记账项目中带入现金流量项目"""
vals = super(AccountPayment, self)._get_liquidity_move_line_vals(amount)
# 添加现金流量项目
vals.update({'fr_cash_flow_id': self.cash_flow_id.id})
return vals
def _create_transfer_entry(self, amount):
"""内部转账日记账项目中带入现金流量项目"""
aml_obj = self.env['account.move.line'].with_context(check_move_validity=False)
debit, credit, amount_currency, dummy = aml_obj.with_context(date=self.payment_date)._compute_amount_fields(
amount, self.currency_id, self.company_id.currency_id)
amount_currency = self.destination_journal_id.currency_id and self.currency_id._convert(amount,
self.destination_journal_id.currency_id,
self.company_id,
self.payment_date or fields.Date.today()) or 0
dst_move = self.env['account.move'].create(self._get_move_vals(self.destination_journal_id))
dst_liquidity_aml_dict = self._get_shared_move_line_vals(debit, credit, amount_currency, dst_move.id)
dst_liquidity_aml_dict.update({
'name': '转账来自 %s' % self.journal_id.name,
'account_id': self.destination_journal_id.default_credit_account_id.id,
'currency_id': self.destination_journal_id.currency_id.id,
'journal_id': self.destination_journal_id.id,
'fr_cash_flow_id': self.cash_flow_id.id})
aml_obj.create(dst_liquidity_aml_dict)
transfer_debit_aml_dict = self._get_shared_move_line_vals(credit, debit, 0, dst_move.id)
transfer_debit_aml_dict.update({
'name': self.name,
'account_id': self.company_id.transfer_account_id.id,
'journal_id': self.destination_journal_id.id})
if self.currency_id != self.company_id.currency_id:
transfer_debit_aml_dict.update({
'currency_id': self.currency_id.id,
'amount_currency': -self.amount,
})
transfer_debit_aml = aml_obj.create(transfer_debit_aml_dict)
if not self.destination_journal_id.post_at_bank_rec:
dst_move.post()
return transfer_debit_aml
def _get_counterpart_move_line_vals(self, invoice=False):
"""根据业务伙伴,带出业务伙伴科目到明细行中"""
if self.payment_type == 'transfer':
name = self.name
else:
name = ''
if self.partner_type == 'customer':
if self.payment_type == 'inbound':
name += _("Customer Payment")
elif self.payment_type == 'outbound':
name += _("Customer Credit Note")
elif self.partner_type == 'supplier':
if self.payment_type == 'inbound':
name += _("Vendor Credit Note")
elif self.payment_type == 'outbound':
name += _("Vendor Payment")
if invoice:
name += ': '
for inv in invoice:
if inv.move_id:
name += inv.number + ', '
name = name[:len(name) - 2]
if self.account_id_payment:
return {
'name': name,
'account_id': self.account_id_payment.id,
'currency_id': self.currency_id != self.company_id.currency_id and self.currency_id.id or False,
}
else:
return {
'name': name,
'account_id': self.destination_account_id.id,
'currency_id': self.currency_id != self.company_id.currency_id and self.currency_id.id or False,
}
# 重写修改odoo14 原生方法,取消登记付款 业务伙伴问题
def _synchronize_from_moves(self, changed_fields):
''' Update the account.payment regarding its related account.move.
Also, check both models are still consistent.
:param changed_fields: A set containing all modified fields on account.move.
'''
if self._context.get('skip_account_move_synchronization'):
return
for pay in self.with_context(skip_account_move_synchronization=True):
# After the migration to 14.0, the journal entry could be shared between the account.payment and the
# account.bank.statement.line. In that case, the synchronization will only be made with the statement line.
if pay.move_id.statement_line_id:
continue
move = pay.move_id
move_vals_to_write = {}
payment_vals_to_write = {}
if 'journal_id' in changed_fields:
if pay.journal_id.type not in ('bank', 'cash'):
raise UserError(_("A payment must always belongs to a bank or cash journal."))
if 'line_ids' in changed_fields:
all_lines = move.line_ids
liquidity_lines, counterpart_lines, writeoff_lines = pay._seek_for_lines()
if len(liquidity_lines) != 1 or len(counterpart_lines) != 1:
raise UserError(_(
"The journal entry %s reached an invalid state relative to its payment.\n"
"To be consistent, the journal entry must always contains:\n"
"- one journal item involving the outstanding payment/receipts account.\n"
"- one journal item involving a receivable/payable account.\n"
"- optional journal items, all sharing the same account.\n\n"
) % move.display_name)
if writeoff_lines and len(writeoff_lines.account_id) != 1:
raise UserError(_(
"The journal entry %s reached an invalid state relative to its payment.\n"
"To be consistent, all the write-off journal items must share the same account."
) % move.display_name)
if any(line.currency_id != all_lines[0].currency_id for line in all_lines):
raise UserError(_(
"The journal entry %s reached an invalid state relative to its payment.\n"
"To be consistent, the journal items must share the same currency."
) % move.display_name)
# 注释 原生系统逻辑,对登记付款的限制
# if any(line.partner_id != all_lines[0].partner_id for line in all_lines):
# raise UserError(_(
# "The journal entry %s reached an invalid state relative to its payment.\n"
# "To be consistent, the journal items must share the same partner."
# ) % move.display_name)
# if counterpart_lines.account_id.user_type_id.type == 'receivable':
# if counterpart_lines.account_id.account == 'receivable':
if counterpart_lines.account_id.account_type == 'asset_receivable':
partner_type = 'customer'
else:
partner_type = 'supplier'
liquidity_amount = liquidity_lines.amount_currency
move_vals_to_write.update({
'currency_id': liquidity_lines.currency_id.id,
'partner_id': liquidity_lines.partner_id.id,
})
payment_vals_to_write.update({
'amount': abs(liquidity_amount),
'payment_type': 'inbound' if liquidity_amount > 0.0 else 'outbound',
'partner_type': partner_type,
'currency_id': liquidity_lines.currency_id.id,
'destination_account_id': counterpart_lines.account_id.id,
# 此处注释,是因为,在客户付款,银行对账单上确认发布的时候,会生成凭证,回写到对应单据,覆盖客户字段。
'partner_id': liquidity_lines.partner_id.id,
})
move.write(move._cleanup_write_orm_values(move, move_vals_to_write))
pay.write(move.with_context(is_payment=True)._cleanup_write_orm_values(pay, payment_vals_to_write))
# 重写修改odoo14 原生方法_prepare_move_line_default_vals 判断从客户、供应商支付菜单下,生成的凭证明细行问题
# 与方法_synchronize_to_moves 联动。回写partner_id 不需要回写操作。
# def _prepare_move_line_default_vals(self, write_off_line_vals=None):
#
# self.ensure_one()
# write_off_line_vals = write_off_line_vals or {}
#
# if not self.journal_id.payment_debit_account_id or not self.journal_id.payment_credit_account_id:
# raise UserError(_(
# "You can't create a new payment without an outstanding payments/receipts account set on the %s journal.",
# self.journal_id.display_name))
#
# # Compute amounts.
# write_off_amount = write_off_line_vals.get('amount', 0.0)
#
# if self.payment_type == 'inbound':
# # Receive money.
# counterpart_amount = -self.amount
# write_off_amount *= -1
# elif self.payment_type == 'outbound':
# # Send money.
# counterpart_amount = self.amount
# else:
# counterpart_amount = 0.0
# write_off_amount = 0.0
#
# balance = self.currency_id._convert(counterpart_amount, self.company_id.currency_id, self.company_id, self.date)
# counterpart_amount_currency = counterpart_amount
# write_off_balance = self.currency_id._convert(write_off_amount, self.company_id.currency_id, self.company_id,
# self.date)
# write_off_amount_currency = write_off_amount
# currency_id = self.currency_id.id
#
# if self.is_internal_transfer:
# if self.payment_type == 'inbound':
# liquidity_line_name = _('Transfer to %s', self.journal_id.name)
# else: # payment.payment_type == 'outbound':
# liquidity_line_name = _('Transfer from %s', self.journal_id.name)
# else:
# liquidity_line_name = self.payment_reference
#
# # Compute a default label to set on the journal items.
#
# payment_display_name = {
# 'outbound-customer': _("Customer Reimbursement"),
# 'inbound-customer': _("Customer Payment"),
# 'outbound-supplier': _("Vendor Payment"),
# 'inbound-supplier': _("Vendor Reimbursement"),
# 'inbound-employee': _("内部员工 收款"),
# 'outbound-employee': _("内部员工 付款"),
# }
#
# default_line_name = self.env['account.move.line']._get_default_line_name(
# _("Internal Transfer") if self.is_internal_transfer else payment_display_name[
# '%s-%s' % (self.payment_type, self.partner_type)],
# self.amount,
# self.currency_id,
# self.date,
# partner=self.partner_id,
# )
# # 此处跟odoo 原生方法做了相关的调整。添加了根据科目进行判断逻辑。
# # 需要现金流量,添加现金流量,判断需要业务伙伴的,添加业务伙伴
#
# line_vals_list = [
# # Liquidity line. 流动资产科目获取Outstanding Receipts 100203,此处科目不需要业务伙伴
# {
# 'name': liquidity_line_name or default_line_name,
# 'date_maturity': self.date,
# 'amount_currency': -counterpart_amount_currency,
# 'currency_id': currency_id,
# 'debit': balance < 0.0 and -balance or 0.0,
# 'credit': balance > 0.0 and balance or 0.0,
# 'partner_id': self.partner_id.id, # 此处科目不需要业务伙伴
# 'account_id': self.journal_id.payment_debit_account_id.id if balance < 0.0 else self.journal_id.payment_credit_account_id.id,
# },
# # Receivable / Payable.
# {
# 'name': self.payment_reference or default_line_name,
# 'date_maturity': self.date,
# 'amount_currency': counterpart_amount_currency + write_off_amount_currency if currency_id else 0.0,
# 'currency_id': currency_id,
# 'debit': balance + write_off_balance > 0.0 and balance + write_off_balance or 0.0,
# 'credit': balance + write_off_balance < 0.0 and -balance - write_off_balance or 0.0,
# 'partner_id': self.partner_id.id,
# 'account_id': self.destination_account_id.id,
# },
# ]
# if write_off_balance:
# # Write-off line.
# line_vals_list.append({
# 'name': write_off_line_vals.get('name') or default_line_name,
# 'amount_currency': -write_off_amount_currency,
# 'currency_id': currency_id,
# 'debit': write_off_balance < 0.0 and -write_off_balance or 0.0,
# 'credit': write_off_balance > 0.0 and write_off_balance or 0.0,
# 'partner_id': self.partner_id.id,
# 'account_id': write_off_line_vals.get('account_id'),
# })
#
# return line_vals_list
# 重写次方法,与上面的方法联动使用。防止 partner 回写
# def _synchronize_to_moves(self, changed_fields):
# if self._context.get('skip_account_move_synchronization'):
# return
#
# if not any(field_name in changed_fields for field_name in (
# 'date', 'amount', 'payment_type', 'partner_type', 'payment_reference', 'is_internal_transfer',
# 'currency_id', 'partner_id', 'destination_account_id', 'partner_bank_id',
# )):
# return
#
# for pay in self.with_context(skip_account_move_synchronization=True):
# liquidity_lines, counterpart_lines, writeoff_lines = pay._seek_for_lines()
#
# # Make sure to preserve the write-off amount.
# # This allows to create a new payment with custom 'line_ids'.
#
# if writeoff_lines:
# writeoff_amount = sum(writeoff_lines.mapped('amount_currency'))
# counterpart_amount = counterpart_lines['amount_currency']
# if writeoff_amount > 0.0 and counterpart_amount > 0.0:
# sign = 1
# else:
# sign = -1
#
# write_off_line_vals = {
# 'name': writeoff_lines[0].name,
# 'amount': writeoff_amount * sign,
# 'account_id': writeoff_lines[0].account_id.id,
# }
# else:
# write_off_line_vals = {}
#
# line_vals_list = pay._prepare_move_line_default_vals(write_off_line_vals=write_off_line_vals)
#
# line_ids_commands = [
# (1, liquidity_lines.id, line_vals_list[0]),
# (1, counterpart_lines.id, line_vals_list[1]),
# ]
#
# for line in writeoff_lines:
# line_ids_commands.append((2, line.id))
#
# if writeoff_lines:
# line_ids_commands.append((0, 0, line_vals_list[2]))
#
# # Update the existing journal items.
# # If dealing with multiple write-off lines, they are dropped and a new one is generated.
#
# pay.move_id.write({
# 'partner_id': pay.partner_id.id,
# 'currency_id': pay.currency_id.id,
# 'partner_bank_id': pay.partner_bank_id.id,
# 'line_ids': line_ids_commands,
# })
class AccountBankStatementLine(models.Model):
"""银行对账单:原生银行对账单扩展"""
_name = "account.bank.statement.line"
_inherit = "account.bank.statement.line"
cash_flow_id = fields.Many2one('account.cash.flow', string='现金流量项目', required=True)
# def _prepare_reconciliation_move_line(self, move, amount):
# """银行对账单日记账项目中带入现金流量项目"""
# aml_dict = super(AccountBankStatementLine, self)._prepare_reconciliation_move_line(move, amount)
#
# aml_dict.update({'fr_cash_flow_id': self.cash_flow_id.id})
# return aml_dict
@api.model
def _prepare_move_line_default_vals(self, counterpart_account_id=None):
''' Prepare the dictionary to create the default account.move.lines for the current account.bank.statement.line
record.
:return: A list of python dictionary to be passed to the account.move.line's 'create' method.
'''
self.ensure_one()
if not counterpart_account_id:
counterpart_account_id = self.journal_id.suspense_account_id.id
if not counterpart_account_id:
raise UserError(_(
"You can't create a new statement line without a suspense account set on the %s journal."
) % self.journal_id.display_name)
liquidity_line_vals = self._prepare_liquidity_move_line_vals()
# Ensure the counterpart will have a balance exactly equals to the amount in journal currency.
# This avoid some rounding issues when the currency rate between two currencies is not symmetrical.
# E.g:
# A.convert(amount_a, B) = amount_b
# B.convert(amount_b, A) = amount_c != amount_a
counterpart_vals = {
'name': self.payment_ref,
'account_id': counterpart_account_id,
'amount_residual': liquidity_line_vals['debit'] - liquidity_line_vals['credit'],
}
if self.foreign_currency_id and self.foreign_currency_id != self.company_currency_id:
# Ensure the counterpart will have exactly the same amount in foreign currency as the amount set in the
# statement line to avoid some rounding issues when making a currency conversion.
counterpart_vals.update({
'currency_id': self.foreign_currency_id.id,
'amount_residual_currency': self.amount_currency,
})
elif liquidity_line_vals['currency_id']:
# Ensure the counterpart will have a balance exactly equals to the amount in journal currency.
# This avoid some rounding issues when the currency rate between two currencies is not symmetrical.
# E.g:
# A.convert(amount_a, B) = amount_b
# B.convert(amount_b, A) = amount_c != amount_a
counterpart_vals.update({
'currency_id': liquidity_line_vals['currency_id'],
'amount_residual_currency': liquidity_line_vals['amount_currency'],
})
counterpart_line_vals = self._prepare_counterpart_move_line_vals(counterpart_vals)
if counterpart_line_vals['account_id']:
account_id = self.env['account.account'].search([('id', '=', counterpart_line_vals['account_id'])])
if account_id.cash_bool:
counterpart_line_vals.update({
'fr_cash_flow_id': self.cash_flow_id.id
})
if liquidity_line_vals['account_id']:
account_id = self.env['account.account'].search([('id', '=', liquidity_line_vals['account_id'])])
if account_id.cash_bool:
liquidity_line_vals.update({
'fr_cash_flow_id': self.cash_flow_id.id
})
return [liquidity_line_vals, counterpart_line_vals]