@@ -1,3 +1,4 @@
|
||||
* Add number to the work efforts
|
||||
* Add support for Python 3.14
|
||||
* Remove support for Python 3.9
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ Work Effort
|
||||
The *Work Effort* concept is used to represent work that must be done for
|
||||
projects, or parts of projects.
|
||||
|
||||
Each *Work Effort* is described by a name and a type, and there is space
|
||||
for additional comments to be added when required.
|
||||
Each *Work Effort* is described by a name, a number and a type, and there is
|
||||
space for additional comments to be added when required.
|
||||
|
||||
The estimated time and effort needed for the work is recorded, and it is also
|
||||
possible to track the actual time and effort required.
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data grouped="1">
|
||||
<record model="ir.message" id="msg_work_number_unique">
|
||||
<field name="text">The number on work must be unique.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_work_invalid_progress_status">
|
||||
<field name="text">To set work "%(work)s" in "%(status)s" status, you must increase its progress up to at least %(progress)s.</field>
|
||||
</record>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
================
|
||||
Project Scenario
|
||||
================
|
||||
|
||||
Imports::
|
||||
|
||||
>>> from proteus import Model
|
||||
>>> from trytond.modules.company.tests.tools import create_company
|
||||
>>> from trytond.tests.tools import activate_modules
|
||||
|
||||
Activate project::
|
||||
|
||||
>>> config = activate_modules('project', create_company)
|
||||
|
||||
>>> Work = Model.get('project.work')
|
||||
|
||||
Create a project with a task::
|
||||
|
||||
>>> project = Work(type='project', name="Project")
|
||||
>>> task = project.children.new(type='task', name="Task")
|
||||
>>> project.save()
|
||||
>>> task, = project.children
|
||||
|
||||
Check works have numbers::
|
||||
|
||||
>>> bool(project.number)
|
||||
True
|
||||
>>> bool(task.number)
|
||||
True
|
||||
@@ -14,6 +14,8 @@ xml:
|
||||
|
||||
[register]
|
||||
model:
|
||||
work.Configuration
|
||||
work.ConfigurationSequence
|
||||
# Before Work because status default value is read from WorkStatus
|
||||
work.WorkStatus
|
||||
work.Work
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<form>
|
||||
<label name="work_sequence"/>
|
||||
<field name="work_sequence"/>
|
||||
</form>
|
||||
@@ -3,9 +3,12 @@
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<form col="6">
|
||||
<label name="name"/>
|
||||
<field name="name" colspan="3"/>
|
||||
<field name="name"/>
|
||||
<label name="parent"/>
|
||||
<field name="parent"/>
|
||||
<label name="number"/>
|
||||
<field name="number"/>
|
||||
|
||||
<label name="type"/>
|
||||
<field name="type"/>
|
||||
<label name="company"/>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tree sequence="sequence">
|
||||
<field name="company" expand="1" optional="1"/>
|
||||
<field name="number"/>
|
||||
<field name="rec_name" expand="1"/>
|
||||
<field name="timesheet_duration" optional="0"/>
|
||||
<field name="total_effort" optional="0"/>
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tree sequence="sequence">
|
||||
<field name="name" expand="1"/>
|
||||
<field name="name" expand="1">
|
||||
<prefix name="number"/>
|
||||
</field>
|
||||
<field name="timesheet_duration" optional="0"/>
|
||||
<field name="total_effort" optional="0"/>
|
||||
<field name="type" optional="1"/>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tree>
|
||||
<field name="company" expand="1" optional="1"/>
|
||||
<field name="number"/>
|
||||
<field name="rec_name" expand="1"/>
|
||||
<field name="type" optional="1"/>
|
||||
<field name="status" optional="0"/>
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tree sequence="sequence">
|
||||
<field name="name" expand="1"/>
|
||||
<field name="name" expand="1">
|
||||
<prefix name="number"/>
|
||||
</field>
|
||||
<field name="company" expand="1" optional="1"/>
|
||||
<field name="timesheet_duration" optional="0"/>
|
||||
<field name="total_effort" optional="0"/>
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tree sequence="sequence">
|
||||
<field name="name" expand="1"/>
|
||||
<field name="name" expand="1">
|
||||
<prefix name="number"/>
|
||||
</field>
|
||||
<field name="company" expand="1" optional="1"/>
|
||||
<field name="type" optional="1"/>
|
||||
<field name="status" optional="0"/>
|
||||
|
||||
+90
-3
@@ -4,17 +4,58 @@
|
||||
import datetime
|
||||
from collections import defaultdict
|
||||
|
||||
from sql import Null
|
||||
from sql.functions import CharLength
|
||||
|
||||
from trytond.cache import Cache
|
||||
from trytond.i18n import gettext
|
||||
from trytond.model import (
|
||||
ChatMixin, DeactivableMixin, Index, ModelSQL, ModelView, fields,
|
||||
sequence_ordered, sum_tree, tree)
|
||||
ChatMixin, DeactivableMixin, Index, ModelSingleton, ModelSQL, ModelView,
|
||||
Unique, ValueMixin, fields, sequence_ordered, sum_tree, tree)
|
||||
from trytond.modules.company.model import CompanyMultiValueMixin
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import Bool, Eval, If, PYSONEncoder, TimeDelta
|
||||
from trytond.pyson import Bool, Eval, Id, If, PYSONEncoder, TimeDelta
|
||||
from trytond.tools import is_full_text, lstrip_wildcard
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
from .exceptions import WorkProgressValidationError
|
||||
|
||||
_work_sequence = fields.Many2One(
|
||||
'ir.sequence', "Work Effort Sequence", required=True,
|
||||
domain=[
|
||||
('sequence_type', '=', Id('project', 'sequence_type_work')),
|
||||
],
|
||||
help="Used to generate the work number.")
|
||||
|
||||
|
||||
class Configuration(
|
||||
ModelSingleton, ModelSQL, ModelView, CompanyMultiValueMixin):
|
||||
__name__ = 'project.configuration'
|
||||
|
||||
work_sequence = fields.MultiValue(_work_sequence)
|
||||
|
||||
@classmethod
|
||||
def multivalue_model(cls, field):
|
||||
pool = Pool()
|
||||
if field == 'work_sequence':
|
||||
return pool.get('project.configuration.sequence')
|
||||
return super().multivalue_model(field)
|
||||
|
||||
@classmethod
|
||||
def default_work_sequence(cls, **pattern):
|
||||
pool = Pool()
|
||||
ModelData = pool.get('ir.model.data')
|
||||
try:
|
||||
return ModelData.get_id('project', 'sequence_work')
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
class ConfigurationSequence(ModelSQL, ValueMixin):
|
||||
__name__ = 'project.configuration.sequence'
|
||||
|
||||
work_sequence = _work_sequence
|
||||
|
||||
|
||||
class WorkStatus(DeactivableMixin, sequence_ordered(), ModelSQL, ModelView):
|
||||
__name__ = 'project.work.status'
|
||||
@@ -113,6 +154,7 @@ class Work(
|
||||
],
|
||||
"Type", required=True)
|
||||
company = fields.Many2One('company.company', "Company", required=True)
|
||||
number = fields.Char("Number", readonly=True)
|
||||
party = fields.Many2One('party.party', 'Party',
|
||||
states={
|
||||
'invisible': Eval('type') != 'project',
|
||||
@@ -191,13 +233,25 @@ class Work(
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
cls.number.search_unaccented = False
|
||||
cls.path.search_unaccented = False
|
||||
super().__setup__()
|
||||
t = cls.__table__()
|
||||
cls._sql_constraints += [
|
||||
('number_unique', Unique(t, t.number),
|
||||
'project.msg_work_number_unique'),
|
||||
]
|
||||
cls._sql_indexes.update({
|
||||
Index(t, (t.number, Index.Equality(cardinality='high'))),
|
||||
Index(t, (t.number, Index.Similarity(cardinality='high'))),
|
||||
Index(t, (t.path, Index.Similarity(begin=True))),
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def order_number(cls, tables):
|
||||
table, _ = tables[None]
|
||||
return [table.number != Null, CharLength(table.number), table.number]
|
||||
|
||||
@staticmethod
|
||||
def default_type():
|
||||
return 'task'
|
||||
@@ -403,6 +457,38 @@ class Work(
|
||||
language = party.lang.code if party and party.lang else None
|
||||
return language
|
||||
|
||||
@classmethod
|
||||
def search_rec_name(cls, name, clause):
|
||||
_, operator, value = clause
|
||||
if operator.startswith('!') or operator.startswith('not '):
|
||||
bool_op = 'AND'
|
||||
else:
|
||||
bool_op = 'OR'
|
||||
code_value = value
|
||||
if operator.endswith('like') and is_full_text(value):
|
||||
code_value = lstrip_wildcard(value)
|
||||
domain = [bool_op,
|
||||
('number', operator, code_value),
|
||||
('name', operator, value),
|
||||
]
|
||||
return domain
|
||||
|
||||
@classmethod
|
||||
def _number_sequence(cls, **pattern):
|
||||
pool = Pool()
|
||||
Configuration = pool.get('project.configuration')
|
||||
config = Configuration(1)
|
||||
return config.get_multivalue('work_sequence', **pattern)
|
||||
|
||||
@classmethod
|
||||
def preprocess_values(cls, mode, values):
|
||||
values = super().preprocess_values(mode, values)
|
||||
if mode == 'create':
|
||||
if not values.get('number'):
|
||||
if sequence := cls._number_sequence():
|
||||
values['number'] = sequence.get()
|
||||
return values
|
||||
|
||||
@classmethod
|
||||
def copy(cls, project_works, default=None):
|
||||
pool = Pool()
|
||||
@@ -411,6 +497,7 @@ class Work(
|
||||
default = {}
|
||||
else:
|
||||
default = default.copy()
|
||||
default.setdefault('number')
|
||||
default.setdefault('progress', None)
|
||||
default.setdefault(
|
||||
'status', lambda data: WorkStatus.get_default_status(data['type']))
|
||||
|
||||
@@ -3,6 +3,56 @@
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="project_configuration_view_form">
|
||||
<field name="model">project.configuration</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">project_configuration_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_project_configuration">
|
||||
<field name="name">Configuration</field>
|
||||
<field name="res_model">project.configuration</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_project_configuration_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="project_configuration_view_form"/>
|
||||
<field name="act_window" ref="act_project_configuration"/>
|
||||
</record>
|
||||
<menuitem
|
||||
parent="menu_configuration"
|
||||
action="act_project_configuration"
|
||||
sequence="10"
|
||||
id="menu_project_configuration"
|
||||
icon="tryton-list"/>
|
||||
|
||||
<record model="ir.model.access" id="access_project_configuration">
|
||||
<field name="model">project.configuration</field>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
<record model="ir.model.access" id="access_project_configuration_party_admin">
|
||||
<field name="model">project.configuration</field>
|
||||
<field name="group" ref="group_project_admin"/>
|
||||
<field name="perm_read" eval="True"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_delete" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.sequence.type" id="sequence_type_work">
|
||||
<field name="name">Project Work Effort</field>
|
||||
</record>
|
||||
<record model="ir.sequence.type-res.group" id="sequence_type_work_group_admin">
|
||||
<field name="sequence_type" ref="sequence_type_work"/>
|
||||
<field name="group" ref="res.group_admin"/>
|
||||
</record>
|
||||
<record model="ir.sequence.type-res.group" id="sequence_type_work_group_project_admin">
|
||||
<field name="sequence_type" ref="sequence_type_work"/>
|
||||
<field name="group" ref="group_project_admin"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="work_status_view_list">
|
||||
<field name="model">project.work.status</field>
|
||||
<field name="type">tree</field>
|
||||
@@ -261,6 +311,15 @@ this repository contains the full copyright notices and license terms. -->
|
||||
|
||||
</data>
|
||||
<data noupdate="1">
|
||||
<record model="ir.sequence" id="sequence_work">
|
||||
<field name="name">Work Effort</field>
|
||||
<field name="sequence_type" ref="sequence_type_work"/>
|
||||
</record>
|
||||
|
||||
<record model="project.configuration.sequence" id="project_configuration_sequence">
|
||||
<field name="work_sequence" ref="sequence_work"/>
|
||||
</record>
|
||||
|
||||
<record model="project.work.status" id="work_open_status">
|
||||
<field name="name">Open</field>
|
||||
<field name="default" eval="True"/>
|
||||
|
||||
Reference in New Issue
Block a user