GoodERP
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

392 行
16KB

  1. # Copyright 2016 上海开阖软件有限公司 (http://www.osbzr.com)
  2. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  3. from odoo import api, fields, models
  4. from odoo.exceptions import UserError, ValidationError
  5. # 字段只读状态
  6. READONLY_STATES = {
  7. 'done': [('readonly', True)],
  8. 'cancel': [('readonly', True)],
  9. }
  10. class Voucher(models.Model):
  11. '''新建凭证'''
  12. _name = 'voucher'
  13. _inherit = ['mail.thread']
  14. _order = 'period_id, name desc'
  15. _description = '会计凭证'
  16. @api.model
  17. def _default_voucher_date(self):
  18. return self._default_voucher_date_impl()
  19. @api.model
  20. def _default_voucher_date_impl(self):
  21. ''' 获得默认的凭证创建日期 '''
  22. voucher_setting = self.env['ir.default']._get(
  23. 'finance.config.settings', 'defaul_voucher_date')
  24. now_date = fields.Date.context_today(self)
  25. if voucher_setting == 'last' and self.search([], limit=1):
  26. create_date = self.search([], limit=1).date
  27. else:
  28. create_date = now_date
  29. return create_date
  30. @api.model
  31. def _select_objects(self):
  32. models = self.env['ir.model'].sudo().search([])
  33. return [(model.model, model.name) for model in models]
  34. @api.depends('date')
  35. def _compute_period_id(self):
  36. for v in self:
  37. v.period_id = self.env['finance.period'].get_period(v.date)
  38. document_word_id = fields.Many2one(
  39. 'document.word', '凭证字', ondelete='restrict', required=True,
  40. default=lambda self: self.env.ref('finance.document_word_1'))
  41. date = fields.Date('凭证日期', required=True, default=_default_voucher_date,
  42. tracking=True, help='本张凭证创建的时间', copy=False)
  43. name = fields.Char('凭证号', tracking=True, copy=False)
  44. att_count = fields.Integer(
  45. '附单据', default=1, help='原始凭证的张数')
  46. period_id = fields.Many2one(
  47. 'finance.period',
  48. '会计期间',
  49. compute='_compute_period_id',
  50. ondelete='restrict',
  51. store=True,
  52. help='本张凭证发生日期对应的,会计期间')
  53. line_ids = fields.One2many(
  54. 'voucher.line',
  55. 'voucher_id',
  56. '凭证明细',
  57. copy=True,)
  58. amount_text = fields.Float('总计', compute='_compute_amount', store=True,
  59. tracking=True, digits='Amount', help='凭证金额')
  60. state = fields.Selection([('draft', '草稿'),
  61. ('done', '已确认'),
  62. ('cancel', '已作废')], '状态', default='draft',
  63. index=True,
  64. tracking=True, help='凭证所属状态!')
  65. is_checkout = fields.Boolean('结账凭证', help='是否是结账凭证')
  66. is_init = fields.Boolean('是否初始化凭证', help='是否是初始化凭证')
  67. company_id = fields.Many2one(
  68. 'res.company',
  69. string='公司',
  70. change_default=True,
  71. default=lambda self: self.env.company)
  72. ref = fields.Reference(string='前置单据',
  73. selection='_select_objects')
  74. brief = fields.Char('摘要', related="line_ids.name", store=True)
  75. details = fields.Html('明细', compute='_compute_details')
  76. @api.depends('line_ids')
  77. def _compute_details(self):
  78. for v in self:
  79. vl = {'col': ['往来单位', '科目', '借方', '贷方'], 'val': []}
  80. for line in v.line_ids:
  81. vl['val'].append([line.partner_id.name or '', line.account_id.name, line.debit, line.credit])
  82. v.details = v.company_id._get_html_table(vl)
  83. def voucher_done(self):
  84. """
  85. 确认 凭证按钮 所调用的方法
  86. :return: 主要是把 凭证的 state改变
  87. """
  88. for v in self:
  89. if v.state == 'done':
  90. raise UserError('凭证%s已经确认,请不要重复确认!' % v.name)
  91. if v.date < self.env.company.start_date:
  92. raise UserError('凭证日期不可早于启用日期')
  93. if v.period_id.is_closed:
  94. raise UserError('该会计期间已结账!不能确认')
  95. if not v.line_ids:
  96. raise ValidationError('请输入凭证行')
  97. for line in v.line_ids:
  98. if line.debit + line.credit == 0:
  99. raise ValidationError(
  100. '单行凭证行 %s 借和贷不能同时为0' % line.account_id.name)
  101. if line.debit * line.credit != 0:
  102. raise ValidationError(
  103. '单行凭证行不能同时输入借和贷\n 摘要为%s的凭证行 借方为:%s 贷方为:%s' %
  104. (line.name, line.debit, line.credit))
  105. debit_sum = sum([line.debit for line in v.line_ids])
  106. credit_sum = sum([line.credit for line in v.line_ids])
  107. precision = self.env['decimal.precision'].precision_get('Amount')
  108. debit_sum = round(debit_sum, precision)
  109. credit_sum = round(credit_sum, precision)
  110. if debit_sum != credit_sum:
  111. raise ValidationError('借贷方不平,无法确认!\n 借方合计:%s 贷方合计:%s' %
  112. (debit_sum, credit_sum))
  113. v.state = 'done'
  114. if v.is_checkout: # 月结凭证不做反转
  115. return True
  116. for line in v.line_ids:
  117. if line.account_id.costs_types == 'out' and line.credit:
  118. # 费用类科目只能在借方记账,比如银行利息收入
  119. line.debit = -line.credit
  120. line.credit = 0
  121. if line.account_id.costs_types == 'in' and line.debit:
  122. # 收入类科目只能在贷方记账,比如退款给客户的情况
  123. line.credit = -line.debit
  124. line.debit = 0
  125. def voucher_can_be_draft(self):
  126. for v in self:
  127. if v.ref:
  128. raise UserError('不能撤销确认由其他单据生成的凭证!')
  129. self.voucher_draft()
  130. def voucher_draft(self):
  131. for v in self:
  132. if v.state == 'draft':
  133. raise UserError('凭证%s已经撤销确认,请不要重复撤销!' % v.name)
  134. if v.period_id.is_closed:
  135. raise UserError('%s期 会计期间已结账!不能撤销确认' % v.period_id.name)
  136. v.state = 'draft'
  137. @api.depends('line_ids')
  138. def _compute_amount(self):
  139. for v in self:
  140. v.amount_text = str(sum([line.debit for line in v.line_ids]))
  141. # 重载write 方法
  142. def write(self, vals):
  143. for order in self: # 还需要进一步优化
  144. if self.env.context.get('call_module', False) == "checkout_wizard":
  145. return super().write(vals)
  146. if order.period_id.is_closed is True:
  147. raise UserError('%s期 会计期间已结账,凭证不能再修改!' % order.period_id.name)
  148. return super().write(vals)
  149. class VoucherLine(models.Model):
  150. '''凭证明细'''
  151. _name = 'voucher.line'
  152. _description = '会计凭证明细'
  153. @api.model
  154. def _default_get(self, data):
  155. ''' 给明细行摘要、借方金额、贷方金额字段赋默认值 '''
  156. move_obj = self.env['voucher']
  157. total = 0.0
  158. context = self._context
  159. # odoo16没这个函数
  160. # if context.get('line_ids'):
  161. # for move_line_dict in move_obj.resolve_2many_commands(
  162. # 'line_ids', context.get('line_ids')):
  163. # data['name'] = data.get('name') or move_line_dict.get('name')
  164. # total += move_line_dict.get('debit', 0.0) - \
  165. # move_line_dict.get('credit', 0.0)
  166. # data['debit'] = total < 0 and -total or 0.0
  167. # data['credit'] = total > 0 and total or 0.0
  168. return data
  169. @api.model
  170. def default_get(self, fields):
  171. ''' 创建记录时,根据字段的 default 值和该方法给字段的赋值 来给 记录上的字段赋默认值 '''
  172. fields_data = super(VoucherLine, self).default_get(fields)
  173. data = self._default_get(fields_data)
  174. # 判断 data key是否在 fields 里,如果不在则删除该 key。程序员开发用
  175. for f in list(data.keys()):
  176. if f not in fields:
  177. del data[f]
  178. return data
  179. voucher_id = fields.Many2one('voucher', '对应凭证', ondelete='cascade')
  180. name = fields.Char('摘要', required=True, help='描述本条凭证行的缘由')
  181. account_id = fields.Many2one(
  182. 'finance.account', '会计科目',
  183. ondelete='restrict',
  184. required=True,
  185. domain="[('account_type','=','normal')]")
  186. debit = fields.Float('借方金额', digits='Amount',
  187. help='每条凭证行中只能记录借方金额或者贷方金额中的一个,\
  188. 一张凭证中所有的凭证行的借方余额,必须等于贷方余额。')
  189. credit = fields.Float('贷方金额', digits='Amount',
  190. help='每条凭证行中只能记录借方金额或者贷方金额中的一个,\
  191. 一张凭证中所有的凭证行的借方余额,必须等于贷方余额。')
  192. partner_id = fields.Many2one(
  193. 'partner', '往来单位', ondelete='restrict', help='凭证行的对应的往来单位')
  194. currency_amount = fields.Float('外币金额', digits='Amount')
  195. currency_id = fields.Many2one('res.currency', '外币币别', ondelete='restrict')
  196. rate_silent = fields.Float('汇率', digits=0)
  197. period_id = fields.Many2one(
  198. related='voucher_id.period_id', string='凭证期间', store=True)
  199. goods_qty = fields.Float('数量',
  200. digits='Quantity')
  201. goods_id = fields.Many2one('goods', '商品', ondelete='restrict')
  202. auxiliary_id = fields.Many2one(
  203. 'auxiliary.financing', '辅助核算', help='辅助核算是对账务处理的一种补充,即实现更广泛的账务处理,\
  204. 以适应企业管理和决策的需要.辅助核算一般通过核算项目来实现', ondelete='restrict')
  205. date = fields.Date(compute='_compute_voucher_date',
  206. store=True, string='凭证日期')
  207. state = fields.Selection(
  208. [('draft', '草稿'), ('done', '已确认'), ('cancel', '已作废')],
  209. compute='_compute_voucher_state',
  210. index=True,
  211. store=True, string='状态')
  212. init_obj = fields.Char('初始化对象', help='描述本条凭证行由哪个单证生成而来')
  213. company_id = fields.Many2one(
  214. 'res.company',
  215. string='公司',
  216. change_default=True,
  217. default=lambda self: self.env.company)
  218. @api.depends('voucher_id.date')
  219. def _compute_voucher_date(self):
  220. for vl in self:
  221. vl.date = vl.voucher_id.date
  222. @api.depends('voucher_id.state')
  223. def _compute_voucher_state(self):
  224. for vl in self:
  225. vl.state = vl.voucher_id.state
  226. @api.onchange('account_id')
  227. def onchange_account_id(self):
  228. self.currency_id = self.account_id.currency_id
  229. self.rate_silent = self.account_id.currency_id.rate
  230. res = {
  231. 'domain': {
  232. 'partner_id': [('name', '=', False)],
  233. 'goods_id': [('name', '=', False)],
  234. 'auxiliary_id': [('name', '=', False)]}}
  235. if not self.account_id or not self.account_id.auxiliary_financing:
  236. return res
  237. if self.account_id.auxiliary_financing == 'customer':
  238. res['domain']['partner_id'] = [('c_category_id', '!=', False)]
  239. elif self.account_id.auxiliary_financing == 'supplier':
  240. res['domain']['partner_id'] = [('s_category_id', '!=', False)]
  241. elif self.account_id.auxiliary_financing == 'goods':
  242. res['domain']['goods_id'] = []
  243. else:
  244. res['domain']['auxiliary_id'] = [
  245. ('type', '=', self.account_id.auxiliary_financing)]
  246. return res
  247. def view_document(self):
  248. self.ensure_one()
  249. return {
  250. 'name': '凭证',
  251. 'view_mode': 'form',
  252. 'res_model': 'voucher',
  253. 'res_id': self.voucher_id.id,
  254. 'type': 'ir.actions.act_window',
  255. }
  256. @api.constrains('account_id')
  257. def _check_account_id(self):
  258. for record in self:
  259. if record.account_id.account_type == 'view':
  260. raise UserError('只能往下级科目记账!')
  261. def check_restricted_account(self):
  262. prohibit_account_debit_ids = self.env['finance.account'].search(
  263. [('restricted_debit', '=', True)])
  264. prohibit_account_credit_ids = self.env['finance.account'].search(
  265. [('restricted_credit', '=', True)])
  266. account_ids = []
  267. account = self.account_id
  268. account_ids.append(account)
  269. while account.parent_id:
  270. account_ids.append(account.parent_id)
  271. account = account.parent_id
  272. inner_account_debit = [
  273. acc for acc in account_ids if acc in prohibit_account_debit_ids]
  274. inner_account_credit = [
  275. acc for acc in account_ids if acc in prohibit_account_credit_ids]
  276. if self.debit and not self.credit and inner_account_debit:
  277. raise UserError(
  278. '借方禁止科目: %s-%s \n\n 提示:%s ' % (
  279. self.account_id.code,
  280. self.account_id.name,
  281. inner_account_debit[0].restricted_debit_msg))
  282. if not self.debit and self.credit and inner_account_credit:
  283. raise UserError(
  284. '贷方禁止科目: %s-%s \n\n 提示:%s ' % (
  285. self.account_id.code,
  286. self.account_id.name,
  287. inner_account_credit[0].restrict_credit_msg))
  288. @api.model_create_multi
  289. def create(self, values):
  290. """
  291. Create a new record for a model VoucherLine
  292. @param values: provides a data for new record
  293. @return: returns a id of new record
  294. """
  295. result = super(VoucherLine, self).create(values)
  296. if not self.env.context.get('entry_manual', False):
  297. return result
  298. for r in result:
  299. r.check_restricted_account()
  300. return result
  301. def write(self, values):
  302. """
  303. Update all record(s) in recordset, with new value comes as {values}
  304. return True on success, False otherwise
  305. @param values: dict of new values to be set
  306. @return: True on success, False otherwise
  307. """
  308. result = super(VoucherLine, self).write(values)
  309. if not self.env.context.get('entry_manual', False):
  310. return result
  311. for record in self:
  312. record.check_restricted_account()
  313. return result
  314. class DocumentWord(models.Model):
  315. '''凭证字'''
  316. _name = 'document.word'
  317. _description = '凭证字'
  318. name = fields.Char('凭证字')
  319. print_title = fields.Char('打印标题', help='凭证在打印时的显示的标题')
  320. company_id = fields.Many2one(
  321. 'res.company',
  322. string='公司',
  323. change_default=True,
  324. default=lambda self: self.env.company)
  325. class ChangeVoucherName(models.Model):
  326. ''' 修改凭证编号 '''
  327. _name = 'change.voucher.name'
  328. _description = '月末凭证变更记录'
  329. period_id = fields.Many2one('finance.period', '会计期间')
  330. before_voucher_name = fields.Char('以前凭证号')
  331. after_voucher_name = fields.Char('更新后凭证号')
  332. company_id = fields.Many2one(
  333. 'res.company',
  334. string='公司',
  335. change_default=True,
  336. default=lambda self: self.env.company)
上海开阖软件有限公司 沪ICP备12045867号-1