Forward port branch '16.0' into 17.0-dev
This commit is contained in:
commit
4e3e56a1eb
48 changed files with 761 additions and 280 deletions
1
account_credit_hold/__init__.py
Normal file
1
account_credit_hold/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import models
|
||||
21
account_credit_hold/__manifest__.py
Normal file
21
account_credit_hold/__manifest__.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
'name': 'Account Credit Hold',
|
||||
'version': '16.0.1.0.0',
|
||||
'summary': 'Allows setting clients on credit hold, blocking the ability confirm a new sales order.',
|
||||
'description': 'Allows setting clients on hold, blocking the ability confirm a new sales order.',
|
||||
'category': 'Accounting/Accounting',
|
||||
'author': 'Bemade Inc.',
|
||||
'maintainer': 'Marc Durepos <marc@bemade.org>',
|
||||
'website': 'http://www.bemade.org',
|
||||
'license': 'LGPL-3',
|
||||
'depends': ['sale', 'account_followup', 'stock'],
|
||||
'data': [
|
||||
'views/account_followup_views.xml',
|
||||
'views/sale_order_views.xml',
|
||||
'views/res_partner_views.xml',
|
||||
'views/stock_picking_views.xml',
|
||||
],
|
||||
'demo': [],
|
||||
'installable': True,
|
||||
'auto_install': False
|
||||
}
|
||||
5
account_credit_hold/models/__init__.py
Normal file
5
account_credit_hold/models/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from . import account_followup
|
||||
from . import res_partner
|
||||
from . import sale_order
|
||||
from . import stock_picking
|
||||
from . import account_followup_report
|
||||
8
account_credit_hold/models/account_followup.py
Normal file
8
account_credit_hold/models/account_followup.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class FollowupLine(models.Model):
|
||||
_inherit = 'account_followup.followup.line'
|
||||
|
||||
account_hold = fields.Boolean(string="Place on Credit Hold",
|
||||
help="Place clients on account hold, restricting confirmation of new orders.")
|
||||
12
account_credit_hold/models/account_followup_report.py
Normal file
12
account_credit_hold/models/account_followup_report.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from odoo import models, fields, api, _
|
||||
|
||||
|
||||
class FollowUpReport(models.AbstractModel):
|
||||
_inherit = 'account.followup.report'
|
||||
|
||||
def _get_line_info(self, followup_line):
|
||||
res = super()._get_line_info(followup_line)
|
||||
res.update({
|
||||
'credit_hold': followup_line.account_hold
|
||||
})
|
||||
return res
|
||||
63
account_credit_hold/models/res_partner.py
Normal file
63
account_credit_hold/models/res_partner.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
from odoo import fields, models, api, _
|
||||
from datetime import date
|
||||
|
||||
|
||||
class Partner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
postpone_hold_until = fields.Date(string="Postpone Hold",
|
||||
help="Grace period specific to this partner despite unpaid invoices.", )
|
||||
|
||||
hold_bg = fields.Boolean(string="Hold (technical)",
|
||||
compute="_compute_hold_bg",
|
||||
store=True,
|
||||
default=False)
|
||||
on_hold = fields.Boolean(string="Account on Hold",
|
||||
help="Client account is on hold for unpaid overdue invoices.",
|
||||
compute="_compute_on_hold")
|
||||
|
||||
@api.depends('postpone_hold_until', 'hold_bg')
|
||||
def _compute_on_hold(self):
|
||||
# manually re-compute hold_bg since followup_status doesn't get updated in Python but gets recalculated
|
||||
# by an SQL query every time
|
||||
self._compute_hold_bg()
|
||||
for rec in self:
|
||||
if rec.hold_bg and not (rec.postpone_hold_until and rec.postpone_hold_until > date.today()):
|
||||
rec.on_hold = True
|
||||
else:
|
||||
if rec.on_hold:
|
||||
rec.message_post(_("Credit hold lifted."))
|
||||
rec.on_hold = False
|
||||
|
||||
@api.autovacuum
|
||||
def _cleanup_expired_hold_postponements(self):
|
||||
expired_holds = self.search([('postpone_hold_until', '<=', date.today())])
|
||||
expired_holds.write({'postpone_hold_until': False})
|
||||
|
||||
def action_credit_hold(self):
|
||||
for rec in self:
|
||||
rec.hold_bg = True
|
||||
rec.message_post(body=_('Placed on credit hold.'))
|
||||
|
||||
def action_lift_credit_hold(self):
|
||||
for rec in self:
|
||||
rec.hold_bg = False
|
||||
rec.message_post(body=_('Credit hold lifted.'))
|
||||
|
||||
def _execute_followup_partner(self):
|
||||
res = super()._execute_followup_partner()
|
||||
if self.followup_status == 'in_need_of_action':
|
||||
if self.followup_line_id.account_hold:
|
||||
self.action_credit_hold()
|
||||
return res
|
||||
|
||||
@api.depends('followup_status', 'followup_line_id')
|
||||
def _compute_hold_bg(self):
|
||||
first_followup_level = self._get_first_followup_level()
|
||||
for rec in self:
|
||||
prev_hold_bg = rec.hold_bg
|
||||
level = rec.followup_line_id
|
||||
if rec.followup_status == 'no_action_needed' and level == first_followup_level:
|
||||
rec.hold_bg = False
|
||||
else:
|
||||
rec.hold_bg = prev_hold_bg
|
||||
17
account_credit_hold/models/sale_order.py
Normal file
17
account_credit_hold/models/sale_order.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from odoo import fields, models, api, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = "sale.order"
|
||||
|
||||
client_on_hold = fields.Boolean(string='Client on Hold',
|
||||
help="Whether or not a client has been put on hold due to unpaid invoices.",
|
||||
related="partner_id.on_hold")
|
||||
|
||||
@api.depends('client_on_hold')
|
||||
def action_confirm(self):
|
||||
if any(self.mapped('client_on_hold')):
|
||||
raise UserError(_("This client is on credit hold. No new orders can be confirmed until past-due invoices "
|
||||
"are paid or the accounting team postpones the hold."))
|
||||
super().action_confirm()
|
||||
9
account_credit_hold/models/stock_picking.py
Normal file
9
account_credit_hold/models/stock_picking.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class ModelName(models.Model):
|
||||
_inherit = "stock.picking"
|
||||
|
||||
client_on_hold = fields.Boolean(string='Client on Hold',
|
||||
help="Whether or not a client has been put on hold due to unpaid invoices.",
|
||||
related="partner_id.on_hold")
|
||||
35
account_credit_hold/readme.md
Normal file
35
account_credit_hold/readme.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Overview
|
||||
|
||||
This module adds the notion of placing clients on credit hold to the followup levels from the Odoo Enterprise
|
||||
account_followup module. It adds an option to followup levels to mark clients matching the followup criteria as on
|
||||
credit hold. This hold restricts the confirmation of new sales orders for these clients.
|
||||
|
||||
Accountant and admin users can set a date until which the account hold will be
|
||||
postponed on a specific partner's form view. This effectively gives clients an extra
|
||||
grace period, allowing orders to be confirmed until the period ends.
|
||||
|
||||
# Change Log
|
||||
## 15.0.2.0.0 (2023-05-04)
|
||||
|
||||
Complete remake of the module, making the "Credit Hold" an action that is either manually or
|
||||
automatically triggered from the Accounting > Followup Reports section or by setting the automatic application field
|
||||
on followup levels.
|
||||
|
||||
## 15.0.1.1.0 (2023-05-03)
|
||||
|
||||
Adds a ribbon to stock pickings for clients on hold, and therefore a dependency on stock.
|
||||
|
||||
## 15.0.1.0.2 (2023-05-03)
|
||||
|
||||
Fix to sale order view and sale order confirmation for clients not on hold.
|
||||
|
||||
## 15.0.1.0.1 (2023-05-03)
|
||||
|
||||
Fix clients on hold when status is "outstanding_invoices".
|
||||
|
||||
## 15.0.1.0.0 (2023-05-02) Initial Release
|
||||
|
||||
Initial release of the module, including a setting on follow-up levels to toggle placing on credit hold. Blocks
|
||||
the confirmation of sales orders for clients on credit hold. Red "Credit Hold" banner appears on sales orders and
|
||||
partner form view when a client is on credit hold. Credit hold can be postponed by setting the "Postpone Hold" field
|
||||
on the partner form view.
|
||||
52
account_credit_hold/views/account_followup_views.xml
Normal file
52
account_credit_hold/views/account_followup_views.xml
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="account_followup_followup_line_form_inherit" model="ir.ui.view">
|
||||
<field name="name">account_credit_hold.account_followup_line.form</field>
|
||||
<field name="model">account_followup.followup.line</field>
|
||||
<field name="inherit_id" ref="account_followup.view_account_followup_followup_line_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='send_email']" position="before">
|
||||
<field name="account_hold"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="customer_statements_form_view_inherit" model="ir.ui.view">
|
||||
<field name="name">customer.statements.form.view.inherit</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="account_followup.customer_statements_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[last()]" position="after">
|
||||
<field name="hold_bg" invisible="1"/>
|
||||
<button
|
||||
string="Credit Hold"
|
||||
type="object"
|
||||
name="action_credit_hold"
|
||||
class="button btn-secondary"
|
||||
attrs="{'invisible': [('hold_bg', '=', True)]}"
|
||||
/>
|
||||
<button
|
||||
string="Lift Credit Hold"
|
||||
type="object"
|
||||
name="action_lift_credit_hold"
|
||||
class="button btn-secondary"
|
||||
attrs="{'invisible': [('hold_bg', '=', False)]}"
|
||||
/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_credit_hold" model="ir.actions.server">
|
||||
<field name="name">action_credit_hold</field>
|
||||
<field name="model_id" ref="base.model_res_partner"/>
|
||||
<field name="binding_model_id" ref="base.model_res_partner"/>
|
||||
<field name="binding_view_types">list,form</field>
|
||||
<field name="state">code</field>
|
||||
<field name="code">
|
||||
records.action_credit_hold()
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
33
account_credit_hold/views/res_partner_views.xml
Normal file
33
account_credit_hold/views/res_partner_views.xml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="res_partner_form_inherit" model="ir.ui.view">
|
||||
<field name="name">account_credit_hold.res_partner.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']" position="before">
|
||||
<field name="hold_bg" invisible="True" />
|
||||
<field name="on_hold" invisible="True" />
|
||||
<widget name="web_ribbon" title="Credit Hold"
|
||||
bg_color="bg-danger" attrs="{'invisible': [('on_hold','=',False)]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_partner_property_form_inherit" model="ir.ui.view">
|
||||
<field name="name">account_credit_hold.view_partner_property_form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="account.view_partner_property_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@name='banks']" position="before">
|
||||
<group string="Credit Hold" >
|
||||
<field name="postpone_hold_until"
|
||||
groups="account.group_account_manager,account.group_account_user"
|
||||
attrs="{'readonly': [('hold_bg','=',False), ('postpone_hold_until','=',False)]}"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
18
account_credit_hold/views/sale_order_views.xml
Normal file
18
account_credit_hold/views/sale_order_views.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="sale_order_form_inherit" model="ir.ui.view">
|
||||
<field name="name">account_credit_hold.sale_order.form</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']" position="before">
|
||||
<field name="client_on_hold" invisible="True" />
|
||||
<widget name="web_ribbon" title="Credit Hold"
|
||||
bg_color="bg-danger" attrs="{'invisible': [('client_on_hold','=',False)] }"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
20
account_credit_hold/views/stock_picking_views.xml
Normal file
20
account_credit_hold/views/stock_picking_views.xml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="stock_picking_form_inherit" model="ir.ui.view">
|
||||
<field name="name">account_credit_hold.stock_picking.form</field>
|
||||
<field name="model">stock.picking</field>
|
||||
<field name="inherit_id" ref="stock.view_picking_form"/>
|
||||
<field name="priority" eval="8"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']" position="before">
|
||||
<field name="client_on_hold" invisible="True" />
|
||||
<widget name="web_ribbon" title="Credit Hold"
|
||||
bg_color="bg-danger" attrs="{'invisible': [('client_on_hold','=',False)] }"/>
|
||||
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -1 +1,2 @@
|
|||
from . import models
|
||||
from . import wizard
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
########################################################################################
|
||||
{
|
||||
'name': 'Improved Field Service Management',
|
||||
'version': '15.0.1.0.0',
|
||||
'version': '16.0.1.0.0',
|
||||
'summary': 'Adds functionality necessary for managing field service operations at Durpro.',
|
||||
'description': 'Adds functionality necessary for managing field service operations at Durpro.',
|
||||
'category': 'Services/Field Service',
|
||||
|
|
@ -55,6 +55,7 @@
|
|||
'views/sale_order_views.xml',
|
||||
# 'reports/worksheet_custom_report_templates.xml',
|
||||
# 'reports/worksheet_custom_reports.xml',
|
||||
'wizard/new_task_from_template.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_tests': [
|
||||
|
|
@ -64,6 +65,13 @@
|
|||
],
|
||||
'web.report_assets_common': [
|
||||
'bemade_fsm/static/src/scss/bemade_fsm.scss'
|
||||
],
|
||||
'web.assets_backend': [
|
||||
'bemade_fsm/static/src/js/kanban_view.js',
|
||||
'bemade_fsm/static/src/js/list_view.js',
|
||||
],
|
||||
'web.assets_qweb': [
|
||||
'bemade_fsm/static/src/xml/project_view_buttons.xml',
|
||||
]
|
||||
},
|
||||
'installable': True,
|
||||
|
|
|
|||
|
|
@ -76,6 +76,12 @@ class Task(models.Model):
|
|||
|
||||
work_order_number = fields.Char(readonly=True)
|
||||
|
||||
propagate_assignment = fields.Boolean(
|
||||
string='Propagate Assignment',
|
||||
help='Propagate assignment of this task to all subtasks.',
|
||||
default=False,
|
||||
)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals):
|
||||
res = super().create(vals)
|
||||
|
|
@ -92,6 +98,16 @@ class Task(models.Model):
|
|||
+ f"-{seq}"
|
||||
return res
|
||||
|
||||
def write(self, vals):
|
||||
super().write(vals)
|
||||
if not self: # End recursion on empty RecordSet
|
||||
return
|
||||
if 'user_ids' in vals:
|
||||
to_propagate = self.filtered(lambda task: task.propagate_assignment)
|
||||
# Here we use child_ids instead of _get_all_subtasks() so as to allow for setting propagate_assignment
|
||||
# to false on a child task.
|
||||
to_propagate.child_ids.write({'user_ids': vals['user_ids']})
|
||||
|
||||
@api.depends('sale_order_id')
|
||||
def _compute_relevant_order_lines(self):
|
||||
for rec in self:
|
||||
|
|
@ -234,6 +250,11 @@ class Task(models.Model):
|
|||
|
||||
task.write(values)
|
||||
|
||||
def _get_full_hierarchy(self):
|
||||
if self.child_ids:
|
||||
return self | self.child_ids._get_full_hierarchy()
|
||||
return self
|
||||
|
||||
def synchronize_name_fsm(self):
|
||||
""" Applies naming to the entire task tree for tasks that are part of this
|
||||
recordset. Root tasks are named:
|
||||
|
|
|
|||
|
|
@ -81,3 +81,38 @@ class TaskTemplate(models.Model):
|
|||
for rec in self:
|
||||
new_equipment_ids = [eq.id for eq in rec.equipment_ids if eq.partner_location_id == rec.customer]
|
||||
rec.write({'equipment_ids': [Command.set(new_equipment_ids)]})
|
||||
|
||||
def _prepare_new_task_values_from_self(self, project, name=False, parent_id=False):
|
||||
vals = {
|
||||
'project_id': project.id,
|
||||
'name': name or self.name,
|
||||
'description': self.description,
|
||||
'parent_id': parent_id,
|
||||
'user_ids': self.assignees.ids,
|
||||
'tag_ids': self.tags.ids,
|
||||
'planned_hours': self.planned_hours,
|
||||
'sequence': self.sequence,
|
||||
'equipment_ids': [Command.set(self.equipment_ids.ids)] if self.equipment_ids else False,
|
||||
'partner_id': project.partner_id and project.partner_id.id,
|
||||
'company_id': self.company_id.id,
|
||||
}
|
||||
return vals
|
||||
|
||||
def create_task_from_self(self, project, name=False, parent_id=False):
|
||||
""" Create a project.task from this template and return it. Can be called on a RecordSet of multiple templates.
|
||||
|
||||
:param project: project.project record the task should be added to
|
||||
:param name: name for the new task (defaults to template name)
|
||||
:param parent_id: parent task for the new task (none by default)
|
||||
:return: project.task record created from this template
|
||||
"""
|
||||
tasks = self.env['project.task']
|
||||
for rec in self:
|
||||
vals = rec._prepare_new_task_values_from_self(project, name, parent_id)
|
||||
task = rec.env['project.task'].create(vals)
|
||||
rec.subtasks.create_task_from_self(project, name=False, parent_id=task.id)
|
||||
tasks |= task
|
||||
return tasks
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@ access_bemade_fsm_equipment,bemade_fsm_equipment,model_bemade_fsm_equipment,base
|
|||
access_bemade_fsm_equipment_tag,bemade_fsm_equipment_tag,model_bemade_fsm_equipment_tag,base.group_user,1,1,1,1
|
||||
access_bemade_fsm_equipment_type,access_bemade_fsm_equipment_type,model_bemade_fsm_equipment_type,base.group_user,1,0,0,0
|
||||
access_bemade_fsm_visit,access_bemade_fsm_visit,model_bemade_fsm_visit,base.group_user,1,1,1,1
|
||||
access_bemade_fsm_task_wizard,access_bemade_fsm_task_wizard,model_project_task_from_template_wizard,project.group_project_user,1,1,1,1
|
||||
|
|
|
|||
|
37
bemade_fsm/static/src/js/kanban_view.js
Normal file
37
bemade_fsm/static/src/js/kanban_view.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import KanbanController from 'web.KanbanController';
|
||||
import KanbanView from 'web.KanbanView';
|
||||
const viewRegistry = require('web.view_registry')
|
||||
|
||||
const ProjectKanbanController = KanbanController.extend({
|
||||
buttons_template: 'project.KanbanView.buttons',
|
||||
custom_events: _.extend({}, KanbanController.prototype.custom_events, {
|
||||
create_from_template: '_onCreateFromTemplate',
|
||||
}),
|
||||
_onCreateFromTemplate(ev) {
|
||||
ev.stopPropagation();
|
||||
const record = this.model.get(this.handle, {raw: true});
|
||||
let context = record.context;
|
||||
context.active_id = record.project_id;
|
||||
this.do_action({
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: 'project.task.from.template.wizard',
|
||||
views: [[false, 'form']],
|
||||
target: 'new',
|
||||
context: {...record.context, res_model: 'project.task'},
|
||||
});
|
||||
},
|
||||
renderButtons ($node) {
|
||||
this._super.apply(this, arguments);
|
||||
this.$buttons.on('click', 'button.o-kanban-button-new-from-template', this._onCreateFromTemplate.bind(this))
|
||||
},
|
||||
})
|
||||
|
||||
const ProjectKanbanView = KanbanView.extend({
|
||||
config: _.extend({}, KanbanView.prototype.config, {
|
||||
Controller: ProjectKanbanController,
|
||||
})
|
||||
});
|
||||
|
||||
viewRegistry.add('project_kanban', ProjectKanbanView)
|
||||
36
bemade_fsm/static/src/js/list_view.js
Normal file
36
bemade_fsm/static/src/js/list_view.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import ListController from 'web.ListController';
|
||||
import ListView from 'web.ListView';
|
||||
const viewRegistry = require('web.view_registry')
|
||||
|
||||
const ProjectListController = ListController.extend({
|
||||
buttons_template: 'project.ListView.buttons',
|
||||
custom_events: _.extend({}, ListController.prototype.custom_events, {
|
||||
create_from_template: '_onCreateFromTemplate',
|
||||
}),
|
||||
_onCreateFromTemplate(ev) {
|
||||
ev.stopPropagation();
|
||||
const record = this.model.get(this.handle, {raw: true});
|
||||
this.do_action({
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: 'project.task.from.template.wizard',
|
||||
views: [[false, 'form']],
|
||||
target: 'new',
|
||||
context: {...record.context, res_model: 'project.task'},
|
||||
});
|
||||
},
|
||||
renderButtons ($node) {
|
||||
self = this;
|
||||
this._super.apply(this, arguments);
|
||||
this.$buttons.on('click', 'button.o_list_button_add_from_template', this._onCreateFromTemplate.bind(this))
|
||||
},
|
||||
})
|
||||
|
||||
const ProjectListView = ListView.extend({
|
||||
config: _.extend({}, ListView.prototype.config, {
|
||||
Controller: ProjectListController,
|
||||
})
|
||||
});
|
||||
|
||||
viewRegistry.add('project_list', ProjectListView)
|
||||
21
bemade_fsm/static/src/xml/project_view_buttons.xml
Normal file
21
bemade_fsm/static/src/xml/project_view_buttons.xml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<!-- Add a button to create a task from a template to task (project) list and kanban views -->
|
||||
<t t-name='project.KanbanView.buttons' t-inherit="web.KanbanView.buttons" t-inherit-mode="primary">
|
||||
<xpath expr="//button[contains(@t-attf-class, 'o-kanban-button-new')]" position="after">
|
||||
<button title="Create Task from Template" t-if="!noCreate" type="button"
|
||||
t-attf-class="btn {{ btnClass }} o-kanban-button-new-from-template">
|
||||
<t t-esc="create_text && (create_text + ' from Template') || _t('Create from Template')"/>
|
||||
</button>
|
||||
</xpath>
|
||||
</t>
|
||||
<t t-name='project.ListView.buttons' t-inherit="web.ListView.buttons" t-inherit-mode="primary">
|
||||
<xpath expr="//button[hasclass('o_list_button_add')]" position="after">
|
||||
<!-- Create is enabled in the parent template at this point; check is done prior -->
|
||||
<button type="button" class="btn ml-1 btn-primary o_list_button_add_from_template"
|
||||
title="Create Task from Template">
|
||||
Create from Template
|
||||
</button>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import tour from 'web_tour.tour';
|
||||
|
||||
const TEST_COMPANY = "Test Partner";
|
||||
const TEST_EQPT1 = "Test Equipment 1";
|
||||
const TEST_EQPT2 = "Test Equipment 2";
|
||||
tour.register('equipment_base_tour', {
|
||||
test: true,
|
||||
url: '/web',
|
||||
}, /* Create a new "Test Equipment" and link it to "Test Partner Company" */
|
||||
[tour.stepUtils.showAppsMenuItem(), {
|
||||
content: 'Navigate to the Service menu',
|
||||
trigger: '.o_app[data-menu-xmlid="industry_fsm.fsm_menu_root"]',
|
||||
}, {
|
||||
content: 'Navigate to the Clients submenu',
|
||||
trigger: 'button.dropdown-toggle[data-menu-xmlid="bemade_fsm.menu_service_client"]',
|
||||
}, {
|
||||
content: 'Navigate to the Equipment menu',
|
||||
trigger: '.dropdown-item[data-menu-xmlid="bemade_fsm.menu_service_client_equipment"]',
|
||||
}, {
|
||||
content: 'Click the create button',
|
||||
trigger: '.o_list_button_add',
|
||||
}, {
|
||||
content: 'Add a tag',
|
||||
trigger: 'input[name="pid_tag"]',
|
||||
run: 'text TestPIDTag',
|
||||
}, {
|
||||
content: 'Set the name',
|
||||
trigger: 'input[name="name"]',
|
||||
run: `text ${TEST_EQPT2}`,
|
||||
}, {
|
||||
content: 'Set the partner',
|
||||
trigger: 'div[name="partner_location_id"] div div input',
|
||||
run: `text ${TEST_COMPANY}`,
|
||||
}, {
|
||||
content: 'Click the partner in the dropdown',
|
||||
trigger: `li a.dropdown-item:contains(${TEST_COMPANY})`,
|
||||
}, {
|
||||
content: 'Save equipment',
|
||||
trigger: 'button.o_list_button_save',
|
||||
}, {
|
||||
/* Navigate to the client and make sure that there are two equipments saved (one from the Python test case) */
|
||||
content: 'Navigate to the Clients submenu',
|
||||
trigger: 'button.dropdown-toggle[data-menu-xmlid="bemade_fsm.menu_service_client"]',
|
||||
}, {
|
||||
content: 'Click on the clients submenu',
|
||||
trigger: '.dropdown-item[data-menu-xmlid="bemade_fsm.menu_service_client_clients"]',
|
||||
}, {
|
||||
content: 'Search for the Test Partner Company',
|
||||
trigger: 'input.o_searchview_input',
|
||||
extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Customers))',
|
||||
run: `text ${TEST_COMPANY}`
|
||||
}, {
|
||||
content: 'Validate Search',
|
||||
trigger: '.o_menu_item.o_selection_focus',
|
||||
run: 'click',
|
||||
}, {
|
||||
content: 'Open the test client.',
|
||||
trigger: `div.o_kanban_record:has(span:contains(${TEST_COMPANY}))`,
|
||||
}, {
|
||||
content: 'Click the Field Service tab.',
|
||||
trigger: 'a.nav-link:contains(Field Service)',
|
||||
extra_trigger: `h1 span.o_field_partner_autocomplete[name="name"]:contains(${TEST_COMPANY})`,
|
||||
}, {
|
||||
content: 'Make sure we have a first test equipment',
|
||||
trigger: `td:contains(${TEST_EQPT1})`,
|
||||
run: function () {
|
||||
},
|
||||
}, {
|
||||
content: 'Make sure we have a second test equipment',
|
||||
trigger: `div[name="equipment_ids"]:has(td:contains(${TEST_EQPT2}))`,
|
||||
run: function () {
|
||||
},
|
||||
}
|
||||
]);
|
||||
|
||||
tour.register('equipment_sale_order_tour', {
|
||||
test: true,
|
||||
url: '/web',
|
||||
}, [tour.stepUtils.showAppsMenuItem(), {
|
||||
content: 'Navigate to the Sales menu',
|
||||
trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]',
|
||||
}, {
|
||||
content: 'Create an order',
|
||||
trigger: 'button.o_list_button_add',
|
||||
}, {
|
||||
content: 'Select the test partner',
|
||||
trigger: 'div[name="partner_id"] input',
|
||||
run: `text ${TEST_COMPANY}`,
|
||||
}, {
|
||||
content: 'Click the partner in the dropdown',
|
||||
trigger: `li a.dropdown-item:contains(${TEST_COMPANY})`,
|
||||
}, {
|
||||
content: 'Save',
|
||||
trigger: 'button.o_form_button_save',
|
||||
}, {
|
||||
content: 'Navigate to the Field Service tab.',
|
||||
trigger: 'a.nav-link[role="tab"]:contains(Field Service)'
|
||||
}, {
|
||||
content: 'Check that the equipment is listed in the tab.',
|
||||
trigger: `li.ui-menu-item > a:contains(${TEST_EQPT1})`,
|
||||
}
|
||||
]);
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import tour from 'web_tour.tour';
|
||||
|
||||
const SO_NAME = "TEST ORDER 2"
|
||||
const PRODUCT_NAME = "Test Product 3"
|
||||
tour.register('sale_order_tour', {
|
||||
test: true,
|
||||
url: '/web',
|
||||
},
|
||||
[tour.stepUtils.showAppsMenuItem(), {
|
||||
content: 'Navigate to the Service menu',
|
||||
trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]',
|
||||
}, {
|
||||
content: 'Search for the sales order',
|
||||
trigger: 'input.o_searchview_input',
|
||||
run: `text ${SO_NAME}`,
|
||||
}, {
|
||||
content: 'Validate Search',
|
||||
trigger: '.o_menu_item.o_selection_focus',
|
||||
run: 'click',
|
||||
}, {
|
||||
content: 'Open the test order',
|
||||
trigger: `.o_data_cell[name="client_order_ref"]:contains(${SO_NAME})`,
|
||||
}, {
|
||||
content: 'Click the view tasks button',
|
||||
trigger: 'button[name="action_view_task"]',
|
||||
}, /*{
|
||||
content: 'Click the first task',
|
||||
trigger: `div.o_kanban_record:has(span:contains(${PRODUCT_NAME}))`,
|
||||
},*/ {
|
||||
content: 'Click on the ready to invoice button',
|
||||
trigger: 'button[name="action_fsm_validate"]',
|
||||
extra_trigger: `li.breadcrumb-item.active:has(span:contains(${PRODUCT_NAME}))`
|
||||
}, {
|
||||
content: 'View the SO',
|
||||
trigger: 'button[name="action_view_so"]',
|
||||
// extra_trigger: 'button[title="Current state"]:contains(Done)',
|
||||
}
|
||||
]);
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import tour from 'web_tour.tour';
|
||||
const TEST_COMPANY = "Test Partner Company";
|
||||
const TEST_EQPT1 = "Test Equipment 1";
|
||||
const TEST_EQPT2 = "Test Equipment 2";
|
||||
tour.register('task_equipment_tour', {
|
||||
test: true,
|
||||
url: '/web',
|
||||
}, /* Create a new "Test Equipment" and link it to "Test Partner Company" */
|
||||
[tour.stepUtils.showAppsMenuItem(), {
|
||||
content: 'Navigate to the Service menu',
|
||||
trigger: '.o_app[data-menu-xmlid="industry_fsm.fsm_menu_root"]',
|
||||
}, {
|
||||
content: 'Navigate to the Clients submenu',
|
||||
trigger: 'button.dropdown-toggle[data-menu-xmlid="bemade_fsm.menu_service_client"]',
|
||||
}, {
|
||||
content: 'Navigate to the Equipment menu',
|
||||
trigger: '.dropdown-item[data-menu-xmlid="bemade_fsm.menu_service_client_equipment"]',
|
||||
}, {
|
||||
content: 'Click the create button',
|
||||
trigger: '.o_list_button_add',
|
||||
extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Equipment))',
|
||||
}, {
|
||||
content: 'Add a tag',
|
||||
trigger: 'input[name="pid_tag"]',
|
||||
run: 'text TestPIDTag',
|
||||
}, {
|
||||
content: 'Set the name',
|
||||
trigger: 'input[name="name"]',
|
||||
run: `text ${TEST_EQPT2}`,
|
||||
}, {
|
||||
content: 'Set the partner',
|
||||
trigger: 'div[name="partner_location_id"] div div input',
|
||||
run: 'text Test Partner Company',
|
||||
}, {
|
||||
content: 'Click the partner in the dropdown',
|
||||
trigger: `li a.dropdown-item:contains(${TEST_COMPANY})`,
|
||||
}, {
|
||||
content: 'Save equipment',
|
||||
trigger: 'button.o_list_button_save',
|
||||
}, {
|
||||
/* Navigate to the client and make sure that there are two equipments saved (one from the Python test case) */
|
||||
content: 'Navigate to the Clients submenu',
|
||||
trigger: 'button.dropdown-toggle[data-menu-xmlid="bemade_fsm.menu_service_client"]',
|
||||
}, {
|
||||
content: 'Click on the clients submenu',
|
||||
trigger: '.dropdown-item[data-menu-xmlid="bemade_fsm.menu_service_client_clients"]',
|
||||
}, {
|
||||
content: 'Search for the Test Partner Company',
|
||||
trigger: 'input.o_searchview_input',
|
||||
extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Customers))',
|
||||
run: `text ${TEST_COMPANY}`
|
||||
}, {
|
||||
content: 'Validate Search',
|
||||
trigger: '.o_menu_item.o_selection_focus',
|
||||
run: 'click',
|
||||
}, {
|
||||
content: 'Open the test client.',
|
||||
trigger: `div.o_kanban_record:has(span:contains(${TEST_COMPANY}))`,
|
||||
}, {
|
||||
content: 'Click the Field Service tab.',
|
||||
trigger: 'a.nav-link:contains(Field Service)',
|
||||
extra_trigger: `h1 span.o_field_partner_autocomplete[name="name"]:contains(${TEST_COMPANY})`,
|
||||
}, {
|
||||
content: 'Make sure we have a first test equipment',
|
||||
/*trigger: `div[name="equipment_ids"]:has(td:contains(${TEST_EQPT1}))`,*/
|
||||
trigger: `td:contains(${TEST_EQPT1})`,
|
||||
run: function() {},
|
||||
}, {
|
||||
content: 'Make sure we have a second test equipment',
|
||||
trigger: `div[name="equipment_ids"]:has(td:contains(${TEST_EQPT2}))`,
|
||||
run: function() {},
|
||||
}
|
||||
])
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import tour from 'web_tour.tour';
|
||||
|
||||
tour.register('task_template_tour', {
|
||||
test: true,
|
||||
url: '/web',
|
||||
}, /* Make sure the test task is visible via the Project > Task Templates path */
|
||||
[tour.stepUtils.showAppsMenuItem(), {
|
||||
trigger: '.o_app[data-menu-xmlid="project.menu_main_pm"]',
|
||||
}, {
|
||||
content: 'Open the task templates list view.',
|
||||
trigger: '.o_nav_entry[data-menu-xmlid="bemade_fsm.project_task_template_menu"]',
|
||||
}, {
|
||||
content: 'Open the existing task template.',
|
||||
trigger: '.o_data_cell:contains("Template 1"):parent()',
|
||||
}, {
|
||||
content: 'Confirm that the header contains the task title.',
|
||||
trigger: 'span[name="name"]:contains("Template 1")',
|
||||
}, /* Make sure we can see the task template from the product */
|
||||
tour.stepUtils.toggleHomeMenu(),
|
||||
{
|
||||
content: 'Open the sales menu.',
|
||||
trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]',
|
||||
}, {
|
||||
content: 'Open the product dropdown menu.',
|
||||
trigger: 'button[data-menu-xmlid="sale.product_menu_catalog"]',
|
||||
}, {
|
||||
content: 'Click the products menu item.',
|
||||
trigger: 'a.dropdown-item[data-menu-xmlid="sale.menu_product_template_action"]',
|
||||
}, {
|
||||
content: 'Search for the test product.',
|
||||
trigger: 'input.o_searchview_input',
|
||||
extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Products))',
|
||||
run: 'text Test Product 1',
|
||||
}, {
|
||||
trigger: '.o_menu_item.o_selection_focus',
|
||||
content: 'Validate search',
|
||||
run: 'click',
|
||||
}, {
|
||||
content: 'Open the test product.',
|
||||
trigger: 'div.o_kanban_record:has(span:contains(Test Product 1))',
|
||||
}, {
|
||||
content: 'Ensure the product_template_id field is displayed with the "Template 1" mention.',
|
||||
trigger: 'a.o_quick_editable[name="task_template_id"]:first-child:contains("Template 1")',
|
||||
},
|
||||
])
|
||||
|
|
@ -4,3 +4,4 @@ from . import test_sale_order
|
|||
from . import test_equipment
|
||||
from . import test_fsm_contact_setting
|
||||
from . import test_fsm_visit
|
||||
from . import test_task
|
||||
|
|
|
|||
|
|
@ -251,7 +251,7 @@ class TestSalesOrder(BemadeFSMBaseTest):
|
|||
self.assertFalse(visit_task.user_ids)
|
||||
self.assertFalse(subtask1.user_ids)
|
||||
self.assertFalse(subtask2.user_ids)
|
||||
|
||||
|
||||
def test_long_line_name_overflows_to_task_description(self):
|
||||
so = self._generate_sale_order()
|
||||
product = self._generate_product()
|
||||
|
|
|
|||
42
bemade_fsm/tests/test_task.py
Normal file
42
bemade_fsm/tests/test_task.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
from .test_bemade_fsm_common import BemadeFSMBaseTest
|
||||
from odoo.tests.common import tagged
|
||||
from odoo import Command
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TaskTest(BemadeFSMBaseTest):
|
||||
|
||||
def test_reassigning_assignment_propagating_task_changes_subtasks(self):
|
||||
# This creation step is a bit lazy - we use defaults to make tasks with a hierachy and settings we want
|
||||
so = self._generate_sale_order()
|
||||
template = self._generate_task_template(names=['Parent', 'Child'], structure=[2])
|
||||
product = self._generate_product(task_template=template)
|
||||
sol = self._generate_sale_order_line(sale_order=so, product=product)
|
||||
user = self._generate_project_manager_user('Bob', 'Bob')
|
||||
so.action_confirm()
|
||||
task = sol.task_id
|
||||
|
||||
task.write({
|
||||
'user_ids': [Command.set([user.id])],
|
||||
'propagate_assignment': True,
|
||||
})
|
||||
|
||||
self.assertTrue(all([t.user_ids == user for t in task | task._get_all_subtasks()]))
|
||||
|
||||
def test_reassigning_assignment_non_propagating_task_doesnt_change_subtasks(self):
|
||||
so = self._generate_sale_order()
|
||||
template = self._generate_task_template(names=['Parent', 'Child', 'Grandchild'], structure=[2, 1])
|
||||
product = self._generate_product(task_template=template)
|
||||
sol = self._generate_sale_order_line(sale_order=so, product=product)
|
||||
user = self._generate_project_manager_user('Bob', 'Bob')
|
||||
so.action_confirm()
|
||||
task = sol.task_id
|
||||
task.child_ids.write({'propagate_assignment': False}) # Stop propagation after the first level
|
||||
|
||||
task.write({
|
||||
'user_ids': [Command.set([user.id])],
|
||||
'propagate_assignment': True,
|
||||
})
|
||||
|
||||
self.assertTrue(all([t.user_ids == user for t in task | task.child_ids]))
|
||||
self.assertFalse(any([t.user_ids for t in task.child_ids.child_ids]))
|
||||
|
|
@ -78,3 +78,17 @@ class TestTaskTemplate(BemadeFSMBaseTest):
|
|||
self.assertEqual(sol1.task_id.name, "Short Name 1")
|
||||
self.assertEqual(sol2.task_id.name, "Short Name 2")
|
||||
self.assertEqual(sol3.task_id.name, "Task")
|
||||
|
||||
def test_task_creation_directly_from_template(self):
|
||||
project = self.env.ref("industry_fsm.fsm_project")
|
||||
template = self._generate_task_template(names=['Task', 'Child', 'Grandchild'], structure=[2, 1])
|
||||
|
||||
task = template.create_task_from_self(project, "My new task")
|
||||
|
||||
self.assertEqual(len(task.child_ids), len(template.subtasks))
|
||||
self.assertEqual(len(task.child_ids[0].child_ids), len(template.subtasks[0].subtasks))
|
||||
self.assertEqual(len(task.child_ids[1].child_ids), len(template.subtasks[1].subtasks))
|
||||
self.assertEqual(task.name, "My new task")
|
||||
self.assertEqual(task.child_ids[0].name, template.subtasks[0].name)
|
||||
self.assertTrue(all([t.project_id == project for t in task | task._get_all_subtasks()]))
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@
|
|||
<xpath expr="//field[@name='child_ids']/tree//field[@name='name']" position="after">
|
||||
<field name="description" string="Description/Comments"/>
|
||||
</xpath>
|
||||
<field name="user_ids" position="after">
|
||||
<field name="propagate_assignment"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
<!-- Add parent_id = false to domain for My Tasks, All Tasks: To Schedule, All Tasks and To Invoice-->
|
||||
|
|
@ -168,4 +171,4 @@
|
|||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
</odoo>
|
||||
|
|
|
|||
1
bemade_fsm/wizard/__init__.py
Normal file
1
bemade_fsm/wizard/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import new_task_from_template
|
||||
51
bemade_fsm/wizard/new_task_from_template.py
Normal file
51
bemade_fsm/wizard/new_task_from_template.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class NewTaskFromTemplateWizard(models.TransientModel):
|
||||
_name = "project.task.from.template.wizard"
|
||||
_description = "Create Task from Template Wizard"
|
||||
|
||||
project_id = fields.Many2one(
|
||||
comodel_name='project.project',
|
||||
string='Project',
|
||||
help='The project the new task should be created in.',
|
||||
required=True,
|
||||
)
|
||||
|
||||
task_template_id = fields.Many2one(
|
||||
comodel_name='project.task.template',
|
||||
string='Task Template',
|
||||
help='The template to use when creating the new task.',
|
||||
required=True,
|
||||
)
|
||||
|
||||
new_task_title = fields.Char(
|
||||
help='The title (name) for the newly created task. If left blank, the name of the template will be used.',
|
||||
)
|
||||
|
||||
def default_get(self, fields_list):
|
||||
res = super().default_get(fields_list)
|
||||
active_id = self.env.context.get('active_id', False)
|
||||
active_model = self.env.context.get('active_model', False)
|
||||
if not active_model:
|
||||
params = self.env.context.get('params', False)
|
||||
active_model = params and params.get('model', False)
|
||||
if active_model == 'project.task.template' and active_id and 'task_template_id' in fields_list:
|
||||
res.update({'task_template_id': active_id})
|
||||
if active_model == 'project.task' and 'project_id' in fields_list:
|
||||
res.update({'project_id': self.env.ref('industry_fsm.fsm_project').id})
|
||||
return res
|
||||
|
||||
def action_create_task_from_template(self):
|
||||
self.ensure_one()
|
||||
task = self.task_template_id.create_task_from_self(self.project_id, self.new_task_title)
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'project.task',
|
||||
'res_id': task.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current',
|
||||
}
|
||||
|
||||
|
||||
29
bemade_fsm/wizard/new_task_from_template.xml
Normal file
29
bemade_fsm/wizard/new_task_from_template.xml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="project_task_from_template_wizard_view_form" model="ir.ui.view">
|
||||
<field name="name">project.task.from.template.wizard.view.form</field>
|
||||
<field name="model">project.task.from.template.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="task_template_id"/>
|
||||
<field name="new_task_title"/>
|
||||
<field name="project_id"/>
|
||||
</group>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button class='btn btn-primary' name="action_create_task_from_template" type="object" string="Create Task"/>
|
||||
<button class='btn btn-secondary' special="cancel" string="Cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.actions.act_window" id="action_new_task_from_template">
|
||||
<field name="name">New Task from Template</field>
|
||||
<field name="res_model">project.task.from.template.wizard</field>
|
||||
<field name="binding_model_id" ref="bemade_fsm.model_project_task_template"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</odoo>
|
||||
0
bemade_full_formview_from_modal/__init__.py
Normal file
0
bemade_full_formview_from_modal/__init__.py
Normal file
37
bemade_full_formview_from_modal/__manifest__.py
Normal file
37
bemade_full_formview_from_modal/__manifest__.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#
|
||||
# Bemade Inc.
|
||||
#
|
||||
# Copyright (C) 2023-June Bemade Inc. (<https://www.bemade.org>).
|
||||
# Author: Marc Durepos (Contact : mdurepos@durpro.com)
|
||||
#
|
||||
# This program is under the terms of the Odoo Proprietary License v1.0 (OPL-1)
|
||||
# It is forbidden to publish, distribute, sublicense, or sell copies of the Software
|
||||
# or modified copies of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
{
|
||||
'name': 'Full Form from Dialog',
|
||||
'version': '16.0.1.0.0',
|
||||
'summary': 'Allows opening the full form view from the dialog (modal) view.',
|
||||
'description': 'Adds a button to open the full form view when viewing the form view for a record in a dialog.',
|
||||
'category': 'Technical',
|
||||
'author': 'Bemade Inc.',
|
||||
'website': 'http://www.bemade.org',
|
||||
'license': 'OPL-1',
|
||||
'depends': [],
|
||||
'data': [],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'bemade_full_formview_from_modal/static/src/**/*',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'auto_install': False
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { FormController } from "@web/views/form/form_controller";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
patch(FormController.prototype, "bemade_full_formview_from_modal.FormController", {
|
||||
setup () {
|
||||
this._super();
|
||||
this.action = useService("action")
|
||||
},
|
||||
onOpenButtonClicked: function () {
|
||||
this.action.doAction({
|
||||
type: "ir.actions.act_window",
|
||||
res_model: this.props.resModel,
|
||||
res_id: this.props.resId,
|
||||
views: [[false, "form"]],
|
||||
target: "current",
|
||||
context: this.props.context,
|
||||
})
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-inherit="web.FormViewDialog.ToMany.buttons" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//button[hasclass('o_form_button_save_new')]" position="after">
|
||||
<button class="btn btn-secondary o_form_button_open" t-on-click="_onOpen">Open</button>
|
||||
</xpath>
|
||||
</t>
|
||||
<t t-inherit="web.FormViewDialog.ToOne.buttons" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//button[hasclass('o_form_button_save')]" position="after">
|
||||
<button class="btn btn-secondary o_form_button_open" t-on-click="onOpenButtonClicked">Open</button>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
'name': 'Packing wizard',
|
||||
'version': '15.0.1.0.0',
|
||||
'version': '16.0.1.0.0',
|
||||
'category': 'Extra Tools',
|
||||
'summary': 'Allow automated packing type creation',
|
||||
'description': """
|
||||
This module allows users simply enter all the package dimensions on every packing and it will create the packing
|
||||
type automatically if it not exists, use it the create the package with the weight and related package type.
|
||||
|
||||
|
||||
You need to activate the auto create package feature on the delivery carrier to use this feature.
|
||||
""",
|
||||
'depends': [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
'name': 'Automated Partner Association by Email Domain',
|
||||
'version': '15.0.0.0.0',
|
||||
'version': '16.0.0.0.0',
|
||||
'category': 'Extra Tools',
|
||||
'summary': 'Automatically associates partners with companies using matching email domains',
|
||||
'description': """
|
||||
|
|
|
|||
0
bemade_payslip_done_as_not_paid/__init__.py
Normal file
0
bemade_payslip_done_as_not_paid/__init__.py
Normal file
31
bemade_payslip_done_as_not_paid/__manifest__.py
Normal file
31
bemade_payslip_done_as_not_paid/__manifest__.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#
|
||||
# Bemade Inc.
|
||||
#
|
||||
# Copyright (C) 2023-June Bemade Inc. (<https://www.bemade.org>).
|
||||
# Author: Marc Durepos (Contact : marc@bemade.org)
|
||||
#
|
||||
# This program is under the terms of the Odoo Proprietary License v1.0 (OPL-1)
|
||||
# It is forbidden to publish, distribute, sublicense, or sell copies of the Software
|
||||
# or modified copies of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
{
|
||||
'name': 'Payslip Done as To Pay',
|
||||
'version': '16.0.1.0.0',
|
||||
'summary': 'Payslips in "Done" state appear as "To Pay"',
|
||||
'category': 'Human Resources/Payroll',
|
||||
'author': 'Bemade Inc.',
|
||||
'website': 'http://www.bemade.org',
|
||||
'license': 'OPL-1',
|
||||
'depends': ['hr_payroll'],
|
||||
'data': ['views/hr_payslip_views.xml'],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="hr_payroll.hr_payslip_action_view_to_pay" model="ir.actions.act_window">
|
||||
<field name="domain">[('state', 'in', ['draft', 'verify', 'done'])]</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
#
|
||||
{
|
||||
'name': 'Sports Clinic Management',
|
||||
'version': '16.0.1.5.0',
|
||||
'version': '16.0.1.5.2',
|
||||
'summary': 'Manage the patients of a sports medicine clinic.',
|
||||
'description': """
|
||||
Adds the notion of sports teams, players (patients), coaches and treatment
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class Patient(models.Model):
|
|||
"return to match play.")
|
||||
is_injured = fields.Boolean(compute="_compute_is_injured")
|
||||
stage = fields.Selection(
|
||||
selection=[('no_play', 'Injured'), ('practice_ok', 'Cleared for Practice'), ('healthy', 'Cleared to Play')],
|
||||
selection=[('no_play', 'Injured'), ('practice_ok', 'Practice OK'), ('healthy', 'Play OK')],
|
||||
compute='_compute_stage')
|
||||
last_consultation_date = fields.Date()
|
||||
active_injury_count = fields.Integer(compute='_compute_active_injury_count')
|
||||
|
|
|
|||
|
|
@ -165,18 +165,18 @@
|
|||
<t t-call="portal.portal_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="20%">Active Injury</th>
|
||||
<th width="80%">Notes</th>
|
||||
<th width="30%">Active Injury</th>
|
||||
<th width="70%">Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="injuries" t-as="injury">
|
||||
<tr>
|
||||
<td>
|
||||
<span t-field="injury.diagnosis"/>
|
||||
<span t-field="injury.diagnosis" class="text-wrap"/>
|
||||
</td>
|
||||
<td>
|
||||
<span t-field="injury.external_notes"/>
|
||||
<span t-raw="injury.external_notes" class="text-wrap"/>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
|
|
|
|||
|
|
@ -62,6 +62,49 @@
|
|||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="sports_patient_view_kanban" model="ir.ui.view">
|
||||
<field name="name">sports.patient.view.kanban</field>
|
||||
<field name="model">sports.patient</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban>
|
||||
<field name="first_name"/>
|
||||
<field name="last_name"/>
|
||||
<field name="date_of_birth"/>
|
||||
<field name="age"/>
|
||||
<field name="team_ids" widget="many2many_tags"/>
|
||||
<field name="match_status" optional="hide"/>
|
||||
<field name="practice_status" optional="hide"/>
|
||||
<field name="stage" optional="show"/>
|
||||
<field name="predicted_return_date" widget="date"/>
|
||||
<field name="return_date" widget="date"/>
|
||||
<field name="activity_ids" widget="list_activity"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<t t-set="stage" t-value="record.stage.raw_value"/>
|
||||
<div t-attf-class="oe_kanban_global_click
|
||||
{{stage === 'no_play' ? 'text-danger' : stage === 'practice_ok' ? 'text-warning' : ''}}">
|
||||
|
||||
<div>
|
||||
<div class="o_kanban_record_top mb16">
|
||||
<div class="o_kanban_record_headings mt-4">
|
||||
<field name="name"/>
|
||||
</div>
|
||||
<div class="oe_kanban_card_header_stage">
|
||||
Status: <field name="stage"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_kanban_record_body">
|
||||
DOB: <field name="date_of_birth" widget="date"/>
|
||||
<field name="team_ids" widget="many2many_tags"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sports_patient_view_form" model="ir.ui.view">
|
||||
<field name="name">sports.patient.view.form</field>
|
||||
<field name="model">sports.patient</field>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
#
|
||||
{
|
||||
'name': 'Stock Quant Valuation',
|
||||
'version': '15.0.1.0.0',
|
||||
'version': '16.0.1.0.0',
|
||||
'summary': 'Adds valuation to stock quant for better inventory adjustment management',
|
||||
'description': '',
|
||||
'category': 'Inventory',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
'name': 'Temporary Change for Image Full Size',
|
||||
'version': '15.0.0.0.0',
|
||||
'version': '16.0.0.0.0',
|
||||
'category': 'Extra Tools',
|
||||
'summary': 'Temporary Change for Image Full Size',
|
||||
'description': """
|
||||
|
|
|
|||
Loading…
Reference in a new issue