From 37ed23b787408d62d6920d937a98d7e1270fe305 Mon Sep 17 00:00:00 2001 From: benjamin <benjamin@le-filament.com> Date: Mon, 11 Apr 2022 12:34:59 +0200 Subject: [PATCH] [mig] Migration 13.0 --- .editorconfig | 20 + .eslintrc.yml | 187 +++ .flake8 | 12 + .gitignore | 75 ++ .isort.cfg | 13 + .pre-commit-config.yaml | 127 ++ .prettierrc.yml | 8 + .pylintrc | 87 ++ .pylintrc-mandatory | 64 + __manifest__.py | 25 +- models/__init__.py | 1 - models/ir_action.py | 5 +- models/ir_http.py | 7 +- models/ir_view.py | 5 +- models/res_users.py | 10 +- static/src/css/cgscop_fullcalendar.css | 27 +- static/src/js/calendar_controller.js | 56 +- static/src/js/calendar_model.js | 18 +- static/src/js/calendar_renderer.js | 234 ++-- static/src/js/calendar_view.js | 21 +- static/src/js/resource_controller.js | 839 +++++++------ static/src/js/resource_model.js | 1546 +++++++++++++----------- static/src/js/resource_renderer.js | 1046 ++++++++-------- static/src/js/resource_view.js | 326 ++--- static/src/xml/resource_view.xml | 103 +- views/assets.xml | 128 +- views/res_users.xml | 11 +- 27 files changed, 2966 insertions(+), 2035 deletions(-) create mode 100644 .editorconfig create mode 100644 .eslintrc.yml create mode 100644 .flake8 create mode 100644 .gitignore create mode 100644 .isort.cfg create mode 100644 .pre-commit-config.yaml create mode 100644 .prettierrc.yml create mode 100644 .pylintrc create mode 100644 .pylintrc-mandatory diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..bfd7ac5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# 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 diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000..d4cc423 --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,187 @@ +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 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..e397e8e --- /dev/null +++ b/.flake8 @@ -0,0 +1,12 @@ +[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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..818770f --- /dev/null +++ b/.gitignore @@ -0,0 +1,75 @@ +# 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/ diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..0ec187e --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,13 @@ +[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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..8a5999a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,127 @@ +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$" + - 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: 20.8b1 + 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.5.1 + hooks: + - id: isort + name: isort except __init__.py + args: + - --settings=. + exclude: /__init__\.py$ + - repo: https://gitlab.com/PyCQA/flake8 + rev: 3.8.3 + hooks: + - id: flake8 + name: flake8 + additional_dependencies: ["flake8-bugbear==20.1.4"] + - repo: https://github.com/PyCQA/pylint + rev: pylint-2.5.3 + hooks: + - id: pylint + name: pylint with optional checks + args: + - --rcfile=.pylintrc + - --exit-zero + verbose: true + additional_dependencies: &pylint_deps + - pylint-odoo==3.5.0 + - id: pylint + name: pylint with mandatory checks + args: + - --rcfile=.pylintrc-mandatory + additional_dependencies: *pylint_deps diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 0000000..5b6d4b3 --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1,8 @@ +# 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" diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..542c686 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,87 @@ +[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=13.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 diff --git a/.pylintrc-mandatory b/.pylintrc-mandatory new file mode 100644 index 0000000..74be5ff --- /dev/null +++ b/.pylintrc-mandatory @@ -0,0 +1,64 @@ +[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=13.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 diff --git a/__manifest__.py b/__manifest__.py index e982ceb..4911abd 100644 --- a/__manifest__.py +++ b/__manifest__.py @@ -2,23 +2,22 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) { - 'name': "CG Scop - Fullcalendar", - 'summary': "Vertical Resource View for CG Scop", - "version": "12.0.1.1.0", - "development_status": "Production/Stable", - 'author': 'Le Filament', + "name": "CG Scop - Fullcalendar", + "summary": "Vertical Resource View for CG Scop", + "version": "13.0.0.1.0", + "author": "Le Filament", "license": "AGPL-3", "application": False, "installable": True, - 'depends': [ - 'base', - 'web', + "depends": [ + "base", + "web", ], - 'qweb': [ - 'static/src/xml/*.xml', + "qweb": [ + "static/src/xml/*.xml", ], - 'data': [ - 'views/assets.xml', - 'views/res_users.xml', + "data": [ + "views/assets.xml", + "views/res_users.xml", ], } diff --git a/models/__init__.py b/models/__init__.py index 100cd04..db8a048 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,4 +1,3 @@ - from . import ir_action from . import ir_http from . import ir_view diff --git a/models/ir_action.py b/models/ir_action.py index 4ed62a1..222a16c 100644 --- a/models/ir_action.py +++ b/models/ir_action.py @@ -3,11 +3,10 @@ from odoo import fields, models - -VIEW_TYPES = ('resource', 'Resource') +VIEW_TYPES = ("resource", "Resource") class IrActionsActWindowView(models.Model): - _inherit = 'ir.actions.act_window.view' + _inherit = "ir.actions.act_window.view" view_mode = fields.Selection(selection_add=[VIEW_TYPES]) diff --git a/models/ir_http.py b/models/ir_http.py index 674a3a5..e17231a 100644 --- a/models/ir_http.py +++ b/models/ir_http.py @@ -2,17 +2,16 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from odoo import models -from odoo.http import request class IrHttp(models.AbstractModel): - _inherit = 'ir.http' + _inherit = "ir.http" def session_info(self): """ - Ajoute l'ur_id dans le contexte + Ajoute l'ur_id dans le contexte """ result = super(IrHttp, self).session_info() user = self.env.user - result['is_weekend'] = user.is_weekend + result["is_weekend"] = user.is_weekend return result diff --git a/models/ir_view.py b/models/ir_view.py index 3b772a0..7d9dc9c 100644 --- a/models/ir_view.py +++ b/models/ir_view.py @@ -3,11 +3,10 @@ from odoo import fields, models - -RESOURCE_VIEW = ('resource', 'Resource') +RESOURCE_VIEW = ("resource", "Resource") class IrUIView(models.Model): - _inherit = 'ir.ui.view' + _inherit = "ir.ui.view" type = fields.Selection(selection_add=[RESOURCE_VIEW]) diff --git a/models/res_users.py b/models/res_users.py index 305dbb7..5ba7b4a 100644 --- a/models/res_users.py +++ b/models/res_users.py @@ -7,22 +7,20 @@ from odoo import fields, models class ResUsersCalendar(models.Model): _inherit = "res.users" - is_weekend = fields.Boolean( - string="Afficher les weekend", - default=True) + is_weekend = fields.Boolean(string="Afficher les weekend", default=True) # ------------------------------------------------------ # Override parent # ------------------------------------------------------ def __init__(self, pool, cr): - """ Override of __init__ to add access rights. + """Override of __init__ to add access rights. Access rights are disabled by default, but allowed on some specific fields defined in self.SELF_{READ/WRITE}ABLE_FIELDS. """ super(ResUsersCalendar, self).__init__(pool, cr) # duplicate list to avoid modifying the original reference type(self).SELF_WRITEABLE_FIELDS = list(self.SELF_WRITEABLE_FIELDS) - type(self).SELF_WRITEABLE_FIELDS.extend(['is_weekend']) + type(self).SELF_WRITEABLE_FIELDS.extend(["is_weekend"]) # duplicate list to avoid modifying the original reference type(self).SELF_READABLE_FIELDS = list(self.SELF_READABLE_FIELDS) - type(self).SELF_READABLE_FIELDS.extend(['is_weekend']) + type(self).SELF_READABLE_FIELDS.extend(["is_weekend"]) diff --git a/static/src/css/cgscop_fullcalendar.css b/static/src/css/cgscop_fullcalendar.css index 602d87b..2f2990e 100644 --- a/static/src/css/cgscop_fullcalendar.css +++ b/static/src/css/cgscop_fullcalendar.css @@ -2,35 +2,36 @@ Resource calendar colors */ .o_calendar_color_none { - background-color: rgba(170, 170, 170, 0.6)!important; - border-color: rgba(170, 170, 170, 0.8)!important; + background-color: rgba(170, 170, 170, 0.6) !important; + border-color: rgba(170, 170, 170, 0.8) !important; border-radius: 0; } -.cgscop_resource .fc-resource-cell, .cgscop_resource .fc-day { - width: 80px; +.cgscop_resource .fc-resource-cell, +.cgscop_resource .fc-day { + width: 80px; } .cgscop_resource .fc-content-skeleton td { - width: 80px; + width: 80px; } .cgscop_resource .fc-view > table { - min-width: 100%; - width: auto!important; + min-width: 100%; + width: auto !important; } .cgscop_resource .fc-time-grid .fc-slats { - z-index: 4; - pointer-events: none; + z-index: 4; + pointer-events: none; } .cgscop_resource .fc-scroller.fc-time-grid-container { - overflow: initial !important; + overflow: initial !important; } .cgscop_resource .fc-axis { - position: sticky; - left: 0; - background: white; + position: sticky; + left: 0; + background: white; } diff --git a/static/src/js/calendar_controller.js b/static/src/js/calendar_controller.js index a3ecee4..065a75f 100644 --- a/static/src/js/calendar_controller.js +++ b/static/src/js/calendar_controller.js @@ -1,35 +1,35 @@ // © 2020 Le Filament (<http://www.le-filament.com>) // License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -odoo.define('cgscop_fullcalendar.CalendarController', function (require) { + +odoo.define("cgscop_fullcalendar.CalendarController", function (require) { "use strict"; - var calendarController = require('web.CalendarController'); + var calendarController = require("web.CalendarController"); calendarController.include({ - custom_events: _.extend({}, calendarController.prototype.custom_events, { - copyEvent: '_onCopyEvent', - }), - /** - * @private - * @param {Infos} event + delta - */ - _onCopyEvent: function (infos) { - var self = this; - var record = infos.data.event.record; + custom_events: _.extend({}, calendarController.prototype.custom_events, { + copyEvent: "_onCopyEvent", + }), + /** + * @private + * @param {Infos} event + delta + */ + _onCopyEvent: function (infos) { + var self = this; + var record = infos.data.event.record; - var values = { - start: record[this.mapping.date_start].add(infos.data.delta), - stop: record[this.mapping.date_stop].add(infos.data.delta), - } - this._rpc({ - model: self.modelName, - method: 'copy', - args: [[record.id], values] - }).then(function (id) { - self.reload(); - }); - return; - }, - }); -}); \ No newline at end of file + var values = { + start: record[this.mapping.date_start].add(infos.data.delta), + stop: record[this.mapping.date_stop].add(infos.data.delta), + }; + this._rpc({ + model: self.modelName, + method: "copy", + args: [[record.id], values], + }).then(function (id) { + self.reload(); + }); + return; + }, + }); +}); diff --git a/static/src/js/calendar_model.js b/static/src/js/calendar_model.js index da0053f..e1fd70f 100644 --- a/static/src/js/calendar_model.js +++ b/static/src/js/calendar_model.js @@ -1,11 +1,11 @@ // © 2020 Le Filament (<http://www.le-filament.com>) // License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -odoo.define('cgscop_fullcalendar.CalendarModel', function (require) { + +odoo.define("cgscop_fullcalendar.CalendarModel", function (require) { "use strict"; - var calendarModel = require('web.CalendarModel'); - var session = require('web.session'); + var calendarModel = require("web.CalendarModel"); + var session = require("web.session"); calendarModel.include({ /** @@ -15,9 +15,9 @@ odoo.define('cgscop_fullcalendar.CalendarModel', function (require) { load: function (params) { var self = this; this.readonly = params.readonly; - return this._super(params) + return this._super(params); }, - /** + /** * @override parent * @returns {Object} */ @@ -28,10 +28,10 @@ odoo.define('cgscop_fullcalendar.CalendarModel', function (require) { fc_option.droppable = !this.readonly; fc_option.editable = !this.readonly; fc_option.editable = !this.readonly; - fc_option.maxTime = '21:00:00'; - fc_option.minTime = '06:00:00'; + fc_option.maxTime = "21:00:00"; + fc_option.minTime = "06:00:00"; - return fc_option + return fc_option; }, }); }); diff --git a/static/src/js/calendar_renderer.js b/static/src/js/calendar_renderer.js index caf6dbe..bc34284 100644 --- a/static/src/js/calendar_renderer.js +++ b/static/src/js/calendar_renderer.js @@ -1,17 +1,17 @@ // © 2020 Le Filament (<http://www.le-filament.com>) // License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -odoo.define('cgscop_fullcalendar.CalendarRenderer', function (require) { +odoo.define("cgscop_fullcalendar.CalendarRenderer", function (require) { "use strict"; - var session = require('web.session'); - var utils = require('web.utils'); - var core = require('web.core'); - var relational_fields = require('web.relational_fields'); - var FieldManagerMixin = require('web.FieldManagerMixin'); - var Widget = require('web.Widget'); - var calendarRenderer = require('web.CalendarRenderer'); - var Dialog = require('web.Dialog'); + var session = require("web.session"); + var utils = require("web.utils"); + var core = require("web.core"); + var relational_fields = require("web.relational_fields"); + var FieldManagerMixin = require("web.FieldManagerMixin"); + var Widget = require("web.Widget"); + var calendarRenderer = require("web.CalendarRenderer"); + var Dialog = require("web.Dialog"); var _t = core._t; @@ -23,17 +23,17 @@ odoo.define('cgscop_fullcalendar.CalendarRenderer', function (require) { /** * @override * Ajoute filtre par UR si dans les options - */ + */ var SidebarFilter = Widget.extend(FieldManagerMixin, { - template: 'CalendarView.sidebar.filter', + template: "CalendarView.sidebar.filter", custom_events: _.extend({}, FieldManagerMixin.custom_events, { - field_changed: '_onFieldChanged', + field_changed: "_onFieldChanged", }), /** - * @constructor + * @class * @param {Widget} parent * @param {Object} options - * @param {string} options.fieldName + * @param {String} options.fieldName * @param {Object[]} options.filters A filter is an object with the * following keys: id, value, label, active, avatar_model, color, * can_be_removed @@ -54,7 +54,10 @@ odoo.define('cgscop_fullcalendar.CalendarRenderer', function (require) { this.filters = options.filters; this.label = options.label; this.getColor = options.getColor; - this.ur_sidebar_filter = utils.toBoolElse(options.ur_sidebar_filter || '', false); + this.ur_sidebar_filter = utils.toBoolElse( + options.ur_sidebar_filter || "", + false + ); }, /** * @override @@ -63,37 +66,45 @@ odoo.define('cgscop_fullcalendar.CalendarRenderer', function (require) { var self = this; var defs = [this._super.apply(this, arguments)]; - var domain = this.fields[self.fieldName].domain + var domain = this.fields[self.fieldName].domain; if (this.ur_sidebar_filter == true) { - domain = domain.concat([["ur_id", "=", session.ur_id]]) + domain = domain.concat([["ur_id", "=", session.ur_id]]); } if (this.write_model || this.write_field) { - var def = this.model.makeRecord(this.write_model, [{ - name: this.write_field, - relation: this.fields[this.fieldName].relation, - type: 'many2one', - domain: domain, - }]).then(function (recordID) { - self.many2one = new SidebarFilterM2O(self, - self.write_field, - self.model.get(recordID), + var def = this.model + .makeRecord(this.write_model, [ { - mode: 'edit', - attrs: { - placeholder: _.str.sprintf(_t("Add %s"), self.title), - can_create: false - }, - domain: self.fields[self.fieldName].domain, - readonly: true, - invisible: true, - }); - }); + name: this.write_field, + relation: this.fields[this.fieldName].relation, + type: "many2one", + domain: domain, + }, + ]) + .then(function (recordID) { + self.many2one = new SidebarFilterM2O( + self, + self.write_field, + self.model.get(recordID), + { + mode: "edit", + attrs: { + placeholder: _.str.sprintf( + _t("Add %s"), + self.title + ), + can_create: false, + }, + domain: self.fields[self.fieldName].domain, + readonly: true, + invisible: true, + } + ); + }); defs.push(def); } return $.when.apply($, defs); - }, /** * @override @@ -102,15 +113,22 @@ odoo.define('cgscop_fullcalendar.CalendarRenderer', function (require) { this._super(); if (this.many2one) { this.many2one.appendTo(this.$el); - this.many2one.filter_ids = _.without(_.pluck(this.filters, 'value'), 'all'); + this.many2one.filter_ids = _.without( + _.pluck(this.filters, "value"), + "all" + ); } - this.$el.on('click', '.o_remove', this._onFilterRemove.bind(this)); - this.$el.on('click', '.custom-checkbox input', this._onFilterActive.bind(this)); + this.$el.on("click", ".o_remove", this._onFilterRemove.bind(this)); + this.$el.on( + "click", + ".custom-checkbox input", + this._onFilterActive.bind(this) + ); }, - //-------------------------------------------------------------------------- + // -------------------------------------------------------------------------- // Handlers - //-------------------------------------------------------------------------- + // -------------------------------------------------------------------------- /** * @private @@ -119,21 +137,20 @@ odoo.define('cgscop_fullcalendar.CalendarRenderer', function (require) { _onFieldChanged: function (event) { var self = this; event.stopPropagation(); - var createValues = {'user_id': session.uid}; + var createValues = {user_id: session.uid}; var value = event.data.changes[this.write_field].id; createValues[this.write_field] = value; this._rpc({ - model: this.write_model, - method: 'create', - args: [createValues], - }) - .then(function () { - self.trigger_up('changeFilter', { - 'fieldName': self.fieldName, - 'value': value, - 'active': true, - }); + model: this.write_model, + method: "create", + args: [createValues], + }).then(function () { + self.trigger_up("changeFilter", { + fieldName: self.fieldName, + value: value, + active: true, }); + }); }, /** * @private @@ -141,10 +158,10 @@ odoo.define('cgscop_fullcalendar.CalendarRenderer', function (require) { */ _onFilterActive: function (e) { var $input = $(e.currentTarget); - this.trigger_up('changeFilter', { - 'fieldName': this.fieldName, - 'value': $input.closest('.o_calendar_filter_item').data('value'), - 'active': $input.prop('checked'), + this.trigger_up("changeFilter", { + fieldName: this.fieldName, + value: $input.closest(".o_calendar_filter_item").data("value"), + active: $input.prop("checked"), }); }, /** @@ -153,24 +170,27 @@ odoo.define('cgscop_fullcalendar.CalendarRenderer', function (require) { */ _onFilterRemove: function (e) { var self = this; - var $filter = $(e.currentTarget).closest('.o_calendar_filter_item'); - Dialog.confirm(this, _t("Do you really want to delete this filter from favorites ?"), { - confirm_callback: function () { - self._rpc({ + var $filter = $(e.currentTarget).closest(".o_calendar_filter_item"); + Dialog.confirm( + this, + _t("Do you really want to delete this filter from favorites ?"), + { + confirm_callback: function () { + self._rpc({ model: self.write_model, - method: 'unlink', - args: [[$filter.data('id')]], - }) - .then(function () { - self.trigger_up('changeFilter', { - 'fieldName': self.fieldName, - 'id': $filter.data('id'), - 'active': false, - 'value': $filter.data('value'), + method: "unlink", + args: [[$filter.data("id")]], + }).then(function () { + self.trigger_up("changeFilter", { + fieldName: self.fieldName, + id: $filter.data("id"), + active: false, + value: $filter.data("value"), }); }); - }, - }); + }, + } + ); }, }); @@ -179,7 +199,7 @@ odoo.define('cgscop_fullcalendar.CalendarRenderer', function (require) { * @override * Réécrit la fonction * Modifie DropEvent pour copier avec la touche ALT - */ + */ _initCalendar: function () { var self = this; @@ -191,59 +211,66 @@ odoo.define('cgscop_fullcalendar.CalendarRenderer', function (require) { var fc_options = $.extend({}, this.state.fc_options, { eventDrop: function (event, delta, revertFunc, jsEvent, ui, view) { if (jsEvent.altKey) { - self.trigger_up('copyEvent', { event: event, delta: delta }); - self.$calendar.fullCalendar('unselect'); + self.trigger_up("copyEvent", {event: event, delta: delta}); + self.$calendar.fullCalendar("unselect"); return; } - else { - self.trigger_up('dropRecord', event); - } + self.trigger_up("dropRecord", event); }, eventResize: function (event) { - self.trigger_up('updateRecord', event); + self.trigger_up("updateRecord", event); }, eventClick: function (event) { - self.trigger_up('openEvent', event); - self.$calendar.fullCalendar('unselect'); + self.trigger_up("openEvent", event); + self.$calendar.fullCalendar("unselect"); }, select: function (target_date, end_date, event, _js_event, _view) { - var data = {'start': target_date, 'end': end_date}; + var data = {start: target_date, end: end_date}; if (self.state.context.default_name) { data.title = self.state.context.default_name; } - self.trigger_up('openCreate', data); - self.$calendar.fullCalendar('unselect'); + self.trigger_up("openCreate", data); + self.$calendar.fullCalendar("unselect"); }, eventRender: function (event, element) { var $render = $(self._eventRender(event)); - event.title = $render.find('.o_field_type_char:first').text(); - element.find('.fc-content').html($render.html()); - element.addClass($render.attr('class')); - var display_hour = ''; + event.title = $render.find(".o_field_type_char:first").text(); + element.find(".fc-content").html($render.html()); + element.addClass($render.attr("class")); + var display_hour = ""; if (!event.allDay) { var start = event.r_start || event.start; var end = event.r_end || event.end; - var timeFormat = _t.database.parameters.time_format.search("%H") != -1 ? 'HH:mm': 'h:mma'; - display_hour = start.format(timeFormat) + ' - ' + end.format(timeFormat); - if (display_hour === '00:00 - 00:00') { - display_hour = _t('All day'); + var timeFormat = + _t.database.parameters.time_format.search("%H") != -1 + ? "HH:mm" + : "h:mma"; + display_hour = + start.format(timeFormat) + " - " + end.format(timeFormat); + if (display_hour === "00:00 - 00:00") { + display_hour = _t("All day"); } } - element.find('.fc-content .fc-time').text(display_hour); + element.find(".fc-content .fc-time").text(display_hour); }, // Dirty hack to ensure a correct first render eventAfterAllRender: function () { - $(window).trigger('resize'); + $(window).trigger("resize"); }, viewRender: function (view) { - // compute mode from view.name which is either 'month', 'agendaWeek' or 'agendaDay' - var mode = view.name === 'month' ? 'month' : (view.name === 'agendaWeek' ? 'week' : 'day'); - self.trigger_up('viewUpdated', { + // Compute mode from view.name which is either 'month', 'agendaWeek' or 'agendaDay' + var mode = + view.name === "month" + ? "month" + : view.name === "agendaWeek" + ? "week" + : "day"; + self.trigger_up("viewUpdated", { mode: mode, title: view.title, }); }, - height: 'parent', + height: "parent", unselectAuto: false, locale: locale, }); @@ -251,12 +278,11 @@ odoo.define('cgscop_fullcalendar.CalendarRenderer', function (require) { this.$calendar.fullCalendar(fc_options); }, - /** * @override * Réécrit la fonction totalement * Ajoute filtre par UR si dans les options - */ + */ _renderFilters: function () { var self = this; _.each(this.filters || (this.filters = []), function (filter) { @@ -266,7 +292,11 @@ odoo.define('cgscop_fullcalendar.CalendarRenderer', function (require) { return; } _.each(this.state.filters, function (options) { - if (!_.find(options.filters, function (f) {return f.display == null || f.display;})) { + if ( + !_.find(options.filters, function (f) { + return f.display == null || f.display; + }) + ) { return; } options.ur_sidebar_filter = self.state.context.ur_sidebar_filter; @@ -277,7 +307,5 @@ odoo.define('cgscop_fullcalendar.CalendarRenderer', function (require) { self.filters.push(filter); }); }, - - }); }); diff --git a/static/src/js/calendar_view.js b/static/src/js/calendar_view.js index 29013e9..3250e61 100644 --- a/static/src/js/calendar_view.js +++ b/static/src/js/calendar_view.js @@ -1,21 +1,26 @@ // © 2020 Le Filament (<http://www.le-filament.com>) // License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -odoo.define('cgscop_fullcalendar.CalendarView', function (require) { + +odoo.define("cgscop_fullcalendar.CalendarView", function (require) { "use strict"; - var utils = require('web.utils'); - var calendarView = require('web.CalendarView'); + var utils = require("web.utils"); + var calendarView = require("web.CalendarView"); calendarView.include({ /** * @override - */ + */ init: function (viewInfo, params) { this._super(viewInfo, params); - this.loadParams.ur_sidebar_filter = utils.toBoolElse(params.context.ur_sidebar_filter || '', false); - this.loadParams.readonly = utils.toBoolElse(params.context.calendar_readonly || '', false); + this.loadParams.ur_sidebar_filter = utils.toBoolElse( + params.context.ur_sidebar_filter || "", + false + ); + this.loadParams.readonly = utils.toBoolElse( + params.context.calendar_readonly || "", + false + ); }, }); }); - diff --git a/static/src/js/resource_controller.js b/static/src/js/resource_controller.js index a3db73c..dd77be1 100644 --- a/static/src/js/resource_controller.js +++ b/static/src/js/resource_controller.js @@ -1,436 +1,469 @@ -odoo.define('cgscop_fullcalendar.ResourceController', function (require) { -"use strict"; +odoo.define("cgscop_fullcalendar.ResourceController", function (require) { + "use strict"; -/** - * ResourceController Controller - * - * This is the controller in the Model-Renderer-Controller architecture of the - * calendar view. Its role is to coordinate the data from the calendar model - * with the renderer, and with the outside world (such as a search view input) - */ - -var AbstractController = require('web.AbstractController'); -var config = require('web.config'); -var core = require('web.core'); -var Dialog = require('web.Dialog'); -var dialogs = require('web.view_dialogs'); -var QuickCreate = require('web.CalendarQuickCreate'); - -var _t = core._t; -var QWeb = core.qweb; - -function dateToServer (date) { - return date.clone().utc().locale('en').format('YYYY-MM-DD HH:mm:ss'); -} - -var ResourceController = AbstractController.extend({ - custom_events: _.extend({}, AbstractController.prototype.custom_events, { - changeDate: '_onChangeDate', - changeFilter: '_onChangeFilter', - dropRecord: '_onDropRecord', - next: '_onNext', - openCreate: '_onOpenCreate', - openEvent: '_onOpenEvent', - prev: '_onPrev', - quickCreate: '_onQuickCreate', - toggleFullWidth: '_onToggleFullWidth', - updateRecord: '_onUpdateRecord', - viewUpdated: '_onViewUpdated', - }), /** - * @override - * @param {Widget} parent - * @param {AbstractModel} model - * @param {AbstractRenderer} renderer - * @param {Object} params - */ - init: function (parent, model, renderer, params) { - this._super.apply(this, arguments); - this.current_start = null; - this.displayName = params.displayName; - this.quickAddPop = params.quickAddPop; - this.disableQuickCreate = params.disableQuickCreate; - this.eventOpenPopup = params.eventOpenPopup; - this.formViewId = params.formViewId; - this.readonlyFormViewId = params.readonlyFormViewId; - this.mapping = params.mapping; - this.context = params.context; - // The quickCreating attribute ensures that we don't do several create - this.quickCreating = false; - }, - /** - * Overrides to unbind handler on the control panel mobile 'Today' button. + * ResourceController Controller * - * @override + * This is the controller in the Model-Renderer-Controller architecture of the + * calendar view. Its role is to coordinate the data from the calendar model + * with the renderer, and with the outside world (such as a search view input) */ - destroy: function () { - this._super.apply(this, arguments); - if (this.$todayButton) { - this.$todayButton.off(); - } - }, - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + var AbstractController = require("web.AbstractController"); + var config = require("web.config"); + var core = require("web.core"); + var Dialog = require("web.Dialog"); + var dialogs = require("web.view_dialogs"); + var QuickCreate = require("web.CalendarQuickCreate"); - /** - * @override - * @returns {string} - */ - getTitle: function () { - return this.get('title'); - }, - /** - * Render the buttons according to the CalendarView.buttons template and - * add listeners on it. Set this.$buttons with the produced jQuery element - * - * @param {jQueryElement} [$node] a jQuery node where the rendered buttons - * should be inserted. $node may be undefined, in which case the Calendar - * inserts them into this.options.$buttons or into a div of its template - */ - renderButtons: function ($node) { - var self = this; - this.$buttons = $(QWeb.render('ResourceView.buttons', { - isMobile: config.device.isMobile, - })); - this.$buttons.on('click', 'button.o_calendar_button_new', function () { - self.trigger_up('switch_view', {view_type: 'form'}); - }); + var _t = core._t; + var QWeb = core.qweb; - _.each(['prev', 'today', 'next'], function (action) { - self.$buttons.on('click', '.o_calendar_button_' + action, function () { - self._move(action); - }); - }); + function dateToServer(date) { + return date.clone().utc().locale("en").format("YYYY-MM-DD HH:mm:ss"); + } - this.$buttons.find('.o_calendar_button_' + this.mode).addClass('active'); + var ResourceController = AbstractController.extend({ + custom_events: _.extend({}, AbstractController.prototype.custom_events, { + changeDate: "_onChangeDate", + changeFilter: "_onChangeFilter", + dropRecord: "_onDropRecord", + next: "_onNext", + openCreate: "_onOpenCreate", + openEvent: "_onOpenEvent", + prev: "_onPrev", + quickCreate: "_onQuickCreate", + toggleFullWidth: "_onToggleFullWidth", + updateRecord: "_onUpdateRecord", + viewUpdated: "_onViewUpdated", + }), + /** + * @override + * @param {Widget} parent + * @param {AbstractModel} model + * @param {AbstractRenderer} renderer + * @param {Object} params + */ + init: function (parent, model, renderer, params) { + this._super.apply(this, arguments); + this.current_start = null; + this.displayName = params.displayName; + this.quickAddPop = params.quickAddPop; + this.disableQuickCreate = params.disableQuickCreate; + this.eventOpenPopup = params.eventOpenPopup; + this.formViewId = params.formViewId; + this.readonlyFormViewId = params.readonlyFormViewId; + this.mapping = params.mapping; + this.context = params.context; + // The quickCreating attribute ensures that we don't do several create + this.quickCreating = false; + }, + /** + * Overrides to unbind handler on the control panel mobile 'Today' button. + * + * @override + */ + destroy: function () { + this._super.apply(this, arguments); + if (this.$todayButton) { + this.$todayButton.off(); + } + }, - if ($node) { - this.$buttons.appendTo($node); - } else { - this.$('.o_calendar_buttons').replaceWith(this.$buttons); - } - }, - /** - * In mobile, we want to display a special 'Today' button on the bottom - * right corner of the control panel. This is the pager area, and as there - * is no pager in Calendar views, we fool the system by defining a fake - * pager (which is actually our button) such that it will be inserted in the - * desired place. - * - * @todo get rid of this hack once the ControlPanel layout will be reworked - * - * @param {jQueryElement} $node the button should be appended to this - * element to be displayed in the bottom right corner of the control panel - */ - renderPager: function ($node) { - if (config.device.isMobile) { - this.$todayButton = $(QWeb.render('CalendarView.TodayButtonMobile')); - this.$todayButton.on('click', this._move.bind(this, 'today')); - $node.append(this.$todayButton); - } - }, + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- + /** + * @override + * @returns {String} + */ + getTitle: function () { + return this.get("title"); + }, + /** + * Render the buttons according to the CalendarView.buttons template and + * add listeners on it. Set this.$buttons with the produced jQuery element + * + * @param {jQueryElement} [$node] a jQuery node where the rendered buttons + * should be inserted. $node may be undefined, in which case the Calendar + * inserts them into this.options.$buttons or into a div of its template + */ + renderButtons: function ($node) { + var self = this; + this.$buttons = $( + QWeb.render("ResourceView.buttons", { + isMobile: config.device.isMobile, + }) + ); + this.$buttons.on("click", "button.o_calendar_button_new", function () { + self.trigger_up("switch_view", {view_type: "form"}); + }); - /** - * Move to the requested direction and reload the view - * - * @private - * @param {string} to either 'prev', 'next' or 'today' - * @returns {Deferred} - */ - _move: function (to) { - this.model[to](); - return this.reload(); - }, - /** - * @private - * @param {Object} record - * @param {integer} record.id - * @returns {Deferred} - */ - _updateRecord: function (record) { - return this.model.updateRecord(record).always(this.reload.bind(this)); - }, + _.each(["prev", "today", "next"], function (action) { + self.$buttons.on("click", ".o_calendar_button_" + action, function () { + self._move(action); + }); + }); - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- + this.$buttons.find(".o_calendar_button_" + this.mode).addClass("active"); - /** - * @private - * @param {OdooEvent} event - */ - _onChangeDate: function (event) { - var modelData = this.model.get(); - if (modelData.target_date.format('YYYY-MM-DD') === event.data.date.format('YYYY-MM-DD')) { - // When clicking on same date, toggle between the two views - switch (modelData.scale) { - case 'day': this.model.setScale('month'); break; + if ($node) { + this.$buttons.appendTo($node); + } else { + this.$(".o_calendar_buttons").replaceWith(this.$buttons); } - } else if (modelData.target_date.week() === event.data.date.week()) { - // When clicking on a date in the same week, switch to day view - this.model.setScale('day'); - } else { - // When clicking on a random day of a random other week, switch to week view - this.model.setScale('week'); - } - this.model.setDate(event.data.date); - this.reload(); - }, - /** - * @private - * @param {OdooEvent} event - */ - _onChangeFilter: function (event) { - if (this.model.changeFilter(event.data) && !event.data.no_reload) { - this.reload(); - } - }, - /** - * @private - * @param {OdooEvent} event - */ - _onDropRecord: function (event) { - this._updateRecord(_.extend({}, event.data, { - 'drop': true, - })); - }, - /** - * @private - * @param {OdooEvent} event - */ - _onNext: function (event) { - event.stopPropagation(); - this._move('next'); - }, - /** - * @private - * @param {OdooEvent} event - */ - _onOpenCreate: function (event) { - var self = this; + }, + /** + * In mobile, we want to display a special 'Today' button on the bottom + * right corner of the control panel. This is the pager area, and as there + * is no pager in Calendar views, we fool the system by defining a fake + * pager (which is actually our button) such that it will be inserted in the + * desired place. + * + * @todo get rid of this hack once the ControlPanel layout will be reworked + * + * @param {jQueryElement} $node the button should be appended to this + * element to be displayed in the bottom right corner of the control panel + */ + renderPager: function ($node) { + if (config.device.isMobile) { + this.$todayButton = $(QWeb.render("CalendarView.TodayButtonMobile")); + this.$todayButton.on("click", this._move.bind(this, "today")); + $node.append(this.$todayButton); + } + }, - var data = this.model.calendarEventToRecord(event.data); + // -------------------------------------------------------------------------- + // Private + // -------------------------------------------------------------------------- - var context = _.extend({}, this.context, event.options && event.options.context); - context.default_name = data.name || null; - context['default_' + this.mapping.date_start] = data[this.mapping.date_start] || null; - if (this.mapping.date_stop) { - context['default_' + this.mapping.date_stop] = data[this.mapping.date_stop] || null; - } - if (this.mapping.date_delay) { - context['default_' + this.mapping.date_delay] = data[this.mapping.date_delay] || null; - } - if (this.mapping.all_day) { - context['default_' + this.mapping.all_day] = data[this.mapping.all_day] || null; - } + /** + * Move to the requested direction and reload the view + * + * @private + * @param {String} to either 'prev', 'next' or 'today' + * @returns {Deferred} + */ + _move: function (to) { + this.model[to](); + return this.reload(); + }, + /** + * @private + * @param {Object} record + * @param {integer} record.id + * @returns {Deferred} + */ + _updateRecord: function (record) { + return this.model.updateRecord(record).always(this.reload.bind(this)); + }, - for (var k in context) { - if (context[k] && context[k]._isAMomentObject) { - context[k] = dateToServer(context[k]); + // -------------------------------------------------------------------------- + // Handlers + // -------------------------------------------------------------------------- + + /** + * @private + * @param {OdooEvent} event + */ + _onChangeDate: function (event) { + var modelData = this.model.get(); + if ( + modelData.target_date.format("YYYY-MM-DD") === + event.data.date.format("YYYY-MM-DD") + ) { + // When clicking on same date, toggle between the two views + switch (modelData.scale) { + case "day": + this.model.setScale("month"); + break; + } + } else if (modelData.target_date.week() === event.data.date.week()) { + // When clicking on a date in the same week, switch to day view + this.model.setScale("day"); + } else { + // When clicking on a random day of a random other week, switch to week view + this.model.setScale("week"); + } + this.model.setDate(event.data.date); + this.reload(); + }, + /** + * @private + * @param {OdooEvent} event + */ + _onChangeFilter: function (event) { + if (this.model.changeFilter(event.data) && !event.data.no_reload) { + this.reload(); } - } + }, + /** + * @private + * @param {OdooEvent} event + */ + _onDropRecord: function (event) { + this._updateRecord( + _.extend({}, event.data, { + drop: true, + }) + ); + }, + /** + * @private + * @param {OdooEvent} event + */ + _onNext: function (event) { + event.stopPropagation(); + this._move("next"); + }, + /** + * @private + * @param {OdooEvent} event + */ + _onOpenCreate: function (event) { + var self = this; - var options = _.extend({}, this.options, event.options, { - context: context, - title: _.str.sprintf(_t('Create: %s'), (this.displayName || this.renderer.arch.attrs.string)) - }); + var data = this.model.calendarEventToRecord(event.data); - if (this.quick != null) { - this.quick.destroy(); - this.quick = null; - } + var context = _.extend( + {}, + this.context, + event.options && event.options.context + ); + context.default_name = data.name || null; + context["default_" + this.mapping.date_start] = + data[this.mapping.date_start] || null; + if (this.mapping.date_stop) { + context["default_" + this.mapping.date_stop] = + data[this.mapping.date_stop] || null; + } + if (this.mapping.date_delay) { + context["default_" + this.mapping.date_delay] = + data[this.mapping.date_delay] || null; + } + if (this.mapping.all_day) { + context["default_" + this.mapping.all_day] = + data[this.mapping.all_day] || null; + } - if (!options.disableQuickCreate && !event.data.disableQuickCreate && this.quickAddPop) { - this.quick = new QuickCreate(this, true, options, data, event.data); - this.quick.open(); - this.quick.opened(function () { - self.quick.focus(); - }); - return; - } + for (var k in context) { + if (context[k] && context[k]._isAMomentObject) { + context[k] = dateToServer(context[k]); + } + } - var title = _t("Create"); - if (this.renderer.arch.attrs.string) { - title += ': ' + this.renderer.arch.attrs.string; - } - if (this.eventOpenPopup) { - new dialogs.FormViewDialog(self, { - res_model: this.modelName, - context: context, - title: title, - view_id: this.formViewId || false, - disable_multiple_selection: true, - on_saved: function () { - if (event.data.on_save) { - event.data.on_save(); - } - self.reload(); - }, - }).open(); - } else { - this.do_action({ - type: 'ir.actions.act_window', - res_model: this.modelName, - views: [[this.formViewId || false, 'form']], - target: 'current', + var options = _.extend({}, this.options, event.options, { context: context, + title: _.str.sprintf( + _t("Create: %s"), + this.displayName || this.renderer.arch.attrs.string + ), }); - } - }, - /** - * @private - * @param {OdooEvent} event - */ - _onOpenEvent: function(event) { - var self = this; - var id = event.data.id; - id = id && parseInt(id).toString() === id ? parseInt(id) : id; - if (!this.eventOpenPopup) { - this._rpc({ - model: self.modelName, - method: 'get_formview_id', - //The event can be called by a view that can have another context than the default one. - args: [[id], event.context || self.context], - }).then(function (viewId) { - self.do_action({ - type:'ir.actions.act_window', - res_id: id, - res_model: self.modelName, - views: [[viewId || false, 'form']], - target: 'current', - context: event.context || self.context, + if (this.quick != null) { + this.quick.destroy(); + this.quick = null; + } + + if ( + !options.disableQuickCreate && + !event.data.disableQuickCreate && + this.quickAddPop + ) { + this.quick = new QuickCreate(this, true, options, data, event.data); + this.quick.open(); + this.quick.opened(function () { + self.quick.focus(); }); - }); - return; - } + return; + } - var open_dialog = function (readonly) { - var options = { - res_model: self.modelName, - res_id: id || null, - context: event.context || self.context, - readonly: readonly, - title: _t("Open: ") + event.data.title, - on_saved: function () { - if (event.data.on_save) { - event.data.on_save(); - } - self.reload(); - }, - }; - if (readonly) { - if (self.readonlyFormViewId) { - options.view_id = parseInt(self.readonlyFormViewId); - } - options.buttons = [ - { - text: _t("Edit"), - classes: 'btn-primary', - close: true, - click: function () { open_dialog(false); } - }, - { - text: _t("Delete"), - click: function () { - Dialog.confirm(this, _t("Are you sure you want to delete this record ?"), { - confirm_callback: function () { - self.model.deleteRecords([id], self.modelName) - .then(function () { - self.dialog.destroy(); - self.reload(); - }); - } - }); - }, + var title = _t("Create"); + if (this.renderer.arch.attrs.string) { + title += ": " + this.renderer.arch.attrs.string; + } + if (this.eventOpenPopup) { + new dialogs.FormViewDialog(self, { + res_model: this.modelName, + context: context, + title: title, + view_id: this.formViewId || false, + disable_multiple_selection: true, + on_saved: function () { + if (event.data.on_save) { + event.data.on_save(); + } + self.reload(); }, - {text: _t("Close"), close: true} - ]; - } else if (self.formViewId) { - options.view_id = parseInt(self.formViewId); + }).open(); + } else { + this.do_action({ + type: "ir.actions.act_window", + res_model: this.modelName, + views: [[this.formViewId || false, "form"]], + target: "current", + context: context, + }); } - self.dialog = new dialogs.FormViewDialog(self, options).open(); - }; - open_dialog(true); - }, - /** - * @private - * @param {OdooEvent} event - */ - _onPrev: function () { - event.stopPropagation(); - this._move('prev'); - }, + }, + /** + * @private + * @param {OdooEvent} event + */ + _onOpenEvent: function (event) { + var self = this; + var id = event.data.id; + id = id && parseInt(id).toString() === id ? parseInt(id) : id; - /** - * Handles saving data coming from quick create box - * - * @private - * @param {OdooEvent} event - */ - _onQuickCreate: function (event) { - var self = this; - if (this.quickCreating) { - return; - } - this.quickCreating = true; - this.model.createRecord(event) - .then(function () { - self.quick.destroy(); - self.quick = null; - self.reload(); - }) - .fail(function (error, errorEvent) { - // This will occurs if there are some more fields required - // Preventdefaulting the error event will prevent the traceback window - errorEvent.preventDefault(); - event.data.options.disableQuickCreate = true; - event.data.data.on_save = self.quick.destroy.bind(self.quick); - self._onOpenCreate(event.data); - }) - .always(function () { - self.quickCreating = false; - }); - }, - /** - * Called when we want to open or close the sidebar. - * - * @private - */ - _onToggleFullWidth: function () { - this.model.toggleFullWidth(); - this.reload(); - }, - /** - * @private - * @param {OdooEvent} event - */ - _onUpdateRecord: function (event) { - this._updateRecord(event.data); - }, - /** - * The internal state of the calendar (mode, period displayed) has changed, - * so update the control panel buttons and breadcrumbs accordingly. - * - * @private - * @param {OdooEvent} event - */ - _onViewUpdated: function (event) { - this.mode = event.data.mode; - if (this.$buttons) { - this.$buttons.find('.active').removeClass('active'); - this.$buttons.find('.o_calendar_button_' + this.mode).addClass('active'); - } - this.set({title: this.displayName + ' (' + event.data.title + ')'}); - }, -}); + if (!this.eventOpenPopup) { + this._rpc({ + model: self.modelName, + method: "get_formview_id", + // The event can be called by a view that can have another context than the default one. + args: [[id], event.context || self.context], + }).then(function (viewId) { + self.do_action({ + type: "ir.actions.act_window", + res_id: id, + res_model: self.modelName, + views: [[viewId || false, "form"]], + target: "current", + context: event.context || self.context, + }); + }); + return; + } + + var open_dialog = function (readonly) { + var options = { + res_model: self.modelName, + res_id: id || null, + context: event.context || self.context, + readonly: readonly, + title: _t("Open: ") + event.data.title, + on_saved: function () { + if (event.data.on_save) { + event.data.on_save(); + } + self.reload(); + }, + }; + if (readonly) { + if (self.readonlyFormViewId) { + options.view_id = parseInt(self.readonlyFormViewId); + } + options.buttons = [ + { + text: _t("Edit"), + classes: "btn-primary", + close: true, + click: function () { + open_dialog(false); + }, + }, + { + text: _t("Delete"), + click: function () { + Dialog.confirm( + this, + _t("Are you sure you want to delete this record ?"), + { + confirm_callback: function () { + self.model + .deleteRecords([id], self.modelName) + .then(function () { + self.dialog.destroy(); + self.reload(); + }); + }, + } + ); + }, + }, + {text: _t("Close"), close: true}, + ]; + } else if (self.formViewId) { + options.view_id = parseInt(self.formViewId); + } + self.dialog = new dialogs.FormViewDialog(self, options).open(); + }; + open_dialog(true); + }, + /** + * @private + * @param {OdooEvent} event + */ + _onPrev: function () { + event.stopPropagation(); + this._move("prev"); + }, -return ResourceController; + /** + * Handles saving data coming from quick create box + * + * @private + * @param {OdooEvent} event + */ + _onQuickCreate: function (event) { + var self = this; + if (this.quickCreating) { + return; + } + this.quickCreating = true; + this.model + .createRecord(event) + .then(function () { + self.quick.destroy(); + self.quick = null; + self.reload(); + }) + .fail(function (error, errorEvent) { + // This will occurs if there are some more fields required + // Preventdefaulting the error event will prevent the traceback window + errorEvent.preventDefault(); + event.data.options.disableQuickCreate = true; + event.data.data.on_save = self.quick.destroy.bind(self.quick); + self._onOpenCreate(event.data); + }) + .always(function () { + self.quickCreating = false; + }); + }, + /** + * Called when we want to open or close the sidebar. + * + * @private + */ + _onToggleFullWidth: function () { + this.model.toggleFullWidth(); + this.reload(); + }, + /** + * @private + * @param {OdooEvent} event + */ + _onUpdateRecord: function (event) { + this._updateRecord(event.data); + }, + /** + * The internal state of the calendar (mode, period displayed) has changed, + * so update the control panel buttons and breadcrumbs accordingly. + * + * @private + * @param {OdooEvent} event + */ + _onViewUpdated: function (event) { + this.mode = event.data.mode; + if (this.$buttons) { + this.$buttons.find(".active").removeClass("active"); + this.$buttons + .find(".o_calendar_button_" + this.mode) + .addClass("active"); + } + this.set({title: this.displayName + " (" + event.data.title + ")"}); + }, + }); + return ResourceController; }); diff --git a/static/src/js/resource_model.js b/static/src/js/resource_model.js index 2b083fe..c83be1d 100644 --- a/static/src/js/resource_model.js +++ b/static/src/js/resource_model.js @@ -1,794 +1,882 @@ -odoo.define('cgscop_fullcalendar.ResourceModel', function (require) { -"use strict"; - -var AbstractModel = require('web.AbstractModel'); -var Context = require('web.Context'); -var core = require('web.core'); -var fieldUtils = require('web.field_utils'); -var session = require('web.session'); - -var _t = core._t; - -var scales = [ - 'day', -]; - -function dateToServer (date) { - return date.clone().utc().locale('en').format('YYYY-MM-DD HH:mm:ss'); -} - -return AbstractModel.extend({ - /** - * @override - */ - init: function () { - this._super.apply(this, arguments); - this.end_date = null; - var week_start = _t.database.parameters.week_start; - // calendar uses index 0 for Sunday but Odoo stores it as 7 - this.week_start = week_start !== undefined && week_start !== false ? week_start % 7 : moment().startOf('week').day(); - this.week_stop = this.week_start + 6; - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * Transform fullcalendar event object to OpenERP Data object - */ - calendarEventToRecord: function (event) { - // Normalize event_end without changing fullcalendars event. - var data = { - 'name': event.title, - 'start': moment(event.start).format('YYYY-MM-DD HH:mm:ss'), - 'stop': moment(event.end).format('YYYY-MM-DD HH:mm:ss'), - 'allday': event.start.allDay, - }; - // var start = moment(event.start.start).clone(); - // var end = moment(event.start.end) && moment(event.start.end).clone(); - - - - // // Detects allDay events (86400000 = 1 day in ms) - // if (event.allDay || (end && end.diff(start) % 86400000 === 0)) { - // event.setProp('allDay', true); - // } - - // // Set end date if not existing - // if (!end || end.diff(start) < 0) { // undefined or invalid end date - // if (event.allDay) { - // end = start.clone(); - // } else { - // // in week mode or day mode, convert allday event to event - // end = start.clone().add(2, 'h'); - // } - // } else if (event.allDay) { - // // For an "allDay", FullCalendar gives the end day as the - // // next day at midnight (instead of 23h59). - // end.add(-1, 'days'); - // } - - // var isDateEvent = this.fields[this.mapping.date_start].type === 'date'; - // // An "allDay" event without the "all_day" option is not considered - // // as a 24h day. It's just a part of the day (by default: 7h-19h). - // if (event.allDay) { - // if (!this.mapping.all_day && !isDateEvent) { - // if (event.r_start) { - // start.hours(event.r_start.hours()) - // .minutes(event.r_start.minutes()) - // .seconds(event.r_start.seconds()) - // .utc(); - // end.hours(event.r_end.hours()) - // .minutes(event.r_end.minutes()) - // .seconds(event.r_end.seconds()) - // .utc(); - // } else { - // // default hours in the user's timezone - // start.hours(7); - // end.hours(19); - // } - // start.add(-this.getSession().getTZOffset(start), 'minutes'); - // end.add(-this.getSession().getTZOffset(end), 'minutes'); - // } - // } else { - // start.add(-this.getSession().getTZOffset(start), 'minutes'); - // end.add(-this.getSession().getTZOffset(end), 'minutes'); - // } - - // if (this.mapping.all_day) { - // if (event.record) { - // data[this.mapping.all_day] = - // (this.scale !== 'month' && event.allDay) || - // event.record[this.mapping.all_day] && - // end.diff(start) < 10 || - // false; - // } else { - // data[this.mapping.all_day] = event.allDay; - // } - // } - - // data[this.mapping.date_start] = start; - // if (this.mapping.date_stop) { - // data[this.mapping.date_stop] = end; - // } - - // if (this.mapping.date_delay) { - // if (this.data.scale !== 'month' || (this.data.scale === 'month' && !event.drop)) { - // data[this.mapping.date_delay] = (end.diff(start) <= 0 ? end.endOf('day').diff(start) : end.diff(start)) / 1000 / 3600; - // } - // } - - return data; - }, - /** - * @param {Object} filter - * @returns {boolean} - */ - changeFilter: function (filter) { - var Filter = this.data.filters[filter.fieldName]; - if (filter.value === 'all') { - Filter.all = filter.active; - } - var f = _.find(Filter.filters, function (f) { - return f.value === filter.value; - }); - if (f) { - if (f.active !== filter.active) { - f.active = filter.active; - } else { - return false; +odoo.define("cgscop_fullcalendar.ResourceModel", function (require) { + "use strict"; + + var AbstractModel = require("web.AbstractModel"); + var Context = require("web.Context"); + var core = require("web.core"); + var fieldUtils = require("web.field_utils"); + var session = require("web.session"); + + var _t = core._t; + + var scales = ["day"]; + + function dateToServer(date) { + return date.clone().utc().locale("en").format("YYYY-MM-DD HH:mm:ss"); + } + + return AbstractModel.extend({ + /** + * @override + */ + init: function () { + this._super.apply(this, arguments); + this.end_date = null; + var week_start = _t.database.parameters.week_start; + // Calendar uses index 0 for Sunday but Odoo stores it as 7 + this.week_start = + week_start !== undefined && week_start !== false + ? week_start % 7 + : moment().startOf("week").day(); + this.week_stop = this.week_start + 6; + }, + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + /** + * Transform fullcalendar event object to OpenERP Data object + */ + calendarEventToRecord: function (event) { + // Normalize event_end without changing fullcalendars event. + var data = { + name: event.title, + start: moment(event.start).format("YYYY-MM-DD HH:mm:ss"), + stop: moment(event.end).format("YYYY-MM-DD HH:mm:ss"), + allday: event.start.allDay, + }; + // Var start = moment(event.start.start).clone(); + // var end = moment(event.start.end) && moment(event.start.end).clone(); + + // // Detects allDay events (86400000 = 1 day in ms) + // if (event.allDay || (end && end.diff(start) % 86400000 === 0)) { + // event.setProp('allDay', true); + // } + + // // Set end date if not existing + // if (!end || end.diff(start) < 0) { // undefined or invalid end date + // if (event.allDay) { + // end = start.clone(); + // } else { + // // in week mode or day mode, convert allday event to event + // end = start.clone().add(2, 'h'); + // } + // } else if (event.allDay) { + // // For an "allDay", FullCalendar gives the end day as the + // // next day at midnight (instead of 23h59). + // end.add(-1, 'days'); + // } + + // var isDateEvent = this.fields[this.mapping.date_start].type === 'date'; + // // An "allDay" event without the "all_day" option is not considered + // // as a 24h day. It's just a part of the day (by default: 7h-19h). + // if (event.allDay) { + // if (!this.mapping.all_day && !isDateEvent) { + // if (event.r_start) { + // start.hours(event.r_start.hours()) + // .minutes(event.r_start.minutes()) + // .seconds(event.r_start.seconds()) + // .utc(); + // end.hours(event.r_end.hours()) + // .minutes(event.r_end.minutes()) + // .seconds(event.r_end.seconds()) + // .utc(); + // } else { + // // default hours in the user's timezone + // start.hours(7); + // end.hours(19); + // } + // start.add(-this.getSession().getTZOffset(start), 'minutes'); + // end.add(-this.getSession().getTZOffset(end), 'minutes'); + // } + // } else { + // start.add(-this.getSession().getTZOffset(start), 'minutes'); + // end.add(-this.getSession().getTZOffset(end), 'minutes'); + // } + + // if (this.mapping.all_day) { + // if (event.record) { + // data[this.mapping.all_day] = + // (this.scale !== 'month' && event.allDay) || + // event.record[this.mapping.all_day] && + // end.diff(start) < 10 || + // false; + // } else { + // data[this.mapping.all_day] = event.allDay; + // } + // } + + // data[this.mapping.date_start] = start; + // if (this.mapping.date_stop) { + // data[this.mapping.date_stop] = end; + // } + + // if (this.mapping.date_delay) { + // if (this.data.scale !== 'month' || (this.data.scale === 'month' && !event.drop)) { + // data[this.mapping.date_delay] = (end.diff(start) <= 0 ? end.endOf('day').diff(start) : end.diff(start)) / 1000 / 3600; + // } + // } + + return data; + }, + /** + * @param {Object} filter + * @returns {Boolean} + */ + changeFilter: function (filter) { + var Filter = this.data.filters[filter.fieldName]; + if (filter.value === "all") { + Filter.all = filter.active; } - } else if (filter.active) { - Filter.filters.push({ - value: filter.value, - active: true, + var f = _.find(Filter.filters, function (f) { + return f.value === filter.value; }); - } - return true; - }, - /** - * @param {OdooEvent} event - */ - createRecord: function (event) { - var data = this.calendarEventToRecord(event.data.data); - for (var k in data) { - if (data[k] && data[k]._isAMomentObject) { - data[k] = dateToServer(data[k]); + if (f) { + if (f.active !== filter.active) { + f.active = filter.active; + } else { + return false; + } + } else if (filter.active) { + Filter.filters.push({ + value: filter.value, + active: true, + }); + } + return true; + }, + /** + * @param {OdooEvent} event + */ + createRecord: function (event) { + var data = this.calendarEventToRecord(event.data.data); + for (var k in data) { + if (data[k] && data[k]._isAMomentObject) { + data[k] = dateToServer(data[k]); + } } - } - return this._rpc({ + return this._rpc({ model: this.modelName, - method: 'create', + method: "create", args: [data], context: event.data.options.context, }); - }, - /** - * @todo I think this is dead code - * - * @param {any} ids - * @param {any} model - * @returns - */ - deleteRecords: function (ids, model) { - return this._rpc({ + }, + /** + * @todo I think this is dead code + * + * @param {any} ids + * @param {any} model + * @returns + */ + deleteRecords: function (ids, model) { + return this._rpc({ model: model, - method: 'unlink', + method: "unlink", args: [ids], - context: session.user_context, // todo: combine with view context + context: session.user_context, // Todo: combine with view context }); - }, - /** - * @override - * @returns {Object} - */ - get: function () { - return _.extend({}, this.data, { - fields: this.fields - }); - }, - /** - * @override - * @param {any} params - * @returns {Deferred} - */ - load: function (params) { - var self = this; - - this.resourceField = params.resourceField; - - this.modelName = params.modelName; - this.fields = params.fields; - this.fieldNames = params.fieldNames; - this.fieldsInfo = params.fieldsInfo; - this.mapping = params.mapping; - this.mode = params.mode; // one of month, week or day - this.scales = params.scales; // one of month, week or day - - // Check whether the date field is editable (i.e. if the events can be - // dragged and dropped) - this.editable = params.editable; - this.creatable = params.creatable; - - // display more button when there are too much event on one day - this.eventLimit = params.eventLimit; - - // fields to display color, e.g.: user_id.partner_id - this.fieldColor = params.fieldColor; - if (!this.preload_def) { - this.preload_def = $.Deferred(); - $.when( - this._rpc({model: this.modelName, method: 'check_access_rights', args: ["write", false]}), - this._rpc({model: this.modelName, method: 'check_access_rights', args: ["create", false]})) - .then(function (write, create) { - self.write_right = write; - self.create_right = create; - self.preload_def.resolve(); + }, + /** + * @override + * @returns {Object} + */ + get: function () { + return _.extend({}, this.data, { + fields: this.fields, }); - } - - this.data = { - domain: params.domain, - context: params.context, - // get in arch the filter to display in the sidebar and the field to read - filters: params.filters, - }; - - this.setDate(params.initialDate); - // Use mode attribute in xml file to specify zoom timeline (day,week,month) - // by default month. - this.setScale(params.mode); - - _.each(this.data.filters, function (filter) { - if (filter.avatar_field && !filter.avatar_model) { - filter.avatar_model = self.modelName; + }, + /** + * @override + * @param {any} params + * @returns {Deferred} + */ + load: function (params) { + var self = this; + + this.resourceField = params.resourceField; + + this.modelName = params.modelName; + this.fields = params.fields; + this.fieldNames = params.fieldNames; + this.fieldsInfo = params.fieldsInfo; + this.mapping = params.mapping; + this.mode = params.mode; // One of month, week or day + this.scales = params.scales; // One of month, week or day + + // Check whether the date field is editable (i.e. if the events can be + // dragged and dropped) + this.editable = params.editable; + this.creatable = params.creatable; + + // Display more button when there are too much event on one day + this.eventLimit = params.eventLimit; + + // Fields to display color, e.g.: user_id.partner_id + this.fieldColor = params.fieldColor; + if (!this.preload_def) { + this.preload_def = $.Deferred(); + $.when( + this._rpc({ + model: this.modelName, + method: "check_access_rights", + args: ["write", false], + }), + this._rpc({ + model: this.modelName, + method: "check_access_rights", + args: ["create", false], + }) + ).then(function (write, create) { + self.write_right = write; + self.create_right = create; + self.preload_def.resolve(); + }); } - }); - - return this.preload_def.then(this._loadCalendar.bind(this)); - }, - /** - * Move the current date range to the next period - */ - next: function () { - this.setDate(this.data.target_date.clone().add(1, this.data.scale)); - }, - /** - * Move the current date range to the previous period - */ - prev: function () { - this.setDate(this.data.target_date.clone().add(-1, this.data.scale)); - }, - /** - * @override - * @param {Object} [params.context] - * @param {Array} [params.domain] - * @returns {Deferred} - */ - reload: function (handle, params) { - if (params.domain) { - this.data.domain = params.domain; - } - if (params.context) { - this.data.context = params.context; - } - return this._loadCalendar(); - }, - /** - * @param {Moment} start. in local TZ - */ - setDate: function (start) { - // keep highlight/target_date in localtime - this.data.highlight_date = this.data.target_date = start.clone(); - this.data.start_date = this.data.end_date = start; - switch (this.data.scale) { - case 'month': - var monthStart = this.data.start_date.clone().startOf('month'); - - var monthStartDay; - if (monthStart.day() >= this.week_start) { - // the month's first day is after our week start - // Then we are in the right week - monthStartDay = this.week_start; - } else { - // The month's first day is before our week start - // Then we should go back to the the previous week - monthStartDay = this.week_start - 7; - } - this.data.start_date = monthStart.day(monthStartDay).startOf('day'); - this.data.end_date = this.data.start_date.clone().add(5, 'week').day(this.week_stop).endOf('day'); - break; - case 'week': - var weekStart = this.data.start_date.clone().startOf('week'); - var weekStartDay = this.week_start; - if (this.data.start_date.day() < this.week_start) { - // The week's first day is after our current day - // Then we should go back to the previous week - weekStartDay -= 7; + this.data = { + domain: params.domain, + context: params.context, + // Get in arch the filter to display in the sidebar and the field to read + filters: params.filters, + }; + + this.setDate(params.initialDate); + // Use mode attribute in xml file to specify zoom timeline (day,week,month) + // by default month. + this.setScale(params.mode); + + _.each(this.data.filters, function (filter) { + if (filter.avatar_field && !filter.avatar_model) { + filter.avatar_model = self.modelName; } - this.data.start_date = this.data.start_date.clone().day(weekStartDay).startOf('day'); - this.data.end_date = this.data.end_date.clone().day(weekStartDay + 6).endOf('day'); - break; - default: - this.data.start_date = this.data.start_date.clone().startOf('day'); - this.data.end_date = this.data.end_date.clone().endOf('day'); - } - // We have set start/stop datetime as definite begin/end boundaries of a period (month, week, day) - // in local TZ (what is the begining of the week *I am* in ?) - // The following code: - // - converts those to UTC using our homemade method (testable) - // - sets the moment UTC flag to true, to ensure compatibility with third party libs - var manualUtcDateStart = this.data.start_date.clone().add(-this.getSession().getTZOffset(this.data.start_date), 'minutes'); - var formattedUtcDateStart = manualUtcDateStart.format('YYYY-MM-DDTHH:mm:ss') + 'Z'; - this.data.start_date = moment.utc(formattedUtcDateStart); - - var manualUtcDateEnd = this.data.end_date.clone().add(-this.getSession().getTZOffset(this.data.start_date), 'minutes'); - var formattedUtcDateEnd = manualUtcDateEnd.format('YYYY-MM-DDTHH:mm:ss') + 'Z'; - this.data.end_date = moment.utc(formattedUtcDateEnd); - }, - /** - * @param {string} scale the scale to set - */ - setScale: function (scale) { - if (!_.contains(scales, scale)) { - scale = "day"; - } - this.data.scale = scale; - this.setDate(this.data.target_date); - }, - /** - * Move the current date range to the period containing today - */ - today: function () { - this.setDate(moment(new Date())); - }, - /** - * Toggle the sidebar (containing the mini calendar) - */ - toggleFullWidth: function () { - var fullWidth = this.call('local_storage', 'getItem', 'calendar_fullWidth') !== true; - this.call('local_storage', 'setItem', 'calendar_fullWidth', fullWidth); - }, - /** - * @param {Object} record - * @param {integer} record.id - * @returns {Deferred} - */ - updateRecord: function (record) { - // Cannot modify actual name yet - var data = _.omit(this.calendarEventToRecord(record.event), 'name'); - for (var k in data) { - if (data[k] && data[k]._isAMomentObject) { - data[k] = dateToServer(data[k]); - } - } - var context = new Context(this.data.context, {from_ui: true}); - return this._rpc({ - model: this.modelName, - method: 'write', - args: [[record.event.id], data], - context: context - }); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Converts this.data.filters into a domain - * - * @private - * @returns {Array} - */ - _getFilterDomain: function () { - // List authorized values for every field - // fields with an active 'all' filter are skipped - var authorizedValues = {}; - var avoidValues = {}; - - _.each(this.data.filters, function (filter) { - // Skip 'all' filters because they do not affect the domain - if (filter.all) return; - - // Loop over subfilters to complete authorizedValues - _.each(filter.filters, function (f) { - if (filter.write_model) { - if (!authorizedValues[filter.fieldName]) - authorizedValues[filter.fieldName] = []; + }); - if (f.active) { - authorizedValues[filter.fieldName].push(f.value); + return this.preload_def.then(this._loadCalendar.bind(this)); + }, + /** + * Move the current date range to the next period + */ + next: function () { + this.setDate(this.data.target_date.clone().add(1, this.data.scale)); + }, + /** + * Move the current date range to the previous period + */ + prev: function () { + this.setDate(this.data.target_date.clone().add(-1, this.data.scale)); + }, + /** + * @override + * @param {Object} [params.context] + * @param {Array} [params.domain] + * @returns {Deferred} + */ + reload: function (handle, params) { + if (params.domain) { + this.data.domain = params.domain; + } + if (params.context) { + this.data.context = params.context; + } + return this._loadCalendar(); + }, + /** + * @param {Moment} start. in local TZ + */ + setDate: function (start) { + // Keep highlight/target_date in localtime + this.data.highlight_date = this.data.target_date = start.clone(); + this.data.start_date = this.data.end_date = start; + switch (this.data.scale) { + case "month": + var monthStart = this.data.start_date.clone().startOf("month"); + + var monthStartDay; + if (monthStart.day() >= this.week_start) { + // The month's first day is after our week start + // Then we are in the right week + monthStartDay = this.week_start; + } else { + // The month's first day is before our week start + // Then we should go back to the the previous week + monthStartDay = this.week_start - 7; } - } else { - if (!avoidValues[filter.fieldName]) - avoidValues[filter.fieldName] = []; - if (!f.active) { - avoidValues[filter.fieldName].push(f.value); + this.data.start_date = monthStart.day(monthStartDay).startOf("day"); + this.data.end_date = this.data.start_date + .clone() + .add(5, "week") + .day(this.week_stop) + .endOf("day"); + break; + case "week": + var weekStart = this.data.start_date.clone().startOf("week"); + var weekStartDay = this.week_start; + if (this.data.start_date.day() < this.week_start) { + // The week's first day is after our current day + // Then we should go back to the previous week + weekStartDay -= 7; } + this.data.start_date = this.data.start_date + .clone() + .day(weekStartDay) + .startOf("day"); + this.data.end_date = this.data.end_date + .clone() + .day(weekStartDay + 6) + .endOf("day"); + break; + default: + this.data.start_date = this.data.start_date.clone().startOf("day"); + this.data.end_date = this.data.end_date.clone().endOf("day"); + } + // We have set start/stop datetime as definite begin/end boundaries of a period (month, week, day) + // in local TZ (what is the begining of the week *I am* in ?) + // The following code: + // - converts those to UTC using our homemade method (testable) + // - sets the moment UTC flag to true, to ensure compatibility with third party libs + var manualUtcDateStart = this.data.start_date + .clone() + .add(-this.getSession().getTZOffset(this.data.start_date), "minutes"); + var formattedUtcDateStart = + manualUtcDateStart.format("YYYY-MM-DDTHH:mm:ss") + "Z"; + this.data.start_date = moment.utc(formattedUtcDateStart); + + var manualUtcDateEnd = this.data.end_date + .clone() + .add(-this.getSession().getTZOffset(this.data.start_date), "minutes"); + var formattedUtcDateEnd = + manualUtcDateEnd.format("YYYY-MM-DDTHH:mm:ss") + "Z"; + this.data.end_date = moment.utc(formattedUtcDateEnd); + }, + /** + * @param {String} scale the scale to set + */ + setScale: function (scale) { + if (!_.contains(scales, scale)) { + scale = "day"; + } + this.data.scale = scale; + this.setDate(this.data.target_date); + }, + /** + * Move the current date range to the period containing today + */ + today: function () { + this.setDate(moment(new Date())); + }, + /** + * Toggle the sidebar (containing the mini calendar) + */ + toggleFullWidth: function () { + var fullWidth = + this.call("local_storage", "getItem", "calendar_fullWidth") !== true; + this.call("local_storage", "setItem", "calendar_fullWidth", fullWidth); + }, + /** + * @param {Object} record + * @param {integer} record.id + * @returns {Deferred} + */ + updateRecord: function (record) { + // Cannot modify actual name yet + var data = _.omit(this.calendarEventToRecord(record.event), "name"); + for (var k in data) { + if (data[k] && data[k]._isAMomentObject) { + data[k] = dateToServer(data[k]); } + } + var context = new Context(this.data.context, {from_ui: true}); + return this._rpc({ + model: this.modelName, + method: "write", + args: [[record.event.id], data], + context: context, + }); + }, + + // -------------------------------------------------------------------------- + // Private + // -------------------------------------------------------------------------- + + /** + * Converts this.data.filters into a domain + * + * @private + * @returns {Array} + */ + _getFilterDomain: function () { + // List authorized values for every field + // fields with an active 'all' filter are skipped + var authorizedValues = {}; + var avoidValues = {}; + + _.each(this.data.filters, function (filter) { + // Skip 'all' filters because they do not affect the domain + if (filter.all) return; + + // Loop over subfilters to complete authorizedValues + _.each(filter.filters, function (f) { + if (filter.write_model) { + if (!authorizedValues[filter.fieldName]) + authorizedValues[filter.fieldName] = []; + + if (f.active) { + authorizedValues[filter.fieldName].push(f.value); + } + } else { + if (!avoidValues[filter.fieldName]) + avoidValues[filter.fieldName] = []; + + if (!f.active) { + avoidValues[filter.fieldName].push(f.value); + } + } + }); }); - }); - - // Compute the domain - var domain = []; - for (var field in authorizedValues) { - domain.push([field, 'in', authorizedValues[field]]); - } - for (var field in avoidValues) { - if (avoidValues[field].length > 0) { - domain.push([field, 'not in', avoidValues[field]]); + + // Compute the domain + var domain = []; + for (var field in authorizedValues) { + domain.push([field, "in", authorizedValues[field]]); } - } - - return domain; - }, - /** - * @private - * @returns {Object} - */ - _getFullCalendarOptions: function () { - return { - plugins: ['timeGrid', 'interaction', 'resourceDayGrid','resourceTimeGrid', 'moment'], - defaultView: 'resourceTimeGridDay', - allDaySlot: true, - allDayText: _t("All day"), - businessHours: { - daysOfWeek: [ 1, 2, 3, 4, 5 ], - startTime: '08:00', - endTime: '18:00', - }, - dayNames: moment.weekdays(), - dayNamesShort: moment.weekdaysShort(), - droppable: false, - editable: false, - eventStartEditable: false, - eventLimit: this.eventLimit, - eventResizableFromStart: false, - firstDay: this.week_start, - header: false, - locale: 'fr', - longPressDelay: 500, - maxTime: '20:00:00', - minTime: '07:30:00', - monthNames: moment.months(), - monthNamesShort: moment.monthsShort(), - navLinks: false, - nowIndicator: true, - resourceOrder: 'title', - selectable: false, - selectHelper: false, - selectAllow: false, - snapMinutes: 30, - timeZone: 'Europe/Paris', - weekends: true, - weekNumbers: true, - weekNumberTitle: _t("W"), - schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source', - }; - }, - /** - * Return a domain from the date range - * - * @private - * @returns {Array} A domain containing datetimes start and stop in UTC - * those datetimes are formatted according to server's standards - */ - _getRangeDomain: function () { - // Build OpenERP Domain to filter object by this.mapping.date_start field - // between given start, end dates. - var domain = [[this.mapping.date_start, '<=', dateToServer(this.data.end_date)]]; - if (this.mapping.date_stop) { - domain.push([this.mapping.date_stop, '>=', dateToServer(this.data.start_date)]); - } else if (!this.mapping.date_delay) { - domain.push([this.mapping.date_start, '>=', dateToServer(this.data.start_date)]); - } - return domain; - }, - /** - * @private - * @returns {Deferred} - */ - _getResources: function () { - var self = this; - var resource = this.fields[this.resourceField] - return this._rpc({ + for (var field in avoidValues) { + if (avoidValues[field].length > 0) { + domain.push([field, "not in", avoidValues[field]]); + } + } + + return domain; + }, + /** + * @private + * @returns {Object} + */ + _getFullCalendarOptions: function () { + return { + plugins: [ + "timeGrid", + "interaction", + "resourceDayGrid", + "resourceTimeGrid", + "moment", + ], + defaultView: "resourceTimeGridDay", + allDaySlot: true, + allDayText: _t("All day"), + businessHours: { + daysOfWeek: [1, 2, 3, 4, 5], + startTime: "08:00", + endTime: "18:00", + }, + dayNames: moment.weekdays(), + dayNamesShort: moment.weekdaysShort(), + droppable: false, + editable: false, + eventStartEditable: false, + eventLimit: this.eventLimit, + eventResizableFromStart: false, + firstDay: this.week_start, + header: false, + locale: "fr", + longPressDelay: 500, + maxTime: "20:00:00", + minTime: "07:30:00", + monthNames: moment.months(), + monthNamesShort: moment.monthsShort(), + navLinks: false, + nowIndicator: true, + resourceOrder: "title", + selectable: false, + selectHelper: false, + selectAllow: false, + snapMinutes: 30, + timeZone: "Europe/Paris", + weekends: true, + weekNumbers: true, + weekNumberTitle: _t("W"), + schedulerLicenseKey: "GPL-My-Project-Is-Open-Source", + }; + }, + /** + * Return a domain from the date range + * + * @private + * @returns {Array} A domain containing datetimes start and stop in UTC + * those datetimes are formatted according to server's standards + */ + _getRangeDomain: function () { + // Build OpenERP Domain to filter object by this.mapping.date_start field + // between given start, end dates. + var domain = [ + [this.mapping.date_start, "<=", dateToServer(this.data.end_date)], + ]; + if (this.mapping.date_stop) { + domain.push([ + this.mapping.date_stop, + ">=", + dateToServer(this.data.start_date), + ]); + } else if (!this.mapping.date_delay) { + domain.push([ + this.mapping.date_start, + ">=", + dateToServer(this.data.start_date), + ]); + } + return domain; + }, + /** + * @private + * @returns {Deferred} + */ + _getResources: function () { + var self = this; + var resource = this.fields[this.resourceField]; + return this._rpc({ model: resource.relation, - method: 'search_read', + method: "search_read", context: self.data.context, - fields: ['id', 'name'], - domain: resource.domain.concat([["ur_id", "=", session.ur_id], ["active", "=", true]]), - order: 'id', - }) - .then(function (resources) { - self.data.resource = _.map(resources, function(r) { + fields: ["id", "name"], + domain: resource.domain.concat([ + ["ur_id", "=", session.ur_id], + ["active", "=", true], + ]), + order: "id", + }).then(function (resources) { + self.data.resource = _.map(resources, function (r) { return { - 'id': r.id, - 'title': r.name - } + id: r.id, + title: r.name, + }; }); }); - }, - /** - * @private - * @returns {Deferred} - */ - _loadCalendar: function () { - var self = this; - this.data.fullWidth = this.call('local_storage', 'getItem', 'calendar_fullWidth') === true; - this.data.fc_options = this._getFullCalendarOptions(); - this._getResources(); - - var defs = _.map(this.data.filters, this._loadFilter.bind(this)); - - return $.when.apply($, defs).then(function () { - return self._rpc({ - model: self.modelName, - method: 'search_read', - context: self.data.context, - fields: self.fieldNames, - domain: self.data.domain.concat(self._getRangeDomain()).concat(self._getFilterDomain()) - }) - .then(function (events) { - self._parseServerData(events); - self.data.data = _.map(events, self._recordToCalendarEvent.bind(self)); - return $.when( - self._loadColors(self.data, self.data.data), - self._loadRecordsToFilters(self.data, self.data.data) - ); - }); - }); - }, - - - - /** - * @private - * @param {any} element - * @param {any} events - * @returns {Deferred} - */ - _loadColors: function (element, events) { - if (this.fieldColor) { - var fieldName = this.fieldColor; - _.each(events, function (event) { - var value = event.record[fieldName]; - event.color_index = _.isArray(value) ? value[0] : value; + }, + /** + * @private + * @returns {Deferred} + */ + _loadCalendar: function () { + var self = this; + this.data.fullWidth = + this.call("local_storage", "getItem", "calendar_fullWidth") === true; + this.data.fc_options = this._getFullCalendarOptions(); + this._getResources(); + + var defs = _.map(this.data.filters, this._loadFilter.bind(this)); + + return $.when.apply($, defs).then(function () { + return self + ._rpc({ + model: self.modelName, + method: "search_read", + context: self.data.context, + fields: self.fieldNames, + domain: self.data.domain + .concat(self._getRangeDomain()) + .concat(self._getFilterDomain()), + }) + .then(function (events) { + self._parseServerData(events); + self.data.data = _.map( + events, + self._recordToCalendarEvent.bind(self) + ); + return $.when( + self._loadColors(self.data, self.data.data), + self._loadRecordsToFilters(self.data, self.data.data) + ); + }); }); - this.model_color = this.fields[fieldName].relation || element.model; - } - return $.Deferred().resolve(); - }, - /** - * @private - * @param {any} filter - * @returns {Deferred} - */ - _loadFilter: function (filter) { - if (!filter.write_model) { - return; - } - - var field = this.fields[filter.fieldName]; - return this._rpc({ + }, + + /** + * @private + * @param {any} element + * @param {any} events + * @returns {Deferred} + */ + _loadColors: function (element, events) { + if (this.fieldColor) { + var fieldName = this.fieldColor; + _.each(events, function (event) { + var value = event.record[fieldName]; + event.color_index = _.isArray(value) ? value[0] : value; + }); + this.model_color = this.fields[fieldName].relation || element.model; + } + return $.Deferred().resolve(); + }, + /** + * @private + * @param {any} filter + * @returns {Deferred} + */ + _loadFilter: function (filter) { + if (!filter.write_model) { + return; + } + + var field = this.fields[filter.fieldName]; + return this._rpc({ model: filter.write_model, - method: 'search_read', + method: "search_read", domain: [["user_id", "=", session.uid]], fields: [filter.write_field], - }) - .then(function (res) { + }).then(function (res) { var records = _.map(res, function (record) { var _value = record[filter.write_field]; var value = _.isArray(_value) ? _value[0] : _value; - var f = _.find(filter.filters, function (f) {return f.value === value;}); - var formater = fieldUtils.format[_.contains(['many2many', 'one2many'], field.type) ? 'many2one' : field.type]; + var f = _.find(filter.filters, function (f) { + return f.value === value; + }); + var formater = + fieldUtils.format[ + _.contains(["many2many", "one2many"], field.type) + ? "many2one" + : field.type + ]; return { - 'id': record.id, - 'value': value, - 'label': formater(_value, field), - 'active': !f || f.active, + id: record.id, + value: value, + label: formater(_value, field), + active: !f || f.active, }; }); - records.sort(function (f1,f2) { + records.sort(function (f1, f2) { return _.string.naturalCmp(f2.label, f1.label); }); - // add my profile - if (field.relation === 'res.partner' || field.relation === 'res.users') { - var value = field.relation === 'res.partner' ? session.partner_id : session.uid; + // Add my profile + if ( + field.relation === "res.partner" || + field.relation === "res.users" + ) { + var value = + field.relation === "res.partner" + ? session.partner_id + : session.uid; var me = _.find(records, function (record) { return record.value === value; }); if (me) { records.splice(records.indexOf(me), 1); } else { - var f = _.find(filter.filters, function (f) {return f.value === value;}); + var f = _.find(filter.filters, function (f) { + return f.value === value; + }); me = { - 'value': value, - 'label': session.name + _t(" [Me]"), - 'active': !f || f.active, + value: value, + label: session.name + _t(" [Me]"), + active: !f || f.active, }; } records.unshift(me); } - // add all selection + // Add all selection records.push({ - 'value': 'all', - 'label': field.relation === 'res.partner' || field.relation === 'res.users' ? _t("Everybody's calendars") : _t("Everything"), - 'active': filter.all, + value: "all", + label: + field.relation === "res.partner" || + field.relation === "res.users" + ? _t("Everybody's calendars") + : _t("Everything"), + active: filter.all, }); filter.filters = records; }); - }, - /** - * @private - * @param {any} element - * @param {any} events - * @returns {Deferred} - */ - _loadRecordsToFilters: function (element, events) { - var self = this; - var new_filters = {}; - var to_read = {}; - - _.each(this.data.filters, function (filter, fieldName) { - var field = self.fields[fieldName]; - - new_filters[fieldName] = filter; - if (filter.write_model) { - if (field.relation === self.model_color) { - _.each(filter.filters, function (f) { - f.color_index = f.value; - }); + }, + /** + * @private + * @param {any} element + * @param {any} events + * @returns {Deferred} + */ + _loadRecordsToFilters: function (element, events) { + var self = this; + var new_filters = {}; + var to_read = {}; + + _.each(this.data.filters, function (filter, fieldName) { + var field = self.fields[fieldName]; + + new_filters[fieldName] = filter; + if (filter.write_model) { + if (field.relation === self.model_color) { + _.each(filter.filters, function (f) { + f.color_index = f.value; + }); + } + return; } - return; - } - _.each(filter.filters, function (filter) { - filter.display = !filter.active; - }); + _.each(filter.filters, function (filter) { + filter.display = !filter.active; + }); - var fs = []; - var undefined_fs = []; - _.each(events, function (event) { - var data = event.record[fieldName]; - if (!_.contains(['many2many', 'one2many'], field.type)) { - data = [data]; - } else { - to_read[field.relation] = (to_read[field.relation] || []).concat(data); - } - _.each(data, function (_value) { - var value = _.isArray(_value) ? _value[0] : _value; - var f = { - 'color_index': self.model_color === (field.relation || element.model) ? value : false, - 'value': value, - 'label': fieldUtils.format[field.type](_value, field) || _t("Undefined"), - 'avatar_model': field.relation || element.model, - }; - // if field used as color does not have value then push filter in undefined_fs, - // such filters should come last in filter list with Undefined string, later merge it with fs - value ? fs.push(f) : undefined_fs.push(f); + var fs = []; + var undefined_fs = []; + _.each(events, function (event) { + var data = event.record[fieldName]; + if (!_.contains(["many2many", "one2many"], field.type)) { + data = [data]; + } else { + to_read[field.relation] = ( + to_read[field.relation] || [] + ).concat(data); + } + _.each(data, function (_value) { + var value = _.isArray(_value) ? _value[0] : _value; + var f = { + color_index: + self.model_color === (field.relation || element.model) + ? value + : false, + value: value, + label: + fieldUtils.format[field.type](_value, field) || + _t("Undefined"), + avatar_model: field.relation || element.model, + }; + // If field used as color does not have value then push filter in undefined_fs, + // such filters should come last in filter list with Undefined string, later merge it with fs + value ? fs.push(f) : undefined_fs.push(f); + }); + }); + _.each(_.union(fs, undefined_fs), function (f) { + var f1 = _.findWhere(filter.filters, f); + if (f1) { + f1.display = true; + } else { + f.display = f.active = true; + filter.filters.push(f); + } }); }); - _.each(_.union(fs, undefined_fs), function (f) { - var f1 = _.findWhere(filter.filters, f); - if (f1) { - f1.display = true; - } else { - f.display = f.active = true; - filter.filters.push(f); - } + + var defs = []; + _.each(to_read, function (ids, model) { + defs.push( + self + ._rpc({ + model: model, + method: "name_get", + args: [_.uniq(ids)], + }) + .then(function (res) { + to_read[model] = _.object(res); + }) + ); }); - }); - - var defs = []; - _.each(to_read, function (ids, model) { - defs.push(self._rpc({ - model: model, - method: 'name_get', - args: [_.uniq(ids)], - }) - .then(function (res) { - to_read[model] = _.object(res); - })); - }); - return $.when.apply($, defs).then(function () { - _.each(self.data.filters, function (filter) { - if (filter.write_model) { - return; - } - if (filter.filters.length && (filter.filters[0].avatar_model in to_read)) { - _.each(filter.filters, function (f) { - f.label = to_read[f.avatar_model][f.value]; - }); - } + return $.when.apply($, defs).then(function () { + _.each(self.data.filters, function (filter) { + if (filter.write_model) { + return; + } + if ( + filter.filters.length && + filter.filters[0].avatar_model in to_read + ) { + _.each(filter.filters, function (f) { + f.label = to_read[f.avatar_model][f.value]; + }); + } + }); }); - }); - }, - /** - * parse the server values to javascript framwork - * - * @private - * @param {Object} data the server data to parse - */ - _parseServerData: function (data) { - var self = this; - _.each(data, function(event) { - _.each(self.fieldNames, function (fieldName) { - event[fieldName] = self._parseServerValue(self.fields[fieldName], event[fieldName]); + }, + /** + * Parse the server values to javascript framwork + * + * @private + * @param {Object} data the server data to parse + */ + _parseServerData: function (data) { + var self = this; + _.each(data, function (event) { + _.each(self.fieldNames, function (fieldName) { + event[fieldName] = self._parseServerValue( + self.fields[fieldName], + event[fieldName] + ); + }); }); - }); - }, - /** - * Transform OpenERP event object to fullcalendar event object - * - * @private - * @param {Object} evt - */ - _recordToCalendarEvent: function (evt) { - var date_start; - var date_stop; - var date_delay = evt[this.mapping.date_delay] || 1.0, - all_day = this.fields[this.mapping.date_start].type === 'date' || - this.mapping.all_day && evt[this.mapping.all_day] || false, - the_title = '', - attendees = []; - - if (!all_day) { - date_start = evt[this.mapping.date_start].clone(); - date_stop = this.mapping.date_stop ? evt[this.mapping.date_stop].clone() : null; - } else { - date_start = evt[this.mapping.date_start].clone().startOf('day'); - date_stop = this.mapping.date_stop ? evt[this.mapping.date_stop].clone().startOf('day') : null; - } - - if (!date_stop && date_delay) { - date_stop = date_start.clone().add(date_delay,'hours'); - } - - if (!all_day) { - date_start.add(this.getSession().getTZOffset(date_start), 'minutes'); - date_stop.add(this.getSession().getTZOffset(date_stop), 'minutes'); - } - - - if (this.mapping.all_day && evt[this.mapping.all_day]) { - date_stop.add(1, 'days'); - } - var r = { - 'record': evt, - 'start': date_start.format(), - 'end': date_stop.format(), - 'r_start': date_start, - 'r_end': date_stop, - 'title': the_title, - 'allDay': all_day, - 'id': evt.id, - 'resourceIds': evt[this.resourceField], - 'attendees': attendees, - }; - - if (this.mapping.all_day && evt[this.mapping.all_day]) { - r.start = date_start.format('YYYY-MM-DD'); - r.end = date_stop.format('YYYY-MM-DD'); - } else if (this.data.scale === 'month' && this.fields[this.mapping.date_start].type !== 'date') { - // In month, FullCalendar gives the end day as the - // next day at midnight (instead of 23h59). - date_stop.add(1, 'days'); - - // allow to resize in month mode - r.reset_allday = r.allDay; - r.allDay = true; - r.start = date_start.format('YYYY-MM-DD'); - r.end = date_stop.startOf('day').format('YYYY-MM-DD'); - } - - return r; - }, -}); + }, + /** + * Transform OpenERP event object to fullcalendar event object + * + * @private + * @param {Object} evt + */ + _recordToCalendarEvent: function (evt) { + var date_start; + var date_stop; + var date_delay = evt[this.mapping.date_delay] || 1.0, + all_day = + this.fields[this.mapping.date_start].type === "date" || + (this.mapping.all_day && evt[this.mapping.all_day]) || + false, + the_title = "", + attendees = []; + + if (!all_day) { + date_start = evt[this.mapping.date_start].clone(); + date_stop = this.mapping.date_stop + ? evt[this.mapping.date_stop].clone() + : null; + } else { + date_start = evt[this.mapping.date_start].clone().startOf("day"); + date_stop = this.mapping.date_stop + ? evt[this.mapping.date_stop].clone().startOf("day") + : null; + } + + if (!date_stop && date_delay) { + date_stop = date_start.clone().add(date_delay, "hours"); + } + + if (!all_day) { + date_start.add(this.getSession().getTZOffset(date_start), "minutes"); + date_stop.add(this.getSession().getTZOffset(date_stop), "minutes"); + } + + if (this.mapping.all_day && evt[this.mapping.all_day]) { + date_stop.add(1, "days"); + } + var r = { + record: evt, + start: date_start.format(), + end: date_stop.format(), + r_start: date_start, + r_end: date_stop, + title: the_title, + allDay: all_day, + id: evt.id, + resourceIds: evt[this.resourceField], + attendees: attendees, + }; + + if (this.mapping.all_day && evt[this.mapping.all_day]) { + r.start = date_start.format("YYYY-MM-DD"); + r.end = date_stop.format("YYYY-MM-DD"); + } else if ( + this.data.scale === "month" && + this.fields[this.mapping.date_start].type !== "date" + ) { + // In month, FullCalendar gives the end day as the + // next day at midnight (instead of 23h59). + date_stop.add(1, "days"); + + // Allow to resize in month mode + r.reset_allday = r.allDay; + r.allDay = true; + r.start = date_start.format("YYYY-MM-DD"); + r.end = date_stop.startOf("day").format("YYYY-MM-DD"); + } + return r; + }, + }); }); diff --git a/static/src/js/resource_renderer.js b/static/src/js/resource_renderer.js index 8a7e744..9c2ff63 100644 --- a/static/src/js/resource_renderer.js +++ b/static/src/js/resource_renderer.js @@ -1,541 +1,585 @@ -odoo.define('cgscop_fullcalendar.ResourceRenderer', function (require) { -"use strict"; +odoo.define("cgscop_fullcalendar.ResourceRenderer", function (require) { + "use strict"; -var AbstractRenderer = require('web.AbstractRenderer'); -var config = require('web.config'); -var core = require('web.core'); -var Dialog = require('web.Dialog'); -var field_utils = require('web.field_utils'); -var FieldManagerMixin = require('web.FieldManagerMixin'); -var QWeb = require('web.QWeb'); -var relational_fields = require('web.relational_fields'); -var session = require('web.session'); -var utils = require('web.utils'); -var Widget = require('web.Widget'); + var AbstractRenderer = require("web.AbstractRenderer"); + var config = require("web.config"); + var core = require("web.core"); + var Dialog = require("web.Dialog"); + var field_utils = require("web.field_utils"); + var FieldManagerMixin = require("web.FieldManagerMixin"); + var QWeb = require("web.QWeb"); + var relational_fields = require("web.relational_fields"); + var session = require("web.session"); + var utils = require("web.utils"); + var Widget = require("web.Widget"); -var _t = core._t; -var qweb = core.qweb; + var _t = core._t; + var qweb = core.qweb; -var scales = { - day: 'resourceTimeGridDay', -}; + var scales = { + day: "resourceTimeGridDay", + }; -var SidebarFilterM2O = relational_fields.FieldMany2One.extend({ - _getSearchBlacklist: function () { - return this._super.apply(this, arguments).concat(this.filter_ids || []); - }, -}); + var SidebarFilterM2O = relational_fields.FieldMany2One.extend({ + _getSearchBlacklist: function () { + return this._super.apply(this, arguments).concat(this.filter_ids || []); + }, + }); -var SidebarFilter = Widget.extend(FieldManagerMixin, { - template: 'ResourceView.sidebar.filter', - custom_events: _.extend({}, FieldManagerMixin.custom_events, { - field_changed: '_onFieldChanged', - }), - /** - * @constructor - * @param {Widget} parent - * @param {Object} options - * @param {string} options.fieldName - * @param {Object[]} options.filters A filter is an object with the - * following keys: id, value, label, active, avatar_model, color, - * can_be_removed - * @param {Object} [options.favorite] this is an object with the following - * keys: fieldName, model, fieldModel - */ - init: function (parent, options) { - this._super.apply(this, arguments); - FieldManagerMixin.init.call(this); + var SidebarFilter = Widget.extend(FieldManagerMixin, { + template: "ResourceView.sidebar.filter", + custom_events: _.extend({}, FieldManagerMixin.custom_events, { + field_changed: "_onFieldChanged", + }), + /** + * @class + * @param {Widget} parent + * @param {Object} options + * @param {String} options.fieldName + * @param {Object[]} options.filters A filter is an object with the + * following keys: id, value, label, active, avatar_model, color, + * can_be_removed + * @param {Object} [options.favorite] this is an object with the following + * keys: fieldName, model, fieldModel + */ + init: function (parent, options) { + this._super.apply(this, arguments); + FieldManagerMixin.init.call(this); - this.title = options.title; - this.fields = options.fields; - this.fieldName = options.fieldName; - this.write_model = options.write_model; - this.write_field = options.write_field; - this.avatar_field = options.avatar_field; - this.avatar_model = options.avatar_model; - this.filters = options.filters; - this.label = options.label; - this.getColor = options.getColor; - }, - /** - * @override - */ - willStart: function () { - var self = this; - var defs = [this._super.apply(this, arguments)]; - if (this.write_model || this.write_field) { - var def = this.model.makeRecord(this.write_model, [{ - name: this.write_field, - relation: this.fields[this.fieldName].relation, - type: 'many2one', - domain: self.fields[self.fieldName].domain, - }]).then(function (recordID) { - self.many2one = new SidebarFilterM2O(self, - self.write_field, - self.model.get(recordID), - { - mode: 'edit', - attrs: { - placeholder: _.str.sprintf(_t("Add %s"), self.title), - can_create: false + this.title = options.title; + this.fields = options.fields; + this.fieldName = options.fieldName; + this.write_model = options.write_model; + this.write_field = options.write_field; + this.avatar_field = options.avatar_field; + this.avatar_model = options.avatar_model; + this.filters = options.filters; + this.label = options.label; + this.getColor = options.getColor; + }, + /** + * @override + */ + willStart: function () { + var self = this; + var defs = [this._super.apply(this, arguments)]; + if (this.write_model || this.write_field) { + var def = this.model + .makeRecord(this.write_model, [ + { + name: this.write_field, + relation: this.fields[this.fieldName].relation, + type: "many2one", + domain: self.fields[self.fieldName].domain, }, - domain: self.fields[self.fieldName].domain, - readonly: true, - invisible: true, + ]) + .then(function (recordID) { + self.many2one = new SidebarFilterM2O( + self, + self.write_field, + self.model.get(recordID), + { + mode: "edit", + attrs: { + placeholder: _.str.sprintf( + _t("Add %s"), + self.title + ), + can_create: false, + }, + domain: self.fields[self.fieldName].domain, + readonly: true, + invisible: true, + } + ); }); - }); - defs.push(def); - } - return $.when.apply($, defs); - - }, - /** - * @override - */ - start: function () { - this._super(); - if (this.many2one) { - this.many2one.appendTo(this.$el); - this.many2one.filter_ids = _.without(_.pluck(this.filters, 'value'), 'all'); - } - this.$el.on('click', '.o_remove', this._onFilterRemove.bind(this)); - this.$el.on('click', '.custom-checkbox input', this._onFilterActive.bind(this)); - }, + defs.push(def); + } + return $.when.apply($, defs); + }, + /** + * @override + */ + start: function () { + this._super(); + if (this.many2one) { + this.many2one.appendTo(this.$el); + this.many2one.filter_ids = _.without( + _.pluck(this.filters, "value"), + "all" + ); + } + this.$el.on("click", ".o_remove", this._onFilterRemove.bind(this)); + this.$el.on( + "click", + ".custom-checkbox input", + this._onFilterActive.bind(this) + ); + }, - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- + // -------------------------------------------------------------------------- + // Handlers + // -------------------------------------------------------------------------- - /** - * @private - * @param {OdooEvent} event - */ - _onFieldChanged: function (event) { - var self = this; - event.stopPropagation(); - var createValues = {'user_id': session.uid}; - var value = event.data.changes[this.write_field].id; - createValues[this.write_field] = value; - this._rpc({ + /** + * @private + * @param {OdooEvent} event + */ + _onFieldChanged: function (event) { + var self = this; + event.stopPropagation(); + var createValues = {user_id: session.uid}; + var value = event.data.changes[this.write_field].id; + createValues[this.write_field] = value; + this._rpc({ model: this.write_model, - method: 'create', + method: "create", args: [createValues], - }) - .then(function () { - self.trigger_up('changeFilter', { - 'fieldName': self.fieldName, - 'value': value, - 'active': true, + }).then(function () { + self.trigger_up("changeFilter", { + fieldName: self.fieldName, + value: value, + active: true, }); }); - }, - /** - * @private - * @param {MouseEvent} e - */ - _onFilterActive: function (e) { - var $input = $(e.currentTarget); - this.trigger_up('changeFilter', { - 'fieldName': this.fieldName, - 'value': $input.closest('.o_calendar_filter_item').data('value'), - 'active': $input.prop('checked'), - }); - }, - /** - * @private - * @param {MouseEvent} e - */ - _onFilterRemove: function (e) { - var self = this; - var $filter = $(e.currentTarget).closest('.o_calendar_filter_item'); - Dialog.confirm(this, _t("Do you really want to delete this filter from favorites ?"), { - confirm_callback: function () { - self._rpc({ - model: self.write_model, - method: 'unlink', - args: [[$filter.data('id')]], - }) - .then(function () { - self.trigger_up('changeFilter', { - 'fieldName': self.fieldName, - 'id': $filter.data('id'), - 'active': false, - 'value': $filter.data('value'), + }, + /** + * @private + * @param {MouseEvent} e + */ + _onFilterActive: function (e) { + var $input = $(e.currentTarget); + this.trigger_up("changeFilter", { + fieldName: this.fieldName, + value: $input.closest(".o_calendar_filter_item").data("value"), + active: $input.prop("checked"), + }); + }, + /** + * @private + * @param {MouseEvent} e + */ + _onFilterRemove: function (e) { + var self = this; + var $filter = $(e.currentTarget).closest(".o_calendar_filter_item"); + Dialog.confirm( + this, + _t("Do you really want to delete this filter from favorites ?"), + { + confirm_callback: function () { + self._rpc({ + model: self.write_model, + method: "unlink", + args: [[$filter.data("id")]], + }).then(function () { + self.trigger_up("changeFilter", { + fieldName: self.fieldName, + id: $filter.data("id"), + active: false, + value: $filter.data("value"), + }); }); - }); - }, - }); - }, -}); + }, + } + ); + }, + }); -return AbstractRenderer.extend({ - template: "ResourceView", - events: _.extend({}, AbstractRenderer.prototype.events, { - 'click .o_calendar_sidebar_toggler': '_onToggleSidebar', - }), + return AbstractRenderer.extend({ + template: "ResourceView", + events: _.extend({}, AbstractRenderer.prototype.events, { + "click .o_calendar_sidebar_toggler": "_onToggleSidebar", + }), - /** - * @constructor - * @param {Widget} parent - * @param {Object} state - * @param {Object} params - */ - init: function (parent, state, params) { - this._super.apply(this, arguments); - this.displayFields = params.displayFields; - this.model = params.model; - this.filters = []; - this.color_map = {}; + /** + * @class + * @param {Widget} parent + * @param {Object} state + * @param {Object} params + */ + init: function (parent, state, params) { + this._super.apply(this, arguments); + this.displayFields = params.displayFields; + this.model = params.model; + this.filters = []; + this.color_map = {}; - if (params.eventTemplate) { - this.qweb = new QWeb(session.debug, {_s: session.origin}); - this.qweb.add_template(utils.json_node_to_xml(params.eventTemplate)); - } - }, - /** - * @override - * @returns {Deferred} - */ - start: function () { - this._initSidebar(); - this._initCalendar(); - if (config.device.isMobile) { - this._bindSwipe(); - } - return this._super(); - }, - /** - * @override - */ - destroy: function () { - if (this.calendar) { - this.calendar.destroy(); - } - if (this.$small_calendar) { - this.$small_calendar.datepicker('destroy'); - $('#ui-datepicker-div:empty').remove(); - } - this._super.apply(this, arguments); - }, + if (params.eventTemplate) { + this.qweb = new QWeb(session.debug, {_s: session.origin}); + this.qweb.add_template(utils.json_node_to_xml(params.eventTemplate)); + } + }, + /** + * @override + * @returns {Deferred} + */ + start: function () { + this._initSidebar(); + this._initCalendar(); + if (config.device.isMobile) { + this._bindSwipe(); + } + return this._super(); + }, + /** + * @override + */ + destroy: function () { + if (this.calendar) { + this.calendar.destroy(); + } + if (this.$small_calendar) { + this.$small_calendar.datepicker("destroy"); + $("#ui-datepicker-div:empty").remove(); + } + this._super.apply(this, arguments); + }, - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- - /** - * Note: this is not dead code, it is called by two template - * - * @param {any} key - * @returns {integer} - */ - getColor: function (key) { - if (!key) { - return false; - } - if (this.color_map[key]) { - return this.color_map[key]; - } - // check if the key is a css color - if (typeof key === 'string' && key.match(/^((#[A-F0-9]{3})|(#[A-F0-9]{6})|((hsl|rgb)a?\(\s*(?:(\s*\d{1,3}%?\s*),?){3}(\s*,[0-9.]{1,4})?\))|)$/i)) { - return this.color_map[key] = key; - } - var index = (((_.keys(this.color_map).length + 1) * 5) % 24) + 1; - this.color_map[key] = index; - return index; - }, - /** - * @override - */ - getLocalState: function () { - var $fcScroller = this.$calendar.find('.fc-scroller'); - return { - scrollPosition: $fcScroller.scrollTop(), - }; - }, - /** - * @override - */ - setLocalState: function (localState) { - if (localState.scrollPosition) { - var $fcScroller = this.$calendar.find('.fc-scroller'); - $fcScroller.scrollTop(localState.scrollPosition); - } - }, + /** + * Note: this is not dead code, it is called by two template + * + * @param {any} key + * @returns {integer} + */ + getColor: function (key) { + if (!key) { + return false; + } + if (this.color_map[key]) { + return this.color_map[key]; + } + // Check if the key is a css color + if ( + typeof key === "string" && + key.match( + /^((#[A-F0-9]{3})|(#[A-F0-9]{6})|((hsl|rgb)a?\(\s*(?:(\s*\d{1,3}%?\s*),?){3}(\s*,[0-9.]{1,4})?\))|)$/i + ) + ) { + return (this.color_map[key] = key); + } + var index = (((_.keys(this.color_map).length + 1) * 5) % 24) + 1; + this.color_map[key] = index; + return index; + }, + /** + * @override + */ + getLocalState: function () { + var $fcScroller = this.$calendar.find(".fc-scroller"); + return { + scrollPosition: $fcScroller.scrollTop(), + }; + }, + /** + * @override + */ + setLocalState: function (localState) { + if (localState.scrollPosition) { + var $fcScroller = this.$calendar.find(".fc-scroller"); + $fcScroller.scrollTop(localState.scrollPosition); + } + }, - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- + // -------------------------------------------------------------------------- + // Private + // -------------------------------------------------------------------------- - /** - * @private - * Bind handlers to enable swipe navigation - * - * @private - */ - _bindSwipe: function () { - var self = this; - var touchStartX; - var touchEndX; - this.$calendar.on('touchstart', function (event) { - touchStartX = event.originalEvent.touches[0].pageX; - }); - this.$calendar.on('touchend', function (event) { - touchEndX = event.originalEvent.changedTouches[0].pageX; - if (touchStartX - touchEndX > 100) { - self.trigger_up('next'); - } else if (touchStartX - touchEndX < -100) { - self.trigger_up('prev'); - } - }); - }, - /** - * @param {any} event - * @returns {string} the html for the rendered event - */ - _eventRender: function (event) { - var qweb_context = { - event: event, - fields: this.state.fields, - format: this._format.bind(this), - isMobile: config.device.isMobile, - read_only_mode: this.read_only_mode, - record: event.extendedProps.record, - user_context: session.user_context, - widget: this, - }; + /** + * @private + * Bind handlers to enable swipe navigation + * + * @private + */ + _bindSwipe: function () { + var self = this; + var touchStartX; + var touchEndX; + this.$calendar.on("touchstart", function (event) { + touchStartX = event.originalEvent.touches[0].pageX; + }); + this.$calendar.on("touchend", function (event) { + touchEndX = event.originalEvent.changedTouches[0].pageX; + if (touchStartX - touchEndX > 100) { + self.trigger_up("next"); + } else if (touchStartX - touchEndX < -100) { + self.trigger_up("prev"); + } + }); + }, + /** + * @param {any} event + * @returns {String} the html for the rendered event + */ + _eventRender: function (event) { + var qweb_context = { + event: event, + fields: this.state.fields, + format: this._format.bind(this), + isMobile: config.device.isMobile, + read_only_mode: this.read_only_mode, + record: event.extendedProps.record, + user_context: session.user_context, + widget: this, + }; - this.qweb_context = qweb_context; - if (_.isEmpty(qweb_context.record)) { - return ''; - } else { + this.qweb_context = qweb_context; + if (_.isEmpty(qweb_context.record)) { + return ""; + } return (this.qweb || qweb).render("resource-box", qweb_context); - } - }, - /** - * @private - * @param {any} record - * @param {any} fieldName - * @returns {string} - */ - _format: function (record, fieldName) { - var field = this.state.fields[fieldName]; - if (field.type === "one2many" || field.type === "many2many") { - return field_utils.format[field.type]({data: record[fieldName]}, field); - } else { - return field_utils.format[field.type](record[fieldName], field, {forceString: true}); - } - }, - /** - * Initialize the main calendar - * - * @private - */ - _initCalendar: function () { - var self = this; - - this.$calendar = this.$(".o_calendar_widget"); + }, + /** + * @private + * @param {any} record + * @param {any} fieldName + * @returns {String} + */ + _format: function (record, fieldName) { + var field = this.state.fields[fieldName]; + if (field.type === "one2many" || field.type === "many2many") { + return field_utils.format[field.type]({data: record[fieldName]}, field); + } + return field_utils.format[field.type](record[fieldName], field, { + forceString: true, + }); + }, + /** + * Initialize the main calendar + * + * @private + */ + _initCalendar: function () { + var self = this; - //Documentation here : http://arshaw.com/fullcalendar/docs/ - var fc_options = $.extend({}, this.state.fc_options, { - eventDrop: function (info) { - self.trigger_up('dropRecord', info.event); - }, - eventResize: function (info) { - self.trigger_up('updateRecord', info.event); - }, - eventClick: function (info) { - self.trigger_up('openEvent', info.event); - self.calendar.unselect(); - }, - select: function (selectionInfo) { - var data = {'start': selectionInfo.start, 'end': selectionInfo.end}; - if (self.state.context.default_name) { - data.title = self.state.context.default_name; - } - self.trigger_up('openCreate', data); - self.calendar.unselect(); - }, - eventRender: function (info) { - var $render = $(self._eventRender(info.event)); - info.el.querySelector('.fc-content').innerHTML = $render.html(); - if ($render.attr('class') && $render.attr('class') != ' ') { - info.el.className += " " + $render.attr('class'); - } else { - info.el.classList.add("o_calendar_color_none"); - } - var display_hour = ''; - if (!info.event.allDay) { - var start = info.event.extendedProps.r_start || info.event.start; - var end = info.event.extendedProps.r_end || info.event.end; - var timeFormat = _t.database.parameters.time_format.search("%H") != -1 ? 'HH:mm': 'h:mma'; + this.$calendar = this.$(".o_calendar_widget"); - display_hour = moment(start).format(timeFormat) + ' - ' + moment(end).format(timeFormat); - if (display_hour === '00:00 - 00:00') { - display_hour = _t('All day'); + // Documentation here : http://arshaw.com/fullcalendar/docs/ + var fc_options = $.extend({}, this.state.fc_options, { + eventDrop: function (info) { + self.trigger_up("dropRecord", info.event); + }, + eventResize: function (info) { + self.trigger_up("updateRecord", info.event); + }, + eventClick: function (info) { + self.trigger_up("openEvent", info.event); + self.calendar.unselect(); + }, + select: function (selectionInfo) { + var data = {start: selectionInfo.start, end: selectionInfo.end}; + if (self.state.context.default_name) { + data.title = self.state.context.default_name; } - } - info.el.querySelector('.fc-content .fc-time').innerHTML = display_hour; - }, - // Dirty hack to ensure a correct first render - eventAfterAllRender: function () { - $(window).trigger('resize'); - }, - viewSkeletonRender: function (info) { - var mode = 'resourceTimeGridDay'; - self.trigger_up('viewUpdated', { - mode: mode, - title: info.view.title, - }); - }, - datesRender: function (info) { - var mode = 'resourceTimeGridDay'; - self.trigger_up('viewUpdated', { - mode: mode, - title: info.view.title, - }); - }, - height: 'parent', - unselectAuto: true, - isRTL: _t.database.parameters.direction === "rtl", - resources: this.state.resource, - }); - this.calendar = new FullCalendar.Calendar(this.$calendar[0], fc_options); - }, - /** - * Initialize the mini calendar in the sidebar - * - * @private - */ - _initCalendarMini: function () { - var self = this; - this.$small_calendar = this.$(".o_calendar_mini"); - this.$small_calendar.datepicker({ - 'onSelect': function (datum, obj) { - self.trigger_up('changeDate', { - date: moment(new Date(+obj.currentYear , +obj.currentMonth, +obj.currentDay)) - }); - }, - 'dayNamesMin' : this.state.fc_options.dayNamesShort, - 'monthNames': this.state.fc_options.monthNamesShort, - 'firstDay': this.state.fc_options.firstDay, - }); - }, - /** - * Initialize the sidebar - * - * @private - */ - _initSidebar: function () { - this.$sidebar = this.$('.o_calendar_sidebar'); - this.$sidebar_container = this.$(".o_calendar_sidebar_container"); - this._initCalendarMini(); - }, - /** - * Render the calendar view, this is the main entry point. - * - * @override method from AbstractRenderer - * @private - * @returns {Deferred} - */ - _render: function () { - var $calendar = this.$calendar; - var $fc_view = $calendar.find('.fc-view'); - var scrollPosition = $fc_view.scrollLeft(); - var scrollTop = this.$calendar.find('.fc-scroller').scrollTop(); + self.trigger_up("openCreate", data); + self.calendar.unselect(); + }, + eventRender: function (info) { + var $render = $(self._eventRender(info.event)); + info.el.querySelector(".fc-content").innerHTML = $render.html(); + if ($render.attr("class") && $render.attr("class") != " ") { + info.el.className += " " + $render.attr("class"); + } else { + info.el.classList.add("o_calendar_color_none"); + } + var display_hour = ""; + if (!info.event.allDay) { + var start = + info.event.extendedProps.r_start || info.event.start; + var end = info.event.extendedProps.r_end || info.event.end; + var timeFormat = + _t.database.parameters.time_format.search("%H") != -1 + ? "HH:mm" + : "h:mma"; - $fc_view.scrollLeft(0); - this.calendar.unselect(); + display_hour = + moment(start).format(timeFormat) + + " - " + + moment(end).format(timeFormat); + if (display_hour === "00:00 - 00:00") { + display_hour = _t("All day"); + } + } + info.el.querySelector( + ".fc-content .fc-time" + ).innerHTML = display_hour; + }, + // Dirty hack to ensure a correct first render + eventAfterAllRender: function () { + $(window).trigger("resize"); + }, + viewSkeletonRender: function (info) { + var mode = "resourceTimeGridDay"; + self.trigger_up("viewUpdated", { + mode: mode, + title: info.view.title, + }); + }, + datesRender: function (info) { + var mode = "resourceTimeGridDay"; + self.trigger_up("viewUpdated", { + mode: mode, + title: info.view.title, + }); + }, + height: "parent", + unselectAuto: true, + isRTL: _t.database.parameters.direction === "rtl", + resources: this.state.resource, + }); + this.calendar = new FullCalendar.Calendar(this.$calendar[0], fc_options); + }, + /** + * Initialize the mini calendar in the sidebar + * + * @private + */ + _initCalendarMini: function () { + var self = this; + this.$small_calendar = this.$(".o_calendar_mini"); + this.$small_calendar.datepicker({ + onSelect: function (datum, obj) { + self.trigger_up("changeDate", { + date: moment( + new Date( + Number(obj.currentYear), + Number(obj.currentMonth), + Number(obj.currentDay) + ) + ), + }); + }, + dayNamesMin: this.state.fc_options.dayNamesShort, + monthNames: this.state.fc_options.monthNamesShort, + firstDay: this.state.fc_options.firstDay, + }); + }, + /** + * Initialize the sidebar + * + * @private + */ + _initSidebar: function () { + this.$sidebar = this.$(".o_calendar_sidebar"); + this.$sidebar_container = this.$(".o_calendar_sidebar_container"); + this._initCalendarMini(); + }, + /** + * Render the calendar view, this is the main entry point. + * + * @override method from AbstractRenderer + * @private + * @returns {Deferred} + */ + _render: function () { + var $calendar = this.$calendar; + var $fc_view = $calendar.find(".fc-view"); + var scrollPosition = $fc_view.scrollLeft(); + var scrollTop = this.$calendar.find(".fc-scroller").scrollTop(); - if (this.target_date !== this.state.target_date.toString()) { - this.calendar.gotoDate(moment(this.state.target_date).format()); - this.target_date = this.state.target_date.toString(); - } + $fc_view.scrollLeft(0); + this.calendar.unselect(); - this.$small_calendar.datepicker("setDate", this.state.highlight_date.toDate()) - .find('.o_selected_range') - .removeClass('o_color o_selected_range'); - var $a; - switch (this.state.scale) { - case 'week': $a = this.$small_calendar.find('tr:has(.ui-state-active) a'); break; - case 'day': $a = this.$small_calendar.find('a.ui-state-active'); break; - } - $a.addClass('o_selected_range'); - setTimeout(function () { - $a.not('.ui-state-active').addClass('o_color'); - }); + if (this.target_date !== this.state.target_date.toString()) { + this.calendar.gotoDate(moment(this.state.target_date).format()); + this.target_date = this.state.target_date.toString(); + } - $fc_view.scrollLeft(scrollPosition); + this.$small_calendar + .datepicker("setDate", this.state.highlight_date.toDate()) + .find(".o_selected_range") + .removeClass("o_color o_selected_range"); + var $a; + switch (this.state.scale) { + case "week": + $a = this.$small_calendar.find("tr:has(.ui-state-active) a"); + break; + case "day": + $a = this.$small_calendar.find("a.ui-state-active"); + break; + } + $a.addClass("o_selected_range"); + setTimeout(function () { + $a.not(".ui-state-active").addClass("o_color"); + }); - var fullWidth = this.state.fullWidth; - this.$('.o_calendar_sidebar_toggler') - .toggleClass('fa-close', !fullWidth) - .toggleClass('fa-chevron-left', fullWidth) - .attr('title', !fullWidth ? _('Close Sidebar') : _('Open Sidebar')); - this.$sidebar_container.toggleClass('o_sidebar_hidden', fullWidth); - this.$sidebar.toggleClass('o_hidden', fullWidth); + $fc_view.scrollLeft(scrollPosition); - this._renderFilters(); - this.$calendar.appendTo('body'); - if (scrollTop) { - this.calendar.render(); - } else { - this.calendar.render(); - } - this._renderEvents(); - this.$calendar.prependTo(this.$('.o_calendar_view')); + var fullWidth = this.state.fullWidth; + this.$(".o_calendar_sidebar_toggler") + .toggleClass("fa-close", !fullWidth) + .toggleClass("fa-chevron-left", fullWidth) + .attr("title", !fullWidth ? _("Close Sidebar") : _("Open Sidebar")); + this.$sidebar_container.toggleClass("o_sidebar_hidden", fullWidth); + this.$sidebar.toggleClass("o_hidden", fullWidth); - return this._super.apply(this, arguments); - }, - /** - * Render all events - * - * @private - */ - _renderEvents: function () { - var eventSources = this.calendar.getEventSources(); - if (eventSources.length > 0) { - eventSources[0].remove(); - } - this.calendar.addEventSource(this.state.data); + this._renderFilters(); + this.$calendar.appendTo("body"); + if (scrollTop) { + this.calendar.render(); + } else { + this.calendar.render(); + } + this._renderEvents(); + this.$calendar.prependTo(this.$(".o_calendar_view")); - }, - /** - * Render all filters - * - * @private - */ - _renderFilters: function () { - var self = this; - _.each(this.filters || (this.filters = []), function (filter) { - filter.destroy(); - }); - if (this.state.fullWidth) { - return; - } - _.each(this.state.filters, function (options) { - if (!_.find(options.filters, function (f) {return f.display == null || f.display;})) { + return this._super.apply(this, arguments); + }, + /** + * Render all events + * + * @private + */ + _renderEvents: function () { + var eventSources = this.calendar.getEventSources(); + if (eventSources.length > 0) { + eventSources[0].remove(); + } + this.calendar.addEventSource(this.state.data); + }, + /** + * Render all filters + * + * @private + */ + _renderFilters: function () { + var self = this; + _.each(this.filters || (this.filters = []), function (filter) { + filter.destroy(); + }); + if (this.state.fullWidth) { return; } - options.getColor = self.getColor.bind(self); - options.fields = self.state.fields; - var filter = new SidebarFilter(self, options); - filter.appendTo(self.$sidebar); - self.filters.push(filter); - }); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- + _.each(this.state.filters, function (options) { + if ( + !_.find(options.filters, function (f) { + return f.display == null || f.display; + }) + ) { + return; + } + options.getColor = self.getColor.bind(self); + options.fields = self.state.fields; + var filter = new SidebarFilter(self, options); + filter.appendTo(self.$sidebar); + self.filters.push(filter); + }); + }, - /** - * Toggle the sidebar - * - * @private - */ - _onToggleSidebar: function () { - this.trigger_up('toggleFullWidth'); - }, -}); + // -------------------------------------------------------------------------- + // Handlers + // -------------------------------------------------------------------------- + /** + * Toggle the sidebar + * + * @private + */ + _onToggleSidebar: function () { + this.trigger_up("toggleFullWidth"); + }, + }); }); diff --git a/static/src/js/resource_view.js b/static/src/js/resource_view.js index a1592b8..b3d87c9 100644 --- a/static/src/js/resource_view.js +++ b/static/src/js/resource_view.js @@ -1,171 +1,189 @@ -odoo.define('cgscop_fullcalendar.ResourceView', function (require) { -"use strict"; - -var AbstractView = require('web.AbstractView'); -var ResourceModel = require('cgscop_fullcalendar.ResourceModel'); -var ResourceController = require('cgscop_fullcalendar.ResourceController'); -var ResourceRenderer = require('cgscop_fullcalendar.ResourceRenderer'); -var core = require('web.core'); -var utils = require('web.utils'); -var view_registry = require('web.view_registry'); - -var _lt = core._lt; - -// gather the fields to get -var fieldsToGather = [ - "date_start", - "date_delay", - "date_stop", - "all_day", -]; - -var ResourceView = AbstractView.extend({ - display_name: 'Ressources', - icon: 'fa-calendar-check-o', - jsLibs: [ - '/cgscop_fullcalendar/static/lib/fullcalendar/packages/core/main.min.js', - '/cgscop_fullcalendar/static/lib/fullcalendar/packages/core/locales/fr.js', - '/cgscop_fullcalendar/static/lib/fullcalendar/packages/daygrid/main.min.js', - '/cgscop_fullcalendar/static/lib/fullcalendar/packages/timegrid/main.min.js', - '/cgscop_fullcalendar/static/lib/fullcalendar/packages/moment/main.min.js', - '/cgscop_fullcalendar/static/lib/fullcalendar/packages/interaction/main.min.js', - '/cgscop_fullcalendar/static/lib/fullcalendar/premium/resource-common/main.min.js', - '/cgscop_fullcalendar/static/lib/fullcalendar/premium/resource-daygrid/main.min.js', - '/cgscop_fullcalendar/static/lib/fullcalendar/premium/resource-timegrid/main.min.js', +odoo.define("cgscop_fullcalendar.ResourceView", function (require) { + "use strict"; + + var AbstractView = require("web.AbstractView"); + var ResourceModel = require("cgscop_fullcalendar.ResourceModel"); + var ResourceController = require("cgscop_fullcalendar.ResourceController"); + var ResourceRenderer = require("cgscop_fullcalendar.ResourceRenderer"); + var core = require("web.core"); + var utils = require("web.utils"); + var view_registry = require("web.view_registry"); + + var _lt = core._lt; + + // Gather the fields to get + var fieldsToGather = ["date_start", "date_delay", "date_stop", "all_day"]; + + var ResourceView = AbstractView.extend({ + display_name: "Ressources", + icon: "fa-calendar-check-o", + jsLibs: [ + "/cgscop_fullcalendar/static/lib/fullcalendar/packages/core/main.min.js", + "/cgscop_fullcalendar/static/lib/fullcalendar/packages/core/locales/fr.js", + "/cgscop_fullcalendar/static/lib/fullcalendar/packages/daygrid/main.min.js", + "/cgscop_fullcalendar/static/lib/fullcalendar/packages/timegrid/main.min.js", + "/cgscop_fullcalendar/static/lib/fullcalendar/packages/moment/main.min.js", + "/cgscop_fullcalendar/static/lib/fullcalendar/packages/interaction/main.min.js", + "/cgscop_fullcalendar/static/lib/fullcalendar/premium/resource-common/main.min.js", + "/cgscop_fullcalendar/static/lib/fullcalendar/premium/resource-daygrid/main.min.js", + "/cgscop_fullcalendar/static/lib/fullcalendar/premium/resource-timegrid/main.min.js", ], - cssLibs: [ - '/cgscop_fullcalendar/static/lib/fullcalendar/packages/core/main.min.css', - '/cgscop_fullcalendar/static/lib/fullcalendar/packages/timegrid/main.min.css', + cssLibs: [ + "/cgscop_fullcalendar/static/lib/fullcalendar/packages/core/main.min.css", + "/cgscop_fullcalendar/static/lib/fullcalendar/packages/timegrid/main.min.css", ], - config: { - Model: ResourceModel, - Controller: ResourceController, - Renderer: ResourceRenderer, - }, - viewType: 'resource', - groupable: false, - - /** - * @override - */ - init: function (viewInfo, params) { - this._super.apply(this, arguments); - var arch = this.arch; - var fields = this.fields; - var attrs = arch.attrs; - - if (!attrs.date_start) { - throw new Error(_lt("Calendar view has not defined 'date_start' attribute.")); - } - - var mapping = {}; - var fieldNames = fields.display_name ? ['display_name'] : []; - var displayFields = {}; - - _.each(fieldsToGather, function (field) { - if (arch.attrs[field]) { - var fieldName = attrs[field]; - mapping[field] = fieldName; - fieldNames.push(fieldName); + config: { + Model: ResourceModel, + Controller: ResourceController, + Renderer: ResourceRenderer, + }, + viewType: "resource", + groupable: false, + + /** + * @override + */ + init: function (viewInfo, params) { + this._super.apply(this, arguments); + var arch = this.arch; + var fields = this.fields; + var attrs = arch.attrs; + + if (!attrs.date_start) { + throw new Error( + _lt("Calendar view has not defined 'date_start' attribute.") + ); } - }); - var filters = {}; + var mapping = {}; + var fieldNames = fields.display_name ? ["display_name"] : []; + var displayFields = {}; + + _.each(fieldsToGather, function (field) { + if (arch.attrs[field]) { + var fieldName = attrs[field]; + mapping[field] = fieldName; + fieldNames.push(fieldName); + } + }); + + var filters = {}; - var eventLimit = attrs.event_limit !== null && (isNaN(+attrs.event_limit) ? _.str.toBool(attrs.event_limit) : +attrs.event_limit); + var eventLimit = + attrs.event_limit !== null && + (isNaN(Number(attrs.event_limit)) + ? _.str.toBool(attrs.event_limit) + : Number(attrs.event_limit)); - var modelFilters = []; - _.each(arch.children, function (child) { - if (child.tag !== 'field') return; - var fieldName = child.attrs.name; - fieldNames.push(fieldName); - if (!child.attrs.invisible) { - displayFields[fieldName] = child.attrs; + var modelFilters = []; + _.each(arch.children, function (child) { + if (child.tag !== "field") return; + var fieldName = child.attrs.name; + fieldNames.push(fieldName); + if (!child.attrs.invisible) { + displayFields[fieldName] = child.attrs; - if (params.sidebar === false) return; // if we have not sidebar, (eg: Dashboard), we don't use the filter "coworkers" + if (params.sidebar === false) return; // If we have not sidebar, (eg: Dashboard), we don't use the filter "coworkers" - if (child.attrs.write_model) { - filters[fieldName] = filters[fieldName] || { - 'title': fields[fieldName].string, - 'fieldName': fieldName, - 'filters': [], - }; + if (child.attrs.write_model) { + filters[fieldName] = filters[fieldName] || { + title: fields[fieldName].string, + fieldName: fieldName, + filters: [], + }; - filters[fieldName].write_model = child.attrs.write_model; - filters[fieldName].write_field = child.attrs.write_field; // can't use a x2many fields + filters[fieldName].write_model = child.attrs.write_model; + filters[fieldName].write_field = child.attrs.write_field; // Can't use a x2many fields - modelFilters.push(fields[fieldName].relation); + modelFilters.push(fields[fieldName].relation); + } } - } - }); - - if (attrs.color) { - var fieldName = attrs.color; - fieldNames.push(fieldName); - filters[fieldName] = { - 'title': fields[fieldName].string, - 'fieldName': fieldName, - 'filters': [], - }; - if (fields[fieldName].relation) { - if (['res.users', 'res.partner'].indexOf(fields[fieldName].relation) !== -1) { - filters[fieldName].avatar_field = 'image_small'; + }); + + if (attrs.color) { + var fieldName = attrs.color; + fieldNames.push(fieldName); + filters[fieldName] = { + title: fields[fieldName].string, + fieldName: fieldName, + filters: [], + }; + if (fields[fieldName].relation) { + if ( + ["res.users", "res.partner"].indexOf( + fields[fieldName].relation + ) !== -1 + ) { + filters[fieldName].avatar_field = "image_small"; + } + filters[fieldName].avatar_model = fields[fieldName].relation; } - filters[fieldName].avatar_model = fields[fieldName].relation; } - } - - if (_.isEmpty(displayFields)) { - displayFields = fields.display_name ? {'display_name': {}} : []; - } - - //if quick_add = False, we don't allow quick_add - //if quick_add = not specified in view, we use the default widgets.QuickCreate - //if quick_add = is NOT False and IS specified in view, we this one for widgets.QuickCreate' - this.controllerParams.quickAddPop = (!('quick_add' in attrs) || utils.toBoolElse(attrs.quick_add+'', true)); - this.controllerParams.disableQuickCreate = params.disable_quick_create || !this.controllerParams.quickAddPop; - - this.loadParams.resourceField = attrs.resource_field - - // If form_view_id is set, then the calendar view will open a form view - // with this id, when it needs to edit or create an event. - this.controllerParams.formViewId = - attrs.form_view_id ? parseInt(attrs.form_view_id, 10) : false; - if (!this.controllerParams.formViewId && params.action) { - var formViewDescr = _.find(params.action.views, function (v) { - return v.type === 'form'; - }); - if (formViewDescr) { - this.controllerParams.formViewId = formViewDescr.viewID; + + if (_.isEmpty(displayFields)) { + displayFields = fields.display_name ? {display_name: {}} : []; } - } - - this.controllerParams.readonlyFormViewId = !attrs.readonly_form_view_id || !utils.toBoolElse(attrs.readonly_form_view_id, true) ? false : attrs.readonly_form_view_id; - this.controllerParams.eventOpenPopup = utils.toBoolElse(attrs.event_open_popup || '', false); - this.controllerParams.mapping = mapping; - this.controllerParams.context = params.context || {}; - this.controllerParams.displayName = params.action && params.action.name; - - this.rendererParams.displayFields = displayFields; - this.rendererParams.eventTemplate = _.findWhere(arch.children, {'tag': 'templates'}); - this.rendererParams.model = viewInfo.model; - - this.loadParams.fieldNames = _.uniq(fieldNames); - this.loadParams.mapping = mapping; - this.loadParams.fields = fields; - this.loadParams.fieldsInfo = viewInfo.fieldsInfo; - this.loadParams.editable = !fields[mapping.date_start].readonly; - this.loadParams.creatable = true; - this.loadParams.eventLimit = eventLimit; - this.loadParams.fieldColor = attrs.color; - - this.loadParams.filters = filters; - this.loadParams.mode = (params.context && params.context.default_mode) || attrs.mode; - this.loadParams.initialDate = moment(params.initialDate || new Date()); - }, -}); -view_registry.add('resource', ResourceView); -return ResourceView; + // If quick_add = False, we don't allow quick_add + // if quick_add = not specified in view, we use the default widgets.QuickCreate + // if quick_add = is NOT False and IS specified in view, we this one for widgets.QuickCreate' + this.controllerParams.quickAddPop = + !("quick_add" in attrs) || + utils.toBoolElse(String(attrs.quick_add), true); + this.controllerParams.disableQuickCreate = + params.disable_quick_create || !this.controllerParams.quickAddPop; + + this.loadParams.resourceField = attrs.resource_field; + + // If form_view_id is set, then the calendar view will open a form view + // with this id, when it needs to edit or create an event. + this.controllerParams.formViewId = attrs.form_view_id + ? parseInt(attrs.form_view_id, 10) + : false; + if (!this.controllerParams.formViewId && params.action) { + var formViewDescr = _.find(params.action.views, function (v) { + return v.type === "form"; + }); + if (formViewDescr) { + this.controllerParams.formViewId = formViewDescr.viewID; + } + } + this.controllerParams.readonlyFormViewId = + !attrs.readonly_form_view_id || + !utils.toBoolElse(attrs.readonly_form_view_id, true) + ? false + : attrs.readonly_form_view_id; + this.controllerParams.eventOpenPopup = utils.toBoolElse( + attrs.event_open_popup || "", + false + ); + this.controllerParams.mapping = mapping; + this.controllerParams.context = params.context || {}; + this.controllerParams.displayName = params.action && params.action.name; + + this.rendererParams.displayFields = displayFields; + this.rendererParams.eventTemplate = _.findWhere(arch.children, { + tag: "templates", + }); + this.rendererParams.model = viewInfo.model; + + this.loadParams.fieldNames = _.uniq(fieldNames); + this.loadParams.mapping = mapping; + this.loadParams.fields = fields; + this.loadParams.fieldsInfo = viewInfo.fieldsInfo; + this.loadParams.editable = !fields[mapping.date_start].readonly; + this.loadParams.creatable = true; + this.loadParams.eventLimit = eventLimit; + this.loadParams.fieldColor = attrs.color; + + this.loadParams.filters = filters; + this.loadParams.mode = + (params.context && params.context.default_mode) || attrs.mode; + this.loadParams.initialDate = moment(params.initialDate || new Date()); + }, + }); + + view_registry.add("resource", ResourceView); + return ResourceView; }); diff --git a/static/src/xml/resource_view.xml b/static/src/xml/resource_view.xml index 78a4460..c44a2d8 100644 --- a/static/src/xml/resource_view.xml +++ b/static/src/xml/resource_view.xml @@ -1,25 +1,34 @@ <template> <div t-name="ResourceView" class="o_calendar_container"> <div class="o_calendar_view"> - <div class="o_calendar_buttons" role="toolbar" aria-label="Calendar toolbar"/> + <div + class="o_calendar_buttons" + role="toolbar" + aria-label="Calendar toolbar" + /> <div class="o_calendar_widget cgscop_resource" /> </div> <div class="o_calendar_sidebar_container d-none d-md-block"> - <i class="o_calendar_sidebar_toggler fa"/> + <i class="o_calendar_sidebar_toggler fa" /> <div class="o_calendar_sidebar"> - <div class="o_calendar_mini"/> + <div class="o_calendar_mini" /> </div> </div> </div> <t t-name="resource-box"> - <t t-set="color" t-value="widget.getColor(event.extendedProps.color_index)"/> - <div t-att-style="typeof color === 'string' ? ('background-color:'+color)+';' : ''" t-attf-class="#{record.is_highlighted ? 'o_event_hightlight' : ''} #{typeof color === 'number' ? 'o_calendar_color_'+color : ''}"> - <div class="fc-time"/> + <t t-set="color" t-value="widget.getColor(event.extendedProps.color_index)" /> + <div + t-att-style="typeof color === 'string' ? ('background-color:'+color)+';' : ''" + t-attf-class="#{record.is_highlighted ? 'o_event_hightlight' : ''} #{typeof color === 'number' ? 'o_calendar_color_'+color : ''}" + > + <div class="fc-time" /> <div class="o_fields"> <t t-foreach="widget.displayFields" t-as="name"> - <div t-attf-class="o_field_#{name} o_field_type_#{fields[name].type}"> - <t t-esc="format(record, name)"/> + <div + t-attf-class="o_field_#{name} o_field_type_#{fields[name].type}" + > + <t t-esc="format(record, name)" /> </div> </t> </div> @@ -29,41 +38,85 @@ <t t-name="ResourceView.buttons"> <div class="o_calendar_buttons"> <t t-if="!isMobile"> - <button class="o_calendar_button_prev btn btn-primary" aria-label="Previous" title="Previous"><span class="fa fa-arrow-left"/></button> + <button + class="o_calendar_button_prev btn btn-primary" + aria-label="Previous" + title="Previous" + ><span class="fa fa-arrow-left" /></button> <button class="o_calendar_button_today btn btn-primary">Today</button> - <button class="o_calendar_button_next btn btn-primary" aria-label="Next" title="Next"><span class="fa fa-arrow-right"/></button> + <button + class="o_calendar_button_next btn btn-primary" + aria-label="Next" + title="Next" + ><span class="fa fa-arrow-right" /></button> </t> </div> </t> <t t-name="ResourceView.sidebar.filter"> <div class="o_calendar_filter"> - <h3 t-if="widget.title"><t t-esc="widget.title"/></h3> + <h3 t-if="widget.title"><t t-esc="widget.title" /></h3> <div class="o_calendar_filter_items"> - <div t-foreach="widget.filters" t-as="filter" t-if="filter.display == null || filter.display" class="o_calendar_filter_item" t-att-data-value="filter.value" t-att-data-id="filter.id"> - <t t-set="id_for_label" t-value="_.uniqueId('o_calendar_filter_item_')"/> + <div + t-foreach="widget.filters" + t-as="filter" + t-if="filter.display == null || filter.display" + class="o_calendar_filter_item" + t-att-data-value="filter.value" + t-att-data-id="filter.id" + > + <t + t-set="id_for_label" + t-value="_.uniqueId('o_calendar_filter_item_')" + /> <div class="custom-control custom-control-inline custom-checkbox"> - <input type="checkbox" + <input + type="checkbox" t-att-id="id_for_label" name="selection" class="custom-control-input" - t-att-checked="(filter.active ? true : undefined)"/> - <label t-att-for="id_for_label" - class="custom-control-label"> + t-att-checked="(filter.active ? true : undefined)" + /> + <label t-att-for="id_for_label" class="custom-control-label"> <t t-if="filter.value == 'all'"> - <span><i class="fa fa-users fa-fw o_cal_avatar" role="img" aria-label="Avatar" title="Avatar"/></span> + <span><i + class="fa fa-users fa-fw o_cal_avatar" + role="img" + aria-label="Avatar" + title="Avatar" + /></span> </t> - <t t-if="widget.avatar_field && (filter.value != 'all') && (filter.value)"> - <img t-attf-src="/web/image/#{widget.avatar_model}/#{filter.value}/#{widget.avatar_field}" class="o_cal_avatar" alt="Avatar"/> + <t + t-if="widget.avatar_field && (filter.value != 'all') && (filter.value)" + > + <img + t-attf-src="/web/image/#{widget.avatar_model}/#{filter.value}/#{widget.avatar_field}" + class="o_cal_avatar" + alt="Avatar" + /> </t> - <t t-set="color" t-value="widget.getColor(filter.color_index)"/> - <span t-if="typeof color === 'number'" t-attf-class="color_filter o_underline_color_#{widget.getColor(filter.color_index)}"><t t-esc="filter.label"/></span> - <span t-elif="color" t-attf-style="border-bottom: 4px solid #{color};"><t t-esc="filter.label"/></span> - <span t-else=""><t t-esc="filter.label"/></span> + <t + t-set="color" + t-value="widget.getColor(filter.color_index)" + /> + <span + t-if="typeof color === 'number'" + t-attf-class="color_filter o_underline_color_#{widget.getColor(filter.color_index)}" + ><t t-esc="filter.label" /></span> + <span + t-elif="color" + t-attf-style="border-bottom: 4px solid #{color};" + ><t t-esc="filter.label" /></span> + <span t-else=""><t t-esc="filter.label" /></span> </label> </div> <t t-if="filter.id"> - <span class="o_remove fa fa-times" title="Remove this favorite from the list" role="img" aria-label="Remove this favorite from the list"/> + <span + class="o_remove fa fa-times" + title="Remove this favorite from the list" + role="img" + aria-label="Remove this favorite from the list" + /> </t> </div> </div> diff --git a/views/assets.xml b/views/assets.xml index 6c7adbb..3008872 100644 --- a/views/assets.xml +++ b/views/assets.xml @@ -1,37 +1,113 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8" ?> <odoo> - <template id="assets_backend" name="cgscop_fullcalendar assets" inherit_id="web.assets_backend"> + <template + id="assets_backend" + name="cgscop_fullcalendar assets" + inherit_id="web.assets_backend" + > <xpath expr="." position="inside"> - <link href="/cgscop_fullcalendar/static/lib/fullcalendar/packages/core/main.min.css" rel="stylesheet" /> - <link href="/cgscop_fullcalendar/static/lib/fullcalendar/packages/daygrid/main.min.css" rel="stylesheet" /> - <link href="/cgscop_fullcalendar/static/lib/fullcalendar/packages/list/main.min.css" rel="stylesheet" /> - <link href="/cgscop_fullcalendar/static/lib/fullcalendar/packages/timegrid/main.min.css" rel="stylesheet" /> - <link href="/cgscop_fullcalendar/static/lib/fullcalendar/packages/timegrid/main.min.css" rel="stylesheet" /> - <link href="/cgscop_fullcalendar/static/src/css/cgscop_fullcalendar.css" rel="stylesheet" /> + <link + href="/cgscop_fullcalendar/static/lib/fullcalendar/packages/core/main.min.css" + rel="stylesheet" + /> + <link + href="/cgscop_fullcalendar/static/lib/fullcalendar/packages/daygrid/main.min.css" + rel="stylesheet" + /> + <link + href="/cgscop_fullcalendar/static/lib/fullcalendar/packages/list/main.min.css" + rel="stylesheet" + /> + <link + href="/cgscop_fullcalendar/static/lib/fullcalendar/packages/timegrid/main.min.css" + rel="stylesheet" + /> + <link + href="/cgscop_fullcalendar/static/lib/fullcalendar/packages/timegrid/main.min.css" + rel="stylesheet" + /> + <link + href="/cgscop_fullcalendar/static/src/css/cgscop_fullcalendar.css" + rel="stylesheet" + /> <!-- librairies common --> - <script type="text/javascript" src="/cgscop_fullcalendar/static/lib/fullcalendar/packages/core/main.min.js"/> - <script type="text/javascript" src="/cgscop_fullcalendar/static/lib/fullcalendar/packages/core/locales/fr.js"/> - <script type="text/javascript" src="/cgscop_fullcalendar/static/lib/fullcalendar/packages/daygrid/main.min.js"/> - <script type="text/javascript" src="/cgscop_fullcalendar/static/lib/fullcalendar/packages/timegrid/main.min.js"/> - <script type="text/javascript" src="/cgscop_fullcalendar/static/lib/fullcalendar/packages/list/main.min.js"/> - <script type="text/javascript" src="/cgscop_fullcalendar/static/lib/fullcalendar/packages/interaction/main.min.js"/> - <script type="text/javascript" src="/cgscop_fullcalendar/static/lib/fullcalendar/packages/moment/main.min.js"/> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/lib/fullcalendar/packages/core/main.min.js" + /> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/lib/fullcalendar/packages/core/locales/fr.js" + /> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/lib/fullcalendar/packages/daygrid/main.min.js" + /> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/lib/fullcalendar/packages/timegrid/main.min.js" + /> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/lib/fullcalendar/packages/list/main.min.js" + /> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/lib/fullcalendar/packages/interaction/main.min.js" + /> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/lib/fullcalendar/packages/moment/main.min.js" + /> <!-- librairies premium --> - <script type="text/javascript" src="/cgscop_fullcalendar/static/lib/fullcalendar/premium/resource-common/main.min.js"/> - <script type="text/javascript" src="/cgscop_fullcalendar/static/lib/fullcalendar/premium/resource-daygrid/main.min.js"/> - <script type="text/javascript" src="/cgscop_fullcalendar/static/lib/fullcalendar/premium/resource-timegrid/main.min.js"/> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/lib/fullcalendar/premium/resource-common/main.min.js" + /> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/lib/fullcalendar/premium/resource-daygrid/main.min.js" + /> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/lib/fullcalendar/premium/resource-timegrid/main.min.js" + /> <!-- custom js --> - <script type="text/javascript" src="/cgscop_fullcalendar/static/src/js/calendar_controller.js"/> - <script type="text/javascript" src="/cgscop_fullcalendar/static/src/js/calendar_model.js"/> - <script type="text/javascript" src="/cgscop_fullcalendar/static/src/js/calendar_renderer.js"/> - <script type="text/javascript" src="/cgscop_fullcalendar/static/src/js/calendar_view.js"/> - <script type="text/javascript" src="/cgscop_fullcalendar/static/src/js/resource_controller.js"/> - <script type="text/javascript" src="/cgscop_fullcalendar/static/src/js/resource_model.js"/> - <script type="text/javascript" src="/cgscop_fullcalendar/static/src/js/resource_renderer.js"/> - <script type="text/javascript" src="/cgscop_fullcalendar/static/src/js/resource_view.js"/> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/src/js/calendar_controller.js" + /> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/src/js/calendar_model.js" + /> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/src/js/calendar_renderer.js" + /> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/src/js/calendar_view.js" + /> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/src/js/resource_controller.js" + /> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/src/js/resource_model.js" + /> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/src/js/resource_renderer.js" + /> + <script + type="text/javascript" + src="/cgscop_fullcalendar/static/src/js/resource_view.js" + /> </xpath> </template> diff --git a/views/res_users.xml b/views/res_users.xml index fc452da..45e6a3d 100644 --- a/views/res_users.xml +++ b/views/res_users.xml @@ -1,17 +1,16 @@ -<?xml version="1.0"?> +<?xml version="1.0" ?> <!-- Copyright 2020 Le Filament License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). --> - <odoo> <data> <record id="res_users_calendar_form" model="ir.ui.view"> <field name="name">res.users.calendar.form</field> <field name="model">res.users</field> - <field name="inherit_id" ref="base.view_users_form"/> + <field name="inherit_id" ref="base.view_users_form" /> <field name="arch" type="xml"> <xpath expr="//group[@name='preferences']" position="after"> <group name="calendar" string="Calendrier"> - <field name="is_weekend" widget="boolean_toggle"/> + <field name="is_weekend" widget="boolean_toggle" /> </group> </xpath> </field> @@ -20,11 +19,11 @@ <record id="res_users_calendar_form_simple" model="ir.ui.view"> <field name="name">res.users.preferences.calendar.form</field> <field name="model">res.users</field> - <field name="inherit_id" ref="base.view_users_form_simple_modif"/> + <field name="inherit_id" ref="base.view_users_form_simple_modif" /> <field name="arch" type="xml"> <xpath expr="//group[@name='preferences']" position="after"> <group name="calendar" string="Calendrier"> - <field name="is_weekend" widget="boolean_toggle" readonly="0"/> + <field name="is_weekend" widget="boolean_toggle" readonly="0" /> </group> </xpath> </field> -- GitLab