GoodERP
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

603 lines
21KB

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