GoodERP
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

308 lines
13KB

  1. # Copyright 2016 上海开阖软件有限公司 (http://www.osbzr.com)
  2. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
  3. from odoo.exceptions import UserError
  4. from odoo import fields, models, api
  5. class MoneyInvoice(models.Model):
  6. _name = 'money.invoice'
  7. _description = '结算单'
  8. _order = 'date DESC'
  9. @api.model
  10. def _get_category_id(self):
  11. cate_type = self.env.context.get('type')
  12. if cate_type:
  13. return self.env['core.category'].search(
  14. [('type', '=', cate_type)])[0]
  15. return False
  16. def name_get(self):
  17. '''在many2one字段里有order则显示单号否则显示名称_票号'''
  18. res = []
  19. for invoice in self:
  20. if self.env.context.get('order'):
  21. res.append((invoice.id, invoice.name))
  22. else:
  23. res.append(
  24. (invoice.id, invoice.bill_number
  25. and invoice.bill_number or invoice.name))
  26. return res
  27. @api.depends('bill_number', 'name')
  28. def _compute_display_name(self):
  29. '''在many2one字段里有order则显示单号否则显示名称_票号, 注意原来的 name_get 被name_search使用, 不能删除'''
  30. for record in self:
  31. if self.env.context.get('order'):
  32. record.display_name = record.name
  33. else:
  34. record.display_name = record.bill_number and (record.name + '_' + record.bill_number) or record.name
  35. @api.depends('date_due', 'to_reconcile')
  36. def compute_overdue(self):
  37. """
  38. 计算逾期天数: 当前日期 - 到期日,< 0则显示为0;如果逾期金额为0则逾期天数也为0
  39. 计算逾期金额: 逾期时等于未核销金额,否则为0
  40. :return: 逾期天数
  41. """
  42. for s in self:
  43. # 只计算未核销的
  44. s.overdue_days = 0
  45. s.overdue_amount = 0
  46. if s.to_reconcile and s.state == 'done':
  47. d1 = fields.Date.context_today(s)
  48. d2 = s.date_due or d1
  49. day = (d1 - d2).days
  50. if day > 0:
  51. s.overdue_days = day
  52. s.overdue_amount = s.to_reconcile
  53. @api.depends('reconciled')
  54. def _get_sell_amount_state(self):
  55. for mi in self:
  56. if mi.reconciled:
  57. mi.get_amount_date = mi.write_date
  58. state = fields.Selection([
  59. ('draft', '草稿'),
  60. ('done', '完成')
  61. ], string='状态',
  62. default='draft', copy=False, index=True,
  63. help='结算单状态标识,新建时状态为草稿;确认后状态为完成')
  64. partner_id = fields.Many2one('partner', string='往来单位',
  65. required=True,
  66. ondelete='restrict',
  67. help='该单据对应的业务伙伴')
  68. display_name = fields.Char(compute='_compute_display_name', store=True)
  69. name = fields.Char(string='前置单据编号', copy=False,
  70. readonly=True, required=True,
  71. help='该结算单编号,取自生成结算单的采购入库单和销售入库单')
  72. category_id = fields.Many2one(
  73. 'core.category',
  74. string='类别',
  75. domain="[('type', 'in', ('income','expense'))]",
  76. ondelete='restrict',
  77. default=_get_category_id,
  78. help='结算单类别:采购 或者 销售等')
  79. date = fields.Date(string='日期', required=True,
  80. default=lambda self: fields.Date.context_today(self),
  81. help='单据创建日期')
  82. amount = fields.Float(string='金额(含税)',
  83. digits='Amount',
  84. help='原始单据对应金额')
  85. reconciled = fields.Float(string='已核销金额', readonly=True,
  86. digits='Amount',
  87. help='原始单据已核销掉的金额')
  88. to_reconcile = fields.Float(string='未核销金额', readonly=True,
  89. digits='Amount',
  90. help='原始单据未核销掉的金额')
  91. tax_amount = fields.Float('税额',
  92. digits='Amount',
  93. help='对应税额')
  94. get_amount_date = fields.Date('最后收款日期', compute=_get_sell_amount_state,
  95. store=True, copy=False)
  96. auxiliary_id = fields.Many2one('auxiliary.financing', '辅助核算',
  97. help='辅助核算')
  98. pay_method = fields.Many2one('pay.method',
  99. string='付款方式',
  100. ondelete='restrict')
  101. date_due = fields.Date(string='到期日',
  102. help='结算单的到期日')
  103. currency_id = fields.Many2one('res.currency', '外币币别',
  104. help='原始单据对应的外币币别')
  105. is_multi_currency = fields.Boolean('多币种', related='company_id.is_multi_currency')
  106. bill_number = fields.Char('纸质发票号',
  107. help='纸质发票号')
  108. invoice_date = fields.Date('开票日期')
  109. is_init = fields.Boolean('是否初始化单')
  110. company_id = fields.Many2one(
  111. 'res.company',
  112. string='公司',
  113. change_default=True,
  114. default=lambda self: self.env.company)
  115. overdue_days = fields.Float('逾期天数', readonly=True,
  116. compute='compute_overdue',
  117. help='当前日期 - 到期日')
  118. overdue_amount = fields.Float('逾期金额', readonly=True,
  119. compute='compute_overdue',
  120. help='超过到期日后仍未核销的金额')
  121. note = fields.Char('备注',
  122. help='可填入到期日计算的依据')
  123. handwork = fields.Boolean('手工结算')
  124. def money_invoice_done(self):
  125. """
  126. 结算单审核方法
  127. """
  128. for inv in self:
  129. if inv.state == 'done':
  130. raise UserError('请不要重复确认')
  131. inv.reconciled = 0.0
  132. inv.to_reconcile = inv.amount
  133. inv.state = 'done'
  134. if inv.pay_method:
  135. inv.date_due = inv.pay_method.get_due_date(inv.invoice_date)
  136. else:
  137. inv.date_due = inv.partner_id.pay_method.get_due_date(
  138. inv.invoice_date)
  139. if inv.category_id.type == 'income':
  140. inv.partner_id.receivable += inv.amount
  141. if inv.category_id.type == 'expense':
  142. inv.partner_id.payable += inv.amount
  143. if inv.handwork:
  144. # 手工结算单开票日期
  145. inv.invoice_date = inv.date
  146. if inv.is_init:
  147. inv.bill_number = '期初余额'
  148. inv.invoice_date = inv.date
  149. return True
  150. def money_invoice_draft(self):
  151. """
  152. 结算单反审核方法
  153. :return:
  154. """
  155. for inv in self:
  156. if inv.state == 'draft':
  157. raise UserError('请不要重复撤销 %s' % self._description)
  158. if inv.reconciled != 0.0:
  159. raise UserError('已核销的结算单不允许撤销')
  160. # 查找发票相关的收款单
  161. source_line = self.env['source.order.line'].search(
  162. [('name', '=', inv.id)])
  163. for line in source_line:
  164. ref_name = False
  165. if line.money_id:
  166. ref_name = line.money_id.name
  167. if line.payable_reconcile_id:
  168. ref_name = line.payable_reconcile_id.name
  169. if line.receivable_reconcile_id:
  170. ref_name = line.receivable_reconcile_id.name
  171. if ref_name:
  172. raise UserError('发票已被单据 %s 引用,无法撤销' % ref_name)
  173. inv.reconciled = 0.0
  174. inv.to_reconcile = 0.0
  175. inv.state = 'draft'
  176. if inv.category_id.type == 'income':
  177. inv.partner_id.receivable -= inv.amount
  178. if inv.category_id.type == 'expense':
  179. inv.partner_id.payable -= inv.amount
  180. @api.model_create_multi
  181. def create(self, vals_list):
  182. """
  183. 创建结算单时,如果公司上的‘根据发票确认应收应付’字段没有勾上,则直接审核结算单,否则不审核。
  184. """
  185. new_ids = super(MoneyInvoice, self).create(vals_list)
  186. for new_id in new_ids:
  187. if not self.env.user.company_id.draft_invoice:
  188. new_id.money_invoice_done()
  189. return new_ids
  190. def write(self, values):
  191. """
  192. 当更新结算单到期日时,纸质发票号 相同的结算单到期日一起更新
  193. """
  194. if values.get('date_due') and self.bill_number \
  195. and not self.env.context.get('other_invoice_date_due'):
  196. invoices = self.search([('bill_number', '=', self.bill_number)])
  197. for inv in invoices:
  198. inv.with_context({'other_invoice_date_due': True}).write(
  199. {'date_due': values.get('date_due')})
  200. return super(MoneyInvoice, self).write(values)
  201. def unlink(self):
  202. """
  203. 只允许删除未确认的单据
  204. """
  205. for invoice in self:
  206. if invoice.name == '.' and invoice.reconciled == 0.0:
  207. self.money_invoice_draft()
  208. continue
  209. return super(MoneyInvoice, self).unlink()
  210. def find_source_order(self):
  211. '''
  212. 查看原始单据,有以下情况:销售发货单、销售退货单、采购退货单、采购入库单、
  213. 项目、委外加工单、核销单、采购订单、固定资产、固定资产变更以及期初应收应付。
  214. '''
  215. self.ensure_one()
  216. code = False
  217. res_models = [
  218. 'reconcile.order',
  219. ]
  220. views = [
  221. 'money.reconcile_order_form',
  222. ]
  223. # 判断当前数据库中否存在该 model
  224. if self.env.get('sell.delivery') is not None:
  225. res_models += ['sell.delivery']
  226. views += ['sell.sell_delivery_form']
  227. if self.env.get('outsource') is not None:
  228. res_models += ['outsource']
  229. views += ['warehouse.outsource_form']
  230. if self.env.get('buy.order') is not None:
  231. res_models += ['buy.order']
  232. views += ['buy.buy_order_form']
  233. if self.env.get('buy.receipt') is not None:
  234. res_models += ['buy.receipt']
  235. views += ['buy.buy_receipt_form']
  236. if self.env.get('project') is not None:
  237. res_models += ['project']
  238. views += ['task.project_form']
  239. if self.env.get('asset') is not None:
  240. res_models += ['asset']
  241. views += ['asset.asset_form']
  242. if self.env.get('cost.order') is not None:
  243. res_models += ['cost.order']
  244. views += ['account_cost.cost_order_form']
  245. if self.env.get('hr.expense') is not None:
  246. res_models += ['hr.expense']
  247. views += ['staff_expense.hr_expense_form']
  248. if '固定资产变更' in self.name:
  249. code = self.name.replace('固定资产变更', '')
  250. elif '固定资产' in self.name:
  251. code = self.name.replace('固定资产', '')
  252. domain = code and [('code', '=', code)] or [('name', '=', self.name)]
  253. for i in range(len(res_models)):
  254. # 若code存在说明 model为asset,view为固定资产form视图。
  255. res_model = code and 'asset' or res_models[i]
  256. view = code and self.env.ref(
  257. 'asset.asset_form') or self.env.ref(views[i])
  258. res = self.env[res_model].search(domain)
  259. if res: # 如果找到res_id,则退出for循环。
  260. break
  261. if not res:
  262. raise UserError('没有原始单据可供查看。')
  263. if res_model == 'sell.delivery' and res.is_return:
  264. view = self.env.ref('sell.sell_return_form')
  265. elif res_model == 'buy.receipt' and res.is_return:
  266. view = self.env.ref('buy.buy_return_form')
  267. return {
  268. 'view_mode': 'form',
  269. 'view_id': False,
  270. 'views': [(view.id, 'form')],
  271. 'res_model': res_model,
  272. 'type': 'ir.actions.act_window',
  273. 'res_id': res.id,
  274. }
  275. def amount_add(self):
  276. self.amount += 0.01
  277. def amount_sub(self):
  278. self.amount -= 0.01
  279. def tax_add(self):
  280. self.tax_amount += 0.01
  281. def tax_sub(self):
  282. self.tax_amount -= 0.01
上海开阖软件有限公司 沪ICP备12045867号-1