Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • lefilament/cgscop/cgscop_timesheet
  • hsilvant/cgscop_timesheet
2 results
Show changes
Commits on Source (72)
Showing with 1065 additions and 854 deletions
# Configuration for known file extensions
[*.{css,js,json,less,md,py,rst,sass,scss,xml,yaml,yml}]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.{json,yml,yaml,rst,md}]
indent_size = 2
# Do not configure editor for libs and autogenerated content
[{*/static/{lib,src/lib}/**,*/static/description/index.html,*/readme/../README.rst}]
charset = unset
end_of_line = unset
indent_size = unset
indent_style = unset
insert_final_newline = false
trim_trailing_whitespace = false
env:
browser: true
es6: true
# See https://github.com/OCA/odoo-community.org/issues/37#issuecomment-470686449
parserOptions:
ecmaVersion: 2017
overrides:
- files:
- "**/*.esm.js"
parserOptions:
sourceType: module
# Globals available in Odoo that shouldn't produce errorings
globals:
_: readonly
$: readonly
fuzzy: readonly
jQuery: readonly
moment: readonly
odoo: readonly
openerp: readonly
owl: readonly
# Styling is handled by Prettier, so we only need to enable AST rules;
# see https://github.com/OCA/maintainer-quality-tools/pull/618#issuecomment-558576890
rules:
accessor-pairs: warn
array-callback-return: warn
callback-return: warn
capitalized-comments:
- warn
- always
- ignoreConsecutiveComments: true
ignoreInlineComments: true
complexity:
- warn
- 15
constructor-super: warn
dot-notation: warn
eqeqeq: warn
global-require: warn
handle-callback-err: warn
id-blacklist: warn
id-match: warn
init-declarations: error
max-depth: warn
max-nested-callbacks: warn
max-statements-per-line: warn
no-alert: warn
no-array-constructor: warn
no-caller: warn
no-case-declarations: warn
no-class-assign: warn
no-cond-assign: error
no-const-assign: error
no-constant-condition: warn
no-control-regex: warn
no-debugger: error
no-delete-var: warn
no-div-regex: warn
no-dupe-args: error
no-dupe-class-members: error
no-dupe-keys: error
no-duplicate-case: error
no-duplicate-imports: error
no-else-return: warn
no-empty-character-class: warn
no-empty-function: error
no-empty-pattern: error
no-empty: warn
no-eq-null: error
no-eval: error
no-ex-assign: error
no-extend-native: warn
no-extra-bind: warn
no-extra-boolean-cast: warn
no-extra-label: warn
no-fallthrough: warn
no-func-assign: error
no-global-assign: error
no-implicit-coercion:
- warn
- allow: ["~"]
no-implicit-globals: warn
no-implied-eval: warn
no-inline-comments: warn
no-inner-declarations: warn
no-invalid-regexp: warn
no-irregular-whitespace: warn
no-iterator: warn
no-label-var: warn
no-labels: warn
no-lone-blocks: warn
no-lonely-if: error
no-mixed-requires: error
no-multi-str: warn
no-native-reassign: error
no-negated-condition: warn
no-negated-in-lhs: error
no-new-func: warn
no-new-object: warn
no-new-require: warn
no-new-symbol: warn
no-new-wrappers: warn
no-new: warn
no-obj-calls: warn
no-octal-escape: warn
no-octal: warn
no-param-reassign: warn
no-path-concat: warn
no-process-env: warn
no-process-exit: warn
no-proto: warn
no-prototype-builtins: warn
no-redeclare: warn
no-regex-spaces: warn
no-restricted-globals: warn
no-restricted-imports: warn
no-restricted-modules: warn
no-restricted-syntax: warn
no-return-assign: error
no-script-url: warn
no-self-assign: warn
no-self-compare: warn
no-sequences: warn
no-shadow-restricted-names: warn
no-shadow: warn
no-sparse-arrays: warn
no-sync: warn
no-this-before-super: warn
no-throw-literal: warn
no-undef-init: warn
no-undef: error
no-unmodified-loop-condition: warn
no-unneeded-ternary: error
no-unreachable: error
no-unsafe-finally: error
no-unused-expressions: error
no-unused-labels: error
no-unused-vars: error
no-use-before-define: error
no-useless-call: warn
no-useless-computed-key: warn
no-useless-concat: warn
no-useless-constructor: warn
no-useless-escape: warn
no-useless-rename: warn
no-void: warn
no-with: warn
operator-assignment: [error, always]
prefer-const: warn
radix: warn
require-yield: warn
sort-imports: warn
spaced-comment: [error, always]
strict: [error, function]
use-isnan: error
valid-jsdoc:
- warn
- prefer:
arg: param
argument: param
augments: extends
constructor: class
exception: throws
func: function
method: function
prop: property
return: returns
virtual: abstract
yield: yields
preferType:
array: Array
bool: Boolean
boolean: Boolean
number: Number
object: Object
str: String
string: String
requireParamDescription: false
requireReturn: false
requireReturnDescription: false
requireReturnType: false
valid-typeof: warn
yoda: warn
[flake8]
max-line-length = 88
max-complexity = 16
# B = bugbear
# B9 = bugbear opinionated (incl line length)
select = C,E,F,W,B,B9
# E203: whitespace before ':' (black behaviour)
# E501: flake8 line length (covered by bugbear B950)
# W503: line break before binary operator (black behaviour)
ignore = E203,E501,W503
per-file-ignores=
__init__.py:F401
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
/.venv
/.pytest_cache
# C extensions
*.so
# Distribution / packaging
.Python
env/
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
*.eggs
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
# Pycharm
.idea
# Eclipse
.settings
# Visual Studio cache/options directory
.vs/
.vscode
# OSX Files
.DS_Store
# Django stuff:
*.log
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Rope
.ropeproject
# Sphinx documentation
docs/_build/
# Backup files
*~
*.swp
# OCA rules
!static/lib/
[settings]
; see https://github.com/psf/black
multi_line_output=3
include_trailing_comma=True
force_grid_wrap=0
combine_as_imports=True
use_parentheses=True
line_length=88
known_odoo=odoo
known_odoo_addons=odoo.addons
sections=FUTURE,STDLIB,THIRDPARTY,ODOO,ODOO_ADDONS,FIRSTPARTY,LOCALFOLDER
default_section=THIRDPARTY
ensure_newline_before_comments = True
exclude: |
(?x)
# NOT INSTALLABLE ADDONS
# END NOT INSTALLABLE ADDONS
# Files and folders generated by bots, to avoid loops
^setup/|/static/description/index\.html$|
# We don't want to mess with tool-generated files
.svg$|/tests/([^/]+/)?cassettes/|^.copier-answers.yml$|^.github/|
# Maybe reactivate this when all README files include prettier ignore tags?
^README\.md$|
# Library files can have extraneous formatting (even minimized)
/static/(src/)?lib/|
# Repos using Sphinx to generate docs don't need prettying
^docs/_templates/.*\.html$|
# You don't usually want a bot to modify your legal texts
(LICENSE.*|COPYING.*)
default_language_version:
python: python3
node: "14.13.0"
repos:
- repo: local
hooks:
# These files are most likely copier diff rejection junks; if found,
# review them manually, fix the problem (if needed) and remove them
- id: forbidden-files
name: forbidden files
entry: found forbidden files; remove them
language: fail
files: "\\.rej$"
- id: en-po-files
name: en.po files cannot exist
entry: found a en.po file
language: fail
files: '[a-zA-Z0-9_]*/i18n/en\.po$'
- repo: https://github.com/oca/maintainer-tools
rev: ab1d7f6
hooks:
# update the NOT INSTALLABLE ADDONS section above
- id: oca-update-pre-commit-excluded-addons
- id: oca-fix-manifest-website
args: ["https://le-filament.com"]
- repo: https://github.com/myint/autoflake
rev: v1.4
hooks:
- id: autoflake
args:
- --expand-star-imports
- --ignore-init-module-imports
- --in-place
- --remove-all-unused-imports
- --remove-duplicate-keys
- --remove-unused-variables
- repo: https://github.com/psf/black
rev: 22.3.0
hooks:
- id: black
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.1.2
hooks:
- id: prettier
name: prettier (with plugin-xml)
additional_dependencies:
- "prettier@2.1.2"
- "@prettier/plugin-xml@0.12.0"
args:
- --plugin=@prettier/plugin-xml
files: \.(css|htm|html|js|json|jsx|less|md|scss|toml|ts|xml|yaml|yml)$
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v7.8.1
hooks:
- id: eslint
verbose: true
args:
- --color
- --fix
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
# exclude autogenerated files
exclude: /README\.rst$|\.pot?$
- id: end-of-file-fixer
# exclude autogenerated files
exclude: /README\.rst$|\.pot?$
- id: debug-statements
- id: fix-encoding-pragma
args: ["--remove"]
- id: check-case-conflict
- id: check-docstring-first
- id: check-executables-have-shebangs
- id: check-merge-conflict
# exclude files where underlines are not distinguishable from merge conflicts
exclude: /README\.rst$|^docs/.*\.rst$
- id: check-symlinks
- id: check-xml
- id: mixed-line-ending
args: ["--fix=lf"]
- repo: https://github.com/asottile/pyupgrade
rev: v2.7.2
hooks:
- id: pyupgrade
args: ["--keep-percent-format"]
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
name: isort except __init__.py
args:
- --settings=.
exclude: /__init__\.py$
- repo: https://github.com/PyCQA/flake8
rev: 3.8.3
hooks:
- id: flake8
name: flake8
additional_dependencies: ["flake8-bugbear==20.1.4"]
- repo: https://github.com/OCA/pylint-odoo
rev: 7.0.2
hooks:
- id: pylint_odoo
name: pylint with optional checks
args:
- --rcfile=.pylintrc
- --exit-zero
verbose: true
- id: pylint_odoo
args:
- --rcfile=.pylintrc-mandatory
# Defaults for all prettier-supported languages.
# Prettier will complete this with settings from .editorconfig file.
bracketSpacing: false
printWidth: 88
proseWrap: always
semi: true
trailingComma: "es5"
xmlWhitespaceSensitivity: "strict"
[MASTER]
load-plugins=pylint_odoo
score=n
[ODOOLINT]
readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst"
manifest_required_authors=Le Filament
manifest_required_keys=license
manifest_deprecated_keys=description,active
license_allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3
valid_odoo_versions=14.0
[MESSAGES CONTROL]
disable=all
# This .pylintrc contains optional AND mandatory checks and is meant to be
# loaded in an IDE to have it check everything, in the hope this will make
# optional checks more visible to contributors who otherwise never look at a
# green travis to see optional checks that failed.
# .pylintrc-mandatory containing only mandatory checks is used the pre-commit
# config as a blocking check.
enable=anomalous-backslash-in-string,
api-one-deprecated,
api-one-multi-together,
assignment-from-none,
attribute-deprecated,
class-camelcase,
dangerous-default-value,
dangerous-view-replace-wo-priority,
development-status-allowed,
duplicate-id-csv,
duplicate-key,
duplicate-xml-fields,
duplicate-xml-record-id,
eval-referenced,
eval-used,
incoherent-interpreter-exec-perm,
license-allowed,
manifest-author-string,
manifest-deprecated-key,
manifest-required-author,
manifest-required-key,
manifest-version-format,
method-compute,
method-inverse,
method-required-super,
method-search,
openerp-exception-warning,
pointless-statement,
pointless-string-statement,
print-used,
redundant-keyword-arg,
redundant-modulename-xml,
reimported,
relative-import,
return-in-init,
rst-syntax-error,
sql-injection,
too-few-format-args,
translation-field,
translation-required,
unreachable,
use-vim-comment,
wrong-tabs-instead-of-spaces,
xml-syntax-error,
# messages that do not cause the lint step to fail
consider-merging-classes-inherited,
create-user-wo-reset-password,
dangerous-filter-wo-user,
deprecated-module,
file-not-used,
invalid-commit,
missing-manifest-dependency,
missing-newline-extrafiles,
no-utf8-coding-comment,
odoo-addons-relative-import,
old-api7-method-defined,
redefined-builtin,
too-complex,
unnecessary-utf8-coding-comment
[REPORTS]
msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
output-format=colorized
reports=no
[MASTER]
load-plugins=pylint_odoo
score=n
[ODOOLINT]
readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst"
manifest_required_authors=Le Filament
manifest_required_keys=license
manifest_deprecated_keys=description,active
license_allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3
valid_odoo_versions=14.0
[MESSAGES CONTROL]
disable=all
enable=anomalous-backslash-in-string,
api-one-deprecated,
api-one-multi-together,
assignment-from-none,
attribute-deprecated,
class-camelcase,
dangerous-default-value,
dangerous-view-replace-wo-priority,
development-status-allowed,
duplicate-id-csv,
duplicate-key,
duplicate-xml-fields,
duplicate-xml-record-id,
eval-referenced,
eval-used,
incoherent-interpreter-exec-perm,
license-allowed,
manifest-author-string,
manifest-deprecated-key,
manifest-required-author,
manifest-required-key,
manifest-version-format,
method-compute,
method-inverse,
method-required-super,
method-search,
openerp-exception-warning,
pointless-statement,
pointless-string-statement,
print-used,
redundant-keyword-arg,
redundant-modulename-xml,
reimported,
relative-import,
return-in-init,
rst-syntax-error,
sql-injection,
too-few-format-args,
translation-field,
translation-required,
unreachable,
use-vim-comment,
wrong-tabs-instead-of-spaces,
xml-syntax-error
[REPORTS]
msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
output-format=colorized
reports=no
......@@ -14,7 +14,6 @@ Il ajoute les éléments suivants :
* menu pour configurer les projets (cf Codes Activité UR) et les vues associées
* table de configuration des activités CG
* table de configuration des dispositifs
*
Credits
......
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
# © 2019 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from . import models
from . import models, wizard
# © 2019 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "CG SCOP - Feuille de temps",
"summary": "CG SCOP - Feuille de temps",
"version": "12.0.1.0.1",
"development_status": "Beta",
"version": "14.0.1.0.0",
"author": "Le Filament",
"maintainers": ["remi-filament"],
"license": "AGPL-3",
"application": False,
"installable": True,
"depends": [
"hr_timesheet",
"analytic",
"account",
"project",
"cgscop_partner",
"cgscop_fullcalendar",
],
"data": [
"security/ir.model.access.csv",
"security/security_rules.xml",
"wizard/cgscop_timesheet_print_wizard.xml",
"views/assets.xml",
"views/cgscop_timesheet_code.xml",
"views/cgscop_timesheet_sheet.xml",
"views/hr_timesheet.xml",
"views/hr_timesheet_cgscop.xml",
"views/res_company.xml",
"views/res_partner.xml",
"views/ur_financial_system.xml",
"views/ur_month_timesheet.xml",
"views/ur_regional_convention.xml",
"report/report_hr_timesheet.xml",
"report/report_hr_timesheet_act.xml",
"datas/cgscop_timesheet_code_data.xml",
]
],
"qweb": [
"static/src/xml/*.xml",
],
"installable": True,
"auto_install": False,
}
<?xml version="1.0"?>
<?xml version="1.0" ?>
<odoo>
<data noupdate="1">
<record id="code_cg_activite_0" model="cgscop.timesheet.code">
......@@ -18,7 +18,9 @@
</record>
<record id="code_cg_activite_4" model="cgscop.timesheet.code">
<field name="name">Accompagnement - Suivi syndical Scop en difficultés</field>
<field
name="name"
>Accompagnement - Suivi syndical Scop en difficultés</field>
</record>
<record id="code_cg_activite_5" model="cgscop.timesheet.code">
......@@ -30,7 +32,9 @@
</record>
<record id="code_cg_activite_7" model="cgscop.timesheet.code">
<field name="name">Accompagnement - Animation coopérative et intercoopérative (réseau)</field>
<field
name="name"
>Accompagnement - Animation coopérative et intercoopérative (réseau)</field>
</record>
<record id="code_cg_activite_8" model="cgscop.timesheet.code">
......@@ -46,7 +50,9 @@
</record>
<record id="code_cg_activite_11" model="cgscop.timesheet.code">
<field name="name">Formation des coopératives - Préparation et gestion administrative</field>
<field
name="name"
>Formation des coopératives - Préparation et gestion administrative</field>
</record>
<record id="code_cg_activite_12" model="cgscop.timesheet.code">
......@@ -58,7 +64,9 @@
</record>
<record id="code_cg_activite_14" model="cgscop.timesheet.code">
<field name="name">Vie du mouvement - Réunions équipe technique (régional et national)</field>
<field
name="name"
>Vie du mouvement - Réunions équipe technique (régional et national)</field>
</record>
<record id="code_cg_activite_15" model="cgscop.timesheet.code">
......
This diff is collapsed.
......@@ -5,7 +5,8 @@ from . import cgscop_timesheet_code
from . import cgscop_timesheet_sheet
from . import hr_timesheet
from . import project
from . import res_company
from . import res_partner
from . import ur_financial_system
from . import ur_month_timesheet
from . import ur_regional_convention
# © 2019 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields
from odoo import fields, models
class ScopTimesheetCode(models.Model):
_name = "cgscop.timesheet.code"
_description = "Code activité National"
_order = 'name'
_order = "name"
name = fields.Char('Nom')
name = fields.Char("Nom")
domain = fields.Selection(
[
("A", "Accompagnement"),
("D", "Développement"),
("F", "Formation"),
("R", "Révision"),
("Z", "Autre"),
],
string="Domaine",
)
# © 2020 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
class ScopHrTimesheetSheet(models.Model):
_name = "cgscop.timesheet.sheet"
_inherit = ['mail.thread', 'mail.activity.mixin']
_inherit = ["mail.thread", "mail.activity.mixin"]
_description = "Timesheet Report"
_order = "create_date desc, validation_date desc, id desc"
def _default_ur(self):
return self.env['res.company']._ur_default_get()
return self.env["res.company"]._ur_default_get()
name = fields.Char('Nom', required=True)
name = fields.Char("Nom", required=True)
timesheet_line_ids = fields.One2many(
comodel_name='account.analytic.line',
inverse_name='sheet_id',
string='Lignes de temps',
states={'valid': [('readonly', True)]},
copy=False)
state = fields.Selection([
('draft', 'Brouillon'),
('submit', 'Soumis'),
('valid', 'Validé')],
string='Statut',
comodel_name="account.analytic.line",
inverse_name="sheet_id",
string="Lignes de temps",
states={"valid": [("readonly", True)]},
copy=False,
)
state = fields.Selection(
[("draft", "Brouillon"), ("submit", "Soumis"), ("valid", "Validé")],
string="Statut",
index=True,
track_visibility='onchange',
tracking=True,
copy=False,
default='draft',
required=True)
default="draft",
required=True,
)
employee_id = fields.Many2one(
comodel_name='hr.employee',
comodel_name="hr.employee",
string="Employé",
required=True,
readonly=True,
states={'draft': [('readonly', False)]},
default=lambda self: self.env['hr.employee'].search(
[('user_id', '=', self.env.uid)], limit=1))
total_hour = fields.Float(
string='Total',
compute='_compute_hour',
store=True)
states={"draft": [("readonly", False)]},
default=lambda self: self.env["hr.employee"].search(
[("user_id", "=", self.env.uid)], limit=1
),
)
total_hour = fields.Float(string="Total", compute="_compute_hour", store=True)
company_id = fields.Many2one(
comodel_name='res.company',
string='Company',
comodel_name="res.company",
string="Company",
readonly=True,
states={'draft': [('readonly', False)]},
default=lambda self: self.env.user.company_id)
states={"draft": [("readonly", False)]},
default=lambda self: self.env.company,
)
validation_date = fields.Date("Date de validation")
submit_date = fields.Date("Soumis le")
ur_id = fields.Many2one(
'union.regionale',
string='Union Régionale',
"union.regionale",
string="Union Régionale",
index=True,
on_delete='restrict',
default=_default_ur)
can_edit = fields.Boolean('Can Reset', compute='_compute_can_reset')
ondelete="restrict",
default=_default_ur,
)
can_edit = fields.Boolean("Can Reset", compute="_compute_can_reset")
# ------------------------------------------------------
# Compute Functions
# ------------------------------------------------------
@api.depends(
'timesheet_line_ids',
'timesheet_line_ids.unit_amount')
@api.depends("timesheet_line_ids", "timesheet_line_ids.unit_amount")
def _compute_hour(self):
for sheet in self:
sheet.total_hour = sum(
sheet.timesheet_line_ids.mapped('unit_amount'))
sheet.total_hour = sum(sheet.timesheet_line_ids.mapped("unit_amount"))
@api.multi
def _compute_can_reset(self):
is_timesheet_user = self.user_has_groups(
'hr_timesheet.group_timesheet_manager')
is_timesheet_user = self.user_has_groups("hr_timesheet.group_timesheet_manager")
for sheet in self:
if sheet.state == 'draft' or is_timesheet_user:
if sheet.state == "draft" or is_timesheet_user:
sheet.can_edit = True
else:
sheet.can_edit = False
......@@ -83,48 +79,113 @@ class ScopHrTimesheetSheet(models.Model):
# ------------------------------------------------------
# Constain Functions
# ------------------------------------------------------
@api.constrains('timesheet_line_ids', 'employee_id')
@api.constrains("timesheet_line_ids", "employee_id")
def _check_employee(self):
for sheet in self:
employee_ids = sheet.timesheet_line_ids.mapped('employee_id')
if len(employee_ids) > 1 or (len(employee_ids) == 1 and employee_ids != sheet.employee_id):
employee_ids = sheet.timesheet_line_ids.mapped("employee_id")
if len(employee_ids) > 1 or (
len(employee_ids) == 1 and employee_ids != sheet.employee_id
):
raise ValidationError(
'Vous ne pouvez pas ajouter les lignes'
' de temps de plusieurs employés.')
_(
"Vous ne pouvez pas ajouter les lignes"
" de temps de plusieurs employés."
)
)
# ------------------------------------------------------
# Override ORM
# ------------------------------------------------------
@api.multi
def unlink(self):
for timesheet in self:
if timesheet.state in ['submit', 'valid']:
raise UserError('Vous ne pouvez pas supprimer une '
'feuille de temps soumise ou validée')
if timesheet.state in ["submit", "valid"]:
raise UserError(
_(
"Vous ne pouvez pas supprimer une "
"feuille de temps soumise ou validée"
)
)
super(ScopHrTimesheetSheet, self).unlink()
# ------------------------------------------------------
# Action button
# ------------------------------------------------------
@api.multi
def action_submit_timesheet(self):
self.write({
'state': 'submit',
'submit_date': fields.Date.today()})
self.write({"state": "submit", "submit_date": fields.Date.today()})
@api.multi
def approve_timesheet_sheets(self):
self.write({
'state': 'valid',
'validation_date': fields.Date.today()})
self.write({"state": "valid", "validation_date": fields.Date.today()})
@api.multi
def reset_timesheet_sheets(self):
self.write({
'state': 'draft',
'submit_date': False,
'validation_date': False})
self.write({"state": "draft", "submit_date": False, "validation_date": False})
def print_timesheet(self):
return self.env.ref(
'cgscop_timesheet.cgscop_timesheet_sheet_report').report_action(self)
"cgscop_timesheet.cgscop_timesheet_sheet_report"
).report_action(self)
# ------------------------------------------------------
# Retourne les lignes de la Fdt avec les totaux par projet
# ------------------------------------------------------
def _to_duration(self, infloat):
return "{:02.0f}:{:02.0f}".format(*divmod(infloat * 60, 60))
def _get_timesheet_line_act(self):
for sheet in self:
lines = sheet.timesheet_line_ids.sorted(
key=lambda b: (b.project_id.name, b.date)
)
rows = []
last_project = False
tot_project = 0
for line in lines:
# On insère un total intermédiaire
if last_project != line.project_id.name and tot_project != 0:
rows.append(
{
"total": 1,
"name": False,
"partner": False,
"project": False,
"date": False,
"ur_financial_system": "Total "
+ last_project
+ " : "
+ self._to_duration(tot_project),
"unit_amount": False,
}
)
tot_project = 0
# On insère la ligne lue
rows.append(
{
"total": 0,
"name": line.name,
"partner": line.partner_id.name,
"project": line.project_id.name,
"date": line.date,
"ur_financial_system": line.ur_financial_system_id.name,
"unit_amount": self._to_duration(line.unit_amount),
}
)
last_project = line.project_id.name
tot_project = tot_project + line.unit_amount
# On insère le dernier total
rows.append(
{
"total": 1,
"name": False,
"partner": False,
"project": False,
"date": False,
"ur_financial_system": "Total "
+ last_project
+ " : "
+ self._to_duration(tot_project),
"unit_amount": False,
}
)
return rows
......@@ -3,7 +3,7 @@
from datetime import datetime, time
from odoo import models, fields, api
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
......@@ -11,67 +11,119 @@ class ScopHrTimesheet(models.Model):
_inherit = "account.analytic.line"
def _default_ur(self):
return self.env['res.company']._ur_default_get()
return self.env["res.company"]._ur_default_get()
# Inherited fields
name = fields.Char(required=False)
partner_id = fields.Many2one(required=True)
# New fields
cgscop_timesheet_code_id = fields.Many2one(
related='project_id.cgscop_timesheet_code_id',
string='Code Activité National',
store=True)
related="project_id.cgscop_timesheet_code_id",
string="Code Activité National",
store=True,
)
ur_financial_system_id = fields.Many2one(
comodel_name='ur.financial.system',
string='Dispositif Financier')
comodel_name="ur.financial.system", string="Dispositif Financier"
)
ur_regional_convention_id = fields.Many2one(
comodel_name='ur.regional.convention',
string='Convention Régionale')
comodel_name="ur.regional.convention", string="Convention Régionale"
)
ur_id = fields.Many2one(
'union.regionale',
string='Union Régionale',
"union.regionale",
string="Union Régionale",
index=True,
ondelete='restrict',
default=_default_ur)
ondelete="restrict",
default=_default_ur,
)
sheet_id = fields.Many2one(
comodel_name='cgscop.timesheet.sheet',
comodel_name="cgscop.timesheet.sheet",
string="Feuille de temps",
readonly=True,
copy=False)
state = fields.Selection([
('to_report', 'A rapporter'),
('draft', 'Brouillon'),
('submit', 'Soumis'),
('valid', 'Validé')],
compute='_compute_state',
string='Statut',
copy=False,
)
state = fields.Selection(
[
("to_report", "A rapporter"),
("draft", "Brouillon"),
("submit", "Soumis"),
("valid", "Validé"),
],
compute="_compute_state",
string="Statut",
copy=False,
index=True,
readonly=True,
store=True,)
store=True,
)
ur_financial_system_nb = fields.Integer(
string="Nb Dispositifs Financiers",
compute="_compute_ur_system_nb")
string="Nb Dispositifs Financiers", compute="_compute_ur_system_nb"
)
ur_regional_convention_nb = fields.Integer(
string="Nb conventions régionales",
compute="_compute_ur_system_nb")
string="Nb conventions régionales", compute="_compute_ur_system_nb"
)
is_present = fields.Boolean("Présentiel")
location = fields.Selection(
[
("R", "Rendez-vous"),
("D", "Déplacement"),
("B", "Bureau"),
],
string="Lieu",
)
justificatifs = fields.Char(string="Justificatifs", required=False)
is_overtime = fields.Boolean(
string="Heures supplémentaires",
default=False,
)
is_overtime_allowed = fields.Boolean(
string="Heures supplémentaires autorisées",
compute="_compute_overtime_allowed",
)
travel_time = fields.Float(
string="Temps déplacement",
)
is_travel_time_allowed = fields.Boolean(
string="Temps de déplacement autorisé",
compute="_compute_travel_time_allowed",
)
calendar_l1 = fields.Char(
string="Ligne 1 calendrier",
compute="_compute_calendar_l1",
)
calendar_l2 = fields.Char(
string="Ligne 2 calendrier",
compute="_compute_calendar_l2",
)
# ------------------------------------------------------
# Compute Functions
# ------------------------------------------------------
@api.depends('ur_id')
@api.depends("ur_id")
def _compute_overtime_allowed(self):
for rec in self:
rec.is_overtime_allowed = self.env.company.overtime_working
@api.depends("ur_id")
def _compute_travel_time_allowed(self):
for rec in self:
rec.is_travel_time_allowed = self.env.company.use_travel_time
@api.depends("ur_id")
def _compute_ur_system_nb(self):
for timesheet in self:
# Calcul nombre de dispositifs financiers
financial_system = timesheet.env['ur.financial.system'].search([
('ur_id', '=', timesheet.ur_id.id)])
timesheet.ur_financial_system_nb = len(
financial_system)
financial_system = timesheet.env["ur.financial.system"].search(
[("ur_id", "=", timesheet.ur_id.id)]
)
timesheet.ur_financial_system_nb = len(financial_system)
# Calcul nombre de conventions
regional_convention = timesheet.env['ur.regional.convention'].search([
('ur_id', '=', timesheet.ur_id.id)])
timesheet.ur_regional_convention_nb = len(
regional_convention)
regional_convention = timesheet.env["ur.regional.convention"].search(
[("ur_id", "=", timesheet.ur_id.id)]
)
timesheet.ur_regional_convention_nb = len(regional_convention)
@api.depends('sheet_id', 'sheet_id.state')
@api.depends("sheet_id", "sheet_id.state")
def _compute_state(self):
for timesheet in self:
if not timesheet.sheet_id:
......@@ -79,89 +131,180 @@ class ScopHrTimesheet(models.Model):
else:
timesheet.state = timesheet.sheet_id.state
@api.depends("project_id", "partner_id")
def _compute_calendar_l1(self):
for ts in self:
ts.calendar_l1 = ""
if ts.project_id:
ts.calendar_l1 += ts.project_id.name + ", "
if ts.partner_id:
ts.calendar_l1 += ts.partner_id.name
@api.depends("unit_amount")
def _compute_calendar_l2(self):
for ts in self:
ts.calendar_l2 = "Durée : " + str(ts.unit_amount) + " heure(s)"
# ------------------------------------------------------
# Override le _rec_name
# ------------------------------------------------------
@api.depends("project_id", "partner_id")
def name_get(self):
result = []
for ts in self:
name = ts.calendar_l1
result.append((ts.id, name))
return result
# ------------------------------------------------------
# OnChange Functions
# ------------------------------------------------------
@api.onchange('project_id')
@api.onchange("project_id")
def onchange_project_id(self):
self.partner_id = self.project_id.partner_id
@api.onchange('partner_id')
@api.onchange("partner_id")
def onchange_partner_id(self):
if not self.partner_id.ur_financial_system_date or \
fields.Datetime.now() > self.partner_id.ur_financial_system_date:
# affiche le Dispositif Financier par défaut sur la LdT
# si il n'y a pas de date limite du dispositif
# ou si la date de la Ldt est inférieure à la date limite du dispositif
if (
not self.partner_id.ur_financial_system_date
or self.date <= self.partner_id.ur_financial_system_date
):
self.ur_financial_system_id = self.partner_id.ur_financial_system_id
if not self.partner_id.ur_regional_convention_date or \
fields.Datetime.now() > self.partner_id.ur_regional_convention_date:
# affiche la Convention par défaut sur la LdT
# si il n'y a pas de date limite de la convention
# ou si la date de la LdT est inférieure à la date limite de la convention
if (
not self.partner_id.ur_regional_convention_date
or self.date <= self.partner_id.ur_regional_convention_date
):
self.ur_regional_convention_id = self.partner_id.ur_regional_convention_id
# ------------------------------------------------------
# Contrains
# ------------------------------------------------------
@api.constrains('unit_amount')
@api.constrains("unit_amount", "date")
def _check_hours(self):
for record in self:
lines = self.search([
('date', '=', record.date),
('employee_id', '=', record.employee_id.id)])
total = sum(lines.mapped('unit_amount'))
if total > 8:
raise ValidationError(
"Vous ne pourvez imputer plus de 8h sur la même journée.\n"
"Journée du %s" % record.date.strftime("%d/%m/%Y"))
if record.project_id:
lines = self.search(
[
("date", "=", record.date),
("employee_id", "=", record.employee_id.id),
]
)
total = sum(lines.mapped("unit_amount"))
if (
not self.env.company.day_working
and total > self.env.company.day_duration
):
raise ValidationError(
_(
"Vous ne pouvez imputer plus de %sh sur la même journée.\n"
"Journée du %s"
)
% (
self.env.company.day_duration,
record.date.strftime("%d/%m/%Y"),
)
)
@api.constrains('date')
@api.constrains("date")
def _check_weekday(self):
if self.env.company.weekend_working:
return
for line in self:
dt = datetime.combine(line.date, time(12, 00))
holiday = self.env['resource.calendar.leaves'].search([
'|',
('company_id', '=', False),
('company_id', '=', self.env.user.company_id.id),
('date_from', '<=', dt),
('date_to', '>=', dt),
])
if line.date.weekday() in (5, 6) or holiday:
holiday = self.env["resource.calendar.leaves"].search(
[
"|",
("company_id", "=", False),
("company_id", "=", self.env.company.id),
("date_from", "<=", dt),
("date_to", ">=", dt),
("resource_id", "=", False),
]
)
if not line.holiday_id and (line.date.weekday() in (5, 6) or holiday):
raise ValidationError(
"Vous ne pourvez imputer du temps sur un weekend "
"ou un jour férié.")
_(
"Vous ne pouvez imputer du temps sur un weekend "
"ou un jour férié."
)
)
# ------------------------------------------------------
# Override ORM
# ------------------------------------------------------
@api.multi
def unlink(self):
for timesheet in self:
if timesheet.state in ['submit', 'valid']:
raise UserError('Vous ne pouvez pas supprimer une '
'ligne de temps soumise ou validée')
if timesheet.state in ["submit", "valid"]:
raise UserError(
_(
"Vous ne pouvez pas supprimer une "
"ligne de temps soumise ou validée"
)
)
super(ScopHrTimesheet, self).unlink()
# ------------------------------------------------------
# Actions
# ------------------------------------------------------
@api.multi
def action_submit_timesheet_lines(self):
"""
Crée une feuille de temps avec les lignes sélectionnées
Crée une feuille de temps avec les lignes sélectionnées
"""
if any(time.state != 'to_report' or time.sheet_id for time in self):
raise UserError(
"Vous ne pouvez pas insérer 2 fois la même ligne !")
if len(self.mapped('employee_id')) != 1:
if any(time.state != "to_report" or time.sheet_id for time in self):
raise UserError(_("Vous ne pouvez pas insérer 2 fois la même ligne !"))
if len(self.mapped("employee_id")) != 1:
raise UserError(
"Il ne peut y avoir plusieurs employés dans une "
"même feuille de temps.")
_(
"Il ne peut y avoir plusieurs employés dans une "
"même feuille de temps."
)
)
return {
'name': 'New Expense Report',
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'cgscop.timesheet.sheet',
'target': 'current',
'context': {
'default_timesheet_line_ids': self.ids,
'default_employee_id': self[0].employee_id.id,
'default_name': self[0].name if len(self) == 1 else ''
}
"name": "New Expense Report",
"type": "ir.actions.act_window",
"view_mode": "form",
"res_model": "cgscop.timesheet.sheet",
"target": "current",
"context": {
"default_timesheet_line_ids": self.ids,
"default_employee_id": self[0].employee_id.id,
"default_name": self[0].name if len(self) == 1 else "",
},
}
# ------------------------------------------------------
# Modification du context pour cacher les colonnes
# ------------------------------------------------------
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
custom_context = self.env.context.copy()
current_ur_id = self.env.context['current_ur_id']
nbfs = self.env["ur.financial.system"].search([("ur_id", "=", current_ur_id)])
if len(nbfs) == 0:
custom_context['hide_financial_system'] = True
nbrc = self.env["ur.regional.convention"].search([("ur_id", "=", current_ur_id)])
if len(nbrc) == 0:
custom_context['hide_regional_convention'] = True
overtime_allowed = self.env.company.overtime_working
if not overtime_allowed:
custom_context['hide_overtime'] = True
travel_time_allowed = self.env.company.use_travel_time
if not travel_time_allowed:
custom_context['hide_travel_time'] = True
res = super(ScopHrTimesheet, self.with_context(custom_context)).fields_view_get(
view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
return res
# © 2019 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api
from odoo import api, fields, models
class ScopProjectTimesheet(models.Model):
_inherit = "project.project"
def _default_ur(self):
return self.env['res.company']._ur_default_get()
return self.env["res.company"]._ur_default_get()
cgscop_timesheet_code_id = fields.Many2one(
comodel_name='cgscop.timesheet.code',
string='Code Activité National')
comodel_name="cgscop.timesheet.code", string="Code Activité National"
)
privacy_visibility = fields.Selection(default="employees")
ur_id = fields.Many2one(
'union.regionale',
string='Union Régionale',
"union.regionale",
string="Union Régionale",
index=True,
on_delete='restrict',
default=_default_ur)
ondelete="restrict",
default=_default_ur,
)
creation_invoiced = fields.Boolean(
string="Facturé au titre de la création",
)
@api.onchange('name')
@api.onchange("name")
def onchange_name(self):
analytic = self.analytic_account_id
if analytic and analytic.ensure_one():
self.analytic_account_id.sudo().write({
'name': self.name,
})
self.analytic_account_id.sudo().write(
{
"name": self.name,
}
)
# © 2019 Le Filament (<http://www.le-filament.com>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import fields, models
class ResCompanyTmesheet(models.Model):
_inherit = "res.company"
day_duration = fields.Float(
string="Nb Heures/Jour",
default=8,
help="Nombre d'heures max pour imputation",
)
day_working = fields.Boolean(
string="Forfait Jour",
default=False,
help="Si cette option est cochée, un employé peut imputer sans limite"
" de temps sur une journée",
)
overtime_working = fields.Boolean(
string="Heures supplémentaires",
default=False,
help="Si cette option est cochée, un employé peut déclarer des heures supplémentaire"
)
weekend_working = fields.Boolean(
string="Travail le weekend",
default=False,
help="Si cette option est cochée, un employé peut imputer le weekend",
)
use_travel_time = fields.Boolean(
string="Saisie des temps de déplacement",
default=False,
help="Si cette option est cochée, un employé peut saisir ses temps de déplacement"
)