GoodERP
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

453 行
13KB

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