GoodERP
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

451 行
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','project']
  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. project_name = fields.Char(string='项目名称', related='auxiliary_id.name', store=True)
  31. type_id = fields.Many2one(
  32. string='分类',
  33. comodel_name='core.value',
  34. ondelete='restrict',
  35. domain=[('type', '=', 'project_type')],
  36. context={'type': 'project_type'},
  37. )
  38. auxiliary_id = fields.Many2one(
  39. string='辅助核算',
  40. comodel_name='auxiliary.financing',
  41. ondelete='cascade',
  42. required=True,
  43. )
  44. task_ids = fields.One2many(
  45. string='任务',
  46. comodel_name='task',
  47. inverse_name='project_id',
  48. )
  49. customer_id = fields.Many2one(
  50. string='客户',
  51. comodel_name='partner',
  52. ondelete='restrict',
  53. )
  54. invoice_ids = fields.One2many(
  55. string='发票行',
  56. comodel_name='project.invoice',
  57. inverse_name='project_id',
  58. )
  59. plan_hours = fields.Float('计划工时')
  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. )
  179. project_id = fields.Many2one(
  180. string='项目',
  181. comodel_name='project',
  182. ondelete='cascade',
  183. )
  184. project_type_id = fields.Many2one(
  185. string='分类',
  186. comodel_name='core.value',
  187. ondelete='restrict',
  188. compute=_compute_project_type_id,
  189. store=True)
  190. timeline_ids = fields.One2many(
  191. string='工作记录',
  192. comodel_name='timeline',
  193. inverse_name='task_id',
  194. )
  195. next_action = fields.Char(
  196. string='下一步计划',
  197. required=False,
  198. help='针对此任务下一步的计划',
  199. )
  200. next_datetime = fields.Datetime(
  201. string='下一步计划时间',
  202. help='下一步计划预计开始的时间',
  203. )
  204. status = fields.Many2one(
  205. 'task.status',
  206. string='状态',
  207. default=_default_status,
  208. ondelete='restrict',
  209. domain="['|', ('project_type_id', '=', project_type_id), ('state','!=','doing')]",
  210. )
  211. plan_hours = fields.Float('计划工时')
  212. hours = fields.Float('实际工时',
  213. compute=_compute_hours,
  214. store=True)
  215. sequence = fields.Integer('顺序')
  216. is_schedule = fields.Boolean('列入计划')
  217. note = fields.Text('描述')
  218. priority = fields.Selection(AVAILABLE_PRIORITIES,
  219. string='优先级',
  220. default=AVAILABLE_PRIORITIES[0][0])
  221. color = fields.Integer('Color Index',
  222. default=0)
  223. company_id = fields.Many2one(
  224. 'res.company',
  225. string='公司',
  226. change_default=True,
  227. default=lambda self: self.env.company
  228. )
  229. tag_ids = fields.Many2many('core.value',
  230. ondelete='restrict',
  231. string='标签',
  232. domain=[('type', '=', 'task_tag')],
  233. context={'type': 'task_tag'})
  234. def assign_to_me(self):
  235. '''将任务指派给自己,并修改状态'''
  236. self.ensure_one()
  237. next_status = self.env['task.status'].search([('state', '=', 'doing')])
  238. self.user_id = self.env.user
  239. self.status = next_status and next_status[0]
  240. class TaskStatus(models.Model):
  241. _name = 'task.status'
  242. _description = '任务阶段'
  243. _order = 'sequence, id'
  244. name = fields.Char('名称', required=True)
  245. project_type_id = fields.Many2one(
  246. string='项目分类',
  247. comodel_name='core.value',
  248. ondelete='restrict',
  249. domain=[('type', '=', 'project_type')],
  250. )
  251. state = fields.Selection(TASK_STATES,
  252. string='任务状态',
  253. index=True,
  254. required=True,
  255. default='doing')
  256. sequence = fields.Integer('顺序', default=10)
  257. company_id = fields.Many2one(
  258. 'res.company',
  259. string='公司',
  260. change_default=True,
  261. default=lambda self: self.env.company)
  262. class Timesheet(models.Model):
  263. _name = 'timesheet'
  264. _description = '今日工作日志'
  265. date = fields.Date(
  266. string='日期',
  267. required=True,
  268. readonly=True,
  269. default=fields.Date.context_today)
  270. user_id = fields.Many2one(
  271. string='用户',
  272. required=True,
  273. readonly=True,
  274. default=lambda self: self.env.user,
  275. comodel_name='res.users',
  276. )
  277. timeline_ids = fields.One2many(
  278. string='工作记录',
  279. comodel_name='timeline',
  280. inverse_name='timesheet_id',
  281. )
  282. task_ids = fields.Many2many(
  283. string='待办事项',
  284. required=False,
  285. readonly=False,
  286. default=lambda self: [(4, t.id) for t in self.env['task'].search(
  287. [('user_id', '=', self.env.user.id),
  288. ('status.state', '=', 'doing')])],
  289. help=False,
  290. comodel_name='task',
  291. domain=[],
  292. context={}
  293. )
  294. company_id = fields.Many2one(
  295. 'res.company',
  296. string='公司',
  297. change_default=True,
  298. default=lambda self: self.env.company
  299. )
  300. color = fields.Integer('Color Index',
  301. default=0)
  302. _sql_constraints = [
  303. ('user_uniq', 'unique(user_id,date)', '同一个人一天只能创建一个工作日志')
  304. ]
  305. def name_get(self):
  306. ret = []
  307. for s in self:
  308. ret.append((s.id, '%s %s' % (s.user_id.name, s.date)))
  309. return ret
  310. class Timeline(models.Model):
  311. _name = 'timeline'
  312. _description = '工作记录'
  313. timesheet_id = fields.Many2one(
  314. string='记录表',
  315. comodel_name='timesheet',
  316. ondelete='cascade',
  317. )
  318. task_id = fields.Many2one(
  319. string='任务',
  320. required=True,
  321. readonly=False,
  322. comodel_name='task',
  323. )
  324. project_id = fields.Many2one(
  325. string='项目',
  326. related='task_id.project_id',
  327. store=True,
  328. ondelete='cascade',
  329. )
  330. user_id = fields.Many2one(
  331. string='指派给',
  332. comodel_name='res.users',
  333. )
  334. start_time = fields.Datetime('开始时间', default=fields.Datetime.now)
  335. end_time = fields.Datetime('结束时间', default=fields.Datetime.now)
  336. hours = fields.Float(
  337. string='小时数',
  338. default=0.5,
  339. digits=(16, 1),
  340. help='实际花的小时数',
  341. )
  342. just_done = fields.Text(
  343. string='具体工作内容',
  344. required=True,
  345. help='在此时长内针对此任务实际完成的工作内容',
  346. )
  347. need_help = fields.Char(
  348. string='需要的帮助',
  349. )
  350. # TODO 以下三个字段用于更新task的同名字段
  351. next_action = fields.Char(
  352. string='下一步计划',
  353. required=False,
  354. help='针对此任务下一步的计划',
  355. )
  356. next_datetime = fields.Datetime(
  357. string='下一步计划时间',
  358. help='下一步计划预计开始的时间',
  359. )
  360. set_status = fields.Many2one('task.status',
  361. string='状态更新到')
  362. company_id = fields.Many2one(
  363. 'res.company',
  364. string='公司',
  365. change_default=True,
  366. default=lambda self: self.env.company
  367. )
  368. color = fields.Integer('Color Index',
  369. default=0)
  370. @api.model_create_multi
  371. def create(self, vals_list):
  372. '''创建工作记录时,更新对应task的status等字段'''
  373. res = super(Timeline, self).create(vals_list)
  374. for vals in vals_list:
  375. set_status = vals.get('set_status')
  376. task_id = vals.get('task_id')
  377. next_action = vals.get('next_action')
  378. next_datetime = vals.get('next_datetime')
  379. user_id = vals.get('user_id')
  380. if task_id:
  381. Task = self.env['task'].search([('id', '=', task_id)])
  382. if set_status:
  383. Task.write({'status': set_status})
  384. if next_action:
  385. Task.write({'next_action': next_action})
  386. if next_datetime:
  387. Task.write({'next_datetime': next_datetime})
  388. if user_id:
  389. Task.write({'user_id': user_id})
  390. return res
上海开阖软件有限公司 沪ICP备12045867号-1