GoodERP
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

598 Zeilen
20KB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2016 上海开阖软件有限公司 (http://www.osbzr.com)
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  4. from odoo import _
  5. from odoo import api, fields, models
  6. from odoo.exceptions import UserError
  7. BALANCE_DIRECTIONS_TYPE = [
  8. ('in', '借'),
  9. ('out', '贷')]
  10. class FinanceAccountType(models.Model):
  11. """ 会计要素
  12. """
  13. _name = 'finance.account.type'
  14. _description = '会计要素'
  15. _rec_name = 'name'
  16. _order = 'name ASC'
  17. name = fields.Char('名称', required=True, translate=True)
  18. active = fields.Boolean(string='启用', default="True")
  19. costs_types = fields.Selection([
  20. ('assets', '资产'),
  21. ('debt', '负债'),
  22. ('equity', '所有者权益'),
  23. ('in', '收入类'),
  24. ('out', '费用类'),
  25. ('cost', '成本类'),
  26. ],'类型', required=True,help='用于会计报表的生成。')
  27. class FinanceAccount(models.Model):
  28. '''科目'''
  29. _name = 'finance.account'
  30. _order = "code"
  31. _description = '会计科目'
  32. _parent_store = True
  33. @api.depends('parent_id')
  34. def _compute_level(self):
  35. for record in self:
  36. level = 1
  37. parent = record.parent_id
  38. while parent:
  39. level = level + 1
  40. parent = parent.parent_id
  41. record.level = level
  42. @api.depends('child_ids', 'voucher_line_ids', 'account_type')
  43. def compute_balance(self):
  44. """
  45. 计算会计科目的当前余额
  46. :return:
  47. """
  48. for record in self:
  49. # 上级科目按下级科目汇总
  50. if record.account_type == 'view':
  51. lines = self.env['voucher.line'].search(
  52. [('account_id', 'child_of', record.id),
  53. ('voucher_id.state', '=', 'done')])
  54. record.debit = sum((line.debit) for line in lines)
  55. record.credit = sum((line.credit) for line in lines)
  56. record.balance = record.debit - record.credit
  57. # 下级科目按记账凭证计算
  58. else:
  59. record.debit = sum(record.voucher_line_ids.filtered(
  60. lambda self: self.state == 'done').mapped('debit'))
  61. record.credit = sum(record.voucher_line_ids.filtered(
  62. lambda self: self.state == 'done').mapped('credit'))
  63. record.balance = record.debit - record.credit
  64. def get_balance(self, period_id=False):
  65. ''' 科目当前或某期间的借方、贷方、差额 '''
  66. self.ensure_one()
  67. domain = []
  68. data = {}
  69. period = self.env['finance.period']
  70. if period_id:
  71. domain.append(('period_id', '=', period_id))
  72. if self.account_type == 'view':
  73. domain.extend([
  74. ('account_id', 'child_of', self.id),
  75. ('voucher_id.state', '=', 'done')])
  76. lines = self.env['voucher.line'].search(domain)
  77. debit = sum((line.debit) for line in lines)
  78. credit = sum((line.credit) for line in lines)
  79. balance = self.debit - self.credit
  80. data.update({'debit': debit, 'credit': credit, 'balance': balance})
  81. # 下级科目按记账凭证计算
  82. else:
  83. if period_id:
  84. period = self.env['finance.period'].browse(period_id)
  85. if period:
  86. debit = sum(self.voucher_line_ids.filtered(
  87. lambda self: self.period_id == period
  88. and self.state == 'done'
  89. ).mapped('debit'))
  90. credit = sum(self.voucher_line_ids.filtered(
  91. lambda self: self.period_id == period
  92. and self.state == 'done'
  93. ).mapped('credit'))
  94. balance = self.debit - self.credit
  95. else:
  96. debit = sum(self.voucher_line_ids.filtered(
  97. lambda self: self.state == 'done').mapped('debit'))
  98. credit = sum(self.voucher_line_ids.filtered(
  99. lambda self: self.state == 'done').mapped('credit'))
  100. balance = self.debit - self.credit
  101. data.update({'debit': debit, 'credit': credit, 'balance': balance})
  102. return data
  103. display_name = fields.Char(compute='_compute_display_name',store=False)
  104. name = fields.Char('名称', required=True, translate=True)
  105. code = fields.Char('编码', required=True)
  106. balance_directions = fields.Selection(
  107. [('in', '借'),('out', '贷')],'余额方向',
  108. required=True,
  109. help='根据科目的类型,判断余额方向是借方或者贷方!')
  110. auxiliary_financing = fields.Selection(
  111. [('customer', '客户'),
  112. ('supplier', '供应商'),
  113. ('member', '个人'),
  114. ('project', '项目'),
  115. ('department', '部门'),
  116. ('goods', '存货'),
  117. ],
  118. '辅助核算',
  119. help='辅助核算是对账务处理的一种补充,即实现更广泛的账务处理,\n\
  120. 以适应企业管理和决策的需要.辅助核算一般通过核算项目来实现')
  121. costs_types = fields.Selection(related='user_type.costs_types')
  122. account_type = fields.Selection(
  123. string='科目类型',
  124. selection=[('view', 'View'), ('normal', 'Normal')],
  125. default='normal')
  126. user_type = fields.Many2one(
  127. string='会计要素',
  128. comodel_name='finance.account.type',
  129. ondelete='restrict',
  130. required=True,
  131. default=lambda s: s.env.get(
  132. 'finance.account.type').search([], limit=1).id)
  133. parent_left = fields.Integer('Left Parent', index=1)
  134. parent_right = fields.Integer('Right Parent', index=1)
  135. parent_id = fields.Many2one(
  136. string='上级科目',
  137. comodel_name='finance.account',
  138. ondelete='restrict',
  139. domain="[('account_type','=','view')]")
  140. parent_path = fields.Char(index=True)
  141. child_ids = fields.One2many(
  142. string='下级科目',
  143. comodel_name='finance.account',
  144. inverse_name='parent_id', )
  145. level = fields.Integer(string='科目级次', compute='_compute_level')
  146. currency_id = fields.Many2one('res.currency', '外币币别')
  147. exchange = fields.Boolean('是否期末调汇')
  148. active = fields.Boolean('启用', default=True)
  149. company_id = fields.Many2one(
  150. 'res.company',
  151. string='公司',
  152. change_default=True,
  153. default=lambda self: self.env.company)
  154. voucher_line_ids = fields.One2many(
  155. string='Voucher Lines',
  156. comodel_name='voucher.line',
  157. inverse_name='account_id', )
  158. debit = fields.Float(
  159. string='借方',
  160. compute='compute_balance',
  161. store=False,
  162. digits='Amount')
  163. credit = fields.Float(
  164. string='贷方',
  165. compute='compute_balance',
  166. store=False,
  167. digits='Amount')
  168. balance = fields.Float('当前余额',
  169. compute='compute_balance',
  170. store=False,
  171. digits='Amount',
  172. help='科目的当前余额',
  173. )
  174. restricted_debit = fields.Boolean(
  175. string='借方限制使用',
  176. help='手工凭证时, 借方限制使用'
  177. )
  178. restricted_debit_msg = fields.Char(
  179. string='借方限制提示消息',
  180. )
  181. restricted_credit = fields.Boolean(
  182. string='贷方限制使用',
  183. help='手工凭证时, 贷方限制使用'
  184. )
  185. restrict_credit_msg = fields.Char(
  186. string='贷方限制提示消息',
  187. )
  188. source = fields.Selection(
  189. string='创建来源',
  190. selection=[('init', '初始化'), ('manual', '手工创建')], default='manual'
  191. )
  192. _sql_constraints = [
  193. ('name_uniq', 'unique(name)', '科目名称必须唯一。'),
  194. ('code', 'unique(code)', '科目编码必须唯一。'),
  195. ]
  196. @api.depends('name', 'code')
  197. def name_get(self):
  198. """
  199. 在其他model中用到account时在页面显示 code name balance如:2202 应付账款 当前余额(更有利于会计记账)
  200. :return:
  201. """
  202. result = []
  203. for line in self:
  204. account_name = line.code + ' ' + line.name
  205. if line.env.context.get('show_balance'):
  206. account_name += ' ' + str(line.balance)
  207. result.append((line.id, account_name))
  208. return result
  209. @api.depends('code', 'name')
  210. def _compute_display_name(self):
  211. """
  212. 在其他model中用到account时在页面显示 code name balance如:2202 应付账款 当前余额(更有利于会计记账)
  213. :return:
  214. 替代 name_get, 注意原来的 name_get 被name_search使用, 不能删除
  215. """
  216. for record in self:
  217. record.display_name = record.code + ' ' + record.name
  218. if record.env.context.get('show_balance'):
  219. record.display_name += ' ' + str(record.balance)
  220. @api.model
  221. def name_search(self, name, args=None, operator='ilike', limit=100):
  222. '''会计科目按名字和编号搜索'''
  223. args = args or []
  224. domain = []
  225. if name:
  226. res_id = self.search([('code', '=', name)]+args)
  227. if res_id:
  228. return res_id.name_get()
  229. domain = ['|', ('code', '=ilike', name + '%'),
  230. ('name', operator, name)]
  231. accounts = self.search(domain + args, limit=limit)
  232. return accounts.name_get()
  233. def get_smallest_code_account(self):
  234. """
  235. 取得最小的code对应的account对象
  236. :return: 最小的code 对应的对象
  237. """
  238. finance_account_row = self.search([], order='code')
  239. return finance_account_row and finance_account_row[0]
  240. def get_max_code_account(self):
  241. """
  242. 取得最大的code对应的account对象
  243. :return: 最大的code 对应的对象
  244. """
  245. finance_account_row = self.search([], order='code desc')
  246. return finance_account_row and finance_account_row[0]
  247. def write(self, values):
  248. """
  249. 限制科目修改条件
  250. """
  251. for record in self:
  252. if record.source == 'init' and record.env.context.get(
  253. 'modify_from_webclient', False):
  254. raise UserError(_('不能修改预设会计科目!'))
  255. if record.voucher_line_ids and record.env.context.get(
  256. 'modify_from_webclient', False):
  257. raise UserError(_('不能修改有记账凭证的会计科目!'))
  258. return super(FinanceAccount, self).write(values)
  259. def unlink(self):
  260. """
  261. 限制科目删除条件
  262. """
  263. parent_ids = []
  264. for record in self:
  265. if record.parent_id not in parent_ids:
  266. parent_ids.append(record.parent_id)
  267. if record.source == 'init' and record.env.context.get(
  268. 'modify_from_webclient', False):
  269. raise UserError(_('不能删除预设会计科目!'))
  270. if record.voucher_line_ids:
  271. raise UserError(_('不能删除有记账凭证的会计科目!'))
  272. if len(record.child_ids) != 0:
  273. raise UserError(_('不能删除有下级科目的会计科目!'))
  274. '''
  275. 此处 https://github.com/osbzr/gooderp_addons/
  276. commit/a4c3f2725ba602854149001563002dcedaa89e3d
  277. 导致代码xml中删除数据时发生混乱,暂时拿掉
  278. ir_record = self.env['ir.model.data'].search(
  279. [('model','=','finance.account'),('res_id','=', record.id)])
  280. if ir_record:
  281. ir_record.res_id = record.parent_id.id
  282. '''
  283. result = super(FinanceAccount, self).unlink()
  284. # 如果 下级科目全删除了,则将 上级科目设置为 普通科目
  285. for parent_id in parent_ids:
  286. if len(parent_id.child_ids.ids) == 0:
  287. parent_id.with_context(
  288. modify_from_webclient=False).account_type = 'normal'
  289. return result
  290. def button_add_child(self):
  291. self.ensure_one()
  292. view = self.env.ref('finance.view_wizard_account_add_child_form')
  293. return {
  294. 'name': _('增加下级科目'),
  295. 'type': 'ir.actions.act_window',
  296. 'view_mode': 'form',
  297. 'res_model': 'wizard.account.add.child',
  298. 'views': [(view.id, 'form')],
  299. 'view_id': view.id,
  300. 'target': 'new',
  301. 'context': dict(
  302. self.env.context, active_id=self.id,
  303. active_ids=[self.id],
  304. modify_from_webclient=False),
  305. }
  306. class WizardAccountAddChild(models.TransientModel):
  307. """ 向导,用于新增下级科目.
  308. """
  309. _name = 'wizard.account.add.child'
  310. _description = 'Wizard Account Add Child'
  311. parent_id = fields.Many2one(
  312. string='上级科目',
  313. comodel_name='finance.account',
  314. ondelete='set null',
  315. )
  316. parent_path = fields.Char(index=True)
  317. parent_name = fields.Char(
  318. string='上级科目名称',
  319. related='parent_id.name',
  320. readonly=True,
  321. )
  322. parent_code = fields.Char(
  323. string='上级科目编码',
  324. related='parent_id.code',
  325. readonly=True,
  326. )
  327. account_code = fields.Char(
  328. string='新增编码', required=True
  329. )
  330. currency_id = fields.Many2one(
  331. 'res.currency', '外币币别')
  332. full_account_code = fields.Char(
  333. string='完整科目编码',
  334. )
  335. account_name = fields.Char(
  336. string='科目名称', required=True
  337. )
  338. account_type = fields.Selection(
  339. string='Account Type',
  340. related='parent_id.account_type'
  341. )
  342. has_journal_items = fields.Boolean(
  343. string='Has Journal Items',
  344. )
  345. @api.model
  346. def default_get(self, fields):
  347. if len(self.env.context.get('active_ids', list())) > 1:
  348. raise UserError(_("一次只能为一个科目增加下级科目!"))
  349. account_id = self.env.context.get('active_id')
  350. account = self.env['finance.account'].browse(account_id)
  351. has_journal_items = False
  352. if account.voucher_line_ids:
  353. has_journal_items = True
  354. if account.level >= int(self.env['ir.default']._get(
  355. 'finance.config.settings', 'defaul_account_hierarchy_level')):
  356. raise UserError(
  357. _('选择的科目层级是%s级,已经是最低层级科目了,不能建立在它下面建立下级科目!')
  358. % account.level)
  359. res = super(WizardAccountAddChild, self).default_get(fields)
  360. res.update({
  361. 'parent_id': account_id,
  362. 'has_journal_items': has_journal_items
  363. })
  364. return res
  365. def create_account(self):
  366. self.ensure_one()
  367. account_type = self.parent_id.account_type
  368. new_account = False
  369. full_account_code = '%s%s' % (self.parent_code, self.account_code)
  370. if account_type == 'normal':
  371. # 挂账科目,需要对现有凭证进行科目转换
  372. # step1, 建新科目
  373. new_account = self.parent_id.copy(
  374. {
  375. 'code': full_account_code,
  376. 'name': self.account_name,
  377. 'account_type': 'normal',
  378. 'source': 'manual',
  379. 'currency_id': self.currency_id.id,
  380. 'parent_id': self.parent_id.id
  381. }
  382. )
  383. # step2, 将关联凭证改到新科目
  384. self.env['voucher.line'].search(
  385. [('account_id', '=', self.parent_id.id)]).write(
  386. {'account_id': new_account.id})
  387. # step3, 老科目改为 视图
  388. self.parent_id.write({
  389. 'account_type': 'view',
  390. })
  391. elif account_type == 'view':
  392. # 直接新增下级科目,无需转换科目
  393. new_account = self.parent_id.copy(
  394. {
  395. 'code': full_account_code,
  396. 'name': self.account_name,
  397. 'account_type': 'normal',
  398. 'source': 'manual',
  399. 'currency_id': self.currency_id.id,
  400. 'parent_id': self.parent_id.id
  401. }
  402. )
  403. if not new_account: # pragma: no cover
  404. raise UserError(_('新科目创建失败!'))
  405. view = self.env.ref('finance.finance_account_list')
  406. return {
  407. 'name': _('科目'),
  408. 'type': 'ir.actions.act_window',
  409. 'view_mode': 'list',
  410. 'res_model': 'finance.account',
  411. 'views': [(view.id, 'list')],
  412. 'view_id': view.id,
  413. 'target': 'current',
  414. 'context': dict(
  415. self.env.context,
  416. hide_button=False,
  417. modify_from_webclient=True)
  418. }
  419. @api.onchange('account_code')
  420. def _onchange_account_code(self):
  421. def is_number(chars):
  422. try:
  423. int(chars)
  424. return True
  425. except ValueError:
  426. return False
  427. if self.account_code and not is_number(self.account_code):
  428. self.account_code = '01'
  429. return {
  430. 'warning': {
  431. 'title': _('错误'),
  432. 'message': _('科目代码必须是数字')
  433. }
  434. }
  435. default_child_step = self.env['ir.default']._get(
  436. 'finance.config.settings', 'defaul_child_step')
  437. if self.account_code:
  438. self.full_account_code = "%s%s" % (
  439. self.parent_code, self.account_code)
  440. if self.account_code and len(
  441. self.account_code) != int(default_child_step):
  442. self.account_code = '01'
  443. self.full_account_code = self.parent_code
  444. return {
  445. 'warning': {
  446. 'title': _('错误'),
  447. 'message': _('下级科目编码长度与"下级科目编码递增长度"规则不符合!')
  448. }
  449. }
  450. class AuxiliaryFinancing(models.Model):
  451. '''辅助核算'''
  452. _name = 'auxiliary.financing'
  453. _description = '辅助核算'
  454. code = fields.Char('编码')
  455. name = fields.Char('名称')
  456. type = fields.Selection([
  457. ('member', '个人'),
  458. ('project', '项目'),
  459. ('department', '部门'),
  460. ], '辅助核算类别', default=lambda self: self.env.context.get('type'))
  461. active = fields.Boolean('启用', default=True)
  462. company_id = fields.Many2one(
  463. 'res.company',
  464. string='公司',
  465. change_default=True,
  466. default=lambda self: self.env.company)
  467. _sql_constraints = [
  468. ('name_uniq', 'unique(name)', '辅助核算不能重名')
  469. ]
  470. class ResCompany(models.Model):
  471. '''继承公司对象,添加字段'''
  472. _inherit = 'res.company'
  473. cogs_account = fields.Many2one(
  474. 'finance.account',
  475. '主营业务成本科目',
  476. ondelete='restrict',
  477. help='主营业务成本科目,销项发票开具时会用到。')
  478. profit_account = fields.Many2one(
  479. 'finance.account',
  480. '本年利润科目',
  481. ondelete='restrict',
  482. help='本年利润科目,本年中盈利的科目,在结转时会用到。')
  483. remain_account = fields.Many2one(
  484. 'finance.account', '未分配利润科目', ondelete='restrict', help='未分配利润科目。')
  485. import_tax_account = fields.Many2one(
  486. 'finance.account',
  487. "进项税科目",
  488. ondelete='restrict',
  489. help='进项税额,是指纳税人购进货物、加工修理修配劳务、服务、无形资产或者不动产,支付或者负担的增值税额。')
  490. output_tax_account = fields.Many2one(
  491. 'finance.account', "销项税科目", ondelete='restrict')
  492. operating_cost_account_id = fields.Many2one(
  493. 'finance.account',
  494. ondelete='restrict',
  495. string='生产费用科目',
  496. help='用在组装拆卸的费用上')
  497. class BankAccount(models.Model):
  498. _inherit = 'bank.account'
  499. account_id = fields.Many2one(
  500. 'finance.account',
  501. '科目',
  502. domain="[('account_type','=','normal')]")
  503. currency_id = fields.Many2one(
  504. 'res.currency', '外币币别', related='account_id.currency_id', store=True)
  505. currency_amount = fields.Float('外币金额', digits='Amount')
  506. class CoreCategory(models.Model):
  507. """继承core category,添加科目类型"""
  508. _inherit = 'core.category'
  509. account_id = fields.Many2one(
  510. 'finance.account',
  511. '科目',
  512. help='科目',
  513. domain="[('account_type','=','normal')]")
上海开阖软件有限公司 沪ICP备12045867号-1