bemade_fsm: all tests passing for 16.0
- Got rid of planned_date_begin and planned_date_end fields. Commented out all related code for now. These were fields pending actual feature development anyway. - Added a product cateogory with income account and expense account to the _generate_product common test method. This fixes an error where Odoo complained about needing a due date for lines with account type = 'asset_receivable'. - Rewrote and refactored Task.action_fsm_validate to have it match with code from industry_fsm in 16.0. NOTE: This change depends on code pushed in: - sale_workflow [16.0 ba7402471] - odoo_addons [16.0 766cd78] - PurchasedAddons [16.0 e5e9b7b]
This commit is contained in:
parent
23d7d34e71
commit
81624ea26c
4 changed files with 133 additions and 72 deletions
|
|
@ -37,23 +37,23 @@ class Task(models.Model):
|
||||||
store=True
|
store=True
|
||||||
)
|
)
|
||||||
|
|
||||||
planned_date_begin = fields.Datetime(
|
# planned_date_begin = fields.Datetime(
|
||||||
string="Planned Start Date",
|
# string="Planned Start Date",
|
||||||
tracking=True,
|
# tracking=True,
|
||||||
task_dependency_tracking=True,
|
# task_dependency_tracking=True,
|
||||||
compute="_compute_planned_dates",
|
# compute="_compute_planned_dates",
|
||||||
inverse="_inverse_planned_dates",
|
# inverse="_inverse_planned_dates",
|
||||||
store=True
|
# store=True
|
||||||
)
|
# )
|
||||||
|
#
|
||||||
planned_date_end = fields.Datetime(
|
# planned_date_end = fields.Datetime(
|
||||||
string="Planned End Date",
|
# string="Planned End Date",
|
||||||
tracking=True,
|
# tracking=True,
|
||||||
task_dependency_tracking=True,
|
# task_dependency_tracking=True,
|
||||||
compute="_compute_planned_dates",
|
# compute="_compute_planned_dates",
|
||||||
inverse="_inverse_planned_dates",
|
# inverse="_inverse_planned_dates",
|
||||||
store=True
|
# store=True
|
||||||
)
|
# )
|
||||||
|
|
||||||
# Override related field to make it return false if this is an FSM subtask
|
# Override related field to make it return false if this is an FSM subtask
|
||||||
allow_billable = fields.Boolean(
|
allow_billable = fields.Boolean(
|
||||||
|
|
@ -112,43 +112,40 @@ class Task(models.Model):
|
||||||
for project in self.project_id
|
for project in self.project_id
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_related_planning_slots(self):
|
# def _get_related_planning_slots(self):
|
||||||
domain = expression.AND([
|
# domain = [('task_id', 'in', self.ids + self._get_all_subtasks().ids), ('start_datetime', '!=', False)]
|
||||||
self._get_domain_compute_forecast_hours(),
|
# return self.env['planning.slot'].search(domain)
|
||||||
[('task_id', 'in', self.ids + self._get_all_subtasks().ids)]
|
|
||||||
])
|
|
||||||
return self.env['planning.slot'].search(domain)
|
|
||||||
|
|
||||||
@api.depends('planned_hours')
|
# @api.depends('planned_hours')
|
||||||
def _compute_planned_dates(self):
|
# def _compute_planned_dates(self):
|
||||||
forecast_data = self._get_related_planning_slots()
|
# forecast_data = self._get_related_planning_slots()
|
||||||
mapped_data = {}
|
# mapped_data = {}
|
||||||
TimeSpan = namedtuple('timespan', ['start', 'end'])
|
# TimeSpan = namedtuple('timespan', ['start', 'end'])
|
||||||
for d in forecast_data:
|
# for d in forecast_data:
|
||||||
if d not in mapped_data:
|
# if d not in mapped_data:
|
||||||
mapped_data.update({d: TimeSpan(d.start_datetime, d.end_datetime)})
|
# mapped_data.update({d: TimeSpan(d.start_datetime, d.end_datetime)})
|
||||||
continue
|
# continue
|
||||||
if mapped_data[d].start > d.start_datetime:
|
# if mapped_data[d].start > d.start_datetime:
|
||||||
mapped_data[d].start = d.start_datetime
|
# mapped_data[d].start = d.start_datetime
|
||||||
if mapped_data[d].end < d.end_datetime:
|
# if mapped_data[d].end < d.end_datetime:
|
||||||
mapped_data[d].end = d.end_datetime
|
# mapped_data[d].end = d.end_datetime
|
||||||
for rec in self:
|
# for rec in self:
|
||||||
if rec not in mapped_data:
|
# if rec not in mapped_data:
|
||||||
if not rec.planned_date_end:
|
# if not rec.planned_date_end:
|
||||||
rec.planned_date_end = False
|
# rec.planned_date_end = False
|
||||||
if not rec.planned_date_begin:
|
# if not rec.planned_date_begin:
|
||||||
rec.planned_date_begin = False
|
# rec.planned_date_begin = False
|
||||||
continue
|
# continue
|
||||||
rec.planned_date_begin, rec.planned_date_end = mapped_data[rec]
|
# rec.planned_date_begin, rec.planned_date_end = mapped_data[rec]
|
||||||
|
#
|
||||||
def _inverse_planned_dates(self):
|
# def _inverse_planned_dates(self):
|
||||||
""" Modifying the planned dates for tasks with existing planning records
|
# """ Modifying the planned dates for tasks with existing planning records
|
||||||
(planning.slot) has no defined safe behaviour, so we block it."""
|
# (planning.slot) has no defined safe behaviour, so we block it."""
|
||||||
if self._get_related_planning_slots():
|
# if self._get_related_planning_slots():
|
||||||
raise UserError(_("Modifying the planned start or end time on a task is not "
|
# raise UserError(_("Modifying the planned start or end time on a task is not "
|
||||||
"permitted when that task already has planning records "
|
# "permitted when that task already has planning records "
|
||||||
"associated to it. Please modify or delete the planning "
|
# "associated to it. Please modify or delete the planning "
|
||||||
"records instead."))
|
# "records instead."))
|
||||||
|
|
||||||
@api.depends('sale_line_id.order_id.site_contacts',
|
@api.depends('sale_line_id.order_id.site_contacts',
|
||||||
'sale_line_id.order_id.work_order_contacts')
|
'sale_line_id.order_id.work_order_contacts')
|
||||||
|
|
@ -183,17 +180,59 @@ class Task(models.Model):
|
||||||
else:
|
else:
|
||||||
rec.allow_billable = rec.project_id.allow_billable
|
rec.allow_billable = rec.project_id.allow_billable
|
||||||
|
|
||||||
def action_fsm_validate(self):
|
def action_fsm_validate(self, stop_running_timers=False):
|
||||||
visits = self.filtered(lambda t: t.visit_id)
|
all_tasks = self | self._get_all_subtasks()
|
||||||
non_visits = self - visits
|
non_visit_tasks = all_tasks.filtered(lambda task: not task.visit_id)
|
||||||
super(Task, non_visits).action_fsm_validate()
|
visit_tasks = all_tasks.filtered(lambda task: bool(task.visit_id))
|
||||||
|
result = visit_tasks._validate_task_without_creating_sale_order_line(stop_running_timers)
|
||||||
|
if isinstance(result, dict):
|
||||||
|
return result
|
||||||
|
result = super(Task, non_visit_tasks).action_fsm_validate(stop_running_timers)
|
||||||
|
if isinstance(result, dict):
|
||||||
|
return result
|
||||||
|
return result
|
||||||
|
|
||||||
visits._stop_all_timers_and_create_timesheets()
|
def _validate_task_without_creating_sale_order_line(self, stop_running_timers=False):
|
||||||
closed_stage_by_project = visits._get_closed_stage_by_project()
|
# Reproduce the functionality of action_fsm_validate but don't apply logic from industry_fsm_sale
|
||||||
super(Task, visits.child_ids).action_fsm_validate()
|
# so as to avoid creating order lines for visits
|
||||||
for visit in visits:
|
Timer = self.env['timer.timer']
|
||||||
stage = closed_stage_by_project[visit.project_id]
|
tasks_running_timer_ids = Timer.search([('res_model', '=', 'project.task'), ('res_id', 'in', self.ids)])
|
||||||
visits.write({'stage_id': stage.id, 'fsm_done': True})
|
timesheets = self.env['account.analytic.line'].sudo().search([('task_id', 'in', self.ids)])
|
||||||
|
timesheets_running_timer_ids = None
|
||||||
|
if timesheets:
|
||||||
|
timesheets_running_timer_ids = Timer.search([
|
||||||
|
('res_model', '=', 'account.analytic.line'),
|
||||||
|
('res_id', 'in', timesheets.ids)])
|
||||||
|
if tasks_running_timer_ids or timesheets_running_timer_ids:
|
||||||
|
if stop_running_timers:
|
||||||
|
self._stop_all_timers_and_create_timesheets(tasks_running_timer_ids, timesheets_running_timer_ids,
|
||||||
|
timesheets)
|
||||||
|
else:
|
||||||
|
wizard = self.env['project.task.stop.timers.wizard'].create({
|
||||||
|
'line_ids': [Command.create({'task_id': task.id}) for task in self],
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
'name': _('Do you want to stop the running timers?'),
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'view_id': self.env.ref('industry_fsm.view_task_stop_timer_wizard_form').id,
|
||||||
|
'target': 'new',
|
||||||
|
'res_model': 'project.task.stop.timers.wizard',
|
||||||
|
'res_id': wizard.id,
|
||||||
|
}
|
||||||
|
closed_stage_by_project = {
|
||||||
|
project.id:
|
||||||
|
project.type_ids.filtered(lambda stage: stage.fold)[:1] or project.type_ids[-1:]
|
||||||
|
for project in self.project_id
|
||||||
|
}
|
||||||
|
for task in self:
|
||||||
|
# determine closed stage for task
|
||||||
|
closed_stage = closed_stage_by_project.get(self.project_id.id)
|
||||||
|
values = {'fsm_done': True}
|
||||||
|
if closed_stage:
|
||||||
|
values['stage_id'] = closed_stage.id
|
||||||
|
|
||||||
|
task.write(values)
|
||||||
|
|
||||||
def synchronize_name_fsm(self):
|
def synchronize_name_fsm(self):
|
||||||
""" Applies naming to the entire task tree for tasks that are part of this
|
""" Applies naming to the entire task tree for tasks that are part of this
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,11 @@ class BemadeFSMBaseTest(TransactionCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def _generate_sale_order(cls, partner=None, client_order_ref='Test Order', equipment=None, shipping_location=None):
|
def _generate_sale_order(cls, partner=None, client_order_ref='Test Order', equipment=None, shipping_location=None):
|
||||||
partner = partner or cls._generate_partner()
|
partner = partner or cls._generate_partner()
|
||||||
vals = {'partner_id': partner.id,
|
vals = {
|
||||||
'client_order_ref': client_order_ref}
|
'partner_id': partner.id,
|
||||||
|
'client_order_ref': client_order_ref,
|
||||||
|
'payment_term_id': cls.env.ref('account.account_payment_term_immediate').id,
|
||||||
|
}
|
||||||
if equipment:
|
if equipment:
|
||||||
vals.update({'default_equipment_ids': [Command.set([equipment.id])]})
|
vals.update({'default_equipment_ids': [Command.set([equipment.id])]})
|
||||||
if shipping_location:
|
if shipping_location:
|
||||||
|
|
@ -97,6 +100,22 @@ class BemadeFSMBaseTest(TransactionCase):
|
||||||
'partner_location_id': partner and partner.id or False,
|
'partner_location_id': partner and partner.id or False,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _generate_product_category(cls):
|
||||||
|
company_id = cls.env.company.id
|
||||||
|
return cls.env['product.category'].create({
|
||||||
|
'name': 'Test Category',
|
||||||
|
'parent_id': cls.env.ref('product.product_category_all', False).id or False,
|
||||||
|
'property_account_income_categ_id': cls.env['account.account'].search(
|
||||||
|
[('account_type', '=', 'income'), ('company_id', '=', company_id)],
|
||||||
|
limit=1
|
||||||
|
).id,
|
||||||
|
'property_account_expense_categ_id': cls.env['account.account'].search(
|
||||||
|
[('account_type', '=', 'expense_direct_cost'), ('company_id', '=', company_id)],
|
||||||
|
limit=1
|
||||||
|
).id,
|
||||||
|
})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _generate_product(cls, name='Test Product', product_type='service', service_tracking='task_global_project',
|
def _generate_product(cls, name='Test Product', product_type='service', service_tracking='task_global_project',
|
||||||
project=None, task_template=None, service_policy='delivered_manual', uom=None):
|
project=None, task_template=None, service_policy='delivered_manual', uom=None):
|
||||||
|
|
@ -113,6 +132,7 @@ class BemadeFSMBaseTest(TransactionCase):
|
||||||
'service_policy': service_policy,
|
'service_policy': service_policy,
|
||||||
'uom_id': uom_id,
|
'uom_id': uom_id,
|
||||||
'uom_po_id': uom_id,
|
'uom_po_id': uom_id,
|
||||||
|
'categ_id': cls._generate_product_category().id,
|
||||||
})
|
})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -171,7 +191,7 @@ class BemadeFSMBaseTest(TransactionCase):
|
||||||
|
|
||||||
def _invoice_sale_order(self, so):
|
def _invoice_sale_order(self, so):
|
||||||
wiz = self.env['sale.advance.payment.inv'].with_context(
|
wiz = self.env['sale.advance.payment.inv'].with_context(
|
||||||
{'active_ids': [so.id]}).create({})
|
{'active_ids': [so.id]}).create([{}])
|
||||||
wiz.create_invoices()
|
wiz.create_invoices()
|
||||||
inv = so.invoice_ids[-1]
|
inv = so.invoice_ids[-1]
|
||||||
inv.action_post()
|
inv.action_post()
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ class FSMVisitTest(BemadeFSMBaseTest):
|
||||||
so.action_confirm()
|
so.action_confirm()
|
||||||
task = so.order_line.filtered(lambda l: l.task_id).task_id
|
task = so.order_line.filtered(lambda l: l.task_id).task_id
|
||||||
|
|
||||||
task.action_fsm_validate()
|
task.action_fsm_validate(stop_running_timers=True)
|
||||||
|
|
||||||
self.assertTrue(visit.is_completed)
|
self.assertTrue(visit.is_completed)
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ class FSMVisitTest(BemadeFSMBaseTest):
|
||||||
self._generate_sale_order_line(so)
|
self._generate_sale_order_line(so)
|
||||||
so.action_confirm()
|
so.action_confirm()
|
||||||
task = so.order_line.filtered(lambda l: l.task_id).task_id
|
task = so.order_line.filtered(lambda l: l.task_id).task_id
|
||||||
task.action_fsm_validate()
|
task.action_fsm_validate(stop_running_timers=True)
|
||||||
|
|
||||||
self._invoice_sale_order(so)
|
self._invoice_sale_order(so)
|
||||||
|
|
||||||
|
|
@ -106,7 +106,7 @@ class FSMVisitTest(BemadeFSMBaseTest):
|
||||||
so.action_confirm()
|
so.action_confirm()
|
||||||
parent, child1, child2 = visit.task_id, sol1.task_id, sol2.task_id
|
parent, child1, child2 = visit.task_id, sol1.task_id, sol2.task_id
|
||||||
|
|
||||||
parent.action_fsm_validate()
|
parent.action_fsm_validate(stop_running_timers=True)
|
||||||
|
|
||||||
self.assertTrue(parent.is_closed)
|
self.assertTrue(parent.is_closed)
|
||||||
self.assertTrue(child1.is_closed)
|
self.assertTrue(child1.is_closed)
|
||||||
|
|
@ -119,6 +119,6 @@ class FSMVisitTest(BemadeFSMBaseTest):
|
||||||
so, visit, sol1, sol2 = self._generate_so_with_one_visit_two_lines()
|
so, visit, sol1, sol2 = self._generate_so_with_one_visit_two_lines()
|
||||||
so.action_confirm()
|
so.action_confirm()
|
||||||
|
|
||||||
visit.task_id.action_fsm_validate()
|
visit.task_id.action_fsm_validate(stop_running_timers=True)
|
||||||
|
|
||||||
self.assertEqual(len(so.order_line), 3)
|
self.assertEqual(len(so.order_line), 3)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@
|
||||||
<field name="valid_equipment_ids" invisible="1"/>
|
<field name="valid_equipment_ids" invisible="1"/>
|
||||||
<field name="summary_equipment_ids"
|
<field name="summary_equipment_ids"
|
||||||
context="{'default_partner_location_id': partner_shipping_id,}"
|
context="{'default_partner_location_id': partner_shipping_id,}"
|
||||||
widget="many2many_tags"/>
|
widget="many2many_tags"
|
||||||
|
groups="account.group_delivery_invoice_address"/>
|
||||||
<field name="site_contacts"
|
<field name="site_contacts"
|
||||||
context="{'tree_view_ref': 'bemade_fsm.fsm_contacts_view_tree'}"/>
|
context="{'tree_view_ref': 'bemade_fsm.fsm_contacts_view_tree'}"/>
|
||||||
<field name="work_order_contacts"
|
<field name="work_order_contacts"
|
||||||
|
|
@ -25,7 +26,8 @@
|
||||||
<field name="default_equipment_ids"
|
<field name="default_equipment_ids"
|
||||||
context="{'default_partner_location_id': partner_shipping_id,}"
|
context="{'default_partner_location_id': partner_shipping_id,}"
|
||||||
widget="many2many_tags"
|
widget="many2many_tags"
|
||||||
domain="[('id', 'in', valid_equipment_ids)]"/>
|
domain="[('id', 'in', valid_equipment_ids)]"
|
||||||
|
groups="account.group_delivery_invoice_address"/>
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue