GoodERP
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

461 lines
13KB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2016 上海开阖软件有限公司 (http://www.osbzr.com)
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  4. from odoo import _
  5. from odoo import api, fields, models
  6. from odoo.tools import float_is_zero
  7. from odoo.exceptions import UserError
  8. # 状态可选值
  9. TASK_STATES = [
  10. ('todo', 'New'),
  11. ('doing', 'Ongoing'),
  12. ('done', 'Done'),
  13. ('cancel', 'Cancelled'),
  14. ]
  15. AVAILABLE_PRIORITIES = [
  16. ('0', '0'),
  17. ('1', '1'),
  18. ('2', '2'),
  19. ('3', '3'),
  20. ]
  21. class Project(models.Model):
  22. _name = 'project'
  23. _description = '项目'
  24. _inherits = {'auxiliary.financing': 'auxiliary_id'}
  25. _inherit = ['mail.thread', 'project']
  26. @api.depends('task_ids.hours')
  27. def _compute_hours(self):
  28. '''计算项目的实际工时'''
  29. for project in self:
  30. for task in project.task_ids:
  31. project.hours += task.hours
  32. project_name = fields.Char(string='项目名称', related='auxiliary_id.name', store=True, required=True)
  33. type_id = fields.Many2one(
  34. string='项目分类',
  35. comodel_name='core.value',
  36. ondelete='restrict',
  37. domain=[('type', '=', 'project_type')],
  38. context={'type': 'project_type'},
  39. )
  40. auxiliary_id = fields.Many2one(
  41. string='辅助核算',
  42. comodel_name='auxiliary.financing',
  43. ondelete='cascade',
  44. required=True,
  45. )
  46. task_ids = fields.One2many(
  47. string='任务',
  48. comodel_name='task',
  49. inverse_name='project_id',
  50. )
  51. customer_id = fields.Many2one(
  52. string='客户',
  53. comodel_name='partner',
  54. ondelete='restrict',
  55. )
  56. invoice_ids = fields.One2many(
  57. string='发票行',
  58. comodel_name='project.invoice',
  59. inverse_name='project_id',
  60. )
  61. plan_hours = fields.Float('计划工时')
  62. hours = fields.Float('实际工时',
  63. compute=_compute_hours,
  64. store=True)
  65. address = fields.Char('地址')
  66. note = fields.Text('备注')
  67. active = fields.Boolean('启用', default=True)
  68. class ProjectInvoice(models.Model):
  69. _name = 'project.invoice'
  70. _description = '项目的发票'
  71. @api.depends('tax_rate', 'amount')
  72. def _compute_tax_amount(self):
  73. '''计算税额'''
  74. self.ensure_one()
  75. if self.tax_rate > 100:
  76. raise UserError(_('税率不能输入超过100的数\n当前税率:%s') % self.tax_rate)
  77. if self.tax_rate < 0:
  78. raise UserError(_('税率不能输入负数\n当前税率:%s') % self.tax_rate)
  79. self.tax_amount = self.amount / (100 + self.tax_rate) * self.tax_rate
  80. project_id = fields.Many2one(
  81. string='项目',
  82. comodel_name='project',
  83. ondelete='cascade',
  84. )
  85. tax_rate = fields.Float(
  86. string='税率',
  87. default=lambda self: self.env.user.company_id.output_tax_rate,
  88. help='默认值取公司销项税率',
  89. )
  90. tax_amount = fields.Float(
  91. string='税额',
  92. compute=_compute_tax_amount,
  93. )
  94. amount = fields.Float(
  95. string='含税金额',
  96. help='含税金额',
  97. )
  98. date_due = fields.Date(
  99. string='到期日',
  100. default=lambda self: fields.Date.context_today(self),
  101. required=True,
  102. help='收款截止日期',
  103. )
  104. invoice_id = fields.Many2one(
  105. string='发票号',
  106. comodel_name='money.invoice',
  107. readonly=True,
  108. copy=False,
  109. ondelete='set null',
  110. help='产生的发票号',
  111. )
  112. company_id = fields.Many2one(
  113. 'res.company',
  114. string='公司',
  115. change_default=True,
  116. default=lambda self: self.env.company
  117. )
  118. def _get_invoice_vals(self, category_id, project_id, amount, tax_amount):
  119. '''返回创建 money_invoice 时所需数据'''
  120. return {
  121. 'name': project_id.name,
  122. 'partner_id': project_id.customer_id and project_id.customer_id.id,
  123. 'category_id': category_id.id,
  124. 'auxiliary_id': project_id.auxiliary_id.id,
  125. 'date' : fields.Date.context_today(self),
  126. 'amount': amount,
  127. 'reconciled': 0,
  128. 'to_reconcile': amount,
  129. 'tax_amount': tax_amount,
  130. 'date_due': self.date_due,
  131. 'state': 'draft',
  132. }
  133. def make_invoice(self):
  134. '''生成结算单'''
  135. for line in self:
  136. invoice_id = False
  137. if not line.project_id.customer_id:
  138. raise UserError(_('生成发票前请输入客户'))
  139. category = self.env.ref('money.core_category_sale')
  140. if not float_is_zero(self.amount, 2):
  141. invoice_id = self.env['money.invoice'].create(
  142. self._get_invoice_vals(
  143. category, line.project_id, line.amount, line.tax_amount)
  144. )
  145. line.invoice_id = invoice_id.id
  146. return invoice_id
  147. class Task(models.Model):
  148. _name = 'task'
  149. _description = '任务'
  150. _inherit = ['mail.thread']
  151. _order = 'sequence, priority desc, id'
  152. @api.depends('timeline_ids.hours')
  153. def _compute_hours(self):
  154. """计算任务的实际工时"""
  155. for Task in self:
  156. for line in Task.timeline_ids:
  157. Task.hours += line.hours
  158. def _default_status_impl(self):
  159. """任务阶段默认值的实现方法"""
  160. status_id = self.env['task.status'].search(
  161. [('state', '=', 'todo')])
  162. return status_id and status_id[0]
  163. @api.model
  164. def _default_status(self):
  165. '''创建任务时,任务阶段默认为todo状态的阶段'''
  166. return self._default_status_impl()
  167. @api.depends('project_id.type_id')
  168. def _compute_project_type_id(self):
  169. for line in self:
  170. line.project_type_id = False
  171. if line.project_id:
  172. line.project_type_id = self.project_id.type_id
  173. name = fields.Char(
  174. string='名称',
  175. required=True,
  176. )
  177. user_id = fields.Many2one(
  178. string='指派给',
  179. comodel_name='res.users',
  180. )
  181. project_id = fields.Many2one(
  182. string='项目',
  183. comodel_name='project',
  184. ondelete='cascade',
  185. )
  186. project_type_id = fields.Many2one(
  187. string='分类',
  188. comodel_name='core.value',
  189. ondelete='restrict',
  190. compute=_compute_project_type_id,
  191. store=True)
  192. timeline_ids = fields.One2many(
  193. string='工作记录',
  194. comodel_name='timeline',
  195. inverse_name='task_id',
  196. )
  197. next_action = fields.Char(
  198. string='下一步计划',
  199. required=False,
  200. help='针对此任务下一步的计划',
  201. )
  202. next_datetime = fields.Datetime(
  203. string='下一步计划时间',
  204. help='下一步计划预计开始的时间',
  205. )
  206. status = fields.Many2one(
  207. 'task.status',
  208. string='状态',
  209. default=_default_status,
  210. ondelete='restrict',
  211. domain="['|', ('project_type_id', '=', project_type_id), ('state','!=','doing')]",
  212. )
  213. plan_hours = fields.Float('计划工时')
  214. hours = fields.Float('实际工时',
  215. compute=_compute_hours,
  216. store=True)
  217. sequence = fields.Integer('顺序')
  218. is_schedule = fields.Boolean('列入计划')
  219. note = fields.Text('描述')
  220. priority = fields.Selection(AVAILABLE_PRIORITIES,
  221. string='优先级',
  222. default=AVAILABLE_PRIORITIES[0][0])
  223. color = fields.Integer('Color Index',
  224. default=0)
  225. company_id = fields.Many2one(
  226. 'res.company',
  227. string='公司',
  228. change_default=True,
  229. default=lambda self: self.env.company
  230. )
  231. tag_ids = fields.Many2many('core.value',
  232. ondelete='restrict',
  233. string='标签',
  234. domain=[('type', '=', 'task_tag')],
  235. context={'type': 'task_tag'})
  236. def assign_to_me(self):
  237. '''将任务指派给自己,并修改状态'''
  238. self.ensure_one()
  239. next_status = self.env['task.status'].search([('state', '=', 'doing')])
  240. self.user_id = self.env.user
  241. self.status = next_status and next_status[0]
  242. class TaskStatus(models.Model):
  243. _name = 'task.status'
  244. _description = '任务阶段'
  245. _order = 'sequence, id'
  246. name = fields.Char('名称', required=True)
  247. project_type_id = fields.Many2one(
  248. string='项目分类',
  249. comodel_name='core.value',
  250. ondelete='restrict',
  251. domain=[('type', '=', 'project_type')],
  252. )
  253. state = fields.Selection(TASK_STATES,
  254. string='任务状态',
  255. index=True,
  256. required=True,
  257. default='doing')
  258. sequence = fields.Integer('顺序', default=10)
  259. company_id = fields.Many2one(
  260. 'res.company',
  261. string='公司',
  262. change_default=True,
  263. default=lambda self: self.env.company)
  264. class Timesheet(models.Model):
  265. _name = 'timesheet'
  266. _description = '今日工作日志'
  267. date = fields.Date(
  268. string='日期',
  269. required=True,
  270. readonly=True,
  271. default=fields.Date.context_today)
  272. user_id = fields.Many2one(
  273. string='用户',
  274. required=True,
  275. readonly=True,
  276. default=lambda self: self.env.user,
  277. comodel_name='res.users',
  278. )
  279. timeline_ids = fields.One2many(
  280. string='工作记录',
  281. comodel_name='timeline',
  282. inverse_name='timesheet_id',
  283. )
  284. task_ids = fields.Many2many(
  285. string='待办事项',
  286. required=False,
  287. readonly=False,
  288. default=lambda self: [(4, t.id) for t in self.env['task'].search(
  289. [('user_id', '=', self.env.user.id),
  290. ('status.state', '=', 'doing')])],
  291. help=False,
  292. comodel_name='task',
  293. domain=[],
  294. context={}
  295. )
  296. company_id = fields.Many2one(
  297. 'res.company',
  298. string='公司',
  299. change_default=True,
  300. default=lambda self: self.env.company
  301. )
  302. color = fields.Integer('Color Index',
  303. default=0)
  304. display_name = fields.Char(compute='_compute_display_name', store=True)
  305. _sql_constraints = [
  306. ('user_uniq', 'unique(user_id,date)', '同一个人一天只能创建一个工作日志')
  307. ]
  308. def name_get(self):
  309. ret = []
  310. for s in self:
  311. ret.append((s.id, '%s %s' % (s.user_id.name, s.date)))
  312. return ret
  313. @api.depends('user_id', 'user_id.name', 'date')
  314. def _compute_display_name(self):
  315. '''显示完整的任务名称'''
  316. for record in self:
  317. record.display_name = '%s %s' % (record.user_id.name, record.date)
  318. class Timeline(models.Model):
  319. _name = 'timeline'
  320. _description = '工作记录'
  321. timesheet_id = fields.Many2one(
  322. string='记录表',
  323. comodel_name='timesheet',
  324. ondelete='cascade',
  325. )
  326. task_id = fields.Many2one(
  327. string='任务',
  328. required=True,
  329. readonly=False,
  330. comodel_name='task',
  331. )
  332. project_id = fields.Many2one(
  333. string='项目',
  334. related='task_id.project_id',
  335. store=True,
  336. ondelete='cascade',
  337. )
  338. user_id = fields.Many2one(
  339. string='指派给',
  340. comodel_name='res.users',
  341. )
  342. start_time = fields.Datetime('开始时间', default=fields.Datetime.now)
  343. end_time = fields.Datetime('结束时间', default=fields.Datetime.now)
  344. hours = fields.Float(
  345. string='小时数',
  346. default=0.5,
  347. digits=(16, 1),
  348. help='实际花的小时数',
  349. )
  350. just_done = fields.Text(
  351. string='具体工作内容',
  352. required=True,
  353. help='在此时长内针对此任务实际完成的工作内容',
  354. )
  355. need_help = fields.Char(
  356. string='需要的帮助',
  357. )
  358. # TODO 以下三个字段用于更新task的同名字段
  359. next_action = fields.Char(
  360. string='下一步计划',
  361. required=False,
  362. help='针对此任务下一步的计划',
  363. )
  364. next_datetime = fields.Datetime(
  365. string='下一步计划时间',
  366. help='下一步计划预计开始的时间',
  367. )
  368. set_status = fields.Many2one('task.status',
  369. string='状态更新到')
  370. company_id = fields.Many2one(
  371. 'res.company',
  372. string='公司',
  373. change_default=True,
  374. default=lambda self: self.env.company
  375. )
  376. color = fields.Integer('Color Index',
  377. default=0)
  378. @api.model_create_multi
  379. def create(self, vals_list):
  380. '''创建工作记录时,更新对应task的status等字段'''
  381. res = super(Timeline, self).create(vals_list)
  382. for vals in vals_list:
  383. set_status = vals.get('set_status')
  384. task_id = vals.get('task_id')
  385. next_action = vals.get('next_action')
  386. next_datetime = vals.get('next_datetime')
  387. user_id = vals.get('user_id')
  388. if task_id:
  389. Task = self.env['task'].search([('id', '=', task_id)])
  390. if set_status:
  391. Task.write({'status': set_status})
  392. if next_action:
  393. Task.write({'next_action': next_action})
  394. if next_datetime:
  395. Task.write({'next_datetime': next_datetime})
  396. if user_id:
  397. Task.write({'user_id': user_id})
  398. return res
上海开阖软件有限公司 沪ICP备12045867号-1