575 lines
28 KiB
Python
575 lines
28 KiB
Python
# -*- coding: utf-8 -*-
|
|
import ast
|
|
import calendar
|
|
from datetime import datetime, time, timedelta
|
|
from dateutil import relativedelta
|
|
from odoo.addons.resource.models.resource import float_to_time, HOURS_PER_DAY
|
|
from odoo.osv import expression
|
|
from odoo import api, fields, models, _
|
|
from odoo.exceptions import UserError, ValidationError
|
|
|
|
|
|
class Project(models.Model):
|
|
_inherit = "project.project"
|
|
_order = "project_no desc"
|
|
|
|
|
|
def _get_task_type(self):
|
|
"""
|
|
:return: project task type if it default
|
|
"""
|
|
type_ids = self.env['project.task.type'].search([('is_default', '=', True)])
|
|
return type_ids
|
|
|
|
name = fields.Char("Name", index=True, tracking=True, required=False)
|
|
project_no = fields.Char("Project Number", default=lambda self: self._get_next_projectno(), tracking=True,
|
|
copy=False)
|
|
category_id = fields.Many2one('project.category', string='Project Category', tracking=True)
|
|
parent_id = fields.Many2one('project.project', index=True, string='Parent Project', tracking=True)
|
|
sub_project_id = fields.One2many('project.project', 'parent_id', string='Sub-Project', tracking=True)
|
|
department_id = fields.Many2one('hr.department', string='Department')
|
|
department_execute_id = fields.Many2one('hr.department', string='Department to which executed')
|
|
consultant_id = fields.Many2one('res.partner', string='Consultant')
|
|
beneficiary_id = fields.Many2one('res.partner', string='Beneficiary')
|
|
lessons_learned = fields.Html(string='Lessons learned')
|
|
launch_date = fields.Datetime("Launch date", tracking=True)
|
|
start = fields.Date(string="Start Date", index=True, tracking=True)
|
|
date = fields.Date(string='End Date', index=True, tracking=True)
|
|
project_phase_ids = fields.One2many('project.phase', 'project_id', string="Project Phases")
|
|
tag_ids = fields.Many2many('project.tags', string='Tags')
|
|
type_ids = fields.Many2many('project.task.type', 'project_task_type_rel', 'project_id', 'type_id',
|
|
string='Tasks Stages', default=_get_task_type)
|
|
priority = fields.Selection([('0', 'Normal'),('1', 'Important'),], default='0', index=True, string="Priority")
|
|
|
|
# address fields
|
|
street = fields.Char()
|
|
street2 = fields.Char()
|
|
zip = fields.Char(change_default=True)
|
|
country_id = fields.Many2one('res.country', string='Country', default=lambda self: self.env.company.country_id,
|
|
ondelete='restrict', tracking=True)
|
|
state_id = fields.Many2one("res.country.state", string='Region', ondelete='restrict')
|
|
city = fields.Char()
|
|
# Contract fields
|
|
contract_number = fields.Char(string="Contract Number", tracking=True)
|
|
signature_date = fields.Date(string="Signature Date", tracking=True)
|
|
contract_value = fields.Float("Total Contract Value")
|
|
consultant_cost = fields.Float(
|
|
string='Consultant Cost',)
|
|
tax_amount = fields.Float("Taxes Amount")
|
|
contract_value_untaxed = fields.Float("Contract Value")
|
|
state = fields.Selection([
|
|
('draft', 'PMO'),
|
|
('confirm', 'PM'),
|
|
('done', 'Done')], string='State', copy=False, default='draft', required=True, tracking=True)
|
|
status = fields.Selection([
|
|
('new', 'New'), ('open', 'Running'),
|
|
('hold', 'Hold'), ('close', 'Closed')], string='Status', copy=False, default='new', tracking=True)
|
|
hold_reason = fields.Text(string="Hold Reason")
|
|
close_reason = fields.Text(string="Close Reason")
|
|
project_team_ids = fields.One2many('project.team', 'project_id', string="Project Team")
|
|
invoice_ids = fields.One2many('project.invoice', 'project_id', "Invoice", copy=False, help="The Details of invoice")
|
|
total_invoiced_amount = fields.Float('Total Invoiced Amount',store=True, compute='compute_total_invoiced_amount')
|
|
no_of_invoices = fields.Integer(compute='compute_total_invoiced_payment')
|
|
no_of_paid_invoices = fields.Integer(compute='compute_total_invoiced_payment')
|
|
total_invoiced_payment = fields.Float('Paid Amount',store=True, compute='compute_total_invoiced_payment')
|
|
residual_amount = fields.Float('Remaining Amount',store=True, compute='compute_residual_amount')
|
|
back_log_amount = fields.Float('Back Log Amount',store=True, compute='compute_back_log_amount')
|
|
# TODO check if this fields needed
|
|
is_down_payment = fields.Boolean('Down Payment')
|
|
to_invoice = fields.Boolean(compute="compute_to_invoice", store=True)
|
|
invoice_method = fields.Selection([
|
|
('per_stage', 'Invoicing Per Phase'),
|
|
('per_period', 'Invoicing Per Period'),
|
|
], string='Invoicing Method', default=lambda self: self.env.company.invoice_method, )
|
|
invoice_period = fields.Integer(string="Invoicing Every", default=lambda self: self.env.company.invoice_period)
|
|
resource_calendar_id = fields.Many2one('resource.calendar', string='Working Time',
|
|
default=lambda self: self.env.company.resource_calendar_id, related=False,
|
|
help="Working Time used in this project")
|
|
sale_order_amount = fields.Monetary("SO Total Amount", tracking=True)
|
|
type = fields.Selection([
|
|
('revenue', 'Revenue'),
|
|
('expense', 'Expense'),
|
|
('internal', 'Internal')], default=lambda self: self.env.company.type, string='Type')
|
|
man_hours = fields.Boolean('Man hours', default=False)
|
|
progress = fields.Float(compute="get_progress", store=True, string="Progress")
|
|
task_count_all = fields.Integer(compute='_compute_task_count_all', string="All Task Count")
|
|
task_count_finished = fields.Integer(compute='_compute_task_count_finished', string="Finished Task Count")
|
|
task_count_new = fields.Integer(compute='_compute_task_count_new', string="New Task Count")
|
|
task_count_inprogress = fields.Integer(compute='_compute_task_count_inprogress', string="In progress Task Count")
|
|
user_id = fields.Many2one('res.users', string='Project Manager', default=lambda self: self.env.user, tracking=True,domain=lambda self: [("groups_id", "in", self.env.ref("project.group_project_manager").id)])
|
|
|
|
privacy_visibility = fields.Selection([
|
|
('followers', 'Invited internal users'),
|
|
('employees', 'All internal users'),
|
|
('portal', 'Invited portal users and all internal users'),
|
|
],
|
|
string='Visibility', required=False,
|
|
default='portal',
|
|
help="People to whom this project and its tasks will be visible.\n\n"
|
|
"- Invited internal users: when following a project, internal users will get access to all of its tasks without distinction. "
|
|
"Otherwise, they will only get access to the specific tasks they are following.\n "
|
|
"A user with the project > administrator access right level can still access this project and its tasks, even if they are not explicitly part of the followers.\n\n"
|
|
"- All internal users: all internal users can access the project and all of its tasks without distinction.\n\n"
|
|
"- Invited portal users and all internal users: all internal users can access the project and all of its tasks without distinction.\n"
|
|
"When following a project, portal users will get access to all of its tasks without distinction. Otherwise, they will only get access to the specific tasks they are following.")
|
|
|
|
allowed_internal_user_ids = fields.Many2many('res.users', 'project_allowed_internal_users_rel',
|
|
string="Allowed Internal Users", default=lambda self: self.env.user, domain=[('share', '=', False)])
|
|
allowed_portal_user_ids = fields.Many2many('res.users', 'project_allowed_portal_users_rel', string="Allowed Portal Users", domain=[('share', '=', True)])
|
|
|
|
allow_timesheets = fields.Boolean(
|
|
"Timesheets", compute='_compute_allow_timesheets', store=True, readonly=False,
|
|
default=False, help="Enable timesheeting on the project.")
|
|
def get_attached_domain(self):
|
|
return ['|','|','|',
|
|
'&',('res_model', '=', 'project.project'),
|
|
('res_id', 'in', self.ids),
|
|
'&',('res_model', '=', 'project.task'),
|
|
('res_id', 'in', self.task_ids.ids),
|
|
'&',('res_model', '=', 'project.invoice'),
|
|
('res_id', 'in', self.invoice_ids.ids),
|
|
'&',('res_model', '=', 'project.phase'),
|
|
('res_id', 'in', self.project_phase_ids.ids),
|
|
]
|
|
|
|
def _compute_attached_docs_count(self):
|
|
Attachment = self.env['ir.attachment']
|
|
for project in self:
|
|
project.doc_count = Attachment.search_count(self.get_attached_domain())
|
|
|
|
def attachment_tree_view(self):
|
|
action = self.env['ir.actions.act_window']._for_xml_id('base.action_attachment')
|
|
action['domain'] = str(self.get_attached_domain())
|
|
action['context'] = "{'default_res_model': '%s','default_res_id': %d}" % (self._name, self.id)
|
|
return action
|
|
def _compute_task_count_all(self):
|
|
task_data = self.env['project.task'].read_group([('project_id', 'in', self.ids)], ['project_id'],
|
|
['project_id'])
|
|
result = dict((data['project_id'][0], data['project_id_count']) for data in task_data)
|
|
for project in self:
|
|
project.task_count_all = result.get(project.id, 0)
|
|
|
|
def _compute_task_count_finished(self):
|
|
task_data = self.env['project.task'].read_group([('project_id', 'in', self.ids),
|
|
('stage_id.is_closed', '=', True)], ['project_id'],
|
|
['project_id'])
|
|
result = dict((data['project_id'][0], data['project_id_count']) for data in task_data)
|
|
for project in self:
|
|
project.task_count_finished = result.get(project.id, 0)
|
|
|
|
def _compute_task_count_new(self):
|
|
task_data = self.env['project.task'].read_group([('project_id', 'in', self.ids),
|
|
('stage_id.sequence', '=', 0)], ['project_id'], ['project_id'])
|
|
result = dict((data['project_id'][0], data['project_id_count']) for data in task_data)
|
|
for project in self:
|
|
project.task_count_new = result.get(project.id, 0)
|
|
|
|
def _compute_task_count_inprogress(self):
|
|
task_data = self.env['project.task'].read_group(
|
|
[('project_id', 'in', self.ids), ('stage_id.is_closed', '=', False),
|
|
('stage_id.fold', '=', False), ('stage_id.sequence', '!=', 0)], ['project_id'], ['project_id'])
|
|
result = dict((data['project_id'][0], data['project_id_count']) for data in task_data)
|
|
for project in self:
|
|
project.task_count_inprogress = result.get(project.id, 0)
|
|
|
|
@api.depends('project_phase_ids', 'project_phase_ids.weight', 'project_phase_ids.progress')
|
|
def get_progress(self):
|
|
for rec in self:
|
|
progress = 0.0
|
|
for phase in rec.project_phase_ids:
|
|
progress += phase.weight * phase.progress / 100
|
|
rec.progress = progress
|
|
|
|
def compute_total_invoiced_amount(self):
|
|
for rec in self:
|
|
rec.total_invoiced_amount = sum(rec.invoice_ids.mapped('amount'))
|
|
|
|
def compute_total_invoiced_payment(self):
|
|
for rec in self:
|
|
rec.total_invoiced_payment = 0
|
|
rec.no_of_invoices = 0
|
|
rec.no_of_paid_invoices = 0
|
|
if rec.invoice_ids:
|
|
rec.no_of_invoices = len(rec.invoice_ids)
|
|
rec.no_of_paid_invoices = len(rec.invoice_ids.sudo().filtered(lambda x: x.invoice_id))
|
|
rec.total_invoiced_payment = sum(rec.invoice_ids.mapped("payment_amount"))
|
|
|
|
@api.depends('total_invoiced_amount', 'total_invoiced_payment')
|
|
def compute_residual_amount(self):
|
|
for rec in self:
|
|
rec.residual_amount = rec.total_invoiced_amount - rec.total_invoiced_payment
|
|
|
|
@api.depends('total_invoiced_amount', 'contract_value')
|
|
def compute_back_log_amount(self):
|
|
for rec in self:
|
|
rec.back_log_amount = rec.contract_value - rec.total_invoiced_amount
|
|
|
|
@api.model
|
|
def _get_next_projectno(self):
|
|
next_sequence = "/ "
|
|
sequence = self.env['ir.sequence'].search(
|
|
['|', ('company_id', '=', self.env.company[0].id), ('company_id', '=', False),
|
|
('code', '=', 'project.project')], limit=1)
|
|
if sequence:
|
|
next_sequence = sequence.get_next_char(sequence.number_next_actual)
|
|
return next_sequence
|
|
|
|
@api.model
|
|
def create(self, vals):
|
|
company_id = vals.get('company_id', self.default_get(['company_id'])['company_id'])
|
|
self_comp = self.with_company(company_id)
|
|
if not vals.get('project_no', False):
|
|
vals['project_no'] = self_comp.env['ir.sequence'].next_by_code('project.project') or '/'
|
|
return super().create(vals)
|
|
|
|
def write(self, vals):
|
|
"""
|
|
If invoice method=per_stage when add stage create project invoice
|
|
"""
|
|
line_ids = []
|
|
res = super(Project, self).write(vals)
|
|
if self.type != 'internal' and vals.get('project_phase_ids') and self.invoice_method == 'per_stage':
|
|
phase_ids = self.invoice_ids.mapped('phase_id').ids
|
|
for line in self.sudo().sale_order_id.order_line:
|
|
line_ids.append(
|
|
(0, 0,
|
|
{'order_line_id': line.id, 'product_id': line.product_id.id, 'product_uom': line.product_uom.id,
|
|
'price_unit': line.price_unit, 'discount': line.discount, 'tax_id': [(6, 0, line.tax_id.ids)]}))
|
|
self.invoice_ids = [(0, 0,
|
|
{'name': phase.display_name, 'phase_id': phase.id, 'project_invline_ids': line_ids})
|
|
for phase in
|
|
self.project_phase_ids.filtered(lambda x: x.id not in phase_ids)]
|
|
return res
|
|
|
|
def name_get(self):
|
|
res = []
|
|
for record in self:
|
|
if record.project_no:
|
|
res.append((record.id, ("[" + record.project_no + "] " + (record.name or " "))))
|
|
else:
|
|
res.append((record.id, record.name))
|
|
return res
|
|
|
|
@api.model
|
|
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
|
|
args = args or []
|
|
if operator == 'ilike' and not (name or '').strip():
|
|
domain = []
|
|
else:
|
|
connector = '&' if operator in expression.NEGATIVE_TERM_OPERATORS else '|'
|
|
domain = [connector, ('project_no', operator, name), ('name', operator, name)]
|
|
return self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
|
|
|
|
# State funtions
|
|
def action_draft(self):
|
|
return self.write({'state': 'draft'})
|
|
|
|
def action_confirm(self):
|
|
for record in self:
|
|
# Send Notification to Project manger
|
|
if record.user_id:
|
|
users = [record.user_id]
|
|
notification_ids = [(0, 0, {
|
|
'res_partner_id': user.partner_id.id,
|
|
'notification_type': 'inbox'}) for user in users if user.partner_id]
|
|
if notification_ids:
|
|
self.env['mail.message'].create({
|
|
'message_type': "notification",
|
|
'body': _(
|
|
"You Have been assign as a manger for The project %s kindly complete project data") % record.display_name,
|
|
'subject': _("You Have been assign As project manger"),
|
|
'partner_ids': [(4, user.partner_id.id) for user in users if users],
|
|
'model': record._name,
|
|
'res_id': record.id,
|
|
'notification_ids': notification_ids,
|
|
'author_id': self.env.user.partner_id and self.env.user.partner_id.id})
|
|
if record.type != 'internal':
|
|
record.create_project_invoice()
|
|
return self.write({'state': 'confirm'})
|
|
|
|
def action_done(self):
|
|
for record in self:
|
|
line_ids = []
|
|
# check project Stages
|
|
if record.type != 'internal':
|
|
if not record.project_phase_ids and record.invoice_method == 'per_stage':
|
|
raise ValidationError(_("Kindly Enter project Stage first."))
|
|
return self.write({'state': 'done'})
|
|
|
|
def create_project_invoice(self):
|
|
"""
|
|
Create Project Invoice in confirm base on invoice_method = per_period
|
|
"""
|
|
for record in self:
|
|
line_ids = []
|
|
if record.invoice_ids:
|
|
return True
|
|
if (record.date and record.start) and record.invoice_method == 'per_period':
|
|
for line in record.sudo().sale_order_id.order_line.filtered(lambda x: not x.is_downpayment):
|
|
line_ids.append(
|
|
(0, 0, {'order_line_id': line.id, 'product_id': line.product_id.id,
|
|
'product_uom': line.product_uom.id,
|
|
'price_unit': line.price_unit, 'discount': line.discount,
|
|
'tax_id': [(6, 0, line.tax_id.ids)]}))
|
|
diff = record.date - record.start
|
|
project_days = diff.days
|
|
period = project_days / record.invoice_period
|
|
invoice_date = record.start + relativedelta.relativedelta(days=record.invoice_period)
|
|
for rec in range(int(period)):
|
|
record.invoice_ids = [(0, 0, {'project_invline_ids': line_ids, 'plan_date': invoice_date})]
|
|
invoice_date = invoice_date + relativedelta.relativedelta(days=record.invoice_period)
|
|
|
|
def action_view_tasks_analysis(self):
|
|
""" return the action to see the tasks analysis report of the project """
|
|
action = self.env['ir.actions.act_window']._for_xml_id('project.action_project_task_user_tree')
|
|
action_context = ast.literal_eval(action['context']) if action['context'] else {}
|
|
action_context['search_default_project_id'] = self.id
|
|
return dict(action, context=action_context)
|
|
|
|
def action_creat_tasks(self):
|
|
action = self.with_context(active_id=self.id, active_ids=self.ids) \
|
|
.env.ref('project_base.create_tast_proj') \
|
|
.sudo().read()[0]
|
|
action['display_name'] = self.name
|
|
return action
|
|
|
|
def action_view_finished_tasks(self):
|
|
action = self.with_context(active_id=self.id, active_ids=self.ids) \
|
|
.env.ref('project_base.act_project_project_2_project_task_finished') \
|
|
.sudo().read()[0]
|
|
action['display_name'] = self.name
|
|
return action
|
|
|
|
def action_view_inprogress_tasks(self):
|
|
action = self.with_context(active_id=self.id, active_ids=self.ids) \
|
|
.env.ref('project_base.act_project_project_2_project_task_inprogress') \
|
|
.sudo().read()[0]
|
|
action['display_name'] = self.name
|
|
return action
|
|
|
|
def action_view_new_tasks(self):
|
|
action = self.with_context(active_id=self.id, active_ids=self.ids) \
|
|
.env.ref('project_base.act_project_project_2_project_task_new') \
|
|
.sudo().read()[0]
|
|
action['display_name'] = self.name
|
|
return action
|
|
|
|
# Status funtions
|
|
def action_open(self):
|
|
for rec in self:
|
|
# check that all invoice planned issue dates is entred
|
|
invoice_ids = self.invoice_ids.filtered(lambda x: x.plan_date == False)
|
|
if invoice_ids:
|
|
raise ValidationError(_('Please be sure to enter all planned issue dates for invoices.'))
|
|
return self.write({'status': 'open'})
|
|
|
|
def action_reopen(self):
|
|
return self.write({'status': 'open', 'close_reason': False})
|
|
|
|
def action_hold(self):
|
|
return self.write({'status': 'hold'})
|
|
|
|
def action_close(self):
|
|
return self.write({'status': 'close'})
|
|
|
|
def action_project_invoice(self):
|
|
action_window = {
|
|
"type": "ir.actions.act_window",
|
|
"res_model": "project.invoice",
|
|
"name": "project Invoice",
|
|
'view_type': 'form',
|
|
'view_mode': 'tree,form',
|
|
"context": {"create": True, "project_id": self.id, 'search_default_project': 1},
|
|
"domain": [('project_id', '=', self.id)]
|
|
}
|
|
return action_window
|
|
|
|
def action_paid_invoice(self):
|
|
action_window = {
|
|
"type": "ir.actions.act_window",
|
|
"res_model": "project.invoice",
|
|
"name": "project Invoice",
|
|
'view_type': 'form',
|
|
'view_mode': 'tree,form',
|
|
"context": {"create": True, "project_id": self.id, 'search_default_project': 1},
|
|
"domain": [('project_id', '=', self.id), ('state', '=', 'done')]
|
|
}
|
|
return action_window
|
|
|
|
def action_view_expense(self):
|
|
self.ensure_one()
|
|
action_window = {
|
|
"type": "ir.actions.act_window",
|
|
"res_model": "hr.expense",
|
|
"name": "Expenses",
|
|
"views": [[False, "tree"]],
|
|
"context": {"create": False},
|
|
"domain": [('analytic_account_id', '=', self.analytic_account_id.id)]
|
|
}
|
|
return action_window
|
|
|
|
def action_view_purchase(self):
|
|
self.ensure_one()
|
|
action_window = {
|
|
"type": "ir.actions.act_window",
|
|
"res_model": "purchase.order",
|
|
"name": "Purchase",
|
|
"views": [[False, "tree"]],
|
|
"context": {"create": False},
|
|
"domain": [('project_id', '=', self.id)]
|
|
}
|
|
return action_window
|
|
|
|
@api.model
|
|
def update_project_status(self):
|
|
"""
|
|
this funtion call by cron to close the project if end date come
|
|
"""
|
|
projects = self.env['project.project'].search([('status', '=', 'open'), ('date', '<=', fields.Date.today())])
|
|
for record in projects:
|
|
close_reason = "The Project Auto Closed by system because reach end date %s" % (record.date)
|
|
record.write({'status': 'close', 'close_reason': close_reason, 'state': 'confirm'})
|
|
users = [record.user_id]
|
|
notification_ids = []
|
|
if users:
|
|
notification_ids = [(0, 0,
|
|
{
|
|
'res_partner_id': user.partner_id.id,
|
|
'notification_type': 'inbox'
|
|
}
|
|
) for user in users if user.partner_id]
|
|
if notification_ids:
|
|
self.env['mail.message'].create({
|
|
'message_type': "notification",
|
|
'body': close_reason,
|
|
'subject': _("The project %s was automatically Closed by system") % record.display_name,
|
|
'partner_ids': [(4, user.partner_id.id) for user in users if users],
|
|
'model': record._name,
|
|
'res_id': record.id,
|
|
'notification_ids': notification_ids,
|
|
'author_id': self.env.user.partner_id and self.env.user.partner_id.id
|
|
})
|
|
|
|
@api.model
|
|
def project_invoice_notification(self):
|
|
"""
|
|
send notification to PM in invoice date
|
|
"""
|
|
projects = self.env['project.invoice'].search([])
|
|
for record in projects:
|
|
if record.plan_date == datetime.now().date() and not record.invoice_id:
|
|
record.to_invoice = True
|
|
user_ids = self.env.ref('project.group_project_manager').users
|
|
self.env['mail.message'].create({'message_type': "notification",
|
|
'body': _('This invoice is due and must be issue %s' % (record.name)),
|
|
'subject': _("Project Invoice Creation"),
|
|
'partner_ids': [(6, 0, user_ids.mapped('partner_id').ids)],
|
|
'notification_ids': [(0, 0, {'res_partner_id': user.partner_id.id,
|
|
'notification_type': 'inbox'})
|
|
for user in user_ids if user_ids],
|
|
'model': self._name,
|
|
'res_id': record.project_id.id,
|
|
})
|
|
else:
|
|
record.to_invoice = False
|
|
|
|
def create_invoice(self):
|
|
self.ensure_one()
|
|
line_id = self.env['project.invoice'].search(
|
|
[('project_id', '=', self.id), ('to_invoice', '=', True)], limit=1)
|
|
return {
|
|
'name': _('Invoices'),
|
|
'res_model': 'project.invoice',
|
|
'type': 'ir.actions.act_window',
|
|
'res_id': line_id.id,
|
|
'view_mode': 'form',
|
|
}
|
|
|
|
def update_invoices(self, is_down_payment):
|
|
InvLine = self.env['project.invoice.line']
|
|
for record in self:
|
|
if is_down_payment:
|
|
downpayment_line = record.sale_order_id.mapped('order_line').filtered(lambda l: l.is_downpayment)
|
|
for line in downpayment_line:
|
|
for inv in record.invoice_ids:
|
|
if not inv.has_downpayment:
|
|
values = {'project_invoice_id': inv.id, 'order_line_id': line.id,
|
|
'product_id': line.product_id.id, 'product_uom': line.product_uom.id,
|
|
'price_unit': line.price_unit, 'product_uom_qty': 0}
|
|
InvLine.create(values)
|
|
|
|
def create_down_payment(self):
|
|
''' Opens a wizard to create down payment '''
|
|
self.ensure_one()
|
|
ctx = {
|
|
'active_model': 'sale.order',
|
|
'active_ids': self.sale_order_id.ids,
|
|
'active_id': self.sale_order_id.id,
|
|
'project_id': self.id,
|
|
}
|
|
return {
|
|
'type': 'ir.actions.act_window',
|
|
'view_mode': 'form',
|
|
'res_model': 'sale.advance.payment.inv',
|
|
'views': [(False, 'form')],
|
|
'view_id': False,
|
|
'target': 'new',
|
|
'context': ctx,
|
|
}
|
|
|
|
@api.depends('invoice_ids', 'invoice_ids.to_invoice')
|
|
def compute_to_invoice(self):
|
|
for rec in self:
|
|
invoice_id = rec.invoice_ids.filtered(lambda x: x.to_invoice == True)
|
|
if invoice_id:
|
|
rec.to_invoice = True
|
|
else:
|
|
rec.to_invoice = False
|
|
|
|
|
|
class ProjectCategory(models.Model):
|
|
_name = "project.category"
|
|
|
|
name = fields.Char(string="Name")
|
|
company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.user.company_id)
|
|
|
|
|
|
class ProjectHoldReason(models.Model):
|
|
_name = "project.hold.reason"
|
|
|
|
name = fields.Char(string="Name")
|
|
company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.user.company_id)
|
|
|
|
|
|
class Stage(models.Model):
|
|
_inherit = 'project.task.type'
|
|
|
|
color_gantt = fields.Char(
|
|
string="Color Task Bar",
|
|
help="Choose your color for Task Bar",
|
|
default="rgba(170,170,13,0.53)")
|
|
stage_for = fields.Selection(
|
|
string='',
|
|
selection=[('project', 'Project'),
|
|
('tasks', 'Tasks'), ],
|
|
required=True, default='project')
|
|
company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.user.company_id)
|
|
is_default = fields.Boolean(string="Default in new project")
|
|
|
|
|
|
class ProjectTeam(models.Model):
|
|
_name = "project.team"
|
|
|
|
employee_id = fields.Many2one('hr.employee', string="Employee")
|
|
project_id = fields.Many2one('project.project', string="Project")
|
|
department_id = fields.Many2one('hr.department', related="employee_id.department_id", string="Department")
|
|
job_id = fields.Many2one('hr.job', related="employee_id.job_id", string="Job")
|
|
project_job = fields.Char(string="Job In Project")
|
|
|
|
@api.onchange('job_id')
|
|
def _onchange_job(self):
|
|
if not self.project_job:
|
|
self.project_job = self.job_id and self.job_id.name or ''
|
|
# project_base.group_project_department_manager
|
|
# <field name="user_id" string="Project Manager" widget="many2one_avatar_user" attrs="{'readonly':[('active','=',False)]}" domain="[('share', '=', False)]"/>
|