money/models/money_order.py
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2016 开阖软件(<http://osbzr.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from odoo.exceptions import UserError, ValidationError
import odoo.addons.decimal_precision as dp
from odoo import fields, models, api
from odoo.tools import float_compare, float_is_zero
import datetime
#from datetime import datetime
class MoneyOrder(models.Model):
_name = 'money.order'
_description = u"收付款单"
_inherit = ['mail.thread']
_order = 'id desc'
TYPE_SELECTION = [
('pay', u'付款'),
('get', u'收款'),
]
@api.model
def create(self, values):
# 创建单据时,根据订单类型的不同,生成不同的单据编号
if self.env.context.get('type') == 'pay':
values.update(
{'name': self.env['ir.sequence'].next_by_code('pay.order')})
else:
values.update(
{'name': self.env['ir.sequence'].next_by_code('get.order')})
# 创建时查找该业务伙伴是否存在 未审核 状态下的收付款单
orders = self.env['money.order'].search([('partner_id', '=', values.get('partner_id')),
('state', '=', 'draft'),
('id', '!=', self.id)])
if orders:
raise UserError(u'该业务伙伴存在未确认的收/付款单,请先确认')
return super(MoneyOrder, self).create(values)
@api.multi
def write(self, values):
# 修改时查找该业务伙伴是否存在 未审核 状态下的收付款单
if values.get('partner_id'):
orders = self.env['money.order'].search([('partner_id', '=', values.get('partner_id')),
('state', '=', 'draft'),
('id', '!=', self.id)])
if orders:
raise UserError(u'业务伙伴(%s)存在未审核的收/付款单,请先审核' %
orders.partner_id.name)
return super(MoneyOrder, self).write(values)
@api.one
@api.depends('discount_amount',
'line_ids.amount',
'source_ids.this_reconcile')
def _compute_advance_payment(self):
"""
计算字段advance_payment(本次预收) 监控 discount_amount, source_ids.this_reconcile line_ids.amount
对应的字段变化则 执行本方法进行重新计算。
:return:
"""
amount, this_reconcile = 0.0, 0.0
for line in self.line_ids:
amount += line.amount
for line in self.source_ids:
this_reconcile += line.this_reconcile
if self.type == 'get':
self.advance_payment = amount - this_reconcile + self.discount_amount
else:
self.advance_payment = amount - this_reconcile - self.discount_amount
self.amount = amount
@api.one
@api.depends('partner_id','type')
def _compute_currency_id(self):
"""
取出币别
:return:
"""
partner_currency_id = (self.type == 'get') \
and self.partner_id.c_category_id.account_id.currency_id.id \
or self.partner_id.s_category_id.account_id.currency_id.id
self.currency_id = partner_currency_id or self.env.user.company_id.currency_id.id
state = fields.Selection([
('draft', u'草稿'),
('done', u'已完成'),
('cancel', u'已作废'),
], string=u'状态', readonly=True,
default='draft', copy=False, index=True,
help=u'收/付款单状态标识,新建时状态为草稿;确认后状态为已完成')
partner_id = fields.Many2one('partner', string=u'往来单位', required=True,
readonly=True, ondelete='restrict',
states={'draft': [('readonly', False)]},
help=u'该单据对应的业务伙伴,单据确认时会影响他的应收应付余额')
date = fields.Date(string=u'单据日期', readonly=True,
default=lambda self: fields.Date.context_today(self),
states={'draft': [('readonly', False)]},
help=u'单据创建日期')
name = fields.Char(string=u'单据编号', copy=False, readonly=True,
help=u'单据编号,创建时会根据类型自动生成')
note = fields.Text(string=u'备注', help=u'可以为该单据添加一些需要的标识信息')
currency_id = fields.Many2one('res.currency', u'币别',
compute='_compute_currency_id', store=True, readonly=True,
help=u'业务伙伴的类别科目上对应的外币币别')
discount_amount = fields.Float(string=u'我方承担费用', readonly=True,
states={'draft': [('readonly', False)]},
digits=dp.get_precision('Amount'),
help=u'收/付款时发生的银行手续费或给业务伙伴的现金折扣。')
discount_account_id = fields.Many2one('finance.account', u'费用科目',
readonly=True,
states={
'draft': [('readonly', False)]},
help=u'收/付款单确认生成凭证时,手续费或折扣对应的科目')
line_ids = fields.One2many('money.order.line', 'money_id',
string=u'收/付款单行', readonly=True,
states={'draft': [('readonly', False)]},
help=u'收/付款单明细行')
source_ids = fields.One2many('source.order.line', 'money_id',
string=u'待核销行', readonly=True,
states={'draft': [('readonly', False)]},
help=u'收/付款单待核销行')
type = fields.Selection(TYPE_SELECTION, string=u'类型',
default=lambda self: self.env.context.get('type'),
help=u'类型:收款单 或者 付款单')
amount = fields.Float(string=u'总金额', compute='_compute_advance_payment',
digits=dp.get_precision('Amount'),
store=True, readonly=True,
help=u'收/付款单行金额总和')
advance_payment = fields.Float(string=u'本次预付',
compute='_compute_advance_payment',
digits=dp.get_precision('Amount'),
store=True, readonly=True,
help=u'根据收/付款单行金额总和,原始单据行金额总和及折扣额计算得来的预收/预付款,'
u'值>=0')
to_reconcile = fields.Float(string=u'未核销金额',
digits=dp.get_precision('Amount'),
help=u'未核销的预收/预付款金额')
reconciled = fields.Float(string=u'已核销金额',
digits=dp.get_precision('Amount'),
help=u'已核销的预收/预付款金额')
origin_name = fields.Char(u'原始单据编号',
help=u'原始单据编号')
bank_name = fields.Char(u'开户行',
readonly=True,
states={'draft': [('readonly', False)]},
help=u'开户行取自业务伙伴,可修改')
bank_num = fields.Char(u'银行账号',
readonly=True,
states={'draft': [('readonly', False)]},
help=u'银行账号取自业务伙伴,可修改')
company_id = fields.Many2one(
'res.company',
string=u'公司',
change_default=True,
default=lambda self: self.env['res.company']._company_default_get())
voucher_id = fields.Many2one('voucher',
u'对应凭证',
readonly=True,
ondelete='restrict',
copy=False,
help=u'收/付款单确认时生成的对应凭证')
@api.multi
def write_off_reset(self):
"""
单据审核前重置计算单行上的本次核销金额
:return:
"""
self.ensure_one()
if self.state != 'draft':
raise ValueError(u'已确认的单据不能执行这个操作')
for source in self.source_ids:
source.this_reconcile = 0
return True
@api.onchange('date')
def onchange_date(self):
"""
当修改日期时,则根据context中的money的type对客户添加过滤,过滤出是供应商还是客户。
(因为date有默认值所以这个过滤是默认触发的) 其实和date是否变化没有关系,页面加载就触发下面的逻辑
:return:
"""
if self.env.context.get('type') == 'get':
return {'domain': {'partner_id': [('c_category_id', '!=', False)]}}
else:
return {'domain': {'partner_id': [('s_category_id', '!=', False)]}}
def _get_source_line(self, invoice):
"""
根据传入的invoice的对象取出对应的值 构造出 source_line的一个dict 包含source line的主要参数
:param invoice: money_invoice对象
:return: dict
"""
return {
'name': invoice.id,
'category_id': invoice.category_id.id,
'amount': invoice.amount,
'date': invoice.date,
'reconciled': invoice.reconciled,
'to_reconcile': invoice.to_reconcile,
'this_reconcile': invoice.to_reconcile,
'date_due': invoice.date_due,
}
def _get_invoice_search_list(self):
"""
构造出 invoice 搜索的domain
:return:
"""
invoice_search_list = [('partner_id', '=', self.partner_id.id),
('to_reconcile', '!=', 0),
('state', '=', 'done')]
if self.env.context.get('type') == 'get':
invoice_search_list.append(('category_id.type', '=', 'income'))
else: # type = 'pay':
invoice_search_list.append(('category_id.type', '=', 'expense'))
return invoice_search_list
@api.onchange('partner_id')
def onchange_partner_id(self):
"""
对partner修改的监控当 partner 修改时,就对 页面相对应的字段进行修改(bank_name,bank_num,source_ids)
:return:
"""
if not self.partner_id:
return {}
source_lines = []
self.bank_name = self.partner_id.bank_name
self.bank_num = self.partner_id.bank_num
for invoice in self.env['money.invoice'].search(self._get_invoice_search_list()):
source_lines.append(self._get_source_line(invoice))
self.source_ids = source_lines
@api.multi
def money_order_done(self):
'''对收付款单的审核按钮'''
for order in self:
if order.state == 'done':
raise UserError(u'请不要重复确认')
if order.type == 'pay' and not order.partner_id.s_category_id.account_id:
raise UserError(u'请输入供应商类别(%s)上的科目' %
order.partner_id.s_category_id.name)
if order.type == 'get' and not order.partner_id.c_category_id.account_id:
raise UserError(u'请输入客户类别(%s)上的科目' %
order.partner_id.c_category_id.name)
if order.advance_payment < 0 and order.source_ids:
raise UserError(u'本次核销金额不能大于付款金额。\n差额: %s' %
(order.advance_payment))
total = 0
for line in order.line_ids:
rate_silent = self.env['res.currency'].get_rate_silent(
order.date, line.currency_id.id)
if order.type == 'pay': # 付款账号余额减少, 退款账号余额增加
decimal_amount = self.env.ref('core.decimal_amount')
balance = line.currency_id != self.env.user.company_id.currency_id \
and line.bank_id.currency_amount or line.bank_id.balance
if float_compare(balance, line.amount,
precision_digits=decimal_amount.digits) == -1:
raise UserError(u'账户余额不足。\n账户余额:%s 付款行金额:%s' %
(balance, line.amount))
if line.currency_id != self.env.user.company_id.currency_id: # 外币
line.bank_id.currency_amount -= line.amount
line.bank_id.balance -= line.amount * rate_silent
else:
line.bank_id.balance -= line.amount
else: # 收款账号余额增加, 退款账号余额减少
if line.currency_id != self.env.user.company_id.currency_id: # 外币
line.bank_id.currency_amount += line.amount
line.bank_id.balance += line.amount * rate_silent
else:
line.bank_id.balance += line.amount
total += line.amount
if order.type == 'pay':
order.partner_id.payable -= total - order.discount_amount
else:
order.partner_id.receivable -= total + order.discount_amount
# 更新结算单的未核销金额、已核销金额
for source in order.source_ids:
'''float_compare(value1,value2): return -1, 0 or 1,
if 'value1' is lower than, equal to, or greater than 'value2' at the given precision'''
decimal_amount = self.env.ref('core.decimal_amount')
if float_compare(source.this_reconcile, abs(source.to_reconcile), precision_digits=decimal_amount.digits) == 1:
raise UserError(u'本次核销金额不能大于未核销金额。\n 核销金额:%s 未核销金额:%s'
% (abs(source.to_reconcile), source.this_reconcile))
source.name.to_reconcile -= source.this_reconcile
source.name.reconciled += source.this_reconcile
if source.this_reconcile == 0: # 如果核销行的本次付款金额为0,删除
source.unlink()
# 生成凭证并审核
if order.type == 'get':
voucher = order.create_money_order_get_voucher(
order.line_ids, order.source_ids, order.partner_id, order.name, order.note or '')
else:
voucher = order.create_money_order_pay_voucher(
order.line_ids, order.source_ids, order.partner_id, order.name, order.note or '')
voucher.voucher_done()
return order.write({
'to_reconcile': order.advance_payment,
'reconciled': order.amount - order.advance_payment,
'voucher_id': voucher.id,
'state': 'done',
})
@api.multi
def money_order_draft(self):
"""
收付款单反审核方法
:return:
"""
for order in self:
if order.state == 'draft':
raise UserError(u'请不要重复撤销')
# 收/付款单 存在已审核金额不为0的核销单
total_current_reconciled = order.amount - order.advance_payment
decimal_amount = self.env.ref('core.decimal_amount')
if float_compare(order.reconciled, total_current_reconciled, precision_digits=decimal_amount.digits) != 0:
raise UserError(u'单据已核销金额不为0,不能反审核!请检查核销单!')
total = 0
for line in order.line_ids:
rate_silent = self.env['res.currency'].get_rate_silent(
order.date, line.currency_id.id)
if order.type == 'pay': # 反审核:付款账号余额增加
if line.currency_id != self.env.user.company_id.currency_id: # 外币
line.bank_id.currency_amount += line.amount
line.bank_id.balance += line.amount * rate_silent
else:
line.bank_id.balance += line.amount
else: # 反审核:收款账号余额减少
balance = line.currency_id != self.env.user.company_id.currency_id \
and line.bank_id.currency_amount or line.bank_id.balance
decimal_amount = self.env.ref('core.decimal_amount')
if float_compare(balance, line.amount, precision_digits=decimal_amount.digits) == -1:
raise UserError(u'账户余额不足。\n 账户余额:%s 收款行金额:%s' %
(balance, line.amount))
if line.currency_id != self.env.user.company_id.currency_id: # 外币
line.bank_id.currency_amount -= line.amount
line.bank_id.balance -= line.amount * rate_silent
else:
line.bank_id.balance -= line.amount
total += line.amount
if order.type == 'pay':
order.partner_id.payable += total - order.discount_amount
else:
order.partner_id.receivable += total + order.discount_amount
for source in order.source_ids:
source.name.to_reconcile += source.this_reconcile
source.name.reconciled -= source.this_reconcile
voucher = order.voucher_id
order.write({
'to_reconcile': 0,
'reconciled': 0,
'voucher_id': False,
'state': 'draft',
})
# 反审核凭证并删除
if voucher.state == 'done':
voucher.voucher_draft()
voucher.unlink()
return True
def _prepare_vouch_line_data(self, line, name, account_id, debit, credit, voucher_id, partner_id, currency_id):
rate_silent = currency_amount = 0
if currency_id:
rate_silent = self.env['res.currency'].get_rate_silent(
self.date, currency_id)
currency_amount = debit or credit
debit = debit * (rate_silent or 1)
credit = credit * (rate_silent or 1)
return {
'name': name,
'account_id': account_id,
'debit': debit,
'credit': credit,
'voucher_id': voucher_id,
'partner_id': partner_id,
'currency_id': currency_id,
'currency_amount': currency_amount,
'rate_silent': rate_silent or ''
}
def _create_voucher_line(self, line, name, account_id, debit, credit, voucher_id, partner_id, currency_id):
line_data = self._prepare_vouch_line_data(
line, name, account_id, debit, credit, voucher_id, partner_id, currency_id)
voucher_line = self.env['voucher.line'].create(line_data)
return voucher_line
@api.multi
def create_money_order_get_voucher(self, line_ids, source_ids, partner, name, note):
"""
为收款单创建凭证
:param line_ids: 收款单明细
:param source_ids: 没用到
:param partner: 客户
:param name: 收款单名称
:return: 创建的凭证
"""
vouch_obj = self.env['voucher'].create({'date': self.date, 'ref': '%s,%s' % (self._name, self.id)})
# self.write({'voucher_id': vouch_obj.id})
amount_all = 0.0
line_data = False
for line in line_ids:
line_data = line
if not line.bank_id.account_id:
raise UserError(u'请配置%s的会计科目' % (line.bank_id.name))
# 生成借方明细行
# param: line, name, account_id, debit, credit, voucher_id, partner_id
self._create_voucher_line(line,
u"%s %s" % (name, note),
line.bank_id.account_id.id,
line.amount,
0,
vouch_obj.id,
'',
line.currency_id.id
)
amount_all += line.amount
if self.discount_amount != 0:
# 生成借方明细行
# param: False, name, account_id, debit, credit, voucher_id, partner_id
self._create_voucher_line(False,
u"%s 现金折扣 %s" % (name, note),
self.discount_account_id.id,
self.discount_amount,
0,
vouch_obj.id,
self.partner_id.id,
line_data and line_data.currency_id.id or self.currency_id.id
)
if partner.c_category_id:
partner_account_id = partner.c_category_id.account_id.id
# 生成贷方明细行
# param: source, name, account_id, debit, credit, voucher_id, partner_id
self._create_voucher_line('',
u"%s %s" % (name, note),
partner_account_id,
0,
amount_all + self.discount_amount,
vouch_obj.id,
self.partner_id.id,
line_data and line.currency_id.id or self.currency_id.id
)
return vouch_obj
@api.multi
def create_money_order_pay_voucher(self, line_ids, source_ids, partner, name, note):
"""
为付款单创建凭证
:param line_ids: 付款单明细
:param source_ids: 没用到
:param partner: 供应商
:param name: 付款单名称
:return: 创建的凭证
"""
vouch_obj = self.env['voucher'].create({'date': self.date, 'ref': '%s,%s' % (self._name, self.id)})
# self.write({'voucher_id': vouch_obj.id})
amount_all = 0.0
line_data = False
for line in line_ids:
line_data = line
if not line.bank_id.account_id:
raise UserError(u'请配置%s的会计科目' % (line.bank_id.name))
# 生成贷方明细行 credit
# param: line, name, account_id, debit, credit, voucher_id, partner_id
self._create_voucher_line(line,
u"%s %s" % (name, note),
line.bank_id.account_id.id,
0,
line.amount,
vouch_obj.id,
'',
line.currency_id.id
)
amount_all += line.amount
partner_account_id = partner.s_category_id.account_id.id
# 生成借方明细行 debit
# param: source, name, account_id, debit, credit, voucher_id, partner_id
self._create_voucher_line('',
u"%s %s" % (name, note),
partner_account_id,
amount_all - self.discount_amount,
0,
vouch_obj.id,
self.partner_id.id,
line_data and line.currency_id.id or self.currency_id.id
)
if self.discount_amount != 0:
# 生成借方明细行 debit
# param: False, name, account_id, debit, credit, voucher_id, partner_id
self._create_voucher_line(line_data and line_data or False,
u"%s 手续费 %s" % (name, note),
self.discount_account_id.id,
self.discount_amount,
0,
vouch_obj.id,
self.partner_id.id,
line_data and line.currency_id.id or self.currency_id.id
)
return vouch_obj
class MoneyOrderLine(models.Model):
_name = 'money.order.line'
_description = u'收付款单明细'
@api.one
@api.depends('bank_id')
def _compute_currency_id(self):
"""
获取币别
:return:
"""
self.currency_id = self.bank_id.account_id.currency_id.id or self.env.user.company_id.currency_id.id
if self.bank_id and self.currency_id != self.money_id.currency_id:
raise ValidationError(u'结算帐户与业务伙伴币别不一致。\n 结算账户币别:%s 业务伙伴币别:%s'
% (self.currency_id.name, self.money_id.currency_id.name))
money_id = fields.Many2one('money.order', string=u'收付款单',
ondelete='cascade',
help=u'订单行对应的收付款单')
bank_id = fields.Many2one('bank.account', string=u'结算账户',
required=True, ondelete='restrict',
help=u'本次收款/付款所用的计算账户,确认收付款单会修改对应账户的金额')
amount = fields.Float(string=u'金额',
digits=dp.get_precision('Amount'),
help=u'本次结算金额')
mode_id = fields.Many2one('settle.mode', string=u'结算方式',
ondelete='restrict',
help=u'结算方式:支票、信用卡等')
currency_id = fields.Many2one('res.currency', u'币别', compute='_compute_currency_id',
store=True, readonly=True,
help=u'结算账户对应的外币币别')
number = fields.Char(string=u'结算号',
help=u'本次结算号')
note = fields.Char(string=u'备注',
help=u'可以为本次结算添加一些需要的标识信息')
company_id = fields.Many2one(
'res.company',
string=u'公司',
change_default=True,
default=lambda self: self.env['res.company']._company_default_get())
class MoneyInvoice(models.Model):
_name = 'money.invoice'
_description = u'结算单'
_order = 'date DESC'
@api.model
def _get_category_id(self):
cate_type = self.env.context.get('type')
if cate_type:
return self.env['core.category'].search([('type', '=', cate_type)])[0]
return False
@api.multi
def name_get(self):
'''在many2one字段里有order则显示单号否则显示名称_票号'''
res = []
for invoice in self:
if self.env.context.get('order'):
res.append((invoice.id, invoice.name))
else:
res.append(
(invoice.id, invoice.bill_number and invoice.bill_number or invoice.name))
return res
@api.one
@api.depends('date_due', 'to_reconcile')
def compute_overdue(self):
"""
计算逾期天数: 当前日期 - 到期日,< 0则显示为0;如果逾期金额为0则逾期天数也为0
计算逾期金额: 逾期时等于未核销金额,否则为0
:return: 逾期天数
"""
d1 = datetime.datetime.strptime(fields.Date.context_today(self), '%Y-%m-%d')
d2 = self.date_due and datetime.datetime.strptime(
self.date_due, '%Y-%m-%d') or d1
day = (d1 - d2).days
self.overdue_days = day > 0 and day or 0.0
self.overdue_amount = self.overdue_days > 0 and self.to_reconcile or 0.0
self.overdue_days = self.overdue_amount and self.overdue_days or 0
@api.one
@api.depends('reconciled')
def _get_sell_amount_state(self):
if self.reconciled:
self.get_amount_date = self.write_date[:10]
state = fields.Selection([
('draft', u'草稿'),
('done', u'完成')
], string=u'状态',
default='draft', copy=False, index=True,
help=u'结算单状态标识,新建时状态为草稿;确认后状态为完成')
partner_id = fields.Many2one('partner', string=u'往来单位',
required=True,
ondelete='restrict',
help=u'该单据对应的业务伙伴')
name = fields.Char(string=u'前置单据编号', copy=False,
readonly=True, required=True,
help=u'该结算单编号,取自生成结算单的采购入库单和销售入库单')
category_id = fields.Many2one('core.category', string=u'类别',
ondelete='restrict',
default=_get_category_id,
help=u'结算单类别:采购 或者 销售等')
date = fields.Date(string=u'日期', required=True,
default=lambda self: fields.Date.context_today(self),
help=u'单据创建日期')
amount = fields.Float(string=u'金额(含税)',
digits=dp.get_precision('Amount'),
help=u'原始单据对应金额')
reconciled = fields.Float(string=u'已核销金额', readonly=True,
digits=dp.get_precision('Amount'),
help=u'原始单据已核销掉的金额')
to_reconcile = fields.Float(string=u'未核销金额', readonly=True,
digits=dp.get_precision('Amount'),
help=u'原始单据未核销掉的金额')
tax_amount = fields.Float(u'税额',
digits=dp.get_precision('Amount'),
help=u'对应税额')
get_amount_date = fields.Date(u'最后收款日期', compute=_get_sell_amount_state,
store=True, copy=False)
auxiliary_id = fields.Many2one('auxiliary.financing', u'辅助核算',
help=u'辅助核算')
date_due = fields.Date(string=u'到期日',
help=u'结算单的到期日')
currency_id = fields.Many2one('res.currency', u'外币币别',
help=u'原始单据对应的外币币别')
bill_number = fields.Char(u'纸质发票号',
help=u'纸质发票号')
is_init = fields.Boolean(u'是否初始化单')
company_id = fields.Many2one(
'res.company',
string=u'公司',
change_default=True,
default=lambda self: self.env['res.company']._company_default_get())
overdue_days = fields.Float(u'逾期天数', readonly=True,
compute='compute_overdue',
help=u'当前日期 - 到期日')
overdue_amount = fields.Float(u'逾期金额', readonly=True,
compute='compute_overdue',
help=u'超过到期日后仍未核销的金额')
note = fields.Char(u'备注',
help=u'可填入到期日计算的依据')
@api.multi
def money_invoice_done(self):
"""
结算单审核方法
:return:
"""
for inv in self:
if inv.state == 'done':
raise UserError(u'请不要重复确认')
inv.reconciled = 0.0
inv.to_reconcile = inv.amount
inv.state = 'done'
#默认到期日取审核日期
#不管有没有到期日,取发票审核日期+客户的信用天数
#if not inv.date_due:
# inv.date_due = fields.Date.context_today(self) + inv.partner_id.credit_time
inv.date_due = datetime.datetime.strptime(fields.Date.context_today(self), '%Y-%m-%d') + datetime.timedelta(days=inv.partner_id.credit_time)
if inv.category_id.type == 'income':
inv.partner_id.receivable += inv.amount
if inv.category_id.type == 'expense':
inv.partner_id.payable += inv.amount
return True
@api.multi
def money_invoice_draft(self):
"""
结算单反审核方法
:return:
"""
for inv in self:
if inv.state == 'draft':
raise UserError(u'请不要重复撤销')
if inv.reconciled != 0.0:
raise UserError(u'已核销的结算单不允许删除')
inv.reconciled = 0.0
inv.to_reconcile = 0.0
inv.state = 'draft'
if inv.category_id.type == 'income':
inv.partner_id.receivable -= inv.amount
if inv.category_id.type == 'expense':
inv.partner_id.payable -= inv.amount
@api.model
def create(self, values):
"""
创建结算单时,如果公司上的‘根据发票确认应收应付’字段没有勾上,则直接审核结算单,否则不审核。
:param values:
:return:
"""
new_id = super(MoneyInvoice, self).create(values)
if not self.env.user.company_id.draft_invoice:
new_id.money_invoice_done()
return new_id
@api.multi
def write(self, values):
"""
当更新结算单到期日时,纸质发票号 相同的结算单到期日一起更新
"""
if values.get('date_due') and self.bill_number and not self.env.context.get('other_invoice_date_due'):
invoices = self.search([('bill_number', '=', self.bill_number)])
for inv in invoices:
inv.with_context({'other_invoice_date_due': True}).write({'date_due': values.get('date_due')})
return super(MoneyInvoice, self).write(values)
@api.multi
def unlink(self):
"""
只允许删除未审核的单据
:return:
"""
for invoice in self:
if invoice.name == '.' and invoice.reconciled == 0.0:
self.money_invoice_draft()
continue
return super(MoneyInvoice, self).unlink()
@api.multi
def find_source_order(self):
'''
查看原始单据,有以下情况:销售发货单、销售退货单、采购退货单、采购入库单、
项目、委外加工单、核销单、购货订单、固定资产、固定资产变更以及期初应收应付。
'''
self.ensure_one()
code = False
res_models = [
'reconcile.order',
]
views = [
'money.reconcile_order_form',
]
# 判断当前数据库中否存在该 model
if self.env.get('sell.delivery') != None:
res_models += ['sell.delivery']
views += ['sell.sell_delivery_form']
if self.env.get('outsource') != None:
res_models += ['outsource']
views += ['warehouse.outsource_form']
if self.env.get('buy.order') != None:
res_models += ['buy.order']
views += ['buy.buy_order_form']
if self.env.get('buy.receipt') != None:
res_models += ['buy.receipt']
views += ['buy.buy_receipt_form']
if self.env.get('project') != None:
res_models += ['project']
views += ['task.project_form']
if self.env.get('asset') != None:
res_models += ['asset']
views += ['asset.asset_form']
if self.env.get('cost.order') != None:
res_models += ['cost.order']
views += ['account_cost.cost_order_form']
if self.env.get('hr.expense') != None:
res_models += ['hr.expense']
views += ['staff_expense.hr_expense_form']
if u'固定资产变更' in self.name:
code = self.name.replace(u'固定资产变更', '')
elif u'固定资产' in self.name:
code = self.name.replace(u'固定资产', '')
domain = code and [('code', '=', code)] or [('name', '=', self.name)]
for i in range(len(res_models)):
# 若code存在说明 model为asset,view为固定资产form视图。
res_model = code and 'asset' or res_models[i]
view = code and self.env.ref(
'asset.asset_form') or self.env.ref(views[i])
res = self.env[res_model].search(domain)
if res: # 如果找到res_id,则退出for循环。
break
if not res:
raise UserError(u'没有原始单据可供查看。')
if res_model == 'sell.delivery' and res.is_return:
view = self.env.ref('sell.sell_return_form')
elif res_model == 'buy.receipt' and res.is_return:
view = self.env.ref('buy.buy_return_form')
return {
'view_type': 'form',
'view_mode': 'form',
'view_id': False,
'views': [(view.id, 'form')],
'res_model': res_model,
'type': 'ir.actions.act_window',
'res_id': res.id,
}
class SourceOrderLine(models.Model):
_name = 'source.order.line'
_description = u'待核销行'
money_id = fields.Many2one('money.order', string=u'收付款单',
ondelete='cascade',
help=u'待核销行对应的收付款单') # 收付款单上的待核销行
receivable_reconcile_id = fields.Many2one('reconcile.order',
string=u'核销单', ondelete='cascade',
help=u'核销单上的应收结算单明细') # 核销单上的应收结算单明细
payable_reconcile_id = fields.Many2one('reconcile.order',
string=u'核销单', ondelete='cascade',
help=u'核销单上的应付结算单明细') # 核销单上的应付结算单明细
name = fields.Many2one('money.invoice', string=u'结算单',
copy=False, required=True,
ondelete='cascade',
help=u'待核销行对应的结算单')
category_id = fields.Many2one('core.category', string=u'类别',
required=True, ondelete='restrict',
help=u'待核销行类别:采购 或者 销售等')
date = fields.Date(string=u'单据日期',
help=u'单据创建日期')
amount = fields.Float(string=u'单据金额',
digits=dp.get_precision('Amount'),
help=u'待核销行对应金额')
reconciled = fields.Float(string=u'已核销金额',
digits=dp.get_precision('Amount'),
help=u'待核销行已核销掉的金额')
to_reconcile = fields.Float(string=u'未核销金额',
digits=dp.get_precision('Amount'),
help=u'待核销行未核销掉的金额')
this_reconcile = fields.Float(string=u'本次核销金额',
digits=dp.get_precision('Amount'),
help=u'本次要核销掉的金额')
date_due = fields.Date(string=u'到期日',
help=u'待核销行的到期日')
company_id = fields.Many2one(
'res.company',
string=u'公司',
change_default=True,
default=lambda self: self.env['res.company']._company_default_get())
class ReconcileOrder(models.Model):
_name = 'reconcile.order'
_description = u'核销单'
_inherit = ['mail.thread']
TYPE_SELECTION = [
('adv_pay_to_get', u'预收冲应收'),
('adv_get_to_pay', u'预付冲应付'),
('get_to_pay', u'应收冲应付'),
('get_to_get', u'应收转应收'),
('pay_to_pay', u'应付转应付'),
]
state = fields.Selection([
('draft', u'草稿'),
('done', u'已确认'),
('cancel', u'已作废'),
], string=u'状态', readonly=True,
default='draft', copy=False, index=True,
help=u'核销单状态标识,新建时状态为草稿;确认后状态为已确认')
partner_id = fields.Many2one('partner', string=u'往来单位', required=True,
readonly=True, ondelete='restrict',
states={'draft': [('readonly', False)]},
help=u'该单据对应的业务伙伴,与业务类型一起带出待核销的明细行')
to_partner_id = fields.Many2one('partner', string=u'转入往来单位',
readonly=True, ondelete='restrict',
states={'draft': [('readonly', False)]},
help=u'应收转应收、应付转应付时对应的转入业务伙伴,'
u'订单确认时会影响该业务伙伴的应收/应付')
advance_payment_ids = fields.One2many(
'advance.payment', 'pay_reconcile_id',
string=u'预收/付款单行', readonly=True,
states={'draft': [('readonly', False)]},
help=u'业务伙伴有预收/付款单,自动带出,用来与应收/应付款单核销')
receivable_source_ids = fields.One2many(
'source.order.line', 'receivable_reconcile_id',
string=u'应收结算单行', readonly=True,
states={'draft': [('readonly', False)]},
help=u'业务伙伴有应收结算单,自动带出,待与预收款单核销')
payable_source_ids = fields.One2many(
'source.order.line', 'payable_reconcile_id',
string=u'应付结算单行', readonly=True,
states={'draft': [('readonly', False)]},
help=u'业务伙伴有应付结算单,自动带出,待与预付款单核销')
business_type = fields.Selection(TYPE_SELECTION, string=u'业务类型',
readonly=True,
states={'draft': [('readonly', False)]},
help=u'类型:预收冲应收,预付冲应付,应收冲应付,应收转应收,应付转应付'
)
name = fields.Char(string=u'单据编号', copy=False, readonly=True,
help=u'单据编号,创建时会自动生成')
date = fields.Date(string=u'单据日期', readonly=True,
default=lambda self: fields.Date.context_today(self),
states={'draft': [('readonly', False)]},
help=u'单据创建日期')
note = fields.Text(string=u'备注',
help=u'可以为该单据添加一些需要的标识信息')
company_id = fields.Many2one(
'res.company',
string=u'公司',
change_default=True,
default=lambda self: self.env['res.company']._company_default_get())
@api.multi
def _get_money_order(self, way='get'):
"""
搜索到满足条件的预收/付款单,为one2many字段赋值构造列表
:param way: 收/付款单的type
:return: list
"""
money_orders = self.env['money.order'].search(
[('partner_id', '=', self.partner_id.id),
('type', '=', way),
('state', '=', 'done'),
('to_reconcile', '!=', 0)])
result = []
for order in money_orders:
result.append((0, 0, {
'name': order.id,
'amount': order.amount,
'date': order.date,
'reconciled': order.reconciled,
'to_reconcile': order.to_reconcile,
'this_reconcile': order.to_reconcile,
}))
return result
@api.multi
def _get_money_invoice(self, way='income'):
"""
搜索到满足条件的money.invoice记录并且取出invoice对象 构造出one2many的
:param way: money.invoice 中的category_id 的type
:return:
"""
MoneyInvoice = self.env['money.invoice'].search([
('category_id.type', '=', way),
('partner_id', '=', self.partner_id.id),
('to_reconcile', '!=', 0)])
result = []
for invoice in MoneyInvoice:
result.append((0, 0, {
'name': invoice.id,
'category_id': invoice.category_id.id,
'amount': invoice.amount,
'date': invoice.date,
'reconciled': invoice.reconciled,
'to_reconcile': invoice.to_reconcile,
'date_due': invoice.date_due,
'this_reconcile': invoice.to_reconcile,
}))
return result
@api.onchange('partner_id', 'to_partner_id', 'business_type')
def onchange_partner_id(self):
"""
onchange 类型字段 当改变 客户或者转入往来单位 业务类型 自动生成 对应的
核销单各种明细。
:return:
"""
if not self.partner_id or not self.business_type:
return {}
# 先清空之前填充的数据
self.advance_payment_ids = None
self.receivable_source_ids = None
self.payable_source_ids = None
if self.business_type == 'adv_pay_to_get': # 预收冲应收
self.advance_payment_ids = self._get_money_order('get')
self.receivable_source_ids = self._get_money_invoice('income')
if self.business_type == 'adv_get_to_pay': # 预付冲应付
self.advance_payment_ids = self._get_money_order('pay')
self.payable_source_ids = self._get_money_invoice('expense')
if self.business_type == 'get_to_pay': # 应收冲应付
self.receivable_source_ids = self._get_money_invoice('income')
self.payable_source_ids = self._get_money_invoice('expense')
if self.business_type == 'get_to_get': # 应收转应收
self.receivable_source_ids = self._get_money_invoice('income')
return {'domain': {'to_partner_id': [('c_category_id', '!=', False)]}}
if self.business_type == 'pay_to_pay': # 应付转应付
self.payable_source_ids = self._get_money_invoice('expense')
return {'domain': {'to_partner_id': [('s_category_id', '!=', False)]}}
@api.multi
def _get_or_pay(self, line, business_type,
partner_id, to_partner_id, name):
"""
核销单 核销时 对具体核销单行进行的操作
:param line:
:param business_type:
:param partner_id:
:param to_partner_id:
:param name:
:return:
"""
decimal_amount = self.env.ref('core.decimal_amount')
if float_compare(line.this_reconcile, line.to_reconcile, precision_digits=decimal_amount.digits) == 1:
raise UserError(u'核销金额不能大于未核销金额。\n核销金额:%s 未核销金额:%s' %
(line.this_reconcile, line.to_reconcile))
# 更新每一行的已核销余额、未核销余额
line.name.to_reconcile -= line.this_reconcile
line.name.reconciled += line.this_reconcile
# 应收转应收、应付转应付
if business_type in ['get_to_get', 'pay_to_pay']:
if not float_is_zero(line.this_reconcile, 2):
# 转入业务伙伴往来增加
self.env['money.invoice'].create({
'name': name,
'category_id': line.category_id.id,
'amount': line.this_reconcile,
'date': self.date,
'reconciled': 0, # 已核销金额
'to_reconcile': line.this_reconcile, # 未核销金额
'date_due': line.date_due,
'partner_id': to_partner_id.id,
})
# 转出业务伙伴往来减少
to_invoice_id = self.env['money.invoice'].create({
'name': name,
'category_id': line.category_id.id,
'amount': -line.this_reconcile,
'date': self.date,
'date_due': line.date_due,
'partner_id': partner_id.id,
})
# 核销 转出业务伙伴 的转出金额
to_invoice_id.to_reconcile = 0
to_invoice_id.reconciled = -line.this_reconcile
# 应收冲应付,应收行、应付行分别生成负的结算单,并且核销
if business_type in ['get_to_pay']:
if not float_is_zero(line.this_reconcile, 2):
invoice_id = self.env['money.invoice'].create({
'name': name,
'category_id': line.category_id.id,
'amount': -line.this_reconcile,
'date': self.date,
'date_due': line.date_due,
'partner_id': partner_id.id,
})
# 核销 业务伙伴 的本次核销金额
invoice_id.to_reconcile = 0
invoice_id.reconciled = -line.this_reconcile
return True
@api.multi
def reconcile_order_done(self):
'''核销单的审核按钮'''
# 核销金额不能大于未核销金额
for order in self:
if order.state == 'done':
raise UserError(u'核销单%s已确认,不能再次确认。' % order.name)
order_reconcile, invoice_reconcile = 0, 0
if order.business_type in ['get_to_get', 'pay_to_pay'] and order.partner_id == order.to_partner_id:
raise UserError(u'业务伙伴和转入往来单位不能相同。\n业务伙伴:%s 转入往来单位:%s'
% (order.partner_id.name, order.to_partner_id.name))
# 核销预收预付
for line in order.advance_payment_ids:
order_reconcile += line.this_reconcile
decimal_amount = self.env.ref('core.decimal_amount')
if float_compare(line.this_reconcile, line.to_reconcile, precision_digits=decimal_amount.digits) == 1:
raise UserError(u'核销金额不能大于未核销金额。\n核销金额:%s 未核销金额:%s' % (
line.this_reconcile, line.to_reconcile))
# 更新每一行的已核销余额、未核销余额
line.name.to_reconcile -= line.this_reconcile
line.name.reconciled += line.this_reconcile
for line in order.receivable_source_ids:
invoice_reconcile += line.this_reconcile
self._get_or_pay(line, order.business_type,
order.partner_id,
order.to_partner_id, order.name)
for line in order.payable_source_ids:
if self.business_type == 'adv_get_to_pay':
invoice_reconcile += line.this_reconcile
else:
order_reconcile += line.this_reconcile
self._get_or_pay(line, order.business_type,
order.partner_id,
order.to_partner_id, order.name)
# 核销金额必须相同
if order.business_type in ['adv_pay_to_get',
'adv_get_to_pay', 'get_to_pay']:
decimal_amount = self.env.ref('core.decimal_amount')
if float_compare(order_reconcile, invoice_reconcile, precision_digits=decimal_amount.digits) != 0:
raise UserError(u'核销金额必须相同, %s 不等于 %s'
% (order_reconcile, invoice_reconcile))
order.state = 'done'
return True
@api.multi
def _get_or_pay_cancel(self, line, business_type, name):
"""
反核销时 对具体核销单行进行的操作
"""
# 每一行的已核销金额减少、未核销金额增加
line.name.to_reconcile += line.this_reconcile
line.name.reconciled -= line.this_reconcile
# 应收转应收、应付转应付、应收冲应付,找到生成的结算单反审核并删除
if business_type in ['get_to_get', 'pay_to_pay', 'get_to_pay']:
invoices = self.env['money.invoice'].search([('name', '=', name)])
for inv in invoices:
if inv.state == 'done':
inv.reconciled = 0.0
inv.money_invoice_draft()
inv.unlink()
return True
@api.multi
def reconcile_order_draft(self):
''' 核销单的反审核按钮 '''
for order in self:
if order.state == 'draft':
raise UserError(u'核销单%s已撤销,不能再次撤销。' % order.name)
order_reconcile, invoice_reconcile = 0, 0
if order.business_type in ['get_to_get', 'pay_to_pay'] and order.partner_id == order.to_partner_id:
raise UserError(u'业务伙伴和转入往来单位不能相同。\n业务伙伴:%s 转入往来单位:%s'
% (order.partner_id.name, order.to_partner_id.name))
# 反核销预收预付
for line in order.advance_payment_ids:
order_reconcile += line.this_reconcile
# 每一行的已核销余额减少、未核销余额增加
line.name.to_reconcile += line.this_reconcile
line.name.reconciled -= line.this_reconcile
# 反核销应收行
for line in order.receivable_source_ids:
invoice_reconcile += line.this_reconcile
self._get_or_pay_cancel(line, order.business_type, order.name)
# 反核销应付行
for line in order.payable_source_ids:
if order.business_type == 'adv_get_to_pay':
invoice_reconcile += line.this_reconcile
else:
order_reconcile += line.this_reconcile
self._get_or_pay_cancel(line, order.business_type, order.name)
# 反核销时,金额必须相同
if self.business_type in ['adv_pay_to_get', 'adv_get_to_pay', 'get_to_pay']:
decimal_amount = self.env.ref('core.decimal_amount')
if float_compare(order_reconcile, invoice_reconcile, precision_digits=decimal_amount.digits) != 0:
raise UserError(u'反核销时,金额必须相同, %s 不等于 %s'
% (order_reconcile, invoice_reconcile))
order.state = 'draft'
return True
class AdvancePayment(models.Model):
_name = 'advance.payment'
_description = u'核销单预收付款行'
pay_reconcile_id = fields.Many2one('reconcile.order',
string=u'核销单', ondelete='cascade',
help=u'核销单预收付款行对应的核销单')
name = fields.Many2one('money.order', string=u'预收/付款单',
copy=False, required=True, ondelete='cascade',
help=u'核销单预收/付款行对应的预收/付款单')
date = fields.Date(string=u'单据日期',
help=u'单据创建日期')
amount = fields.Float(string=u'单据金额',
digits=dp.get_precision('Amount'),
help=u'预收/付款单的预收/付金额')
reconciled = fields.Float(string=u'已核销金额',
digits=dp.get_precision('Amount'),
help=u'已核销的预收/预付款金额')
to_reconcile = fields.Float(string=u'未核销金额',
digits=dp.get_precision('Amount'),
help=u'未核销的预收/预付款金额')
this_reconcile = fields.Float(string=u'本次核销金额',
digits=dp.get_precision('Amount'),
help=u'本次核销的预收/预付款金额')
company_id = fields.Many2one(
'res.company',
string=u'公司',
change_default=True,
default=lambda self: self.env['res.company']._company_default_get())
class CostLine(models.Model):
_name = 'cost.line'
_description = u"采购销售费用"
@api.one
@api.depends('amount', 'tax_rate')
def _compute_tax(self):
"""
计算字段根据 amount 和 tax_rate 是否变化进行判定tax 是否需要重新计算
:return:
"""
self.tax = self.amount * self.tax_rate * 0.01
partner_id = fields.Many2one('partner', u'供应商', ondelete='restrict',
required=True,
help=u'采购/销售费用对应的业务伙伴')
category_id = fields.Many2one('core.category', u'类别',
required=True,
ondelete='restrict',
help=u'分类:其他支出')
amount = fields.Float(u'金额',
required=True,
digits=dp.get_precision('Amount'),
help=u'采购/销售费用金额')
tax_rate = fields.Float(u'税率(%)',
default=lambda self: self.env.user.company_id.import_tax_rate,
help=u'默认值取公司进项税率')
tax = fields.Float(u'税额',
digits=dp.get_precision('Amount'),
compute=_compute_tax,
help=u'采购/销售费用税额')
note = fields.Char(u'备注',
help=u'该采购/销售费用添加的一些标识信息')
company_id = fields.Many2one(
'res.company',
string=u'公司',
change_default=True,
default=lambda self: self.env['res.company']._company_default_get())