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

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