Merge branch 'cor_cr' into 'master'

Cor cr

See merge request prakash.jain/cor-odoo!238
This commit is contained in:
prakash.jain 2022-09-06 18:29:20 +05:30
commit b6c438a82a
85 changed files with 6062 additions and 64 deletions

1
auto_backup/__init__.py Normal file
View File

@ -0,0 +1 @@
from . import models

View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
{
'name': "Database auto-backup",
'summary': 'Automated backups',
'description': """
The Database Auto-Backup module enables the user to make configurations for the automatic backup of the database.
Backups can be taken on the local system or on a remote server, through SFTP.
You only have to specify the hostname, port, backup location and databasename (all will be pre-filled by default with correct data.
If you want to write to an external server with SFTP you will need to provide the IP, username and password for the remote backups.
The base of this module is taken from Odoo SA V6.1 (https://www.odoo.com/apps/modules/6.0/auto_backup/) and then upgraded and heavily expanded.
This module is made and provided by Yenthe Van Ginneken (Oocademy).
Automatic backup for all such configured databases can then be scheduled as follows:
1) Go to Settings / Technical / Automation / Scheduled actions.
2) Search the action 'Backup scheduler'.
3) Set it active and choose how often you wish to take backups.
4) If you want to write backups to a remote location you should fill in the SFTP details.
""",
'author': "Yenthe Van Ginneken",
'website': "http://www.odoo.yenthevg.com",
'category': 'Administration',
'version': '14.0.0.1',
'installable': True,
'license': 'LGPL-3',
# any module necessary for this one to work correctly
'depends': ['base'],
# always loaded
'data': [
'security/user_groups.xml',
'security/ir.model.access.csv',
'views/backup_view.xml',
'data/backup_data.xml',
],
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" ?>
<odoo>
<data noupdate="1">
<record id="backup_scheduler" model="ir.cron">
<field name="interval_type">days</field>
<field name="name">Backup scheduler</field>
<field name="numbercall">-1</field>
<field name="priority">5</field>
<field name="doall">False</field>
<field name="active">False</field>
<field name="interval_number">1</field>
<field name="model_id" ref="model_db_backup"/>
<field name="state">code</field>
<field name="code">model.schedule_backup()</field>
</record>
</data>
</odoo>

314
auto_backup/i18n/ar.po Normal file
View File

@ -0,0 +1,314 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * auto_backup
#
# SaFi J. <info@daleeltech.com>, 2015.
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-03-26 14:17+0000\n"
"PO-Revision-Date: 2015-12-13 10:46+0300\n"
"Last-Translator: SaFi J. <info@daleeltech.com>\n"
"Language-Team: team@daleeltech.com\n"
"Language: ar\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
#. module: auto_backup
#: code:addons/auto_backup/backup_scheduler.py:137
#, python-format
msgid "%s"
msgstr "%s"
#. module: auto_backup
#: help:db.backup,bkp_dir:0
msgid "Absolute path for storing the backups"
msgstr "المسار الكامل لحفظ النسخ الاحتياطي"
#. module: auto_backup
#: field:db.backup,sendmailsftpfail:0
msgid "Auto. E-mail on backup fail"
msgstr "إرسال بريد إلكتروني تلقائياً في حالة فشل النسخ الاحتياطي"
#. module: auto_backup
#: field:db.backup,autoremove:0
msgid "Auto. Remove Backups"
msgstr "إزالة النسخ الاحتياطية تلقائياً"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Automatic backups of the database can be scheduled as follows:"
msgstr "النسخ الاحتياطي التلقائي لقاعدة البيانات يمكن جدولته كالتالي"
#. module: auto_backup
#: field:db.backup,bkp_dir:0
msgid "Backup Directory"
msgstr "دليل النسخ الاحتياطي"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_tree
msgid "Backups"
msgstr "النسخ الاحتياطية"
#. module: auto_backup
#: help:db.backup,daystokeepsftp:0
msgid "Choose after how many days the backup should be deleted from the FTP server. For example:\n"
"If you fill in 5 the backups will be removed after 5 days from the FTP server."
msgstr ""
"اختر بعد كم من الأيام سيتم حذف النسخ الاحتياطي من خادم FTP مثلاً :\n إذا أدخلت "
"5 فإن النسخ الاحتياطية سيتم إزالتها من خادم FTP بعد 5 أيام."
#. module: auto_backup
#: help:db.backup,daystokeep:0
msgid "Choose after how many days the backup should be deleted. For example:\n"
"If you fill in 5 the backups will be removed after 5 days."
msgstr ""
"اختر بعد كم من الأيام سيتم حذف النسخ الاحتياطي مثلاً :\n إذا أدخلت 5 فإن النسخ "
"الاحتياطية سيتم إزالتها بعد 5 أيام."
#. module: auto_backup
#: model:ir.actions.act_window,name:auto_backup.action_backup_conf_form
#: model:ir.ui.menu,name:auto_backup.backup_conf_menu
msgid "Configure Backup"
msgstr "إعدادات النسخ الإحتياطي"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Contact us!"
msgstr "اتصل بنا!"
#. module: auto_backup
#: field:db.backup,create_uid:0
msgid "Created by"
msgstr "تم ألإنشاء بواسطة"
#. module: auto_backup
#: field:db.backup,create_date:0
msgid "Created on"
msgstr "تم ألإنشاء في"
#. module: auto_backup
#: field:db.backup,name:0
msgid "Database"
msgstr "قاعدة البيانات"
#. module: auto_backup
#: help:db.backup,name:0
msgid "Database you want to schedule backups for"
msgstr "قاعدة البيانات التي تريد جدولة النسخ الاحتياطي لها"
#. module: auto_backup
#: field:db.backup,emailtonotify:0
msgid "E-mail to notify"
msgstr "تنبيه البريد الإلكتروني"
#. module: auto_backup
#: code:addons/auto_backup/backup_scheduler.py:106
#: constraint:db.backup:0
#, python-format
msgid "Error ! No such database exists!"
msgstr "خطأ ! لا وجود لقاعدة البيانات هذه !"
#. module: auto_backup
#: help:db.backup,emailtonotify:0
msgid "Fill in the e-mail where you want to be notified that the backup failed on the FTP."
msgstr ""
"ادخل عنوان البريد الإلكتروني الذي تريد تنبيهك من خلاله عند فشل النسخ "
"الاحتياطي على FTP."
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "For example: /odoo/backups/"
msgstr "مثلاً : /odoo/backups/"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Go to Settings / Technical / Automation / Scheduled Actions."
msgstr "اذهب إلى الإعدادات / التقني / ألأتمته / جدولة الإخزاءات."
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Help"
msgstr "المساعدة"
#. module: auto_backup
#: field:db.backup,host:0
msgid "Host"
msgstr "المضيف"
#. module: auto_backup
#: field:db.backup,id:0
msgid "ID"
msgstr "المعرف"
#. module: auto_backup
#: field:db.backup,sftpip:0
msgid "IP Address SFTP Server"
msgstr "عنوان بروتوكول الأنترنت لخادم SFTP"
#. module: auto_backup
#: help:db.backup,sendmailsftpfail:0
msgid "If you check this option you can choose to automaticly get e-mailed when the backup to the external server failed."
msgstr ""
"إذا قمت بتأشير هذا الخيار ستستطيع اختيار استلام البريد الإلكتروني تلقائياً "
"عند فشل النسخ الاحتياطي للخادوم الخارجي."
#. module: auto_backup
#: help:db.backup,autoremove:0
msgid "If you check this option you can choose to automaticly remove the backup after xx days"
msgstr ""
"إذا قمت بتأشير هذا الخيار ستستطيع اختيار الإزالة التلقائية للنسخ الاحتياطي "
"بعد س من الأيام"
#. module: auto_backup
#: help:db.backup,sftpwrite:0
msgid "If you check this option you can specify the details needed to write to a remote server with SFTP."
msgstr ""
"إذا قمت بتأشير هذا الخيار ستستطيع تحديد التفاصيل المطلوبة للكتابة على الخادم "
"البعيد من خلال SFTP."
#. module: auto_backup
#: field:db.backup,write_uid:0
msgid "Last Updated by"
msgstr "آخر تحديث بواسطة"
#. module: auto_backup
#: field:db.backup,write_date:0
msgid "Last Updated on"
msgstr "آخر تحديث في"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Local backup configuration"
msgstr "إعدادات النسخ الاحتياطي المحلي"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Need more help?"
msgstr "هل تحتاج لمزيد من المساعدة ؟"
#. module: auto_backup
#: field:db.backup,sftppassword:0
msgid "Password User SFTP Server"
msgstr "كلمة المرور لمستخدم خادم SFTP"
#. module: auto_backup
#: field:db.backup,sftppath:0
msgid "Path external server"
msgstr "المسار الخارجي للخادم"
#. module: auto_backup
#: field:db.backup,port:0
msgid "Port"
msgstr "المنفذ"
#. module: auto_backup
#: field:db.backup,daystokeepsftp:0
msgid "Remove SFTP after x days"
msgstr "الإزالة من خادم SFTP بعد س من الأيام"
#. module: auto_backup
#: field:db.backup,daystokeep:0
msgid "Remove after x days"
msgstr "الإزالة بعد س من الأيام"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "SFTP"
msgstr "SFTP"
#. module: auto_backup
#: field:db.backup,sftpport:0
msgid "SFTP Port"
msgstr "منفذ SFTP"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_search
msgid "Search options"
msgstr "خيارات البحث"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Search the action named 'Backup scheduler'."
msgstr "ابحث عن الأجزاء المسمى 'Backup scheduler'."
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Set the scheduler to active and fill in how often you want backups generated."
msgstr "قم بتفعيل الجدولة واملأ كم تربد عادة توليد النسخ الاحتياطي."
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Test"
msgstr "اختبار"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Test SFTP Connection"
msgstr "اختبار توصيل SFTP"
#. module: auto_backup
#: help:db.backup,sftpip:0
msgid "The IP address from your remote server. For example 192.168.0.1"
msgstr "عنوان بروتوكول الإنترنت من خادومك البعيد. مثلاً 192.168.0.1"
#. module: auto_backup
#: help:db.backup,sftppath:0
msgid "The location to the folder where the dumps should be written to. For example /odoo/backups/.\n"
"Files will then be written to /odoo/backups/ on your remote server."
msgstr ""
"موقع المجلد التي يجب كتابة ملفات النسخ عليه. مثلاً /odoo/backups/.\n"
"وحينها سيتم كتابة الملفات إلى /odoo/backups/ على خادومك البعيد."
#. module: auto_backup
#: help:db.backup,sftppassword:0
msgid "The password from the user where the SFTP connection should be made with. This is the password from the user on the external server."
msgstr ""
"كلمة المرور الذي من المفترض عمل اتصال SFTP من بها. هذا هي كلمة المرور على "
"الخادم الخارجي."
#. module: auto_backup
#: help:db.backup,sftpport:0
msgid "The port on the FTP server that accepts SSH/SFTP calls."
msgstr "المنفذ على خادم FTP الذي يقبل طلبات SSH/SFTP."
#. module: auto_backup
#: help:db.backup,sftpusername:0
msgid "The username where the SFTP connection should be made with. This is the user on the external server."
msgstr ""
"اسم المستخدم الذي من المفترض عمل اتصال SFTP من به. هذا هو المستخدم على "
"الخادم الخارجي."
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "This configures the scheduler for automatic backup of the given database running on given host at given port on regular intervals."
msgstr ""
"هذا الإعداد سيجدول النسخ الاحتياطي التلقائي لقاعدة بيانات معينة وسينفذ على "
"مضيف معين بمنفذ معين خلال فترات متتابعة. "
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Use SFTP with caution! This writes files to external servers under the path you specify."
msgstr ""
"استخدم SFTP بحذر ! لأن هذا يؤدي لكتابة ملفات لخوادم خارجية على المسار الذي "
"حددته."
#. module: auto_backup
#: field:db.backup,sftpusername:0
msgid "Username SFTP Server"
msgstr "أسم المستخدم لخادم SFTP"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Warning:"
msgstr "تحذير :"
#. module: auto_backup
#: field:db.backup,sftpwrite:0
msgid "Write to external server with sftp"
msgstr "الكتابة على خادم خارجي من خلال sftp"

158
auto_backup/i18n/bg.po Normal file
View File

@ -0,0 +1,158 @@
# Bulgarian translation for openobject-addons
# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2009-11-24 13:49+0000\n"
"PO-Revision-Date: 2011-03-30 07:20+0000\n"
"Last-Translator: Dimitar Markov <dimitar.markov@gmail.com>\n"
"Language-Team: Bulgarian <bg@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-04-20 05:35+0000\n"
"X-Generator: Launchpad (build 16567)\n"
#. module: auto_backup
#: help:db.backup,name:0
msgid "Database you want to schedule backups for"
msgstr ""
#. module: auto_backup
#: constraint:ir.model:0
msgid ""
"The Object name must start with x_ and not contain any special character !"
msgstr ""
"Името на обекта трябва да започва с x_ и не може да никакви специални знаци !"
#. module: auto_backup
#: constraint:ir.actions.act_window:0
msgid "Invalid model name in the action definition."
msgstr "Невалидно име на модел при задаване на действие"
#. module: auto_backup
#: model:ir.model,name:auto_backup.model_db_backup
msgid "db.backup"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid ""
"1) Go to Administration / Configuration / Scheduler / Scheduled Actions"
msgstr ""
#. module: auto_backup
#: model:ir.actions.act_window,name:auto_backup.action_backup_conf_form
#: model:ir.ui.menu,name:auto_backup.backup_conf_menu
msgid "Configure Backup"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid "Test"
msgstr "Тестване"
#. module: auto_backup
#: view:db.backup:0
msgid "IP Configuration"
msgstr ""
#. module: auto_backup
#: help:db.backup,bkp_dir:0
msgid "Absolute path for storing the backups"
msgstr ""
#. module: auto_backup
#: model:ir.module.module,shortdesc:auto_backup.module_meta_information
msgid "Database Auto-Backup"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid "Database Configuration"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid "4) Set other values as per your preference"
msgstr ""
#. module: auto_backup
#: field:db.backup,host:0
msgid "Host"
msgstr "Хост"
#. module: auto_backup
#: view:db.backup:0
msgid ""
"Automatic backup of all the databases under this can be scheduled as "
"follows: "
msgstr ""
#. module: auto_backup
#: constraint:ir.ui.view:0
msgid "Invalid XML for View Architecture!"
msgstr ""
#. module: auto_backup
#: field:db.backup,bkp_dir:0
msgid "Backup Directory"
msgstr ""
#. module: auto_backup
#: field:db.backup,name:0
msgid "Database"
msgstr "База данни"
#. module: auto_backup
#: view:db.backup:0
msgid "2) Schedule new action(create a new record)"
msgstr ""
#. module: auto_backup
#: model:ir.module.module,description:auto_backup.module_meta_information
msgid ""
"The generic Open ERP Database Auto-Backup system enables the user to make "
"configurations for the automatic backup of the database.\n"
"User simply requires to specify host & port under IP Configuration & "
"database(on specified host running at specified port) and backup "
"directory(in which all the backups of the specified database will be stored) "
"under Database Configuration.\n"
"\n"
"Automatic backup for all such configured databases under this can then be "
"scheduled as follows: \n"
" \n"
"1) Go to Administration / Configuration / Scheduler / Scheduled Actions\n"
"2) Schedule new action(create a new record)\n"
"3) Set 'Object' to 'db.backup' and 'Function' to 'schedule_backup' under "
"page 'Technical Data'\n"
"4) Set other values as per your preference"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid ""
"3) Set 'Object' to 'db.backup' and 'Function' to 'schedule_backup' under "
"page 'Technical Data'"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid "Help"
msgstr "Помощ"
#. module: auto_backup
#: view:db.backup:0
msgid ""
"This configures the scheduler for automatic backup of the given database "
"running on given host at given port on regular intervals."
msgstr ""
#. module: auto_backup
#: field:db.backup,port:0
msgid "Port"
msgstr "Порт"

157
auto_backup/i18n/ca.po Normal file
View File

@ -0,0 +1,157 @@
# Catalan translation for openobject-addons
# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2009-11-24 13:49+0000\n"
"PO-Revision-Date: 2014-10-20 06:41+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Catalan <ca@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-10-21 06:30+0000\n"
"X-Generator: Launchpad (build 17203)\n"
#. module: auto_backup
#: help:db.backup,name:0
msgid "Database you want to schedule backups for"
msgstr ""
#. module: auto_backup
#: constraint:ir.model:0
msgid ""
"The Object name must start with x_ and not contain any special character !"
msgstr ""
#. module: auto_backup
#: constraint:ir.actions.act_window:0
msgid "Invalid model name in the action definition."
msgstr ""
#. module: auto_backup
#: model:ir.model,name:auto_backup.model_db_backup
msgid "db.backup"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid ""
"1) Go to Administration / Configuration / Scheduler / Scheduled Actions"
msgstr ""
#. module: auto_backup
#: model:ir.actions.act_window,name:auto_backup.action_backup_conf_form
#: model:ir.ui.menu,name:auto_backup.backup_conf_menu
msgid "Configure Backup"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid "Test"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid "IP Configuration"
msgstr ""
#. module: auto_backup
#: help:db.backup,bkp_dir:0
msgid "Absolute path for storing the backups"
msgstr ""
#. module: auto_backup
#: model:ir.module.module,shortdesc:auto_backup.module_meta_information
msgid "Database Auto-Backup"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid "Database Configuration"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid "4) Set other values as per your preference"
msgstr ""
#. module: auto_backup
#: field:db.backup,host:0
msgid "Host"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid ""
"Automatic backup of all the databases under this can be scheduled as "
"follows: "
msgstr ""
#. module: auto_backup
#: constraint:ir.ui.view:0
msgid "Invalid XML for View Architecture!"
msgstr ""
#. module: auto_backup
#: field:db.backup,bkp_dir:0
msgid "Backup Directory"
msgstr ""
#. module: auto_backup
#: field:db.backup,name:0
msgid "Database"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid "2) Schedule new action(create a new record)"
msgstr ""
#. module: auto_backup
#: model:ir.module.module,description:auto_backup.module_meta_information
msgid ""
"The generic Open ERP Database Auto-Backup system enables the user to make "
"configurations for the automatic backup of the database.\n"
"User simply requires to specify host & port under IP Configuration & "
"database(on specified host running at specified port) and backup "
"directory(in which all the backups of the specified database will be stored) "
"under Database Configuration.\n"
"\n"
"Automatic backup for all such configured databases under this can then be "
"scheduled as follows: \n"
" \n"
"1) Go to Administration / Configuration / Scheduler / Scheduled Actions\n"
"2) Schedule new action(create a new record)\n"
"3) Set 'Object' to 'db.backup' and 'Function' to 'schedule_backup' under "
"page 'Technical Data'\n"
"4) Set other values as per your preference"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid ""
"3) Set 'Object' to 'db.backup' and 'Function' to 'schedule_backup' under "
"page 'Technical Data'"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid "Help"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid ""
"This configures the scheduler for automatic backup of the given database "
"running on given host at given port on regular intervals."
msgstr ""
#. module: auto_backup
#: field:db.backup,port:0
msgid "Port"
msgstr ""

166
auto_backup/i18n/es.po Normal file
View File

@ -0,0 +1,166 @@
# Spanish translation for openobject-addons
# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2009-11-24 13:49+0000\n"
"PO-Revision-Date: 2011-08-23 19:48+0000\n"
"Last-Translator: mgaja (GrupoIsep.com) <Unknown>\n"
"Language-Team: Spanish <es@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-04-20 05:35+0000\n"
"X-Generator: Launchpad (build 16567)\n"
#. module: auto_backup
#: help:db.backup,name:0
msgid "Database you want to schedule backups for"
msgstr "Base de datos que desea programar copias de seguridad para"
#. module: auto_backup
#: constraint:ir.model:0
msgid ""
"The Object name must start with x_ and not contain any special character !"
msgstr ""
"¡El objeto debe empezar con x_ y no puede contener ningún carácter especial!"
#. module: auto_backup
#: constraint:ir.actions.act_window:0
msgid "Invalid model name in the action definition."
msgstr "Nombre del modelo inválido en la definición de acción."
#. module: auto_backup
#: model:ir.model,name:auto_backup.model_db_backup
msgid "db.backup"
msgstr "backup.BBDD"
#. module: auto_backup
#: view:db.backup:0
msgid ""
"1) Go to Administration / Configuration / Scheduler / Scheduled Actions"
msgstr ""
"1) Vaya a Administración / Configuración / Programador / Acciones programadas"
#. module: auto_backup
#: model:ir.actions.act_window,name:auto_backup.action_backup_conf_form
#: model:ir.ui.menu,name:auto_backup.backup_conf_menu
msgid "Configure Backup"
msgstr "Configurar copia de seguridad"
#. module: auto_backup
#: view:db.backup:0
msgid "Test"
msgstr "Prueba"
#. module: auto_backup
#: view:db.backup:0
msgid "IP Configuration"
msgstr "Configuración IP"
#. module: auto_backup
#: help:db.backup,bkp_dir:0
msgid "Absolute path for storing the backups"
msgstr "Ruta absoluta para el almacenamiento de las copias de seguridad"
#. module: auto_backup
#: model:ir.module.module,shortdesc:auto_backup.module_meta_information
msgid "Database Auto-Backup"
msgstr "Copia de seguridad automática de Base de datos"
#. module: auto_backup
#: view:db.backup:0
msgid "Database Configuration"
msgstr "Configuración de Base de Datos"
#. module: auto_backup
#: view:db.backup:0
msgid "4) Set other values as per your preference"
msgstr "4) Establecer los demás valores según su preferencia"
#. module: auto_backup
#: field:db.backup,host:0
msgid "Host"
msgstr "Host"
#. module: auto_backup
#: view:db.backup:0
msgid ""
"Automatic backup of all the databases under this can be scheduled as "
"follows: "
msgstr ""
"Copia de seguridad automática de las bases de datos en virtud de este puede "
"ser programado de la siguiente manera: "
#. module: auto_backup
#: constraint:ir.ui.view:0
msgid "Invalid XML for View Architecture!"
msgstr "¡XML inválido para la definición de la vista!"
#. module: auto_backup
#: field:db.backup,bkp_dir:0
msgid "Backup Directory"
msgstr "Directorio de la copia de seguridad"
#. module: auto_backup
#: field:db.backup,name:0
msgid "Database"
msgstr "Base de datos"
#. module: auto_backup
#: view:db.backup:0
msgid "2) Schedule new action(create a new record)"
msgstr "2) Lista de nuevas acciones (crear un nuevo registro)"
#. module: auto_backup
#: model:ir.module.module,description:auto_backup.module_meta_information
msgid ""
"The generic Open ERP Database Auto-Backup system enables the user to make "
"configurations for the automatic backup of the database.\n"
"User simply requires to specify host & port under IP Configuration & "
"database(on specified host running at specified port) and backup "
"directory(in which all the backups of the specified database will be stored) "
"under Database Configuration.\n"
"\n"
"Automatic backup for all such configured databases under this can then be "
"scheduled as follows: \n"
" \n"
"1) Go to Administration / Configuration / Scheduler / Scheduled Actions\n"
"2) Schedule new action(create a new record)\n"
"3) Set 'Object' to 'db.backup' and 'Function' to 'schedule_backup' under "
"page 'Technical Data'\n"
"4) Set other values as per your preference"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid ""
"3) Set 'Object' to 'db.backup' and 'Function' to 'schedule_backup' under "
"page 'Technical Data'"
msgstr ""
"3) Ajuste \"objeto\" a \"db.backup\" y \"función\" a \"programar copias de "
"seguridad\" en la página \"Datos Técnicos\""
#. module: auto_backup
#: view:db.backup:0
msgid "Help"
msgstr "Ayuda"
#. module: auto_backup
#: view:db.backup:0
msgid ""
"This configures the scheduler for automatic backup of the given database "
"running on given host at given port on regular intervals."
msgstr ""
"Esto configura el planificador de copia de seguridad automática de la base "
"de datos dado que se ejecutan en el host dado en el puerto en intervalos "
"regulares."
#. module: auto_backup
#: field:db.backup,port:0
msgid "Port"
msgstr "Puerto"

395
auto_backup/i18n/ko_KR.po Normal file
View File

@ -0,0 +1,395 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * auto_backup
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 11.0-20180205\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-02-08 03:22+0000\n"
"PO-Revision-Date: 2018-02-08 12:47+0900\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 2.0.6\n"
"Last-Translator: \n"
"Language: ko_KR\n"
#. module: auto_backup
#: model:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid ""
"<b>Warning:</b>\n"
" Use SFTP with caution! This writes files to external "
"servers under the path you specify."
msgstr ""
"<b>경고:</b>\n"
" 조심해서 SFTP를 사용하세요! 이것은 당신이 지정한 경로"
"밑에 외부서버들로 파일들을 쓰게됩니다."
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup_folder
msgid "Absolute path for storing the backups"
msgstr "백업저장용 절대경로명"
#. module: auto_backup
#: model:ir.module.category,name:auto_backup.module_management
msgid "Auto backup access"
msgstr "자동으로 백업 액세스하기"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_send_mail_sftp_fail
msgid "Auto. E-mail on backup fail"
msgstr "백업실패시에 이메일로 알려주기 (자동)"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_autoremove
msgid "Auto. Remove Backups"
msgstr "백업제거하기 (자동)"
#. module: auto_backup
#: model:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Back-up view"
msgstr "백업보기"
#. module: auto_backup
#: model:ir.ui.menu,name:auto_backup.auto_backup_menu
msgid "Back-ups"
msgstr "백업"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_folder
msgid "Backup Directory"
msgstr "백업디렉토리"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_backup_type
msgid "Backup Type"
msgstr "백업타입"
#. module: auto_backup
#: model:ir.actions.server,name:auto_backup.backup_scheduler_ir_actions_server
#: model:ir.cron,cron_name:auto_backup.backup_scheduler
#: model:ir.cron,name:auto_backup.backup_scheduler
msgid "Backup scheduler"
msgstr "백업스케줄러"
#. module: auto_backup
#: model:ir.ui.view,arch_db:auto_backup.view_backup_config_tree
msgid "Backups"
msgstr "백업"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup_days_to_keep_sftp
msgid ""
"Choose after how many days the backup should be deleted from the FTP server. "
"For example:\n"
"If you fill in 5 the backups will be removed after 5 days from the FTP "
"server."
msgstr ""
"FTP서버로부터 몇일지난 백업본을 삭제할지 선택하세요. 예를들면:\n"
"만약 5를 기입하면 그 백업본들은 FTP서버에서 5일후에 삭제됩니다."
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup_days_to_keep
msgid ""
"Choose after how many days the backup should be deleted. For example:\n"
"If you fill in 5 the backups will be removed after 5 days."
msgstr ""
"FTP서버로부터 몇일지난 백업본을 삭제할지 선택하세요. 예를들면:\n"
"만약 5를 기입하면 그 백업본들은 FTP서버에서 5일후에 삭제됩니다."
#. module: auto_backup
#: model:ir.actions.act_window,name:auto_backup.action_backup
#: model:ir.ui.menu,name:auto_backup.backup_conf_menu
msgid "Configure back-ups"
msgstr "백업구성하기"
#. module: auto_backup
#: code:addons/auto_backup/models/db_backup.py:129
#, python-format
msgid "Connection Test Failed!"
msgstr "연결테스트실패~"
#. module: auto_backup
#: code:addons/auto_backup/models/db_backup.py:124
#, python-format
msgid ""
"Connection Test Succeeded!\n"
"Everything seems properly set up for FTP back-ups!"
msgstr ""
"연결테스트성공!\n"
"모든게 적절히 세팅된거 같습니다!"
#. module: auto_backup
#: model:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Contact me!"
msgstr "저에게 연락하세요~"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_create_uid
msgid "Created by"
msgstr "생성됨"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_create_date
msgid "Created on"
msgstr "생성됨"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_name
msgid "Database"
msgstr "데이터베이스"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup_name
msgid "Database you want to schedule backups for"
msgstr "백업을 스케줄하기 원하는 데이터베이스"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_display_name
msgid "Display Name"
msgstr "표시이름"
#. module: auto_backup
#: selection:db.backup,backup_type:0
msgid "Dump"
msgstr "덤프"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_email_to_notify
msgid "E-mail to notify"
msgstr "통보할 이메일주소"
#. module: auto_backup
#: code:addons/auto_backup/models/db_backup.py:98 constraint:db.backup:0
#, python-format
msgid "Error ! No such database exists!"
msgstr "에러~ 그런 데이터베이스가 존재하지 않습니다"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup_email_to_notify
msgid ""
"Fill in the e-mail where you want to be notified that the backup failed on "
"the FTP."
msgstr "FTP서버상에서 백업실패할때 알림받기원하는 이메일주소를 채워넣으세요"
#. module: auto_backup
#: model:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "For example: /odoo/backups/"
msgstr "예: /odoo/backups/"
#. module: auto_backup
#: model:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Go to Settings / Technical / Automation / Scheduled Actions."
msgstr "Settings / Technical / Automation / Scheduled Actions 으로 가세요."
#. module: auto_backup
#: model:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Help"
msgstr "도움말"
#. module: auto_backup
#: code:addons/auto_backup/models/db_backup.py:132
#, python-format
msgid "Here is what we got instead:\n"
msgstr "우리가 대신하는것이 여기있습니다:\n"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_host
msgid "Host"
msgstr "호스트"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_id
msgid "ID"
msgstr "ID"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_sftp_host
msgid "IP Address SFTP Server"
msgstr "SFTP서버 IP주소"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup_send_mail_sftp_fail
msgid ""
"If you check this option you can choose to automaticly get e-mailed when the "
"backup to the external server failed."
msgstr ""
"이 옵션을 체크하면 외부서버에서 백업실패할때 자동적으로 이메일하도록 선택할 "
"수 있습니다."
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup_autoremove
msgid ""
"If you check this option you can choose to automaticly remove the backup "
"after xx days"
msgstr ""
"이 옵션을 선택하면 자동적으로 며칠(xx days)후에 백업을 제거하도록 선택할 수 "
"있습니다"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup_sftp_write
msgid ""
"If you check this option you can specify the details needed to write to a "
"remote server with SFTP."
msgstr ""
"이 옵션을 선택하면 SFTP 원격서버로 쓰려고 할때 요구되는 정보를 지정할 수 있습"
"니다."
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup___last_update
msgid "Last Modified on"
msgstr "최근수정일자"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_write_uid
msgid "Last Updated by"
msgstr "최근수정자"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_write_date
msgid "Last Updated on"
msgstr "최근업데이트일자"
#. module: auto_backup
#: model:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Local backup configuration"
msgstr "로컬백업환경설정"
#. module: auto_backup
#: model:res.groups,name:auto_backup.group_manager
msgid "Manager"
msgstr "관리자"
#. module: auto_backup
#: model:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Need more help?"
msgstr "더 도움이 필요하세요?"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_sftp_password
msgid "Password User SFTP Server"
msgstr "SFTP서버 사용자 패스워드"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_sftp_path
msgid "Path external server"
msgstr "외부서버경로"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_port
msgid "Port"
msgstr "포트"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_days_to_keep_sftp
msgid "Remove SFTP after x days"
msgstr "몇일후에 SFTP 제거"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_days_to_keep
msgid "Remove after x days"
msgstr "몇일후에 제거하기"
#. module: auto_backup
#: model:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "SFTP"
msgstr "SFTP"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_sftp_port
msgid "SFTP Port"
msgstr "SFTP 포트"
#. module: auto_backup
#: model:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Search the action named 'Backup scheduler'."
msgstr "'백업스케줄러'라고 명명된 액션을 검색하세요"
#. module: auto_backup
#: model:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid ""
"Set the scheduler to active and fill in how often you want backups generated."
msgstr "스케줄러를 활성화로 세팅하고 백업주기를 채워넣으세요"
#. module: auto_backup
#: model:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Test SFTP Connection"
msgstr "SFTP연결테스트"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup_sftp_host
msgid "The IP address from your remote server. For example 192.168.0.1"
msgstr "원격서버로부터의 IP주소. 예를들자면, 192.168.0.1"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup_sftp_path
msgid ""
"The location to the folder where the dumps should be written to. For "
"example /odoo/backups/.\n"
"Files will then be written to /odoo/backups/ on your remote server."
msgstr ""
"덤프파일이 씌어져야하는 폴더위치. 예를들어보자면 /odoo/backups/.\n"
"그 다음에 파일들은 당신서버상의 /odoo/backups/ 로 쓰여질겁니다."
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup_sftp_password
msgid ""
"The password from the user where the SFTP connection should be made with. "
"This is the password from the user on the external server."
msgstr ""
"SFTP연결이 만들어져야하는 그 사용자의 비밀번호. 이것은 외부서버상의 그 사용자"
"의 비밀번호예요~"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup_sftp_port
msgid "The port on the FTP server that accepts SSH/SFTP calls."
msgstr "SSH/SFTP접속을 허용하는 FTP서버상의 포트"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup_sftp_user
msgid ""
"The username where the SFTP connection should be made with. This is the user "
"on the external server."
msgstr "SFTP연결할 사용자명. 외부서버상의 사용자명이예요~ "
#. module: auto_backup
#: model:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid ""
"This configures the scheduler for automatic backup of the given database "
"running on given host\n"
" at given port on regular intervals.\n"
" <br/>\n"
" Automatic backups of the database can be scheduled "
"as follows:"
msgstr ""
"이것은 정기적인 간격으로 해당 호스트상에서 실행중인 데이터베이스의 자동백업용"
"으로 스케줄러를 구성합니다. 그 데이터베이스의 자동백업들은 아래처럼 스케줄될 "
"수 있습니다:"
#. module: auto_backup
#: model:ir.module.category,description:auto_backup.module_management
msgid "User access level for this module"
msgstr "이 모듈용 사용자접근레벨"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_sftp_user
msgid "Username SFTP Server"
msgstr "SFTP서버 사용자명"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup_sftp_write
msgid "Write to external server with sftp"
msgstr "sftp와 함께 외부서버로 쓰기"
#. module: auto_backup
#: selection:db.backup,backup_type:0
msgid "Zip"
msgstr "Zip"
#. module: auto_backup
#: model:ir.model,name:auto_backup.model_db_backup
msgid "db.backup"
msgstr "db.backup"

380
auto_backup/i18n/nl.po Normal file
View File

@ -0,0 +1,380 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * auto_backup
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 13.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-20 13:29+0000\n"
"PO-Revision-Date: 2019-10-20 13:29+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid ""
"<b>Warning:</b>\n"
" Use SFTP with caution! This writes files to external servers under the path you specify."
msgstr "<b>Waarschuwing:</b>\n"
" Gebruik SFTP voorzichtig! Dit schrijft bestanden naar externe servers onder het pad dat u opgeeft."
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__folder
msgid "Absolute path for storing the backups"
msgstr "Absoluut pad om backups te bewaren"
#. module: auto_backup
#: model:ir.module.category,name:auto_backup.module_management
msgid "Auto backup access"
msgstr "Auto backup toegang"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__send_mail_sftp_fail
msgid "Auto. E-mail on backup fail"
msgstr "Auto. e-mail bij mislukte back-up"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__autoremove
msgid "Auto. Remove Backups"
msgstr "Auto. e-mailen wanneer backup mislukt"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Back-up view"
msgstr "Back-up weergave"
#. module: auto_backup
#: model:ir.ui.menu,name:auto_backup.auto_backup_menu
msgid "Back-ups"
msgstr "Back-ups"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__folder
msgid "Backup Directory"
msgstr "Backup folder"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__backup_type
msgid "Backup Type"
msgstr "Soort back-up"
#. module: auto_backup
#: model:ir.model,name:auto_backup.model_db_backup
msgid "Backup configuration record"
msgstr "Back-up configuratie"
#. module: auto_backup
#: model:ir.actions.server,name:auto_backup.backup_scheduler_ir_actions_server
#: model:ir.cron,cron_name:auto_backup.backup_scheduler
#: model:ir.cron,name:auto_backup.backup_scheduler
msgid "Backup scheduler"
msgstr "Backup planner"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_tree
msgid "Backups"
msgstr "Back-ups"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__days_to_keep_sftp
msgid ""
"Choose after how many days the backup should be deleted from the FTP server. For example:\n"
"If you fill in 5 the backups will be removed after 5 days from the FTP server."
msgstr "Kies na hoeveel dagen de backups verwijderd moeten worden van de FTP server. Bijvoorbeeld:\n"
"Als u 5 invult zal de backup na 5 dagen verwijderd worden van de FTP server."
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__days_to_keep
msgid ""
"Choose after how many days the backup should be deleted. For example:\n"
"If you fill in 5 the backups will be removed after 5 days."
msgstr "Kies na hoeveel dagen de backups verwijderd moeten worden van de FTP server. Bijvoorbeeld:\n"
"Als u 5 invult zal de backup na 5 dagen verwijderd worden van de FTP server."
#. module: auto_backup
#: model:ir.actions.act_window,name:auto_backup.action_backup
#: model:ir.ui.menu,name:auto_backup.backup_conf_menu
msgid "Configure back-ups"
msgstr "Configureer back-ups"
#. module: auto_backup
#: code:addons/auto_backup/models/db_backup.py:0
#, python-format
msgid "Connection Test Failed!"
msgstr "Connectie test mislukt!"
#. module: auto_backup
#: code:addons/auto_backup/models/db_backup.py:0
#, python-format
msgid ""
"Connection Test Succeeded!\n"
"Everything seems properly set up for FTP back-ups!"
msgstr "Connectie test succesvol!\n"
"Alles lijkt correct opgezet voor FTP back-ups!"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Contact me!"
msgstr "Contacteer mij!"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__create_uid
msgid "Created by"
msgstr "Aangemaakt door"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__create_date
msgid "Created on"
msgstr "Aangemaakt op"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__name
msgid "Database"
msgstr "Database"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__name
msgid "Database you want to schedule backups for"
msgstr "Dataabse waar u back-ups voor wilt plannen"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__display_name
msgid "Display Name"
msgstr "Schermnaam"
#. module: auto_backup
#: model:ir.model.fields.selection,name:auto_backup.selection__db_backup__backup_type__dump
msgid "Dump"
msgstr "Dump"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__email_to_notify
msgid "E-mail to notify"
msgstr "E-mail om te verwittigen"
#. module: auto_backup
#: code:addons/auto_backup/models/db_backup.py:0
#, python-format
msgid "Error ! No such database exists!"
msgstr "Fout! Deze database bestaat niet!"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__email_to_notify
msgid ""
"Fill in the e-mail where you want to be notified that the backup failed on "
"the FTP."
msgstr "Vul de e-mail in waarop u wilt verwittigd worden als de backup mislukt op de FTP."
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "For example: /odoo/backups/"
msgstr "Bijvoorbeeld: /odoo/backups/"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Go to Settings / Technical / Automation / Scheduled Actions."
msgstr "Ga naar Instellingen / Technisch / Automatisering / Geplande acties."
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Help"
msgstr "Help"
#. module: auto_backup
#: code:addons/auto_backup/models/db_backup.py:0
#, python-format
msgid "Here is what we got instead:\n"
msgstr "Hier is wat we in de plaats terugkregen:\n"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__host
msgid "Host"
msgstr "Host"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__id
msgid "ID"
msgstr "ID"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__sftp_host
msgid "IP Address SFTP Server"
msgstr "IP adres SFTP server"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__send_mail_sftp_fail
msgid ""
"If you check this option you can choose to automaticly get e-mailed when the"
" backup to the external server failed."
msgstr "Als u deze optie aanvinkt kan u kiezen om automatisch een e-mail aan te krijgen als de backuaar de externe server mislukt."
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__autoremove
msgid ""
"If you check this option you can choose to automaticly remove the backup "
"after xx days"
msgstr "Als u deze optie aanvinkt kan u kiezen om automatisch backups te verwijderen "
"na xx dagen"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__sftp_write
msgid ""
"If you check this option you can specify the details needed to write to a "
"remote server with SFTP."
msgstr "Als u deze optie aanvinkt kan u de details invullen die nodig zijn om te connecteren met de "
" externe SFTP server"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup____last_update
msgid "Last Modified on"
msgstr "Laatst gewijzigd op"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__write_uid
msgid "Last Updated by"
msgstr "Laatst bijgewerkt door"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__write_date
msgid "Last Updated on"
msgstr "Laatst bijgewerkt op"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Local backup configuration"
msgstr "Lokale back-up configuratie"
#. module: auto_backup
#: model:res.groups,name:auto_backup.group_manager
msgid "Manager"
msgstr "Beheerder"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Need more help?"
msgstr "Meer hulp nodig?"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__sftp_password
msgid "Password User SFTP Server"
msgstr "Wachtwoord gebruiker SFTP server"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__sftp_path
msgid "Path external server"
msgstr "Pad externe server"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__port
msgid "Port"
msgstr "Poort"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__days_to_keep_sftp
msgid "Remove SFTP after x days"
msgstr "Verwijderd SFTP na x dagen"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__days_to_keep
msgid "Remove after x days"
msgstr "Verwijder na x dagen"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "SFTP"
msgstr "SFTP"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__sftp_port
msgid "SFTP Port"
msgstr "SFTP poort"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Search the action named 'Backup scheduler'."
msgstr "Zoek de actie met de naam 'Backup planner'."
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid ""
"Set the scheduler to active and fill in how often you want backups "
"generated."
msgstr "Zet de planner actief en vul in hoe vaak u wilt dat er backups gegenereerd "
"worden."
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Test SFTP Connection"
msgstr "Test SFTP verbinding"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__sftp_host
msgid "The IP address from your remote server. For example 192.168.0.1"
msgstr "Het IP adres van uw externe server. Bijvoorbeeld: 192.168.0.1"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__sftp_path
msgid ""
"The location to the folder where the dumps should be written to. For example /odoo/backups/.\n"
"Files will then be written to /odoo/backups/ on your remote server."
msgstr "De locatie naar de folder waar de backup naar toe moet geschreven worden. Bijvoorbeeld odoo/backups/\n"
"Bestanden worden dan naar /odoo/backups/ geschreven op de externe server"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__sftp_password
msgid ""
"The password from the user where the SFTP connection should be made with. "
"This is the password from the user on the external server."
msgstr "Het wachtwoord van de gebruiker waar de SFTP connectie mee moet gemaakt worden. "
"Dit is het wachtwoord van de gebruiker op de externe server."
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__sftp_port
msgid "The port on the FTP server that accepts SSH/SFTP calls."
msgstr "De poort op de FTP server die SSH/SFTP accepteert."
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__sftp_user
msgid ""
"The username where the SFTP connection should be made with. This is the user"
" on the external server."
msgstr "De gebruikersnaam waar de SFTP connectie mee gemaakt moet worden. Dit is de gebruiker"
" op de externe server."
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid ""
"This configures the scheduler for automatic backup of the given database running on given host\n"
" at given port on regular intervals.\n"
" <br/>\n"
" Automatic backups of the database can be scheduled as follows:"
msgstr "Dit configureert de planner om automatische backups van de opgegeven database te maken die op deze host,\n"
" op een bepaalde poort draaien, op regelmatige intervals.\n"
" <br/>\n"
" Automatische backups kunnen als volgt ingepland worden:"
#. module: auto_backup
#: model:ir.module.category,description:auto_backup.module_management
msgid "User access level for this module"
msgstr "Gebruikerstoegang voor deze module"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__sftp_user
msgid "Username SFTP Server"
msgstr "Gebruikersnaam SFTP server"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__sftp_write
msgid "Write to external server with sftp"
msgstr "Schrijf naar externe server met SFTP"
#. module: auto_backup
#: model:ir.model.fields.selection,name:auto_backup.selection__db_backup__backup_type__zip
msgid "Zip"
msgstr "ZIP"

380
auto_backup/i18n/nl_BE.po Normal file
View File

@ -0,0 +1,380 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * auto_backup
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 13.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-20 13:29+0000\n"
"PO-Revision-Date: 2019-10-20 13:29+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid ""
"<b>Warning:</b>\n"
" Use SFTP with caution! This writes files to external servers under the path you specify."
msgstr "<b>Waarschuwing:</b>\n"
" Gebruik SFTP voorzichtig! Dit schrijft bestanden naar externe servers onder het pad dat u opgeeft."
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__folder
msgid "Absolute path for storing the backups"
msgstr "Absoluut pad om backups te bewaren"
#. module: auto_backup
#: model:ir.module.category,name:auto_backup.module_management
msgid "Auto backup access"
msgstr "Auto backup toegang"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__send_mail_sftp_fail
msgid "Auto. E-mail on backup fail"
msgstr "Auto. e-mail bij mislukte back-up"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__autoremove
msgid "Auto. Remove Backups"
msgstr "Auto. e-mailen wanneer backup mislukt"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Back-up view"
msgstr "Back-up weergave"
#. module: auto_backup
#: model:ir.ui.menu,name:auto_backup.auto_backup_menu
msgid "Back-ups"
msgstr "Back-ups"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__folder
msgid "Backup Directory"
msgstr "Backup folder"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__backup_type
msgid "Backup Type"
msgstr "Soort back-up"
#. module: auto_backup
#: model:ir.model,name:auto_backup.model_db_backup
msgid "Backup configuration record"
msgstr "Back-up configuratie"
#. module: auto_backup
#: model:ir.actions.server,name:auto_backup.backup_scheduler_ir_actions_server
#: model:ir.cron,cron_name:auto_backup.backup_scheduler
#: model:ir.cron,name:auto_backup.backup_scheduler
msgid "Backup scheduler"
msgstr "Backup planner"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_tree
msgid "Backups"
msgstr "Back-ups"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__days_to_keep_sftp
msgid ""
"Choose after how many days the backup should be deleted from the FTP server. For example:\n"
"If you fill in 5 the backups will be removed after 5 days from the FTP server."
msgstr "Kies na hoeveel dagen de backups verwijderd moeten worden van de FTP server. Bijvoorbeeld:\n"
"Als u 5 invult zal de backup na 5 dagen verwijderd worden van de FTP server."
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__days_to_keep
msgid ""
"Choose after how many days the backup should be deleted. For example:\n"
"If you fill in 5 the backups will be removed after 5 days."
msgstr "Kies na hoeveel dagen de backups verwijderd moeten worden van de FTP server. Bijvoorbeeld:\n"
"Als u 5 invult zal de backup na 5 dagen verwijderd worden van de FTP server."
#. module: auto_backup
#: model:ir.actions.act_window,name:auto_backup.action_backup
#: model:ir.ui.menu,name:auto_backup.backup_conf_menu
msgid "Configure back-ups"
msgstr "Configureer back-ups"
#. module: auto_backup
#: code:addons/auto_backup/models/db_backup.py:0
#, python-format
msgid "Connection Test Failed!"
msgstr "Connectie test mislukt!"
#. module: auto_backup
#: code:addons/auto_backup/models/db_backup.py:0
#, python-format
msgid ""
"Connection Test Succeeded!\n"
"Everything seems properly set up for FTP back-ups!"
msgstr "Connectie test succesvol!\n"
"Alles lijkt correct opgezet voor FTP backups!"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Contact me!"
msgstr "Contacteer mij!"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__create_uid
msgid "Created by"
msgstr "Aangemaakt door"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__create_date
msgid "Created on"
msgstr "Aangemaakt op"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__name
msgid "Database"
msgstr "Database"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__name
msgid "Database you want to schedule backups for"
msgstr "Dataabse waar u back-ups voor wilt plannen"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__display_name
msgid "Display Name"
msgstr "Schermnaam"
#. module: auto_backup
#: model:ir.model.fields.selection,name:auto_backup.selection__db_backup__backup_type__dump
msgid "Dump"
msgstr "Dump"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__email_to_notify
msgid "E-mail to notify"
msgstr "E-mail om te verwittigen"
#. module: auto_backup
#: code:addons/auto_backup/models/db_backup.py:0
#, python-format
msgid "Error ! No such database exists!"
msgstr "Fout! Deze database bestaat niet!"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__email_to_notify
msgid ""
"Fill in the e-mail where you want to be notified that the backup failed on "
"the FTP."
msgstr "Vul de e-mail in waarop u wilt verwittigd worden als de backup mislukt op de FTP."
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "For example: /odoo/backups/"
msgstr "Bijvoorbeeld: /odoo/backups/"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Go to Settings / Technical / Automation / Scheduled Actions."
msgstr "Ga naar Instellingen / Technisch / Automatisering / Geplande acties."
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Help"
msgstr "Help"
#. module: auto_backup
#: code:addons/auto_backup/models/db_backup.py:0
#, python-format
msgid "Here is what we got instead:\n"
msgstr "Hier is wat we in de plaats terugkregen:\n"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__host
msgid "Host"
msgstr "Host"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__id
msgid "ID"
msgstr "ID"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__sftp_host
msgid "IP Address SFTP Server"
msgstr "IP adres SFTP server"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__send_mail_sftp_fail
msgid ""
"If you check this option you can choose to automaticly get e-mailed when the"
" backup to the external server failed."
msgstr "Als u deze optie aanvinkt kan u kiezen om automatisch een e-mail aan te krijgen als de backuaar de externe server mislukt."
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__autoremove
msgid ""
"If you check this option you can choose to automaticly remove the backup "
"after xx days"
msgstr "Als u deze optie aanvinkt kan u kiezen om automatisch backups te verwijderen "
"na xx dagen"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__sftp_write
msgid ""
"If you check this option you can specify the details needed to write to a "
"remote server with SFTP."
msgstr "Als u deze optie aanvinkt kan u de details invullen die nodig zijn om te connecteren met de "
" externe SFTP server"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup____last_update
msgid "Last Modified on"
msgstr "Laatst gewijzigd op"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__write_uid
msgid "Last Updated by"
msgstr "Laatst bijgewerkt door"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__write_date
msgid "Last Updated on"
msgstr "Laatst bijgewerkt op"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Local backup configuration"
msgstr "Lokale back-up configuratie"
#. module: auto_backup
#: model:res.groups,name:auto_backup.group_manager
msgid "Manager"
msgstr "Beheerder"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Need more help?"
msgstr "Meer hulp nodig?"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__sftp_password
msgid "Password User SFTP Server"
msgstr "Wachtwoord gebruiker SFTP server"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__sftp_path
msgid "Path external server"
msgstr "Pad externe server"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__port
msgid "Port"
msgstr "Poort"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__days_to_keep_sftp
msgid "Remove SFTP after x days"
msgstr "Verwijderd SFTP na x dagen"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__days_to_keep
msgid "Remove after x days"
msgstr "Verwijder na x dagen"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "SFTP"
msgstr "SFTP"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__sftp_port
msgid "SFTP Port"
msgstr "SFTP poort"
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Search the action named 'Backup scheduler'."
msgstr "Zoek de actie met de naam 'Backup planner'."
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid ""
"Set the scheduler to active and fill in how often you want backups "
"generated."
msgstr "Zet de planner actief en vul in hoe vaak u wilt dat er backups gegenereerd "
"worden."
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid "Test SFTP Connection"
msgstr "Test SFTP verbinding"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__sftp_host
msgid "The IP address from your remote server. For example 192.168.0.1"
msgstr "Het IP adres van uw externe server. Bijvoorbeeld: 192.168.0.1"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__sftp_path
msgid ""
"The location to the folder where the dumps should be written to. For example /odoo/backups/.\n"
"Files will then be written to /odoo/backups/ on your remote server."
msgstr "De locatie naar de folder waar de backup naar toe moet geschreven worden. Bijvoorbeeld odoo/backups/\n"
"Bestanden worden dan naar /odoo/backups/ geschreven op de externe server"
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__sftp_password
msgid ""
"The password from the user where the SFTP connection should be made with. "
"This is the password from the user on the external server."
msgstr "Het wachtwoord van de gebruiker waar de SFTP connectie mee moet gemaakt worden. "
"Dit is het wachtwoord van de gebruiker op de externe server."
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__sftp_port
msgid "The port on the FTP server that accepts SSH/SFTP calls."
msgstr "De poort op de FTP server die SSH/SFTP accepteert."
#. module: auto_backup
#: model:ir.model.fields,help:auto_backup.field_db_backup__sftp_user
msgid ""
"The username where the SFTP connection should be made with. This is the user"
" on the external server."
msgstr "De gebruikersnaam waar de SFTP connectie mee gemaakt moet worden. Dit is de gebruiker"
" op de externe server."
#. module: auto_backup
#: model_terms:ir.ui.view,arch_db:auto_backup.view_backup_config_form
msgid ""
"This configures the scheduler for automatic backup of the given database running on given host\n"
" at given port on regular intervals.\n"
" <br/>\n"
" Automatic backups of the database can be scheduled as follows:"
msgstr "Dit configureert de planner om automatische backups van de opgegeven database te maken die op deze host,\n"
" op een bepaalde poort draaien, op regelmatige intervals.\n"
" <br/>\n"
" Automatische backups kunnen als volgt ingepland worden:"
#. module: auto_backup
#: model:ir.module.category,description:auto_backup.module_management
msgid "User access level for this module"
msgstr "Gebruikerstoegang voor deze module"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__sftp_user
msgid "Username SFTP Server"
msgstr "Gebruikersnaam SFTP server"
#. module: auto_backup
#: model:ir.model.fields,field_description:auto_backup.field_db_backup__sftp_write
msgid "Write to external server with sftp"
msgstr "Schrijf naar externe server met SFTP"
#. module: auto_backup
#: model:ir.model.fields.selection,name:auto_backup.selection__db_backup__backup_type__zip
msgid "Zip"
msgstr "ZIP"

178
auto_backup/i18n/pl.po Normal file
View File

@ -0,0 +1,178 @@
# Polish translation for openobject-addons
# Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2009-11-24 13:49+0000\n"
"PO-Revision-Date: 2011-02-15 15:01+0000\n"
"Last-Translator: OpenERP Administrators <Unknown>\n"
"Language-Team: Polish <pl@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-04-20 05:35+0000\n"
"X-Generator: Launchpad (build 16567)\n"
#. module: auto_backup
#: help:db.backup,name:0
msgid "Database you want to schedule backups for"
msgstr "Baza danych, dla której chcesz zaplanować robienie kopii zapasowej"
#. module: auto_backup
#: constraint:ir.model:0
msgid ""
"The Object name must start with x_ and not contain any special character !"
msgstr ""
"Nazwa obiektu musi zaczynać się od x_ oraz nie może zawierać znaków "
"specjalnych !"
#. module: auto_backup
#: constraint:ir.actions.act_window:0
msgid "Invalid model name in the action definition."
msgstr "Nieprawidłowa nazwa modelu w definicji akcji."
#. module: auto_backup
#: model:ir.model,name:auto_backup.model_db_backup
msgid "db.backup"
msgstr ""
#. module: auto_backup
#: view:db.backup:0
msgid ""
"1) Go to Administration / Configuration / Scheduler / Scheduled Actions"
msgstr "1) Idź do Administracja / Konfirguracja / Planista / Planowane akcje"
#. module: auto_backup
#: model:ir.actions.act_window,name:auto_backup.action_backup_conf_form
#: model:ir.ui.menu,name:auto_backup.backup_conf_menu
msgid "Configure Backup"
msgstr "Konfiguruj kopie zapasowe"
#. module: auto_backup
#: view:db.backup:0
msgid "Test"
msgstr "Przetestuj"
#. module: auto_backup
#: view:db.backup:0
msgid "IP Configuration"
msgstr "Konfiguracja adresu IP"
#. module: auto_backup
#: help:db.backup,bkp_dir:0
msgid "Absolute path for storing the backups"
msgstr "Pełna ścieżka dla kopii zapasowych"
#. module: auto_backup
#: model:ir.module.module,shortdesc:auto_backup.module_meta_information
msgid "Database Auto-Backup"
msgstr "Automatyczne kopie zapasowe bazy danych"
#. module: auto_backup
#: view:db.backup:0
msgid "Database Configuration"
msgstr "Konfiguracja bazy danych"
#. module: auto_backup
#: view:db.backup:0
msgid "4) Set other values as per your preference"
msgstr "4) Ustaw inne dane według uznania"
#. module: auto_backup
#: field:db.backup,host:0
msgid "Host"
msgstr "Host"
#. module: auto_backup
#: view:db.backup:0
msgid ""
"Automatic backup of all the databases under this can be scheduled as "
"follows: "
msgstr ""
"Automatyczne kopie wszystkich baz danych mogą być zaplanowane następująco: "
#. module: auto_backup
#: constraint:ir.ui.view:0
msgid "Invalid XML for View Architecture!"
msgstr "XML niewłaściwy dla tej architektury wyświetlania!"
#. module: auto_backup
#: field:db.backup,bkp_dir:0
msgid "Backup Directory"
msgstr "Katalog kopii zapasowych"
#. module: auto_backup
#: field:db.backup,name:0
msgid "Database"
msgstr "Baza danych"
#. module: auto_backup
#: view:db.backup:0
msgid "2) Schedule new action(create a new record)"
msgstr "2) Zaplanuj nową akcję (utwórz nowy rekord)"
#. module: auto_backup
#: model:ir.module.module,description:auto_backup.module_meta_information
msgid ""
"The generic Open ERP Database Auto-Backup system enables the user to make "
"configurations for the automatic backup of the database.\n"
"User simply requires to specify host & port under IP Configuration & "
"database(on specified host running at specified port) and backup "
"directory(in which all the backups of the specified database will be stored) "
"under Database Configuration.\n"
"\n"
"Automatic backup for all such configured databases under this can then be "
"scheduled as follows: \n"
" \n"
"1) Go to Administration / Configuration / Scheduler / Scheduled Actions\n"
"2) Schedule new action(create a new record)\n"
"3) Set 'Object' to 'db.backup' and 'Function' to 'schedule_backup' under "
"page 'Technical Data'\n"
"4) Set other values as per your preference"
msgstr ""
"System Open ERP Database Auto-Backup pozwala użytkownikowi utworzyć "
"konfiguracje dla automatycznego zachowywania kopii zapasowych baz danych.\n"
"Użytkownik musi tylko podać host i port w Konfiguracji IP i bazę danych "
"oraz katalog dla kopii zapasowej Konfiguracji bazy danych.\n"
"\n"
"Automatyczne kopie wszystkich baz danych mogą być zaplanowane następująco: "
"\n"
" \n"
"1) Idź do Administracja / Konfirguracja / Planista / Planowane akcje\n"
"2) Zaplanuj nową akcję (utwórz nowy rekord)\n"
"3) Ustaw 'Obiekt' na 'db.backup' i 'Funkcja' na 'schedule_backup' na stronie "
"'Dane techniczne'.\n"
"4) Ustaw inne dane według uznania"
#. module: auto_backup
#: view:db.backup:0
msgid ""
"3) Set 'Object' to 'db.backup' and 'Function' to 'schedule_backup' under "
"page 'Technical Data'"
msgstr ""
"3) Ustaw 'Obiekt' na 'db.backup' i 'Funkcja' na 'schedule_backup' na stronie "
"'Dane techniczne'."
#. module: auto_backup
#: view:db.backup:0
msgid "Help"
msgstr "Pomoc"
#. module: auto_backup
#: view:db.backup:0
msgid ""
"This configures the scheduler for automatic backup of the given database "
"running on given host at given port on regular intervals."
msgstr ""
"Tu konfigurujesz planistę do automatycznego zapisywania kopii zapasowych dla "
"określonej bazy danych na na określonym hoście (komputerze) na określonym "
"porcie w regularnych terminach."
#. module: auto_backup
#: field:db.backup,port:0
msgid "Port"
msgstr "Port"

183
auto_backup/i18n/pt_BR.po Normal file
View File

@ -0,0 +1,183 @@
# Brazilian Portuguese translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2009-11-24 13:49+0000\n"
"PO-Revision-Date: 2013-07-20 09:39+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Brazilian Portuguese <pt_BR@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-22 05:50+0000\n"
"X-Generator: Launchpad (build 16696)\n"
#. module: auto_backup
#: help:db.backup,name:0
msgid "Database you want to schedule backups for"
msgstr "Banco de dados que você deseja agendar backups para"
#. module: auto_backup
#: constraint:ir.model:0
msgid ""
"The Object name must start with x_ and not contain any special character !"
msgstr ""
"O nome do objeto deve iniciar com x_ e não conter qualquer caractere "
"especial!"
#. module: auto_backup
#: constraint:ir.actions.act_window:0
msgid "Invalid model name in the action definition."
msgstr "Nome do modelo inválida na definição da ação."
#. module: auto_backup
#: model:ir.model,name:auto_backup.model_db_backup
msgid "db.backup"
msgstr "db.backup"
#. module: auto_backup
#: view:db.backup:0
msgid ""
"1) Go to Administration / Configuration / Scheduler / Scheduled Actions"
msgstr ""
"1) Vá para Administração / Configuração / Programador / Ações Programadas"
#. module: auto_backup
#: model:ir.actions.act_window,name:auto_backup.action_backup_conf_form
#: model:ir.ui.menu,name:auto_backup.backup_conf_menu
msgid "Configure Backup"
msgstr "Configurar backup"
#. module: auto_backup
#: view:db.backup:0
msgid "Test"
msgstr "Teste"
#. module: auto_backup
#: view:db.backup:0
msgid "IP Configuration"
msgstr "Configuração de IP"
#. module: auto_backup
#: help:db.backup,bkp_dir:0
msgid "Absolute path for storing the backups"
msgstr "Caminho absoluto para armazenar os backups"
#. module: auto_backup
#: model:ir.module.module,shortdesc:auto_backup.module_meta_information
msgid "Database Auto-Backup"
msgstr "Banco de Dados Auto-Backup"
#. module: auto_backup
#: view:db.backup:0
msgid "Database Configuration"
msgstr "Configuração do Banco de Dados"
#. module: auto_backup
#: view:db.backup:0
msgid "4) Set other values as per your preference"
msgstr "4) Defina outros valores como por sua preferência"
#. module: auto_backup
#: field:db.backup,host:0
msgid "Host"
msgstr "Host"
#. module: auto_backup
#: view:db.backup:0
msgid ""
"Automatic backup of all the databases under this can be scheduled as "
"follows: "
msgstr ""
"Backup automático de todos os bancos de dados sob este pode ser programado "
"como segue: "
#. module: auto_backup
#: constraint:ir.ui.view:0
msgid "Invalid XML for View Architecture!"
msgstr "Inválido XML para Ver Arquitetura!"
#. module: auto_backup
#: field:db.backup,bkp_dir:0
msgid "Backup Directory"
msgstr "Diretório de backup"
#. module: auto_backup
#: field:db.backup,name:0
msgid "Database"
msgstr "Banco de Dados"
#. module: auto_backup
#: view:db.backup:0
msgid "2) Schedule new action(create a new record)"
msgstr "2) Programe nova ação (criar um novo registro)"
#. module: auto_backup
#: model:ir.module.module,description:auto_backup.module_meta_information
msgid ""
"The generic Open ERP Database Auto-Backup system enables the user to make "
"configurations for the automatic backup of the database.\n"
"User simply requires to specify host & port under IP Configuration & "
"database(on specified host running at specified port) and backup "
"directory(in which all the backups of the specified database will be stored) "
"under Database Configuration.\n"
"\n"
"Automatic backup for all such configured databases under this can then be "
"scheduled as follows: \n"
" \n"
"1) Go to Administration / Configuration / Scheduler / Scheduled Actions\n"
"2) Schedule new action(create a new record)\n"
"3) Set 'Object' to 'db.backup' and 'Function' to 'schedule_backup' under "
"page 'Technical Data'\n"
"4) Set other values as per your preference"
msgstr ""
"O sistema Auto-Backup genérico Aberto ERP banco de dados permite que o "
"usuário faça configurações para o backup automático do banco de dados. \n"
"usuário requer simplesmente para especificar anfitrião e porta em "
"Configuração do IP e banco de dados (on especificado host executando na "
"porta especificada) eo diretório de backup (em . que todos os backups de "
"banco de dados especificado serão armazenados) em Configuração do banco de "
"dados \n"
"de backup automático de todos esses bancos de dados configurados sob este "
"pode ser programado como segue:\n"
" \n"
" \n"
"1) Vá para Administração / Configuração / Programador / Ações Programadas \n"
"2) Programe nova ação (criar um novo registro) \n"
"3) Defina 'objeto' para 'db.backup' e 'função' para 'schedule_backup' em "
"página \"Dados Técnicos\" \n"
"4 ) Defina outros valores de acordo com sua preferência"
#. module: auto_backup
#: view:db.backup:0
msgid ""
"3) Set 'Object' to 'db.backup' and 'Function' to 'schedule_backup' under "
"page 'Technical Data'"
msgstr ""
"3) Defina 'objeto' para 'db.backup' e 'função' para 'schedule_backup \"em\" "
"Dados Técnicos \"página"
#. module: auto_backup
#: view:db.backup:0
msgid "Help"
msgstr "Ajudar"
#. module: auto_backup
#: view:db.backup:0
msgid ""
"This configures the scheduler for automatic backup of the given database "
"running on given host at given port on regular intervals."
msgstr ""
"Isso configura o agendador de backup automático de um determinado banco de "
"dados rodando em determinado host em determinada porta em intervalos "
"regulares."
#. module: auto_backup
#: field:db.backup,port:0
msgid "Port"
msgstr "Porto"

298
auto_backup/i18n/zh_CN.po Normal file
View File

@ -0,0 +1,298 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * auto_backup
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 8.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-03-26 14:17+0000\n"
"PO-Revision-Date: 2015-03-27 00:16+0800\n"
"Last-Translator: <>\n"
"Language-Team: Talway <1473162392@qq.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"Language: zh_CN\n"
"X-Generator: Poedit 1.7.5\n"
#. module: auto_backup
#: code:addons/auto_backup/backup_scheduler.py:137
#, python-format
msgid "%s"
msgstr "%s"
#. module: auto_backup
#: help:db.backup,bkp_dir:0
msgid "Absolute path for storing the backups"
msgstr "备份绝对路径"
#. module: auto_backup
#: field:db.backup,sendmailsftpfail:0
msgid "Auto. E-mail on backup fail"
msgstr "FTP备份失败自动邮件通知你"
#. module: auto_backup
#: field:db.backup,autoremove:0
msgid "Auto. Remove Backups"
msgstr "自动删除备份"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Automatic backups of the database can be scheduled as follows:"
msgstr "数据库的自动备份时间安排如下:"
#. module: auto_backup
#: field:db.backup,bkp_dir:0
msgid "Backup Directory"
msgstr "备份目录"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_tree
msgid "Backups"
msgstr "备份"
#. module: auto_backup
#: help:db.backup,daystokeepsftp:0
msgid ""
"Choose after how many days the backup should be deleted from the FTP server. For example:\n"
"If you fill in 5 the backups will be removed after 5 days from the FTP server."
msgstr ""
"选择后多少天备份应被删除从 FTP 服务器。例如: \n"
"如果你填写 5 将5 天后 从FTP 服务器 删除备份文件。"
#. module: auto_backup
#: help:db.backup,daystokeep:0
msgid ""
"Choose after how many days the backup should be deleted. For example:\n"
"If you fill in 5 the backups will be removed after 5 days."
msgstr ""
"选择后多少天备份应被删除。例如: \n"
"如果你填写5将 5 天后删除备份。"
#. module: auto_backup
#: model:ir.actions.act_window,name:auto_backup.action_backup_conf_form
#: model:ir.ui.menu,name:auto_backup.backup_conf_menu
msgid "Configure Backup"
msgstr "数据库备份"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Contact us!"
msgstr "邮件联系我们!"
#. module: auto_backup
#: field:db.backup,create_uid:0
msgid "Created by"
msgstr "创建者"
#. module: auto_backup
#: field:db.backup,create_date:0
msgid "Created on"
msgstr "创建时间"
#. module: auto_backup
#: field:db.backup,name:0
msgid "Database"
msgstr "数据库"
#. module: auto_backup
#: help:db.backup,name:0
msgid "Database you want to schedule backups for"
msgstr "计划备份的数据库"
#. module: auto_backup
#: field:db.backup,emailtonotify:0
msgid "E-mail to notify"
msgstr "E-mail邮件地址"
#. module: auto_backup
#: code:addons/auto_backup/backup_scheduler.py:106 constraint:db.backup:0
#, python-format
msgid "Error ! No such database exists!"
msgstr "错误 !这个数据库不存在 "
#. module: auto_backup
#: help:db.backup,emailtonotify:0
msgid "Fill in the e-mail where you want to be notified that the backup failed on the FTP."
msgstr "FTP备份失败时邮件通知你详细信息"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "For example: /odoo/backups/"
msgstr "例如: /odoo/backups/"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Go to Settings / Technical / Automation / Scheduled Actions."
msgstr "点击 设置 / 技术 / 自动化 / 计划的动作"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Help"
msgstr "帮助"
#. module: auto_backup
#: field:db.backup,host:0
msgid "Host"
msgstr "服务器"
#. module: auto_backup
#: field:db.backup,id:0
msgid "ID"
msgstr "ID"
#. module: auto_backup
#: field:db.backup,sftpip:0
msgid "IP Address SFTP Server"
msgstr " SFTP 服务器 IP 地址"
#. module: auto_backup
#: help:db.backup,sendmailsftpfail:0
msgid "If you check this option you can choose to automaticly get e-mailed when the backup to the external server failed."
msgstr "如果您选中此选项,您可以选择自动收到通过邮件发送到外部服务器备份失败的信息。"
#. module: auto_backup
#: help:db.backup,autoremove:0
msgid "If you check this option you can choose to automaticly remove the backup after xx days"
msgstr "如果您选中此选项,您可以选择 xx 天后自动删除备份"
#. module: auto_backup
#: help:db.backup,sftpwrite:0
msgid "If you check this option you can specify the details needed to write to a remote server with SFTP."
msgstr "如果您选中此选项,您可以指定需要写入 sftp 的远程服务器的详细信息。"
#. module: auto_backup
#: field:db.backup,write_uid:0
msgid "Last Updated by"
msgstr "最后更新者"
#. module: auto_backup
#: field:db.backup,write_date:0
msgid "Last Updated on"
msgstr "上次更新日期"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Local backup configuration"
msgstr "本地备份配置"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Need more help?"
msgstr "需要更多帮助吗?"
#. module: auto_backup
#: field:db.backup,sftppassword:0
msgid "Password User SFTP Server"
msgstr " SFTP服务器密码"
#. module: auto_backup
#: field:db.backup,sftppath:0
msgid "Path external server"
msgstr "服务器目录"
#. module: auto_backup
#: field:db.backup,port:0
msgid "Port"
msgstr "端口"
#. module: auto_backup
#: field:db.backup,daystokeepsftp:0
msgid "Remove SFTP after x days"
msgstr "多少天后从服务器删除"
#. module: auto_backup
#: field:db.backup,daystokeep:0
msgid "Remove after x days"
msgstr "多少天后删除"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "SFTP"
msgstr "SFTP"
#. module: auto_backup
#: field:db.backup,sftpport:0
msgid "SFTP Port"
msgstr "SFTP 端口"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_search
msgid "Search options"
msgstr "搜索选项"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Search the action named 'Backup scheduler'."
msgstr "搜索计划备份调度程序“Backup scheduler”。"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Set the scheduler to active and fill in how often you want backups generated."
msgstr "设置计划动作为有效,并填写备份间隔时间,间隔时间单位,间隔次数,执行时间等数据库具体备份方案。"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Test"
msgstr "测试"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Test SFTP Connection"
msgstr "测试 SFTP 连接"
#. module: auto_backup
#: help:db.backup,sftpip:0
msgid "The IP address from your remote server. For example 192.168.0.1"
msgstr "SFTP服务器的 IP 地址。例如: 192.168.0.1"
#. module: auto_backup
#: help:db.backup,sftppath:0
msgid ""
"The location to the folder where the dumps should be written to. For example /odoo/backups/.\n"
"Files will then be written to /odoo/backups/ on your remote server."
msgstr ""
"转储应将写入的文件夹位置。例如 /odoo/backups/远程服务器上,然后将写入 /odoo/backups/.\n"
"Files。"
#. module: auto_backup
#: help:db.backup,sftppassword:0
msgid "The password from the user where the SFTP connection should be made with. This is the password from the user on the external server."
msgstr "从 SFTP 服务器连接该用户的密码。这是SFTP服务器上的用户密码。"
#. module: auto_backup
#: help:db.backup,sftpport:0
msgid "The port on the FTP server that accepts SSH/SFTP calls."
msgstr "接受 SSH/SFTP 使用的FTP 服务器上的端口。"
#. module: auto_backup
#: help:db.backup,sftpusername:0
msgid "The username where the SFTP connection should be made with. This is the user on the external server."
msgstr "SFTP 连接使用该用户名。这是在SFTP服务器上的用户。"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "This configures the scheduler for automatic backup of the given database running on given host at given port on regular intervals."
msgstr "配置适用指定数据库备份 在设置服务器端口定期运行"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Use SFTP with caution! This writes files to external servers under the path you specify."
msgstr "请注意你的 SFTP服务器网络安全数据库备份文件将备份到你的SFTP服务器文件保存在设置的目录下面。"
#. module: auto_backup
#: field:db.backup,sftpusername:0
msgid "Username SFTP Server"
msgstr "SFTP服务器用户名"
#. module: auto_backup
#: view:db.backup:auto_backup.view_backup_conf_form
msgid "Warning:"
msgstr "警告:"
#. module: auto_backup
#: field:db.backup,sftpwrite:0
msgid "Write to external server with sftp"
msgstr "备份到外部 sftp 服务器"

View File

@ -0,0 +1,2 @@
from . import db_backup

View File

@ -0,0 +1,329 @@
import os
import datetime
import time
import shutil
import json
import tempfile
from odoo import models, fields, api, tools, _
from odoo.exceptions import Warning, AccessDenied
import odoo
import logging
_logger = logging.getLogger(__name__)
try:
import paramiko
except ImportError:
raise ImportError(
'This module needs paramiko to automatically write backups to the FTP through SFTP. '
'Please install paramiko on your system. (sudo pip3 install paramiko)')
class DbBackup(models.Model):
_name = 'db.backup'
_description = 'Backup configuration record'
def _get_db_name(self):
dbName = self._cr.dbname
return dbName
# Columns for local server configuration
host = fields.Char('Host', required=True, default='localhost')
port = fields.Char('Port', required=True, default=8069)
name = fields.Char('Database', required=True, help='Database you want to schedule backups for',
default=_get_db_name)
folder = fields.Char('Backup Directory', help='Absolute path for storing the backups', required='True',
default='/odoo/backups')
backup_type = fields.Selection([('zip', 'Zip'), ('dump', 'Dump')], 'Backup Type', required=True, default='zip')
autoremove = fields.Boolean('Auto. Remove Backups',
help='If you check this option you can choose to automaticly remove the backup '
'after xx days')
days_to_keep = fields.Integer('Remove after x days',
help="Choose after how many days the backup should be deleted. For example:\n"
"If you fill in 5 the backups will be removed after 5 days.",
required=True)
# Columns for external server (SFTP)
sftp_write = fields.Boolean('Write to external server with sftp',
help="If you check this option you can specify the details needed to write to a remote "
"server with SFTP.")
sftp_path = fields.Char('Path external server',
help='The location to the folder where the dumps should be written to. For example '
'/odoo/backups/.\nFiles will then be written to /odoo/backups/ on your remote server.')
sftp_host = fields.Char('IP Address SFTP Server',
help='The IP address from your remote server. For example 192.168.0.1')
sftp_port = fields.Integer('SFTP Port', help='The port on the FTP server that accepts SSH/SFTP calls.', default=22)
sftp_user = fields.Char('Username SFTP Server',
help='The username where the SFTP connection should be made with. This is the user on the '
'external server.')
sftp_password = fields.Char('Password User SFTP Server',
help='The password from the user where the SFTP connection should be made with. This '
'is the password from the user on the external server.')
days_to_keep_sftp = fields.Integer('Remove SFTP after x days',
help='Choose after how many days the backup should be deleted from the FTP '
'server. For example:\nIf you fill in 5 the backups will be removed after '
'5 days from the FTP server.',
default=30)
send_mail_sftp_fail = fields.Boolean('Auto. E-mail on backup fail',
help='If you check this option you can choose to automaticly get e-mailed '
'when the backup to the external server failed.')
email_to_notify = fields.Char('E-mail to notify',
help='Fill in the e-mail where you want to be notified that the backup failed on '
'the FTP.')
def test_sftp_connection(self, context=None):
self.ensure_one()
# Check if there is a success or fail and write messages
message_title = ""
message_content = ""
error = ""
has_failed = False
for rec in self:
ip_host = rec.sftp_host
port_host = rec.sftp_port
username_login = rec.sftp_user
password_login = rec.sftp_password
# Connect with external server over SFTP, so we know sure that everything works.
try:
s = paramiko.SSHClient()
s.set_missing_host_key_policy(paramiko.AutoAddPolicy())
s.connect(ip_host, port_host, username_login, password_login, timeout=10)
sftp = s.open_sftp()
sftp.close()
message_title = _("Connection Test Succeeded!\nEverything seems properly set up for FTP back-ups!")
except Exception as e:
_logger.critical('There was a problem connecting to the remote ftp: %s', str(e))
error += str(e)
has_failed = True
message_title = _("Connection Test Failed!")
if len(rec.sftp_host) < 8:
message_content += "\nYour IP address seems to be too short.\n"
message_content += _("Here is what we got instead:\n")
finally:
if s:
s.close()
if has_failed:
raise Warning(message_title + '\n\n' + message_content + "%s" % str(error))
else:
raise Warning(message_title + '\n\n' + message_content)
@api.model
def schedule_backup(self):
conf_ids = self.search([])
for rec in conf_ids:
try:
if not os.path.isdir(rec.folder):
os.makedirs(rec.folder)
except:
raise
# Create name for dumpfile.
bkp_file = '%s_%s.%s' % (time.strftime('%Y_%m_%d_%H_%M_%S'), rec.name, rec.backup_type)
file_path = os.path.join(rec.folder, bkp_file)
fp = open(file_path, 'wb')
try:
# try to backup database and write it away
fp = open(file_path, 'wb')
self._take_dump(rec.name, fp, 'db.backup', rec.backup_type)
fp.close()
except Exception as error:
_logger.debug(
"Couldn't backup database %s. Bad database administrator password for server running at "
"http://%s:%s" % (rec.name, rec.host, rec.port))
_logger.debug("Exact error from the exception: %s", str(error))
continue
# Check if user wants to write to SFTP or not.
if rec.sftp_write is True:
try:
# Store all values in variables
dir = rec.folder
path_to_write_to = rec.sftp_path
ip_host = rec.sftp_host
port_host = rec.sftp_port
username_login = rec.sftp_user
password_login = rec.sftp_password
_logger.debug('sftp remote path: %s', path_to_write_to)
try:
s = paramiko.SSHClient()
s.set_missing_host_key_policy(paramiko.AutoAddPolicy())
s.connect(ip_host, port_host, username_login, password_login, timeout=20)
sftp = s.open_sftp()
except Exception as error:
_logger.critical('Error connecting to remote server! Error: %s', str(error))
try:
sftp.chdir(path_to_write_to)
except IOError:
# Create directory and subdirs if they do not exist.
current_directory = ''
for dirElement in path_to_write_to.split('/'):
current_directory += dirElement + '/'
try:
sftp.chdir(current_directory)
except:
_logger.info('(Part of the) path didn\'t exist. Creating it now at %s',
current_directory)
# Make directory and then navigate into it
sftp.mkdir(current_directory, 777)
sftp.chdir(current_directory)
pass
sftp.chdir(path_to_write_to)
# Loop over all files in the directory.
for f in os.listdir(dir):
if rec.name in f:
fullpath = os.path.join(dir, f)
if os.path.isfile(fullpath):
try:
sftp.stat(os.path.join(path_to_write_to, f))
_logger.debug(
'File %s already exists on the remote FTP Server ------ skipped', fullpath)
# This means the file does not exist (remote) yet!
except IOError:
try:
sftp.put(fullpath, os.path.join(path_to_write_to, f))
_logger.info('Copying File % s------ success', fullpath)
except Exception as err:
_logger.critical(
'We couldn\'t write the file to the remote server. Error: %s', str(err))
# Navigate in to the correct folder.
sftp.chdir(path_to_write_to)
_logger.debug("Checking expired files")
# Loop over all files in the directory from the back-ups.
# We will check the creation date of every back-up.
for file in sftp.listdir(path_to_write_to):
if rec.name in file:
# Get the full path
fullpath = os.path.join(path_to_write_to, file)
# Get the timestamp from the file on the external server
timestamp = sftp.stat(fullpath).st_mtime
createtime = datetime.datetime.fromtimestamp(timestamp)
now = datetime.datetime.now()
delta = now - createtime
# If the file is older than the days_to_keep_sftp (the days to keep that the user filled in
# on the Odoo form it will be removed.
if delta.days >= rec.days_to_keep_sftp:
# Only delete files, no directories!
if ".dump" in file or '.zip' in file:
_logger.info("Delete too old file from SFTP servers: %s", file)
sftp.unlink(file)
# Close the SFTP session.
sftp.close()
s.close()
except Exception as e:
try:
sftp.close()
s.close()
except:
pass
_logger.error('Exception! We couldn\'t back up to the FTP server. Here is what we got back '
'instead: %s', str(e))
# At this point the SFTP backup failed. We will now check if the user wants
# an e-mail notification about this.
if rec.send_mail_sftp_fail:
try:
ir_mail_server = self.env['ir.mail_server'].search([], order='sequence asc', limit=1)
message = "Dear,\n\nThe backup for the server " + rec.host + " (IP: " + rec.sftp_host + \
") failed. Please check the following details:\n\nIP address SFTP server: " + \
rec.sftp_host + "\nUsername: " + rec.sftp_user + \
"\n\nError details: " + tools.ustr(e) + \
"\n\nWith kind regards"
catch_all_domain = self.env["ir.config_parameter"].sudo().get_param("mail.catchall.domain")
response_mail = "auto_backup@%s" % catch_all_domain if catch_all_domain else self.env.user.partner_id.email
msg = ir_mail_server.build_email(response_mail, [rec.email_to_notify],
"Backup from " + rec.host + "(" + rec.sftp_host +
") failed",
message)
ir_mail_server.send_email(msg)
except Exception:
pass
# Remove all old files (on local server) in case this is configured..
if rec.autoremove:
directory = rec.folder
# Loop over all files in the directory.
for f in os.listdir(directory):
fullpath = os.path.join(directory, f)
# Only delete the ones wich are from the current database
# (Makes it possible to save different databases in the same folder)
if rec.name in fullpath:
timestamp = os.stat(fullpath).st_ctime
createtime = datetime.datetime.fromtimestamp(timestamp)
now = datetime.datetime.now()
delta = now - createtime
if delta.days >= rec.days_to_keep:
# Only delete files (which are .dump and .zip), no directories.
if os.path.isfile(fullpath) and (".dump" in f or '.zip' in f):
_logger.info("Delete local out-of-date file: %s", fullpath)
os.remove(fullpath)
# This is more or less the same as the default Odoo function at
# https://github.com/odoo/odoo/blob/e649200ab44718b8faefc11c2f8a9d11f2db7753/odoo/service/db.py#L209
# The main difference is that we do not do have a wrapper for the function check_db_management_enabled here and
# that we authenticate based on the cron its user id and by checking if we have 'db.backup' defined in the function
# call. Since this function is called from the cron and since we have these security checks on model and on user_id
# its pretty impossible to hack any way to take a backup. This allows us to disable the Odoo database manager
# which is a MUCH safer way
def _take_dump(self, db_name, stream, model, backup_format='zip'):
"""Dump database `db` into file-like object `stream` if stream is None
return a file object with the dump """
cron_user_id = self.env.ref('auto_backup.backup_scheduler').user_id.id
if self._name != 'db.backup' or cron_user_id != self.env.user.id:
_logger.error('Unauthorized database operation. Backups should only be available from the cron job.')
raise AccessDenied()
_logger.info('DUMP DB: %s format %s', db_name, backup_format)
cmd = ['pg_dump', '--no-owner']
cmd.append(db_name)
if backup_format == 'zip':
with tempfile.TemporaryDirectory() as dump_dir:
filestore = odoo.tools.config.filestore(db_name)
if os.path.exists(filestore):
shutil.copytree(filestore, os.path.join(dump_dir, 'filestore'))
with open(os.path.join(dump_dir, 'manifest.json'), 'w') as fh:
db = odoo.sql_db.db_connect(db_name)
with db.cursor() as cr:
json.dump(self._dump_db_manifest(cr), fh, indent=4)
cmd.insert(-1, '--file=' + os.path.join(dump_dir, 'dump.sql'))
odoo.tools.exec_pg_command(*cmd)
if stream:
odoo.tools.osutil.zip_dir(dump_dir, stream, include_dir=False, fnct_sort=lambda file_name: file_name != 'dump.sql')
else:
t=tempfile.TemporaryFile()
odoo.tools.osutil.zip_dir(dump_dir, t, include_dir=False, fnct_sort=lambda file_name: file_name != 'dump.sql')
t.seek(0)
return t
else:
cmd.insert(-1, '--format=c')
stdin, stdout = odoo.tools.exec_pg_command_pipe(*cmd)
if stream:
shutil.copyfileobj(stdout, stream)
else:
return stdout
def _dump_db_manifest(self, cr):
pg_version = "%d.%d" % divmod(cr._obj.connection.server_version / 100, 100)
cr.execute("SELECT name, latest_version FROM ir_module_module WHERE state = 'installed'")
modules = dict(cr.fetchall())
manifest = {
'odoo_dump': '1',
'db_name': cr.dbname,
'version': odoo.release.version,
'version_info': odoo.release.version_info,
'major_version': odoo.release.major_version,
'pg_version': pg_version,
'modules': modules,
}
return manifest

View File

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
admin_access, db_backup admin access,model_db_backup,base.group_no_one,1,1,1,1
admin_security_rule, Model db_backup admin access,model_db_backup,auto_backup.group_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 admin_access db_backup admin access model_db_backup base.group_no_one 1 1 1 1
3 admin_security_rule Model db_backup admin access model_db_backup auto_backup.group_manager 1 1 1 1

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record model="ir.module.category" id="module_management">
<field name="name">Auto backup access</field>
<field name="description">User access level for this module</field>
<field name="sequence">3</field>
</record>
<record id="group_manager" model="res.groups">
<field name="name">Manager</field>
<field name="category_id" ref="auto_backup.module_management"/>
</record>
</data>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,95 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Automated backups</h2>
<h3 class="oe_slogan">A tool for all your back-ups, internal and external!</h3>
</div>
<div class="oe_span6">
<div class="oe_demo oe_picture oe_screenshot">
<img src="overview.png">
</div>
</div>
<div class="oe_span6">
<p class="oe_mt32">
Keep your Odoo data safe with this module. Take automated back-ups, remove them automatically
and even write them to an external server through an encrypted tunnel.
You can even specify how long local backups and external backups should be kept, automatically!
</p>
<div class="oe_centeralign oe_websiteonly">
<a href="http://www.openerp.com/start?app=mail" class="oe_button oe_big oe_tacky">Start your <span class="oe_emph">free</span> trial</a>
</div>
</div>
</div>
</section>
<!-- Second block -->
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">Connect with an FTP Server</h2>
<h3 class="oe_slogan">Keep your data safe, through an SSH tunnel!</h3>
<div class="oe_span6">
<p class="oe_mt32">
Want to go even further and write your backups to an external server?
You can with this module! Specify the credentials to the server, specify a path and everything will be backed up automatically. This is done through an SSH (encrypted) tunnel, thanks to pysftp, so your data is safe!
</p>
</div>
<div class="oe_span6">
<div class="oe_row_img oe_centered">
<img class="oe_picture oe_screenshot" src="terminalssh.png">
</div>
</div>
</div>
</section>
<!--Third block -->
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Test connection</h2>
<h3 class="oe_slogan">Checks your credentials in one click</h3>
</div>
<div class="oe_span6">
<div class="oe_demo oe_picture oe_screenshot">
<img src="testconnection.png">
<img src="testconnectionfailed.png">
</div>
</div>
<div class="oe_span6">
<p class="oe_mt32">
Want to make sure if the connection details are correct and if Odoo can automatically write them to the remote server? Simply click on the 'Test SFTP Connection' button and you will get message telling you if everything is OK, or what is wrong!
</p>
</div>
</div>
</section>
<!-- Fourth block -->
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<h2 class="oe_slogan">E-mail on backup failure</h2>
<h3 class="oe_slogan">Stay informed of problems, automatically!</h3>
<div class="oe_span6">
<p class="oe_mt32">
Do you want to know if the database backup failed? Check the checkbox 'Auto. E-mail on backup fail' and fill in your e-mail.
Every time a backup fails you will get an e-mail in your mailbox with technical details.
</p>
</div>
<div class="oe_span6">
<div class="oe_row_img oe_centered">
<img class="oe_picture oe_screenshot" src="emailnotification.png">
</div>
</div>
</div>
</section>
<!--Fifth block -->
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Contact / Support</h2>
<h3 class="oe_slogan">Need help or want extra features?</h3>
</div>
<div class="oe_span6">
<p class="oe_mt32">
Need help with the configuration or want this module to have more functionalities?
Please create a bug report <a href="https://github.com/Yenthe666/auto_backup/issues">on the Github issue tracker</a>
</p>
</div>
</div>
</section>

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1,105 @@
<odoo>
<record id="view_backup_config_form" model="ir.ui.view">
<field name="name">db.backup.form</field>
<field name="model">db.backup</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Back-up view">
<sheet>
<group col="4" colspan="4">
<separator col="2" string="Local backup configuration"/>
</group>
<group name="configuration">
<field name="host" colspan="2"/>
<field name="name"/>
<field name="port"/>
<field name="backup_type"/>
<field name="folder"/>
<field name="autoremove"/>
<field name="days_to_keep" attrs="{'invisible': [('autoremove','=',False)]}"/>
</group>
<group name="allow_stfp" col="4" colspan="4">
<separator col="2" string="SFTP"/>
</group>
<div style="width:50%;border-radius:10px;margin: 10px 0px;padding:15px 10px 15px 10px;
background-repeat: no-repeat;background-position: 10px center;color: #9F6000;
background-color: #FEEFB3;"
attrs="{'invisible': [('sftp_write','=',False)]}">
<b>Warning:</b>
Use SFTP with caution! This writes files to external servers under the path you specify.
</div>
<group name="sftp_configuration">
<field name="sftp_write"/>
<field name="sftp_host"
attrs="{'invisible':[('sftp_write', '=', False)],'required':[('sftp_write', '=', True)]}"/>
<field name="sftp_port"
attrs="{'invisible':[('sftp_write', '=', False)],'required':[('sftp_write', '=', True)]}"/>
<field name="sftp_user"
attrs="{'invisible':[('sftp_write', '=', False)], 'required':[('sftp_write', '=', True)]}"/>
<field name="sftp_password"
attrs="{'invisible':[('sftp_write', '=', False)],'required': [('sftp_write', '=', True)]}"
password="True"/>
<field name="sftp_path"
attrs="{'invisible':[('sftp_write', '==', False)],'required':[('sftp_write', '==', True)]}"
placeholder="For example: /odoo/backups/"/>
<field name="days_to_keep_sftp"
attrs="{'invisible': [('sftp_write', '=', False)], 'required': [('sftp_write', '=', True)]}"/>
<field name="send_mail_sftp_fail" attrs="{'invisible': [('sftp_write','=',False)]}"/>
<field name="email_to_notify"
attrs="{'invisible':['|',('send_mail_sftp_fail', '==', False), ('sftp_write', '=', False)], 'required': [('send_mail_sftp_fail', '=', True)]}"/>
<button name="test_sftp_connection" type="object"
attrs="{'invisible': [('sftp_write','=',False)]}" string="Test SFTP Connection"/>
</group>
<separator string="Help" colspan="2"/>
<div name="configuration_details">
This configures the scheduler for automatic backup of the given database running on given host
at given port on regular intervals.
<br/>
Automatic backups of the database can be scheduled as follows:
<ol>
<li>
Go to Settings / Technical / Automation / Scheduled Actions.
</li>
<li>
Search the action named 'Backup scheduler'.
</li>
<li>
Set the scheduler to active and fill in how often you want backups generated.
</li>
</ol>
<p style="font-size:18px;">
Need more help?
<a href="https://github.com/Yenthe666/auto_backup/issues">Contact me!</a>
</p>
</div>
</sheet>
</form>
</field>
</record>
<record id="view_backup_config_tree" model="ir.ui.view">
<field name="name">db.backup.tree</field>
<field name="model">db.backup</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree>
<field name='host'/>
<field name='port'/>
<field name='name'/>
<field name='folder'/>
<field name="autoremove"/>
<field name="sftp_host"/>
</tree>
</field>
</record>
<record id="action_backup" model="ir.actions.act_window">
<field name="name">Configure back-ups</field>
<field name="res_model">db.backup</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_backup_config_tree"/>
</record>
<menuitem id="auto_backup_menu" name="Back-ups" parent="base.menu_custom"/>
<menuitem parent="auto_backup_menu" action="action_backup" id="backup_conf_menu"/>
</odoo>

View File

@ -24,12 +24,13 @@
# always loaded
'data': [
'wizard/project_resource_wizard.xml',
'security/ir.model.access.csv',
'security/cor_custom_security.xml',
'views/crm_view.xml',
'views/sale_views.xml',
'views/project_view.xml',
#'views/project_hours_view.xml',
# 'views/project_hours_view.xml',
'views/hr_employee_views.xml',
'views/res_users.xml',
'views/hr_timesheet_templates.xml',
@ -41,9 +42,9 @@
'views/templates.xml',
'views/assets.xml',
'data/mail_data.xml',
#'views/menu_show_view.xml',
# 'views/menu_show_view.xml',
'wizard/project_create_sale_order_views.xml',
'wizard/project_multi_budget_assign_view.xml',
'wizard/project_multi_budget_assign_view.xml'
],
# only loaded in demonstration mode
'demo': [

View File

@ -26,7 +26,9 @@ class AccountAnalyticLine(models.Model):
return res
def _domain_project_id(self):
domain = [('allow_timesheets', '=', True), ('is_sub_project', '=', False)]
today = fields.Date.today()
#domain = [('allow_timesheets', '=', True), ('is_sub_project', '=', False)]
domain = [('allow_timesheets', '=', True), ('is_sub_project', '=', False),('date', '>=', today),('date_start', '<=', today)]
if not self.user_has_groups('hr_timesheet.group_timesheet_manager'):
return expression.AND([domain,
['|', ('privacy_visibility', '!=', 'followers'),
@ -54,6 +56,8 @@ class AccountAnalyticLine(models.Model):
sub_project = fields.Many2one('project.project', domain="[('is_sub_project', '=', True)]",
string='Sub Project')
active_project = fields.Boolean(related='project_id.active', store=True)
pricing_type = fields.Selection(related='project_id.pricing_type', store=True)
project_type= fields.Selection(related='project_id.project_type', store=True)
@api.onchange('project_id')
def _onchange_sub_project_id(self):
@ -75,21 +79,26 @@ class AccountAnalyticLine(models.Model):
def _onchange_employee_id(self):
project_manager_grp = self.env.ref('project.group_project_manager').users.ids
project_user_grp = self.env.ref('project.group_project_user').users.ids
today = fields.Date.today()
domain = []
if self.employee_id and self.employee_id.user_id and self.employee_id.user_id.id in project_manager_grp:
all_projects = self.env['project.project'].search([('allow_timesheets', '=', True),
('is_sub_project', '=', False)]).ids
('is_sub_project', '=', False),
('date', '>=', today),('date_start', '<=', today)]).ids
domain = [('id', 'in', all_projects)]
else:
if self.employee_id and self.employee_id.user_id and self.employee_id.user_id.id in project_user_grp:
manager_id = self.env['project.project'].search(
[('user_id', '=', self.employee_id.user_id.id), ('allow_timesheets', '=', True),
('date', '>=', today),('date_start', '<=', today),
('is_sub_project', '=', False)]).ids
emp_project_ids = self.env['project.project'].search(
[('privacy_visibility', 'in', ('employees', 'portal')), ('allow_timesheets', '=', True),
('date', '>=', today),('date_start', '<=', today),
('is_sub_project', '=', False)]).ids
project_ids = self.env['project.project'].search(
[('privacy_visibility', '=', 'followers'), ('allow_timesheets', '=', True),
('date', '>=', today),('date_start', '<=', today),
('is_sub_project', '=', False),
('allowed_internal_user_ids', 'in', self.employee_id.user_id.id)]).ids
consul_ids = self.env['project.sale.line.employee.map'].search([('employee_id', '=', self.employee_id.id)])

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
from datetime import datetime
class HrEmployee(models.Model):
_inherit = 'hr.employee'
@ -15,6 +15,27 @@ class HrEmployee(models.Model):
res = list(set(total_week))
self.budgeted_hour_week = len(res)
employee_cost_change = fields.One2many('hr.employee.cost' , 'employee_id')
def write(self, values):
if 'timesheet_cost' in values:
self.employee_cost_change.create({'employee_id': self.id,
'user_id': self.env.uid,
'date': fields.Datetime.today(),
'timesheet_cost': self.timesheet_cost,
})
res = super(HrEmployee, self).write(values)
return res
class EmployeeCostChangeHistory(models.Model):
_name = 'hr.employee.cost'
employee_id = fields.Many2one('hr.employee')
user_id = fields.Many2one('res.users')
date = fields.Datetime()
timesheet_cost = fields.Float("Timesheet Cost", digits=(16, 2), readonly=True, group_operator="sum")
class EmployeePublic(models.Model):
_inherit = 'hr.employee.public'

View File

@ -2,7 +2,8 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError, AccessError, ValidationError
import datetime
from pytz import timezone, UTC
class Project(models.Model):
_inherit = 'project.project'
@ -262,7 +263,9 @@ class Project(models.Model):
def action_view_custom_project_consultant_hrs_report(self):
action = self.env["ir.actions.actions"]._for_xml_id("cor_custom.action_project_consultant_hrs_report")
action['context'] = {'search_default_project_id': self.id, 'search_default_project': 1,
'search_default_consultant': 1, 'search_default_group_by_hours_type': 1,
'search_default_consultant': 1,
#'search_default_sdate': 1,
#'search_default_group_by_hours_type': 1,
'default_res_model': 'project.consultant.hrs.report'}
return action
@ -305,7 +308,7 @@ class InheritProjectProductEmployeeMap(models.Model):
timesheet_hour = fields.Float("Timesheet Hour", compute='_compute_timesheet_hour', default=0.0)
employee_price = fields.Monetary(string="Consultant Price")
budgeted_hour_week = fields.Float("Budgeted Hours per week", compute='_compute_budgeted_hour_week')
price_unit = fields.Float("Hourly Rate")
price_unit = fields.Float("Hourly Rate", readonly=False)
currency_id = fields.Many2one('res.currency', string="Currency", compute='_compute_price_unit', store=True,
readonly=False)
sale_line_id = fields.Many2one('sale.order.line', "Service", domain=[('is_service', '=', True)])
@ -317,6 +320,37 @@ class InheritProjectProductEmployeeMap(models.Model):
role = fields.Selection([('Manager', 'Manager'),
('Employee', 'Employee'), ], string="Role", default="Employee")
distribution_per = fields.Float("%")
start_date = fields.Date(string="Start Date")
end_date = fields.Date(string="End Date")
_sql_constraints = [
('uniqueness_employee', 'CHECK(1=1)', 'An employee cannot be selected more than once in the mapping. Please remove duplicate(s) and try again.'),
]
@api.constrains('start_date', 'end_date')
def _check_dates(self):
for val in self:
if val.start_date:
if val.end_date:
if val.end_date < val.start_date:
raise ValidationError(_('Start date must be earlier than end date.'))
domain = [
('start_date', '<=', val.end_date),
('end_date', '>=', val.start_date),
('project_id', '=', val.project_id.id),
('employee_id', '=', val.employee_id.id),
('id', '!=', val.id)
]
else:
domain = [
('end_date', '>=', val.start_date),
('project_id', '=', val.project_id.id),
('employee_id', '=', val.employee_id.id),
('id', '!=', val.id)
]
res = self.search_count(domain)
if res > 0:
raise ValidationError(_('Same Consultant can not have 2 same date that overlaps on same day!'))
@api.onchange('employee_id')
def onchange_employee_price(self):
@ -327,10 +361,27 @@ class InheritProjectProductEmployeeMap(models.Model):
def _compute_timesheet_hour(self):
for val in self:
self._cr.execute('''SELECT project_id, employee_id, SUM(unit_amount) FROM account_analytic_line
where project_id = %(project_id)s and employee_id = %(employee_id)s
GROUP BY project_id, employee_id''',
{'project_id': val.project_id._origin.id, 'employee_id': val.employee_id.id, })
date_clause = ""
query_params = [val.project_id._origin.id, val.employee_id.id]
#tz = self.env.user.tz
if val.start_date:
s_datetime = datetime.datetime.combine(val.start_date, datetime.time(00, 00))
#s_datetime1 = timezone(tz).localize(s_datetime).astimezone(UTC)
date_clause += "and start_datetime at time zone 'utc' at time zone (select tz from res_partner where id=3) >= %s"
query_params.append(s_datetime)
if val.end_date:
e_datetime = datetime.datetime.combine(val.end_date, datetime.time(23, 59, 59))
#e_datetime1 = timezone(tz).localize(e_datetime).astimezone(UTC)
date_clause += " and end_datetime at time zone 'utc' at time zone (select tz from res_partner where id=3) <= %s"
query_params.append(e_datetime)
query = """SELECT project_id, employee_id, SUM(unit_amount) FROM account_analytic_line
where project_id = %s and employee_id = %s {date_clause}
GROUP BY project_id, employee_id""".format(date_clause=date_clause)
self.env.cr.execute(query, query_params)
#self._cr.execute('''SELECT project_id, employee_id, SUM(unit_amount) FROM account_analytic_line
#where project_id = %(project_id)s and employee_id = %(employee_id)s
#GROUP BY project_id, employee_id''',
# {'project_id': val.project_id._origin.id, 'employee_id': val.employee_id.id, })
res = self._cr.fetchone()
if res and res[2]:
val.timesheet_hour = res[2]
@ -382,6 +433,39 @@ class InheritProjectProductEmployeeMap(models.Model):
# line.price_unit = 0
line.currency_id = False
def edit_sale_line_employee_record(self):
context = dict(self.env.context)
context['form_view_initial_mode'] = 'edit'
return {
'name': ('Edit Resource History'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'target': 'new',
'res_model': 'project.sale.line.employee.map',
'res_id': self.id,
'context': context,
# 'view_id': view_id
}
def update_record(self):
return {
'type': 'ir.actions.act_window_close'}
@api.onchange('start_date', 'end_date')
def _onchange_start_date(self):
# if self.start_date:
# self.end_date = self.start_date + relativedelta.relativedelta(months=1) - relativedelta.relativedelta(days=1)
if self.start_date and self.end_date and (self.start_date > self.end_date):
raise AccessError('End date could not be greater than start date')
if self.project_id:
if self.start_date and not self.project_id.date_start <= self.start_date <= self.project_id.date:
raise AccessError(_('Allocation Start date must be between %s to %s') % (
self.project_id.date_start.strftime('%d-%b-%Y'), self.project_id.date.strftime('%d-%b-%Y')))
if self.end_date and not (self.project_id.date_start <= self.end_date <= self.project_id.date):
raise AccessError(_('Allocation Start date must be between %s to %s') % (
self.project_id.date_start.strftime('%d-%b-%Y'), self.project_id.date.strftime('%d-%b-%Y')))
class CustomProjectTags(models.Model):
""" Tags of project's tasks """

View File

@ -15,7 +15,7 @@ class ProjectConsultantHrs(models.Model):
_order = 'employee_id, end_date desc'
project_id = fields.Many2one('project.project', string="Project", required=True)
employee_id = fields.Many2one('hr.employee', string="Consultant", required=True)
employee_id = fields.Many2one('hr.employee', string="Consultant", required=False)
start_date = fields.Date('Start Date', required=True)
end_date = fields.Date('End Date', required=True)
percentage = fields.Float("Budgeted Percentage (%)")
@ -33,6 +33,13 @@ class ProjectConsultantHrs(models.Model):
@api.constrains('employee_id', 'percentage')
def _check_percent(self):
for val in self:
if not val.employee_id and val.percentage:
rec2 = val.search([('employee_id','=',False),('project_id','=',val.project_id.id)])
#print("rec2", rec2)
per2 = [r2.percentage for r2 in rec2]
#print("perccccccc22222222", per2)
if sum(per2) > 100:
raise ValidationError(_('Non Consultant total percentage should not be greater than 100'))
if val.employee_id and val.percentage:
rec = val.search([('employee_id','=',val.employee_id.id),('project_id','=',val.project_id.id)])
per = [r.percentage for r in rec]

View File

@ -1,6 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_project_consultant_hrs_report_pivot" model="ir.ui.view">
<field name="name">project.consultant.hrs.report.pivot</field>
<field name="model">project.consultant.hrs.report</field>
<field name="arch" type="xml">
<pivot string="Consultant Analysis" disable_linking="True" sample="1">
<field name="project_id" type="row"/>
<field name="hours" type="measure"/>
<field name="start_date" type="col"/>
<field name="hours_type" type="col"/>
<field name="percentage" type="measure"/>
</pivot>
</field>
</record>
<record id="view_project_consultant_hrs_report_form" model="ir.ui.view">
<field name="name">project.consultant.hrs.report.form</field>
<field name="model">project.consultant.hrs.report</field>
@ -45,7 +59,7 @@
<group expand="0" string="Group By">
<filter string="Project" name="project" domain="[]" context="{'group_by':'project_id'}"/>
<filter string="Consultant" name="consultant" domain="[]" context="{'group_by':'employee_id'}"/>
<filter string="Start Date" name="sdate" domain="[]" context="{'group_by':'start_date'}"/>
<filter string="Start Date" name="sdate" domain="[]" context="{'group_by':'start_date:month'}"/>
<filter string="End Date" name="edate" domain="[]" context="{'group_by':'end_date'}"/>
<filter string="Hours type" name="group_by_hours_type" context="{'group_by':'hours_type'}"/>
<!--<filter string="Time" name="ptime" domain="[]" context="{'group_by':'ptime'}"/>-->
@ -88,22 +102,17 @@
<field name="name">Consultant Allocation</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">project.consultant.hrs.report</field>
<field name="view_mode">tree,calendar,graph</field>
<field name="view_mode">pivot,tree,calendar,graph</field>
<field name="search_view_id" ref="view_project_consultant_hrs_report_search"/>
<field name="context">{
'search_default_project': 1,
'search_default_consultant': 1,
'search_default_group_by_hours_type': 1,
'default_res_model': 'project.consultant.hrs.report'
}
<field name="context">{'search_default_project': 1, 'search_default_consultant': 1, 'default_res_model': 'project.consultant.hrs.report'}
</field>
</record>
<menuitem name="View Allocation" id="menu_main_cor_consult_allocation"
<!--<menuitem name="View Allocation" id="menu_main_cor_consult_allocation"
action="cor_custom.action_project_consultant_hrs_report" sequence="53"
groups="base.group_no_one,project.group_project_user"/>
groups="base.group_no_one,project.group_project_user"/>-->
<record id="project_consul_hours_report_view_form" model="ir.ui.view">
<!--<record id="project_consul_hours_report_view_form" model="ir.ui.view">
<field name="name">Project Consul Report Hours</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.edit_project"/>
@ -116,6 +125,6 @@
</button>
</div>
</field>
</record>
</record>-->
</odoo>

View File

@ -10,4 +10,7 @@ access_model_project_multi_budget_assign_line_pmanager,project.multi.budget.assi
access_project_consultant_hrs_report_puser,project.consultant.hrs.report,model_project_consultant_hrs_report,project.group_project_user,1,0,0,0
access_project_consultant_hrs_report_pmanager,project.consultant.hrs.report,model_project_consultant_hrs_report,project.group_project_manager,1,1,1,1
access_custom_project_tags_puser,custom.project.tags,model_custom_project_tags,project.group_project_user,1,1,1,1
access_project_resource_wizard,project.resource.wizard,model_project_resource_wizard,,1,1,1,0
access_project_resource_line,project.resource.line,model_project_resource_line,,1,1,1,1
access_hr_employee_cost,hr.employee.cost,model_hr_employee_cost,,1,1,1,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
10 access_project_consultant_hrs_report_puser project.consultant.hrs.report model_project_consultant_hrs_report project.group_project_user 1 0 0 0
11 access_project_consultant_hrs_report_pmanager project.consultant.hrs.report model_project_consultant_hrs_report project.group_project_manager 1 1 1 1
12 access_custom_project_tags_puser custom.project.tags model_custom_project_tags project.group_project_user 1 1 1 1
13 access_project_resource_wizard project.resource.wizard model_project_resource_wizard 1 1 1 0
14 access_project_resource_line project.resource.line model_project_resource_line 1 1 1 1
15 access_hr_employee_cost hr.employee.cost model_hr_employee_cost 1 1 1 0
16

View File

@ -12,6 +12,18 @@
<xpath expr="//field[@name='user_id']" position="replace">
<field name="user_id" string="Related User" domain="[('share', '=', False)]" required="1"/>
</xpath>
<xpath expr="//page[@name='hr_settings']" position="inside">
<group string="Employee Cost History">
</group>
<field name="employee_cost_change" readonly="1" string="Employee Cost History">
<tree editable="bottom">
<field name="employee_id" invisible="1"/>
<field name="user_id" string="Changed By"/>
<field name="date" string="Changed Date"/>
<field name="timesheet_cost" string="Changed Cost"/>
</tree>
</field>
</xpath>
</field>
</record>

View File

@ -6,8 +6,15 @@
<field name="model">project.project</field>
<field name="inherit_id" ref="sale_timesheet.project_project_view_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='%(portal.portal_share_action)d']" position="replace">
<button name="%(portal.portal_share_action)d" string="Share" type="action" invisible="1"
class="oe_highlight oe_read_only"/>
</xpath>
<xpath expr="//div[@name='options_active']/div" position="after">
<div name="creation_div" attrs="{'invisible': [('id', '=', False)]}">
<div name="creation_div" attrs="{'invisible': [('id', '=', False)]}"
groups="project.group_project_manager">
<label for="create_date" class="oe_inline" string="Created On"/>
<field name="create_date" readonly="1" class="oe_inline"/>
</div>
@ -17,7 +24,7 @@
icon="fa-puzzle-piece" attrs="{'invisible': [('allow_billable', '=', False)]}" invisible="1"/>
</xpath>
<xpath expr="//page[@name='billing_employee_rate']" position="replace">
<page name="billing_employee_rate" string="Invoicing"
<page name="billing_employee_rate" string="Invoicing" groups="project.group_project_manager"
attrs="{'invisible': [('allow_billable', '=', False)]}">
<group>
<field name="display_create_order" invisible="1"/>
@ -61,15 +68,20 @@
options="{'no_create': True, 'no_edit': True, 'delete': False}"/>
<field name="is_check" invisible="1"/>
</group>
<button name="%(action_project_resource_wizard)d" string="Add Resource" type="action"
attrs="{'invisible': [('pricing_type','=','fixed_rate')]}"
groups="project.group_project_manager" class="oe_highlight"/>
<field name="sale_line_employee_ids"
attrs="{'invisible': ['|', ('bill_type', '!=', 'customer_project'), ('pricing_type', '!=', 'employee_rate')]}">
<tree editable="top">
<tree editable="top" create="false">
<field name="company_id" invisible="1"/>
<field name="project_id" invisible="1"/>
<field name="hour_distribution" invisible="1"/>
<field name="employee_id" options="{'no_create': True}" string="Consultant Name"/>
<field name="role"
attrs="{'required': [('hour_distribution','=','Percentage')]}"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="distribution_per"
attrs="{'invisible': [('hour_distribution','!=','Percentage')], 'required': [('hour_distribution','=','Percentage')]}"/>
<!--<field name="sale_line_id" options="{'no_create': True}"
@ -94,6 +106,9 @@
<field name="consultant_cost"/>
<field name="actual_revenue"/>
<field name="budgeted_hour_week" invisible="1"/>
<button type="object" name="edit_sale_line_employee_record" string="Edit"
class="oe_highlight"/>
<!--<button name="unlink" type="object" icon="fa-trash-o"/>-->
</tree>
</field>
<!--<field name="consultant_timesheet_hrs" readonly="1" attrs="{'invisible': [('pricing_type','!=','fixed_rate')]}">-->
@ -147,32 +162,40 @@
</div>-->
<xpath expr="//field[@name='user_id']" position="replace">
<field name="user_id" string="Project Manager" widget="many2one_avatar_user" required="0"
groups="project.group_project_manager"
attrs="{'readonly':[('active','=',False)]}" domain="[('share', '=', False)]"/>
</xpath>
<xpath expr="//field[@name='partner_id']" position="replace">
<field name="partner_id" string="Client" required="0"/>
<field name="date_start"/>
<field name="date"/>
<field name="partner_id" string="Client" required="0" groups="project.group_project_manager"/>
<field name="date_start" groups="project.group_project_manager" required="1"/>
<field name="date" groups="project.group_project_manager" required="1"/>
</xpath>
<xpath expr="//page[@name='settings']" position="after">
<page string="Consultant Allocation"
attrs="{'invisible':['|',('pricing_type','!=','employee_rate'),('project_type','!=','hours_in_consultant')]}">
<field name="project_cons_hrs">
<tree editable="top">
<field name="project_id" invisible="1"/>
<field name="employee_id" options="{'no_create_edit': True, 'no_quick_create': True}"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="percentage"/>
<field name="budgeted_hours"/>
<field name="actual_percentage"/>
<field name="actual_hours"/>
</tree>
</field>
</page>
<!-- <xpath expr="//page[@name='settings']" position="after">
<page string="Consultant Allocation" groups="project.group_project_manager"
attrs="{'invisible':['|',('pricing_type','!=','employee_rate'),('project_type','!=','hours_in_consultant')]}">
<field name="project_cons_hrs">
<tree editable="top">
<field name="project_id" invisible="1"/>
<field name="employee_id" options="{'no_create_edit': True, 'no_quick_create': True}"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="percentage"/>
<field name="budgeted_hours"/>
<field name="actual_percentage"/>
<field name="actual_hours"/>
</tree>
</field>
</page>
</xpath> -->
<xpath expr="//field[@name='privacy_visibility']" position="replace">
<field name="privacy_visibility" widget="radio" groups="project.group_project_manager"/>
</xpath>
<xpath expr="//field[@name='allowed_internal_user_ids']" position="replace">
<field name="allowed_internal_user_ids" widget="many2many_tags" groups="project.group_project_manager"
attrs="{'invisible': [('privacy_visibility', '!=', 'followers')]}"/>
</xpath>
<xpath expr="//field[@name='privacy_visibility']" position="before">
<field name="tag_ids" widget="many2many_tags"/>
<field name="tag_ids" widget="many2many_tags" groups="project.group_project_manager"/>
</xpath>
</field>
</record>
@ -256,6 +279,44 @@
</field>
</record>
<record id="view_project_sale_line_employee_map" model="ir.ui.view">
<field name="name">project.sale.line.employee.map.lines</field>
<field name="model">project.sale.line.employee.map</field>
<field name="arch" type="xml">
<form string="Project Sale Employee Lines">
<group>
<group>
<field name="employee_id"/>
<field name="cost"/>
<field name="start_date" required="1"/>
<field name="budgeted_qty"/>
<field name="price_unit"/>
<field name="timesheet_hour"/>
<field name="project_id" invisible="1"/>
<field name="sale_line_id" invisible="1"/>
<field name="timesheet_product_id" invisible="1"/>
<field name="currency_id" invisible="1"/>
<field name="budgeted_uom" invisible="1"/>
</group>
<group>
<field name="role"/>
<field name="employee_price"/>
<field name="end_date"/>
<field name="distribution_per"/>
<field name="budgeted_hour_week"/>
<field name="consultant_cost"/>
<field name="hour_distribution" invisible="1"/>
<field name="company_id" invisible="1"/>
</group>
</group>
<footer>
<button name="update_record" type="object" string="Update" class="oe_highlight"/>
<button special="cancel" string="Cancel"/>
</footer>
</form>
</field>
</record>
<!-- Done Task action -->
<record id="project_task_server_action_batch_done" model="ir.actions.server">
<field name="name">Done</field>

View File

@ -3,4 +3,5 @@
from . import project_create_sale_order
from . import crm_opportunity_to_quotation
from . import project_multi_budget_assign
from . import project_multi_budget_assign
from . import project_resource_wizard

View File

@ -20,13 +20,23 @@ class ProjectMultiBudgetAssign(models.TransientModel):
res_id = self._context.get('active_id')
record = self.env['project.project'].browse(res_id)
lines = []
for rec in record.sale_line_employee_ids:
emp_id = []
non_emp_id = 0
for i, rec in enumerate(record.sale_line_employee_ids):
cons = {}
all_cons = self.env['project.consultant.hrs'].search([('employee_id','=',rec.employee_id.id),('project_id','=',rec.project_id.id)])
all_per = [v.percentage for v in all_cons]
if sum(all_per) < 100:
if sum(all_per) < 100 and (rec.employee_id and rec.employee_id.id not in emp_id):
emp_id.append(rec.employee_id.id)
cons.update({'employee_id': rec.employee_id.id, 'emp_map_id':rec.id, 'project_id':res_id})
lines.append((0, 0, cons))
non_cons = self.env['project.consultant.hrs'].search([('employee_id','=',False),('project_id', '=', rec.project_id.id)])
non_cons_per = [non_con.percentage for non_con in non_cons]
if sum(non_cons_per) < 100 and non_emp_id == 0:
non_emp_id += 1
non_cons_data = {}
non_cons_data.update({'employee_id': False, 'emp_map_id':False, 'project_id': res_id})
lines.append((0, 0, non_cons_data))
if record and (not record.pricing_type == 'employee_rate' or not record.project_type == 'hours_in_consultant'):
raise ValidationError(_('Not applicable for this project type '))
if not lines:
@ -75,8 +85,8 @@ class ProjectMultiBudgetAssignLine(models.TransientModel):
line_id = fields.Many2one('project.multi.budget.assign', string="Line ID", required=True)
project_id = fields.Many2one('project.project', string="Project", required=True)
emp_map_id = fields.Many2one('project.sale.line.employee.map', string="Consultant Map", required=True)
employee_id = fields.Many2one('hr.employee', string="Consultant", required=True)
emp_map_id = fields.Many2one('project.sale.line.employee.map', string="Consultant Map", required=False)
employee_id = fields.Many2one('hr.employee', string="Consultant", required=False)
budgeted_qty = fields.Float(string='Budgeted Hours', related="emp_map_id.budgeted_qty")
all_percentage = fields.Float("Total Allocated %", compute='_compute_allocation')
rem_percentage = fields.Float("Total Unallocated %", compute='_compute_allocation')
@ -103,6 +113,14 @@ class ProjectMultiBudgetAssignLine(models.TransientModel):
val.all_percentage = all_per
val.rem_percentage = 100 - all_per
val.remaining = 100 - all_per
elif val.project_id and not val.employee_id:
non_cons = self.env['project.consultant.hrs'].search([('employee_id','=',False),('project_id','=',val.project_id.id)])
non_all_per = 0
for res2 in non_cons:
non_all_per += res2.percentage
val.all_percentage = non_all_per
val.rem_percentage = 100 - non_all_per
val.remaining = 100 - non_all_per
else:
val.all_percentage = 0
val.rem_percentage = 0

View File

@ -36,7 +36,7 @@
</field>
</record>
<record id="action_multi_budget_assign" model="ir.actions.act_window">
<!-- <record id="action_multi_budget_assign" model="ir.actions.act_window">
<field name="name">Consultant Budget Hours Assign</field>
<field name="res_model">project.multi.budget.assign</field>
<field name="view_mode">form</field>
@ -44,7 +44,7 @@
<field name="target">new</field>
<field name="binding_model_id" ref="project.model_project_project"/>
<field name="binding_view_types">form</field>
</record>
</record>-->
</data>
</odoo>

View File

@ -0,0 +1,130 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError, AccessError, ValidationError
from datetime import datetime, timedelta
from dateutil import relativedelta
class ProjectResourceWizard(models.TransientModel):
_name = 'project.resource.wizard'
@api.model
def default_get(self, fields):
res = super(ProjectResourceWizard, self).default_get(fields)
res_id = self._context.get('active_id')
record = self.env['project.project'].browse(res_id)
lines = []
emp_id = []
for rec in record.sale_line_employee_ids:
resource_rec = {}
if rec.employee_id and rec.employee_id.id not in emp_id:
emp_id.append(rec.employee_id.id)
resource_rec.update({'employee_id': rec.employee_id.id, 'project_id': res_id,
'employee_price': rec.employee_id.timesheet_cost})
lines.append((0, 0, resource_rec))
res.update({'add_project_resource': lines})
return res
project_id = fields.Many2one('project.project')
add_project_resource = fields.One2many('project.resource.line', 'wizard_id')
def action_add_project_lines(self):
record = self.env['project.sale.line.employee.map']
for val in self.add_project_resource:
############# Here we check all start date should be filled
if any([val.start_date is False]):
raise ValidationError("Please Select Start Date For Every Resource")
#############
#################
if self.project_id.sale_line_employee_ids:
for emp in self.project_id.sale_line_employee_ids:
if emp.employee_id == val.employee_id and emp.end_date == False and val.start_date:
emp.write({'end_date': val.start_date - relativedelta.relativedelta(days=1)})
#################
values = {
'project_id': self.project_id.id,
'employee_id': val.employee_id.id,
'role': val.role,
'start_date': val.start_date,
'end_date': val.end_date,
'price_unit': val.price_unit,
'budgeted_qty': val.budgeted_qty,
'cost': val.cost,
'employee_price': val.employee_price,
}
if self.project_id.hour_distribution == 'Percentage':
values.update({'distribution_per': val.distribution_per})
record.create(values)
return True
class ProjectResourceLine(models.Model):
_name = "project.resource.line"
_description = "Project Resource Line"
wizard_id = fields.Integer()
project_id = fields.Many2one('project.project')
employee_id = fields.Many2one('hr.employee')
employee_ids = fields.Many2many('hr.employee', compute='_compute_allow_employee_ids')
role = fields.Selection([('Manager', 'Manager'), ('Employee', 'Employee'), ], string="Role", default="Employee")
start_date = fields.Date()
end_date = fields.Date()
price_unit = fields.Float("Hourly Rate")
budgeted_qty = fields.Float(string='Bud. Hours', digits=(16, 2))
cost = fields.Float("Bud. Revenue", default=0.0)
employee_price = fields.Float(string="Cons. Price")
hour_distribution = fields.Selection(related='project_id.hour_distribution')
distribution_per = fields.Float("%")
@api.depends('employee_id')
def _compute_allow_employee_ids(self):
for rec in self:
if rec.project_id.privacy_visibility == 'followers':
users = rec.project_id.allowed_internal_user_ids
employees = self.env['hr.employee'].search([('user_id', 'in', users.ids)]).ids
elif rec.project_id.privacy_visibility == 'employees':
employees = self.env['hr.employee'].search([]).ids
else:
portal_users = rec.project_id.allowed_portal_user_ids
employees = self.env['hr.employee'].search([]).ids + portal_users.ids
rec.employee_ids = employees
@api.onchange('employee_id')
def onchange_employee_price(self):
if self.employee_id:
self.employee_price = self.employee_id.timesheet_cost
else:
self.employee_price = 0.0
@api.onchange('price_unit', 'budgeted_qty')
def _calculate_total_cost(self):
if self.project_id.project_type == 'hours_in_consultant':
if self.hour_distribution == 'Manual':
self.cost = self.price_unit * self.budgeted_qty
@api.onchange('price_unit', 'distribution_per', 'employee_id', 'role', 'budgeted_qty')
def _compute_total_cost(self):
for val in self:
if val.project_id.project_type == 'hours_in_consultant':
if val.hour_distribution == 'Percentage':
if val.role == 'Manager':
val.cost = val.project_id.budgeted_revenue * (val.project_id.manager_per / 100) * (
val.distribution_per / 100)
else:
val.cost = val.project_id.budgeted_revenue * (val.project_id.employee_per / 100) * (
val.distribution_per / 100)
if val.price_unit > 0.0:
val.budgeted_qty = val.cost / val.price_unit
@api.onchange('start_date', 'end_date')
def _onchange_start_end_date(self):
# if self.start_date:
# self.end_date = self.start_date + relativedelta.relativedelta(months=1) - relativedelta.relativedelta(days=1)
if self.start_date and self.end_date and (self.start_date > self.end_date):
raise AccessError('End date could not be greater than start date')
if self.project_id:
if self.start_date and not self.project_id.date_start <= self.start_date <= self.project_id.date:
raise AccessError(_('Allocation Start date must be between %s to %s') % (
self.project_id.date_start.strftime('%d-%b-%Y'), self.project_id.date.strftime('%d-%b-%Y')))
if self.end_date and not (self.project_id.date_start <= self.end_date <= self.project_id.date):
raise AccessError(_('Allocation Start date must be between %s to %s') % (
self.project_id.date_start.strftime('%d-%b-%Y'), self.project_id.date.strftime('%d-%b-%Y')))

View File

@ -0,0 +1,43 @@
<odoo>
<record id="view_edit_project_resource_lines" model="ir.ui.view">
<field name="name">project.resource.wizard</field>
<field name="model">project.resource.wizard</field>
<field name="arch" type="xml">
<form string="Project Resource Wizard">
<group>
<group>
<field name="project_id" readonly="1"/>
</group>
<field name="add_project_resource" context="{'default_project_id': active_id}">
<tree editable="bottom">
<field name="employee_id" domain="[('id', 'in', employee_ids)]" required="1"/>
<field name="employee_ids" widget="many2many_tags" invisible="1"/>
<field name="role"/>
<field name="start_date" required="1"/>
<field name="end_date"/>
<field name="distribution_per" attrs="{'invisible': [('hour_distribution','!=','Percentage')], 'required': [('hour_distribution','=','Percentage')]}"/>
<field name="price_unit"/>
<field name="budgeted_qty"/>
<field name="cost"/>
<field name="employee_price"/>
<field name="project_id" invisible="1"/>
<field name="hour_distribution" invisible="1"/>
</tree>
</field>
</group>
<footer>
<button string="OK" type="object" name="action_add_project_lines" class="btn-primary"/>
<button string="Cancel" class="btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_project_resource_wizard" model="ir.actions.act_window">
<field name="name">Project Resource wizard</field>
<field name="res_model">project.resource.wizard</field>
<field name="view_id" ref="cor_custom.view_edit_project_resource_lines"/>
<field name="target">new</field>
<field name="context">{ 'default_project_id':active_id, 'project_id': active_id}</field>
</record>
</odoo>

0
month_filter/__init__.py Normal file
View File

View File

@ -0,0 +1,15 @@
{
'name': 'Month Filter',
'version': '1.0.1',
'author': 'SunArc Technologies',
'website': 'www.sunarctechnologies.com',
'license': 'LGPL-3',
'depends': ['base','web' ,'hr_timesheet'],
'data': [
'views/templates.xml',
],
'installable': True,
'auto_install': False,
}

View File

@ -0,0 +1,39 @@
odoo.define('month_filter.ModeExtension', function (require) {
"use strict";
const components = {
ControlPanelModelExtension: require("web/static/src/js/control_panel/control_panel_model_extension.js"),
};
const { patch } = require("web.utils");
const Domain = require('web.Domain');
const pyUtils = require('web.py_utils');
const { DEFAULT_INTERVAL, DEFAULT_PERIOD,
getComparisonOptions, getIntervalOptions, getPeriodOptions,
constructDateDomain, rankInterval, yearSelected } = require('month_filter.searchUtils');
patch(
components.ControlPanelModelExtension,
"month_filter/static/src/js/control_panel/control_panel_custom_extension.js",
{
toggleFilterWithOptions(filterId, optionId) {
this.referenceMoment = moment();
this.optionGenerators = getPeriodOptions(this.referenceMoment);
return this._super(filterId, optionId);
},
_enrichFilterCopy(filter, filterQueryElements) {
this.referenceMoment = moment();
this.optionGenerators = getPeriodOptions(this.referenceMoment);
return this._super(filter, filterQueryElements);
},
_getDateFilterDomain(filter, filterQueryElements, key = 'domain') {
const { fieldName, fieldType } = filter;
const selectedOptionIds = filterQueryElements.map(queryElem => queryElem.optionId);
const dateFilterRange = constructDateDomain(
this.referenceMoment, fieldName, fieldType, selectedOptionIds,
);
return dateFilterRange[key];
},
}
);
});

View File

@ -0,0 +1,582 @@
odoo.define('month_filter.searchUtils', function (require) {
"use strict";
const { _lt, _t } = require('web.core');
const Domain = require('web.Domain');
const pyUtils = require('web.py_utils');
//-------------------------------------------------------------------------
// Constants
//-------------------------------------------------------------------------
// Filter menu parameters
const FIELD_OPERATORS = {
boolean: [
{ symbol: "=", description: _lt("is true"), value: true },
{ symbol: "!=", description: _lt("is false"), value: true },
],
char: [
{ symbol: "ilike", description: _lt("contains") },
{ symbol: "not ilike", description: _lt("doesn't contain") },
{ symbol: "=", description: _lt("is equal to") },
{ symbol: "!=", description: _lt("is not equal to") },
{ symbol: "!=", description: _lt("is set"), value: false },
{ symbol: "=", description: _lt("is not set"), value: false },
],
date: [
{ symbol: "=", description: _lt("is equal to") },
{ symbol: "!=", description: _lt("is not equal to") },
{ symbol: ">", description: _lt("is after") },
{ symbol: "<", description: _lt("is before") },
{ symbol: ">=", description: _lt("is after or equal to") },
{ symbol: "<=", description: _lt("is before or equal to") },
{ symbol: "between", description: _lt("is between") },
{ symbol: "!=", description: _lt("is set"), value: false },
{ symbol: "=", description: _lt("is not set"), value: false },
],
datetime: [
{ symbol: "between", description: _lt("is between") },
{ symbol: "=", description: _lt("is equal to") },
{ symbol: "!=", description: _lt("is not equal to") },
{ symbol: ">", description: _lt("is after") },
{ symbol: "<", description: _lt("is before") },
{ symbol: ">=", description: _lt("is after or equal to") },
{ symbol: "<=", description: _lt("is before or equal to") },
{ symbol: "!=", description: _lt("is set"), value: false },
{ symbol: "=", description: _lt("is not set"), value: false },
],
id: [
{ symbol: "=", description: _lt("is") },
{ symbol: "<=", description: _lt("less than or equal to")},
{ symbol: ">", description: _lt("greater than")},
],
number: [
{ symbol: "=", description: _lt("is equal to") },
{ symbol: "!=", description: _lt("is not equal to") },
{ symbol: ">", description: _lt("greater than") },
{ symbol: "<", description: _lt("less than") },
{ symbol: ">=", description: _lt("greater than or equal to") },
{ symbol: "<=", description: _lt("less than or equal to") },
{ symbol: "!=", description: _lt("is set"), value: false },
{ symbol: "=", description: _lt("is not set"), value: false },
],
selection: [
{ symbol: "=", description: _lt("is") },
{ symbol: "!=", description: _lt("is not") },
{ symbol: "!=", description: _lt("is set"), value: false },
{ symbol: "=", description: _lt("is not set"), value: false },
],
};
const FIELD_TYPES = {
boolean: 'boolean',
char: 'char',
date: 'date',
datetime: 'datetime',
float: 'number',
id: 'id',
integer: 'number',
html: 'char',
many2many: 'char',
many2one: 'char',
monetary: 'number',
one2many: 'char',
text: 'char',
selection: 'selection',
};
const DEFAULT_PERIOD = 'this_month';
const QUARTERS = {
1: { description: _lt("Q1"), coveredMonths: [0, 1, 2] },
2: { description: _lt("Q2"), coveredMonths: [3, 4, 5] },
3: { description: _lt("Q3"), coveredMonths: [6, 7, 8] },
4: { description: _lt("Q4"), coveredMonths: [9, 10, 11] },
};
const MONTH_OPTIONS = {
first_month: {
id: 'first_month', groupNumber: 1, format: 'MMMM',
addParam: {}, granularity: 'month',
},
second_month: {
id: 'second_month', groupNumber: 1, format: 'MMMM',
addParam: { months: +1 }, granularity: 'month',
},
third_month: {
id: 'third_month', groupNumber: 1, format: 'MMMM',
addParam: { months: +2 }, granularity: 'month',
},
fourth_month: {
id: 'fourth_month', groupNumber: 1, format: 'MMMM',
addParam: { months: +3 }, granularity: 'month',
},
fifth_month: {
id: 'fifth_month', groupNumber: 1, format: 'MMMM',
addParam: { months: +4 }, granularity: 'month',
},
sixth_month: {
id: 'sixth_month', groupNumber: 1, format: 'MMMM',
addParam: { months: +5 }, granularity: 'month',
},
seventh_month: {
id: 'seventh_month', groupNumber: 1, format: 'MMMM',
addParam: { months: +6 }, granularity: 'month',
},
eighth_month: {
id: 'eighth_month', groupNumber: 1, format: 'MMMM',
addParam: { months: +7 }, granularity: 'month',
},
ninth_month: {
id: 'ninth_month', groupNumber: 1, format: 'MMMM',
addParam: { months: +8 }, granularity: 'month',
},
tenth_month: {
id: 'tenth_month', groupNumber: 1, format: 'MMMM',
addParam: { months: +9 }, granularity: 'month',
},
eleventh_month: {
id: 'eleventh_month', groupNumber: 1, format: 'MMMM',
addParam: { months: +10 }, granularity: 'month',
},
twelfth_month: {
id: 'twelfth_month', groupNumber: 1, format: 'MMMM',
addParam: { months: +11 }, granularity: 'month',
},
};
const QUARTER_OPTIONS = {
fourth_quarter: {
id: 'fourth_quarter', groupNumber: 1, description: QUARTERS[4].description,
setParam: { quarter: 4 }, granularity: 'quarter',
},
third_quarter: {
id: 'third_quarter', groupNumber: 1, description: QUARTERS[3].description,
setParam: { quarter: 3 }, granularity: 'quarter',
},
second_quarter: {
id: 'second_quarter', groupNumber: 1, description: QUARTERS[2].description,
setParam: { quarter: 2 }, granularity: 'quarter',
},
first_quarter: {
id: 'first_quarter', groupNumber: 1, description: QUARTERS[1].description,
setParam: { quarter: 1 }, granularity: 'quarter',
},
};
const YEAR_OPTIONS = {
this_year: {
id: 'this_year', groupNumber: 2, format: 'YYYY',
addParam: {}, granularity: 'year',
},
last_year: {
id: 'last_year', groupNumber: 2, format: 'YYYY',
addParam: { years: -1 }, granularity: 'year',
},
antepenultimate_year: {
id: 'antepenultimate_year', groupNumber: 2, format: 'YYYY',
addParam: { years: -2 }, granularity: 'year',
},
};
const PERIOD_OPTIONS = Object.assign({}, MONTH_OPTIONS, QUARTER_OPTIONS, YEAR_OPTIONS);
// GroupBy menu parameters
const GROUPABLE_TYPES = [
'boolean',
'char',
'date',
'datetime',
'integer',
'many2one',
'selection',
];
const DEFAULT_INTERVAL = 'month';
const INTERVAL_OPTIONS = {
year: { description: _lt("Year"), id: 'year', groupNumber: 1 },
quarter: { description: _lt("Quarter"), id: 'quarter', groupNumber: 1 },
month: { description: _lt("Month"), id: 'month', groupNumber: 1 },
week: { description: _lt("Week"), id: 'week', groupNumber: 1 },
day: { description: _lt("Day"), id: 'day', groupNumber: 1 }
};
// Comparison menu parameters
const COMPARISON_OPTIONS = {
previous_period: {
description: _lt("Previous Period"), id: 'previous_period',
},
previous_year: {
description: _lt("Previous Year"), id: 'previous_year', addParam: { years: -1 },
},
};
const PER_YEAR = {
year: 1,
quarter: 4,
month: 12,
};
// Search bar
const FACET_ICONS = {
filter: 'fa fa-filter',
groupBy: 'fa fa-bars',
favorite: 'fa fa-star',
comparison: 'fa fa-adjust',
};
//-------------------------------------------------------------------------
// Functions
//-------------------------------------------------------------------------
/**
* Constructs the string representation of a domain and its description. The
* domain is of the form:
* ['|',..., '|', d_1,..., d_n]
* where d_i is a time range of the form
* ['&', [fieldName, >=, leftBound_i], [fieldName, <=, rightBound_i]]
* where leftBound_i and rightBound_i are date or datetime computed accordingly
* to the given options and reference moment.
* (@see constructDateRange).
* @param {moment} referenceMoment
* @param {string} fieldName
* @param {string} fieldType
* @param {string[]} selectedOptionIds
* @param {string} [comparisonOptionId]
* @returns {{ domain: string, description: string }}
*/
function constructDateDomain(
referenceMoment,
fieldName,
fieldType,
selectedOptionIds,
comparisonOptionId
) {
let addParam;
let selectedOptions;
if (comparisonOptionId) {
[addParam, selectedOptions] = getComparisonParams(
referenceMoment,
selectedOptionIds,
comparisonOptionId);
} else {
selectedOptions = getSelectedOptions(referenceMoment, selectedOptionIds);
}
const yearOptions = selectedOptions.year;
const otherOptions = [
...(selectedOptions.quarter || []),
...(selectedOptions.month || [])
];
sortPeriodOptions(yearOptions);
sortPeriodOptions(otherOptions);
const ranges = [];
for (const yearOption of yearOptions) {
const constructRangeParams = {
referenceMoment,
fieldName,
fieldType,
addParam,
};
if (otherOptions.length) {
for (const option of otherOptions) {
const setParam = Object.assign({},
yearOption.setParam,
option ? option.setParam : {}
);
const { granularity } = option;
const range = constructDateRange(Object.assign(
{ granularity, setParam },
constructRangeParams
));
ranges.push(range);
}
} else {
const { granularity, setParam } = yearOption;
const range = constructDateRange(Object.assign(
{ granularity, setParam },
constructRangeParams
));
ranges.push(range);
}
}
const domain = pyUtils.assembleDomains(ranges.map(range => range.domain), 'OR');
const description = ranges.map(range => range.description).join("/");
return { domain, description };
}
/**
* Constructs the string representation of a domain and its description. The
* domain is a time range of the form:
* ['&', [fieldName, >=, leftBound],[fieldName, <=, rightBound]]
* where leftBound and rightBound are some date or datetime determined by setParam,
* addParam, granularity and the reference moment.
* @param {Object} params
* @param {moment} params.referenceMoment
* @param {string} params.fieldName
* @param {string} params.fieldType
* @param {string} params.granularity
* @param {Object} params.setParam
* @param {Object} [params.addParam]
* @returns {{ domain: string, description: string }}
*/
function constructDateRange({
referenceMoment,
fieldName,
fieldType,
granularity,
setParam,
addParam,
}) {
const date = referenceMoment.clone().set(setParam).add(addParam || {});
// compute domain
let leftBound = date.clone().locale('en').startOf(granularity);
let rightBound = date.clone().locale('en').endOf(granularity);
if (fieldType === 'date') {
leftBound = leftBound.format('YYYY-MM-DD');
rightBound = rightBound.format('YYYY-MM-DD');
} else {
leftBound = leftBound.utc().format('YYYY-MM-DD HH:mm:ss');
rightBound = rightBound.utc().format('YYYY-MM-DD HH:mm:ss');
}
const domain = Domain.prototype.arrayToString([
'&',
[fieldName, '>=', leftBound],
[fieldName, '<=', rightBound]
]);
// compute description
const descriptions = [date.format("YYYY")];
const method = _t.database.parameters.direction === "rtl" ? "push" : "unshift";
if (granularity === "month") {
descriptions[method](date.format("MMMM"));
} else if (granularity === "quarter") {
descriptions[method](QUARTERS[date.quarter()].description);
}
const description = descriptions.join(" ");
return { domain, description, };
}
/**
* Returns a version of the options in COMPARISON_OPTIONS with translated descriptions.
* @see getOptionsWithDescriptions
*/
function getComparisonOptions() {
return getOptionsWithDescriptions(COMPARISON_OPTIONS);
}
/**
* Returns the params addParam and selectedOptions necessary for the computation
* of a comparison domain.
* @param {moment} referenceMoment
* @param {string{}} selectedOptionIds
* @param {string} comparisonOptionId
* @returns {Object[]}
*/
function getComparisonParams(referenceMoment, selectedOptionIds, comparisonOptionId) {
const comparisonOption = COMPARISON_OPTIONS[comparisonOptionId];
const selectedOptions = getSelectedOptions(referenceMoment, selectedOptionIds);
let addParam = comparisonOption.addParam;
if (addParam) {
return [addParam, selectedOptions];
}
addParam = {};
let globalGranularity = 'year';
if (selectedOptions.month) {
globalGranularity = 'month';
} else if (selectedOptions.quarter) {
globalGranularity = 'quarter';
}
const granularityFactor = PER_YEAR[globalGranularity];
const years = selectedOptions.year.map(o => o.setParam.year);
const yearMin = Math.min(...years);
const yearMax = Math.max(...years);
let optionMin = 0;
let optionMax = 0;
if (selectedOptions.quarter) {
const quarters = selectedOptions.quarter.map(o => o.setParam.quarter);
if (globalGranularity === 'month') {
delete selectedOptions.quarter;
for (const quarter of quarters) {
for (const month of QUARTERS[quarter].coveredMonths) {
const monthOption = selectedOptions.month.find(
o => o.setParam.month === month
);
if (!monthOption) {
selectedOptions.month.push({
setParam: { month, }, granularity: 'month',
});
}
}
}
} else {
optionMin = Math.min(...quarters);
optionMax = Math.max(...quarters);
}
}
if (selectedOptions.month) {
const months = selectedOptions.month.map(o => o.setParam.month);
optionMin = Math.min(...months);
optionMax = Math.max(...months);
}
addParam[globalGranularity] = -1 +
granularityFactor * (yearMin - yearMax) +
optionMin - optionMax;
return [addParam, selectedOptions];
}
/**
* Returns a version of the options in INTERVAL_OPTIONS with translated descriptions.
* @see getOptionsWithDescriptions
*/
function getIntervalOptions() {
return getOptionsWithDescriptions(INTERVAL_OPTIONS);
}
/**
* Returns a version of the options in PERIOD_OPTIONS with translated descriptions
* and a key defautlYearId used in the control panel model when toggling a period option.
* @param {moment} referenceMoment
* @returns {Object[]}
*/
function getPeriodOptions(referenceMoment) {
const options = [];
for (const option of Object.values(PERIOD_OPTIONS)) {
const { id, groupNumber, description, } = option;
const res = { id, groupNumber, };
const ref_date = referenceMoment.startOf('year');
const date = ref_date.clone().set(option.setParam).add(option.addParam);
if (description) {
res.description = description.toString();
} else {
res.description = date.format(option.format.toString());
}
res.setParam = getSetParam(option, referenceMoment);
res.defaultYear = date.year();
options.push(res);
}
for (const option of options) {
const yearOption = options.find(
o => o.setParam && o.setParam.year === option.defaultYear
);
option.defaultYearId = yearOption.id;
delete option.defaultYear;
delete option.setParam;
}
return options;
}
/**
* Returns a version of the options in OPTIONS with translated descriptions (if any).
* @param {Object{}} OPTIONS
* @returns {Object[]}
*/
function getOptionsWithDescriptions(OPTIONS) {
const options = [];
for (const option of Object.values(OPTIONS)) {
const { id, groupNumber, description, } = option;
const res = { id, };
if (description) {
res.description = description.toString();
}
if (groupNumber) {
res.groupNumber = groupNumber;
}
options.push(res);
}
return options;
}
/**
* Returns a version of the period options whose ids are in selectedOptionIds
* partitioned by granularity.
* @param {moment} referenceMoment
* @param {string[]} selectedOptionIds
* @param {Object}
*/
function getSelectedOptions(referenceMoment, selectedOptionIds) {
const selectedOptions = { year: [] };
for (const optionId of selectedOptionIds) {
const option = PERIOD_OPTIONS[optionId];
const setParam = getSetParam(option, referenceMoment);
const granularity = option.granularity;
if (!selectedOptions[granularity]) {
selectedOptions[granularity] = [];
}
selectedOptions[granularity].push({ granularity, setParam });
}
return selectedOptions;
}
/**
* Returns the setParam object associated with the given periodOption and
* referenceMoment.
* @param {Object} periodOption
* @param {moment} referenceMoment
* @returns {Object}
*/
function getSetParam(periodOption, referenceMoment) {
if (periodOption.setParam) {
return periodOption.setParam;
}
const date = referenceMoment.clone().add(periodOption.addParam);
const setParam = {};
setParam[periodOption.granularity] = date[periodOption.granularity]();
return setParam;
}
/**
* @param {string} intervalOptionId
* @returns {number} index
*/
function rankInterval(intervalOptionId) {
return Object.keys(INTERVAL_OPTIONS).indexOf(intervalOptionId);
}
/**
* Sorts in place an array of 'period' options.
* @param {Object[]} options supposed to be of the form:
* { granularity, setParam, }
*/
function sortPeriodOptions(options) {
options.sort((o1, o2) => {
const granularity1 = o1.granularity;
const granularity2 = o2.granularity;
if (granularity1 === granularity2) {
return o1.setParam[granularity1] - o2.setParam[granularity1];
}
return granularity1 < granularity2 ? -1 : 1;
});
}
/**
* Checks if a year id is among the given array of period option ids.
* @param {string[]} selectedOptionIds
* @returns {boolean}
*/
function yearSelected(selectedOptionIds) {
return selectedOptionIds.some(optionId => !!YEAR_OPTIONS[optionId]);
}
return {
COMPARISON_OPTIONS,
DEFAULT_INTERVAL,
DEFAULT_PERIOD,
FACET_ICONS,
FIELD_OPERATORS,
FIELD_TYPES,
GROUPABLE_TYPES,
INTERVAL_OPTIONS,
PERIOD_OPTIONS,
constructDateDomain,
getComparisonOptions,
getIntervalOptions,
getPeriodOptions,
rankInterval,
yearSelected,
};
});

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="filter_extend_assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/month_filter/static/src/js/control_panel/custom_search_utils.js"/>
<script type="text/javascript" src="/month_filter/static/src/js/control_panel/control_panel_custom_extension.js"/>
</xpath>
</template>
</odoo>

View File

@ -16,13 +16,14 @@
'security/ir.model.access.csv',
'wizard/project_create_expenses_views.xml',
'views/assets.xml',
#'views/project_view.xml',
'views/project_view.xml',
'report/project_budget_hrs_analysis_views.xml',
'report/project_budget_amt_analysis_views.xml',
'report/project_timeline_report_views.xml',
'report/project_timesheet_report_views.xml',
'report/project_revenue_custom_report_views.xml',
'report/project_revenue_custom_report2_views.xml',
'report/project_revenue_custom_report3_views.xml',
'report/project_consultant_custom_report_views.xml',
#'report/cor_project_report_views.xml',
],

View File

@ -7,4 +7,15 @@ class Project(models.Model):
_inherit = 'project.project'
def action_view_allocation_custom_report3(self):
action = self.env["ir.actions.actions"]._for_xml_id("project_report.project_revenue_custom_report3_action")
action['context'] = {'search_default_project_id': self.id,
'search_default_project': 1,
'search_default_group_employee': 1,
'search_default_group_enddate': 1,
#'default_res_model': 'project.revenue.custom.report3'
}
return action

View File

@ -7,5 +7,6 @@ from . import project_timeline_report
from . import project_timesheet_report
from . import project_revenue_custom_report
from . import project_revenue_custom_report2
from . import project_revenue_custom_report3
from . import project_consultant_custom_report
#from . import cor_project_report

View File

@ -73,10 +73,10 @@
<field name="context">{'search_default_group_project': 1,'search_default_group_employee': 1}</field>
</record>
<menuitem id="menu_project_consultant_custom_report"
<!--<menuitem id="menu_project_consultant_custom_report"
parent="project.menu_project_report"
action="project_consultant_custom_report_action"
name="Consultant Allocation Reports"
sequence="51"/>
sequence="51"/>-->
</odoo>

View File

@ -0,0 +1,560 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools
class ProjectRevenueCustomReport3(models.Model):
_name = "project.revenue.custom.report3"
_description = "Project Revenue Custom Analysis report3"
#_order = 'project_id'
_auto = False
start_date = fields.Date(string='Start Date', readonly=True)
#start_datetime = fields.Datetime(string='Start Datetime111111111', readonly=True)
end_date = fields.Date(string='End Date', readonly=True)
project_id = fields.Many2one('project.project', string='Project', readonly=True)
parent_project = fields.Many2one('project.project', string='Parent Project', readonly=True)
partner_id = fields.Many2one('res.partner', string='Client', readonly=True)
timesheet_id = fields.Integer(string='Timesheet ID', readonly=True)
pricing_type = fields.Selection([
('fixed_rate', 'Fixed rate'),
('employee_rate', 'Consultant rate')
], string="Pricing", readonly=True)
project_type = fields.Selection([
('hours_in_consultant', 'Hours are budgeted according to a consultant'),
('hours_no_limit', 'Total hours are budgeted without division to consultant'),
], string="Project Type", readonly=True)
employee_id = fields.Many2one('hr.employee', string='Consultant', readonly=True)
timesheet_sdatetime = fields.Datetime(string='Timesheet Start Time', readonly=True)
unit_amount = fields.Float('Timesheet Hours', digits=(16, 2))
timesheet_cost = fields.Float('Hourly Cost', digits=(16, 2))
profit_per = fields.Float(string='Profit (%)', digits=(16, 2))
profit_amt = fields.Float(string='Profit Amount', digits=(16, 2))
expenses_amt = fields.Float(string='Expenses Amount', digits=(16, 2))
pro_hourly_rate = fields.Float("Hourly Revenue", digits=(16, 2), group_operator="max")
budgeted_hours = fields.Float("Budgeted Hours", digits=(16, 2), readonly=True, group_operator="sum")
budgeted_revenue = fields.Float("Budgeted Revenue", digits=(16, 2), readonly=True, group_operator="sum")
actual_revenue = fields.Float("Actual Revenue", digits=(16, 2), readonly=True, group_operator="sum")
actual_cost = fields.Float("Actual Cost", digits=(16, 2), readonly=True, group_operator="sum")
overall_budgeted_revenue = fields.Float("Overall Budgeted Rev.", digits=(16, 2), readonly=True, group_operator="sum")
overall_hourly_rate = fields.Float("Overall Hourly Rate", digits=(16, 2), group_operator="sum")
project_active = fields.Boolean('Active')
tag_name = fields.Char("Tag Name")
role = fields.Selection([('Manager', 'Manager'), ('Employee', 'Employee')], string="Role")
#is_sub_project = fields.Boolean("Is Sub Project")
sub_project = fields.Many2one('project.project', string='Sub Project')
def init(self):
'''Create the view'''
tools.drop_view_if_exists(self._cr, self._table)
self._cr.execute("""
CREATE OR REPLACE VIEW %s AS (
with pro_data as (
SELECT
PRO.id as pproject_id,
PRO.date_start as pro_sdate,
PRO.date as pro_edate,
PRO.active as project_active,
PRO.project_type,
PRO.pricing_type,
(select project_id from project_subproject_rel as par where pro.id=par.id limit 1) as parent_project,
--(select PRO.id from project_subproject_rel as PAR where PRO.id=PAR.project_id limit 1) as parent_project,
STRING_AGG(distinct tag_master.name, ', ') as tag_name,
PRO.partner_id AS partner_id,
PRO.budgeted_revenue as budgeted_revenue,
PRO.budgeted_hours2 as budgeted_hours,
PRO.expenses_amt as expenses_amt,
PRO.hourly_rate AS pro_hourly_rate
FROM project_project PRO
LEFT JOIN custom_project_tags_project_project_rel AS cus_pro_tag ON pro.id = cus_pro_tag.project_project_id
LEFT JOIN custom_project_tags as tag_master ON tag_master.id = cus_pro_tag.custom_project_tags_id
group by pro.id
),
cons_data1 as (
SELECT
date_trunc('month', min(PRO_EMP.start_date)) AS min,
date_trunc('month', max(coalesce(PRO_EMP.end_date, current_date))) AS max,
PRO_EMP.start_date as start_date,
coalesce(PRO_EMP.end_date, current_date) as end_date,
PRO_EMP.project_id,
PRO_EMP.employee_id,
PRO_EMP.role,
PRO_EMP.cost as budgeted_revenue,
PRO_EMP.budgeted_qty as budgeted_hours,
PRO_EMP.employee_price as cons_timesheet_cost,
PRO_EMP.price_unit as cons_hourly_cost
FROM project_sale_line_employee_map PRO_EMP
group by start_date, end_date, project_id, employee_id, role, budgeted_revenue, budgeted_hours, cons_timesheet_cost, cons_hourly_cost
),
tsheet_data1 as (
SELECT
date_trunc('month', min(AAL.start_datetime::date)) AS min,
date_trunc('month', max(AAL.end_datetime::date)) AS max,
AAL.project_id,
AAL.employee_id
FROM account_analytic_line AAL
group by AAL.project_id, AAL.employee_id
),
drange_data1 as (
SELECT
tsheet_data1.project_id,
tsheet_data1.employee_id,
generate_series(min, max,'1 month'):: date AS start_date,
(generate_series(min, max, '1 month'):: date + '1 month' :: interval - '1 day' :: interval):: date AS end_date
FROM tsheet_data1
),
drange_data2 as (
SELECT
cons_data1.project_id,
cons_data1.employee_id,
generate_series(min, max,'1 month'):: date AS start_date,
(generate_series(min, max, '1 month'):: date + '1 month' :: interval - '1 day' :: interval):: date AS end_date
FROM cons_data1
),
invoice_date as (
SELECT
date,
project_id,
(SELECT id FROM hr_employee WHERE LOWER(name)=LOWER('Fixed amount') limit 1) as employee_id,
fixed_amount from project_revenue_lines
),
data1 as (
SELECT
drange_data1.start_date,
drange_data1.end_date,
drange_data1.project_id,
pro_data.project_active,
pro_data.project_type,
pro_data.pricing_type,
pro_data.parent_project,
AAL.sub_project,
null::char as role,
pro_data.tag_name,
pro_data.partner_id,
drange_data1.employee_id,
AAL.id as timesheet_id,
AAL.start_datetime as timesheet_sdatetime,
0 as overall_budgeted_revenue,
0 as budgeted_revenue,
0 as budgeted_hours,
0 as overall_hourly_rate,
sum(AAL.unit_amount) as unit_amount,
0 AS pro_hourly_rate,
((sum(AAL.amount) * -1)/NULLIF(sum(AAL.unit_amount), 0)) as timesheet_cost,
0.0 AS actual_revenue,
(sum(AAL.amount) * -1) as actual_cost,
0.0 AS expenses_amt,
(0.0 - (sum(AAL.amount) * -1)) AS profit_amt,
0.0 AS profit_per
FROM drange_data1
left join pro_data on pro_data.pproject_id = drange_data1.project_id
left join tsheet_data1 on tsheet_data1.project_id=drange_data1.project_id and tsheet_data1.employee_id=drange_data1.employee_id
left join account_analytic_line AAL on AAL.project_id=tsheet_data1.project_id and AAL.employee_id=tsheet_data1.employee_id
and AAL.start_datetime at time zone 'utc' at time zone (select tz from res_partner where id=3)
>= drange_data1.start_date::date + '00:00:00'::time
and AAL.end_datetime at time zone 'utc' at time zone (select tz from res_partner where id=3)
<= drange_data1.end_date::date + '23:59:59'::time
where pro_data.pricing_type = 'fixed_rate'
group by
drange_data1.start_date,
drange_data1.end_date,
drange_data1.project_id,
pro_data.project_active,
pro_data.project_type,
pro_data.pricing_type,
pro_data.parent_project,
AAL.sub_project,
pro_data.tag_name,
pro_data.partner_id,
drange_data1.employee_id,
AAL.id,
AAL.start_datetime
),
data2 as (
SELECT
coalesce(min(pro_data.pro_sdate), min(AAL.start_datetime::date)) as start_date,
coalesce(max(pro_data.pro_edate), max(AAL.end_datetime::date)) as end_date,
pro_data.pproject_id as project_id,
pro_data.project_active,
pro_data.project_type,
pro_data.pricing_type,
pro_data.parent_project,
null::int as sub_project,
null::char as role,
pro_data.tag_name,
pro_data.partner_id,
null::int AS employee_id,
null::int As timesheet_id,
null::timestamp as timesheet_sdatetime,
0 as overall_budgeted_revenue,
pro_data.budgeted_revenue,
pro_data.budgeted_hours,
0 as overall_hourly_rate,
0 as unit_amount,
0.0 AS pro_hourly_rate,
0 as timesheet_cost,
0.0 AS actual_revenue,
0 as actual_cost,
pro_data.expenses_amt,
0.0 AS profit_amt,
0.0 AS profit_per
FROM pro_data
left join account_analytic_line AAL on AAL.project_id=pro_data.pproject_id
WHERE pro_data.pricing_type='employee_rate' and pro_data.project_type='hours_no_limit'
group by
pro_data.pproject_id,
pro_data.project_active,
pro_data.project_type,
pro_data.pricing_type,
pro_data.parent_project,
sub_project,
pro_data.tag_name,
pro_data.partner_id,
pro_data.budgeted_revenue,
pro_data.budgeted_hours,
pro_data.expenses_amt
UNION
SELECT
drange_data1.start_date,
drange_data1.end_date,
drange_data1.project_id,
pro_data.project_active,
pro_data.project_type,
pro_data.pricing_type,
pro_data.parent_project,
AAL.sub_project,
null::char as role,
pro_data.tag_name,
pro_data.partner_id,
drange_data1.employee_id,
AAL.id as timesheet_id,
AAL.start_datetime as timesheet_sdatetime,
0 as overall_budgeted_revenue,
0 as budgeted_revenue,
0 as budgeted_hours,
0 as overall_hourly_rate,
AAL.unit_amount,
pro_data.pro_hourly_rate,
((AAL.amount * -1)/NULLIF(AAL.unit_amount, 0)) as timesheet_cost,
(AAL.unit_amount * pro_data.pro_hourly_rate) AS actual_revenue,
(AAL.amount * -1) as actual_cost,
0 AS expenses_amt,
((AAL.unit_amount * pro_data.pro_hourly_rate) - (AAL.amount * -1)) AS profit_amt,
((AAL.unit_amount * pro_data.pro_hourly_rate) - (AAL.amount * -1))/NULLIF((AAL.unit_amount * pro_data.pro_hourly_rate),0) AS profit_per
FROM drange_data1
left join pro_data on pro_data.pproject_id = drange_data1.project_id
left join account_analytic_line AAL on AAL.project_id=drange_data1.project_id and AAL.employee_id=drange_data1.employee_id
and AAL.start_datetime at time zone 'utc' at time zone (select tz from res_partner where id=3)
>= drange_data1.start_date::date + '00:00:00'::time
and AAL.end_datetime at time zone 'utc' at time zone (select tz from res_partner where id=3)
<= drange_data1.end_date::date + '23:59:59'::time
WHERE pro_data.pricing_type='employee_rate' and pro_data.project_type='hours_no_limit'
group by
drange_data1.start_date,
drange_data1.end_date,
pro_data.pro_sdate,
pro_data.pro_edate,
drange_data1.project_id,
pro_data.project_active,
pro_data.project_type,
pro_data.pricing_type,
pro_data.parent_project,
AAL.sub_project,
pro_data.tag_name,
pro_data.partner_id,
drange_data1.employee_id,
AAL.id,
AAL.start_datetime,
pro_data.pro_hourly_rate,
aal.unit_amount,
aal.amount,
pro_data.budgeted_hours,
pro_data.expenses_amt
),
data3 as (
SELECT
coalesce(min(pro_data.pro_sdate), min(AAL.start_datetime::date)) as start_date,
coalesce(max(pro_data.pro_edate), max(AAL.end_datetime::date)) as end_date,
pro_data.pproject_id as project_id,
pro_data.project_active,
pro_data.project_type,
pro_data.pricing_type,
pro_data.parent_project,
null::int as sub_project,
null::char as role,
pro_data.tag_name,
pro_data.partner_id,
null::int as employee_id,
null::int As timesheet_id,
null::timestamp as timesheet_sdatetime,
pro_data.budgeted_revenue AS overall_budgeted_revenue,
0 as budgeted_revenue,
0 as budgeted_hours,
pro_data.pro_hourly_rate AS overall_hourly_rate,
0.0 as unit_amount,
0.0 as pro_hourly_rate,
0.0 as timesheet_cost,
0.0 as actual_revenue,
0.0 as actual_cost,
pro_data.expenses_amt,
0.0 as profit_amt,
0.0 as profit_per
FROM pro_data
left join account_analytic_line AAL on AAL.project_id=pro_data.pproject_id
where pro_data.pricing_type='employee_rate' and pro_data.project_type='hours_in_consultant'
group by
pro_data.pproject_id,
pro_data.project_active,
pro_data.project_type,
pro_data.parent_project,
pro_data.budgeted_revenue,
pro_data.tag_name,
pro_data.pricing_type,
pro_data.partner_id,
pro_data.pro_hourly_rate,
pro_data.expenses_amt
UNION
SELECT
drange_data2.start_date,
drange_data2.end_date,
drange_data2.project_id,
pro_data.project_active,
pro_data.project_type,
pro_data.pricing_type,
pro_data.parent_project,
--AAL.sub_project,
null::int as sub_project,
cons_data1.role,
pro_data.tag_name,
pro_data.partner_id,
drange_data2.employee_id,
null::int As timesheet_id,
null::timestamp as timesheet_sdatetime,
0 as overall_budgeted_revenue,
cons_data1.budgeted_revenue,
cons_data1.budgeted_hours,
0 as overall_hourly_rate,
0 as unit_amount,
0 as pro_hourly_rate,
0 as timesheet_cost,
0 as actual_revenue,
0 as actual_cost,
0 as expenses_amt,
0 as profit_amt,
0 as profit_per
FROM drange_data2
left join pro_data on pro_data.pproject_id = drange_data2.project_id
left join cons_data1 on cons_data1.project_id=drange_data2.project_id and cons_data1.employee_id=drange_data2.employee_id
and drange_data2.start_date >= cons_data1.start_date and drange_data2.end_date <= cons_data1.end_date
where pro_data.pricing_type='employee_rate' and pro_data.project_type='hours_in_consultant'
group by
drange_data2.start_date,
drange_data2.end_date,
drange_data2.project_id,
drange_data2.employee_id,
pro_data.project_active,
pro_data.project_type,
pro_data.parent_project,
cons_data1.role,
cons_data1.budgeted_revenue,
cons_data1.budgeted_hours,
pro_data.tag_name,
pro_data.pricing_type,
pro_data.partner_id
UNION
SELECT
drange_data1.start_date,
drange_data1.end_date,
drange_data1.project_id,
pro_data.project_active,
pro_data.project_type,
pro_data.pricing_type,
pro_data.parent_project,
AAL.sub_project,
cons_data1.role,
pro_data.tag_name,
pro_data.partner_id,
drange_data1.employee_id,
AAL.id as timesheet_id,
AAL.start_datetime as timesheet_sdatetime,
0 as overall_budgeted_revenue,
0 as budgeted_revenue,
0 as budgeted_hours,
0 as overall_hourly_rate,
AAL.unit_amount as unit_amount,
0 as pro_hourly_rate,
coalesce(((AAL.amount * -1)/NULLIF(AAL.unit_amount, 0)), cons_data1.cons_timesheet_cost) as timesheet_cost,
(AAL.unit_amount * COALESCE(cons_data1.cons_hourly_cost, 0)) as actual_revenue,
case when cons_data1.cons_timesheet_cost is null then (AAL.amount * -1) else (AAL.unit_amount * cons_data1.cons_timesheet_cost)
end as actual_cost,
0 as expenses_amt,
(AAL.unit_amount * COALESCE(cons_data1.cons_hourly_cost, 0)) - case when cons_data1.cons_timesheet_cost is null
then (AAL.amount * -1) else (AAL.unit_amount * cons_data1.cons_timesheet_cost) end as profit_amt,
(((AAL.unit_amount * COALESCE(cons_data1.cons_hourly_cost, 0)) - case when cons_data1.cons_timesheet_cost is null then (AAL.amount * -1)
else (AAL.unit_amount * cons_data1.cons_timesheet_cost) end) / NULLIF((AAL.unit_amount * COALESCE(cons_data1.cons_hourly_cost, 0)), 0)) * 100 as profit_per
FROM drange_data1
left join pro_data on pro_data.pproject_id = drange_data1.project_id
left join tsheet_data1 on tsheet_data1.project_id=drange_data1.project_id and tsheet_data1.employee_id=drange_data1.employee_id
left join account_analytic_line AAL on AAL.project_id=tsheet_data1.project_id and AAL.employee_id=tsheet_data1.employee_id
and AAL.start_datetime at time zone 'utc' at time zone (select tz from res_partner where id=3)
>= drange_data1.start_date::date + '00:00:00'::time
and AAL.end_datetime at time zone 'utc' at time zone (select tz from res_partner where id=3)
<= drange_data1.end_date::date + '23:59:59'::time
left join cons_data1 on cons_data1.project_id=drange_data1.project_id and cons_data1.employee_id=drange_data1.employee_id
and AAL.start_datetime at time zone 'utc' at time zone (select tz from res_partner where id=3)
>= cons_data1.start_date::date + '00:00:00'::time
and AAL.end_datetime at time zone 'utc' at time zone (select tz from res_partner where id=3)
<= cons_data1.end_date::date + '23:59:59'::time
where pro_data.pricing_type='employee_rate' and pro_data.project_type='hours_in_consultant'
group by
drange_data1.start_date,
drange_data1.end_date,
drange_data1.project_id,
drange_data1.employee_id,
AAL.id,
AAL.start_datetime,
pro_data.project_active,
pro_data.project_type,
pro_data.parent_project,
AAL.sub_project,
cons_data1.role,
pro_data.tag_name,
pro_data.pricing_type,
pro_data.partner_id,
AAL.unit_amount,
AAL.amount,
cons_data1.cons_timesheet_cost,
cons_data1.cons_hourly_cost
),
invoice_data as (SELECT
invoice_date.date as start_date,
invoice_date.date as end_date,
invoice_date.project_id,
pro_data.project_active,
pro_data.project_type,
pro_data.pricing_type,
pro_data.parent_project,
null::int as sub_project,
null::char as role,
pro_data.tag_name,
pro_data.partner_id,
invoice_date.employee_id,
null::int As timesheet_id,
null::timestamp as timesheet_sdatetime,
0 as overall_budgeted_revenue,
0 as budgeted_revenue,
0 as budgeted_hours,
0 as overall_hourly_rate,
0 as unit_amount,
0 as pro_hourly_rate,
0 as timesheet_cost,
invoice_date.fixed_amount as actual_revenue,
0 as actual_cost,
0.0 AS expenses_amt,
0.0 AS profit_amt,
0.0 AS profit_per
from invoice_date
left join pro_data on pro_data.pproject_id = invoice_date.project_id --and pro_data.employee_id = invoice_date.employee_id
left join drange_data1 on invoice_date.project_id = drange_data1.project_id and invoice_date.employee_id = drange_data1.employee_id
and invoice_date.date >= drange_data1.start_date and invoice_date.date <= drange_data1.end_date
),
res as (
select * from data1
UNION
select * from data2
UNION
select * from data3
UNION
select * from invoice_data
)
select
ROW_NUMBER() OVER() as id,
start_date,
end_date,
project_id,
project_active,
project_type,
pricing_type,
parent_project,
sub_project,
role,
tag_name,
partner_id,
employee_id,
timesheet_id,
timesheet_sdatetime,
overall_budgeted_revenue,
budgeted_revenue,
budgeted_hours,
overall_hourly_rate,
unit_amount,
pro_hourly_rate,
timesheet_cost,
actual_revenue,
actual_cost,
expenses_amt,
profit_amt,
profit_per
from res
group by
start_date,
end_date,
project_id,
project_active,
project_type,
pricing_type,
parent_project,
sub_project,
role,
tag_name,
partner_id,
employee_id,
timesheet_id,
timesheet_sdatetime,
overall_budgeted_revenue,
budgeted_revenue,
budgeted_hours,
overall_hourly_rate,
unit_amount,
pro_hourly_rate,
timesheet_cost,
actual_revenue,
actual_cost,
expenses_amt,
profit_amt,
profit_per
)""" % (self._table,))
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
res = super(ProjectRevenueCustomReport3, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
pro_hourly_rate = 0
actual_cost = 0
for line in res:
try:
if 'actual_cost' in line:
actual_cost = line['actual_cost']
if 'overall_budgeted_revenue' in line and 'budgeted_hours' in line:
if line['budgeted_hours'] > 0:
line['overall_hourly_rate'] = line['overall_budgeted_revenue'] / line['budgeted_hours']
if 'pro_hourly_rate' in line and 'budgeted_hours' in line:
if line['budgeted_hours'] > 0:
line['pro_hourly_rate'] = line['budgeted_revenue'] / line['budgeted_hours']
if 'actual_revenue' in line and 'actual_cost' in line and 'expenses_amt' in line:
line['profit_amt'] = line['actual_revenue'] - line['actual_cost'] - line['expenses_amt']
if 'profit_amt' in line and 'actual_revenue' in line:
try:
line['profit_per'] = (line['profit_amt'] / line['actual_revenue']) * 100
except ZeroDivisionError:
pass
if 'unit_amount' in line:
try:
line['timesheet_cost'] = actual_cost / line['unit_amount']
except ZeroDivisionError:
pass
except Exception:
pass
return res

View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="project_revenue_custom_report3_view_pivot" model="ir.ui.view">
<field name="name">project.revenue.custom.report3.pivot</field>
<field name="model">project.revenue.custom.report3</field>
<field name="arch" type="xml">
<pivot string="Revenue Analysis" disable_linking="True" sample="1">
<field name="project_id" type="row"/>
<field name="overall_budgeted_revenue" type="measure"/>
<field name="budgeted_revenue" type="measure"/>
<field name="actual_revenue" type="measure"/>
<field name="actual_cost" type="measure"/>
<field name="budgeted_hours" type="measure"/>
<field name="overall_hourly_rate" type="measure"/>
<field name="pro_hourly_rate" type="measure"/>
<field name="expenses_amt" type="measure"/>
<field name="profit_amt" type="measure"/>
<field name="profit_per" type="measure"/>
<field name="unit_amount" widget="float_time" type="measure"/>
<field name="timesheet_cost" type="measure"/>
</pivot>
</field>
</record>
<record id="project_revenue_custom_report3_view_graph" model="ir.ui.view">
<field name="name">project.revenue.custom.report3.graph</field>
<field name="model">project.revenue.custom.report3</field>
<field name="arch" type="xml">
<graph string="Revenue Analysis" type="bar" stacked="True" sample="1" disable_linking="1">
<field name="project_id" type="col"/>
<field name="budgeted_revenue" type="row"/>
<field name="actual_revenue" type="row"/>
<field name="actual_cost" type="row"/>
</graph>
</field>
</record>
<record id="project_revenue_custom_report3_view_tree" model="ir.ui.view">
<field name="name">project.revenue.custom.report3.tree</field>
<field name="model">project.revenue.custom.report3</field>
<field name="arch" type="xml">
<tree string="Revenue Analysis" create="false" edit="false" delete="false">
<field name="project_id"/>
<field name="parent_project"/>
<field name="sub_project"/>
<field name="employee_id"/>
<field name="overall_budgeted_revenue"/>
<field name="budgeted_revenue"/>
<field name="actual_revenue"/>
<field name="actual_cost"/>
<field name="budgeted_hours"/>
<field name="pro_hourly_rate"/>
<field name="profit_amt"/>
<field name="profit_per"/>
<field name="unit_amount" widget="float_time"/>
<field name="timesheet_cost"/>
<field name="start_date"/>
<field name="end_date"/>
</tree>
</field>
</record>
<record id="project_revenue_custom_report3_view_search" model="ir.ui.view">
<field name="name">project.revenue.custom.report3.search</field>
<field name="model">project.revenue.custom.report3</field>
<field name="arch" type="xml">
<search string="Revenue Analysis">
<field name="project_id"/>
<field name="employee_id"/>
<field name="tag_name"/>
<filter string="Fixed rate" name="fixed" domain="[('pricing_type','=','fixed_rate')]"/>
<filter string="Hours are budgeted according to a consultant" name="cons" domain="[('pricing_type','=','employee_rate'),('project_type','=','hours_in_consultant')]"/>
<filter string="Total hours are budgeted without division to consultant" name="limit" domain="[('pricing_type','=','employee_rate'),('project_type','=','hours_no_limit')]"/>
<filter string="Start Date" name="filter_start_date" date="start_date"/>
<filter string="End Date" name="filter_end_date" date="end_date"/>
<filter string="Active" name="active_project" domain="[('project_active','=',True)]"/>
<filter string="Archived" name="active_project" domain="[('project_active','=',False)]"/>
<!-- <filter string="Is Sub Project" name="subproject" domain="[('is_sub_project','=',True)]"/> -->
<filter string="Manager Role" name="manager_role" domain="[('role','=','Manager')]"/>
<filter string="Employee Role" name="employee_role" domain="[('role','=','Employee')]"/>
<group expand="1" string="Group By">
<filter string="Project" name="group_project" context="{'group_by':'project_id'}"/>
<filter string="Sub Project" name="group_sub_project" context="{'group_by':'sub_project'}"/>
<filter string="Consultant" name="group_employee" context="{'group_by':'employee_id'}"/>
<filter string="End Date" name="group_enddate" domain="[]" context="{'group_by':'end_date:month'}"/>
<filter string="Role" name="group_role" domain="[]" context="{'group_by':'role'}"/>
<filter string="Tags" name="group_tags" context="{'group_by':'tag_name'}"/>
<filter string="Parent Project" name="group_parent_project" context="{'group_by':'parent_project'}"/>
<!--<filter string="Budgeted Hours" name="budgethrs"
domain="[('timesheet_sdatetime','=',False)]"/>
<filter string="Current Year" name="currentyear"
domain="[('timesheet_sdatetime','&lt;=', time.strftime('%%Y-12-31')),('timesheet_sdatetime','&gt;=',time.strftime('%%Y-01-01'))]"/>
<filter string="Last Year" name="lyear" domain="[('timesheet_sdatetime','&gt;=',(context_today()-relativedelta(years=1)).strftime('%%Y-01-01')),('timesheet_sdatetime','&lt;', time.strftime('%%Y-01-01'))]"/>-->
</group>
</search>
</field>
</record>
<record id="project_revenue_custom_report3_action" model="ir.actions.act_window">
<field name="name">Projects Revenue</field>
<field name="res_model">project.revenue.custom.report3</field>
<field name="view_mode">pivot,tree,graph</field>
<field name="search_view_id" ref="project_revenue_custom_report3_view_search"/>
<field name="context">{'search_default_group_project': 1, 'search_default_group_employee': 1,
'search_default_group_enddate': 1}
</field>
</record>
<menuitem id="menu_project_revenue_custom_report3"
parent="project.menu_project_report"
action="project_revenue_custom_report3_action"
name="Projects Revenue (Updated)"
sequence="50"/>
</odoo>

View File

@ -15,5 +15,5 @@ access_project_consultant_custom_report_manager,project_consultant_custom_report
access_project_consultant_custom_report_user,project_consultant_custom_report_user,model_project_consultant_custom_report,project.group_project_user,1,0,0,0
access_project_revenue_custom_report2_manager,project_revenue_custom_report2_manager,model_project_revenue_custom_report2,project.group_project_manager,1,1,1,1
access_project_revenue_custom_report2_user,project_revenue_custom_report2_user,model_project_revenue_custom_report2,project.group_project_user,1,0,0,0
access_project_revenue_custom_report3,access_project_revenue_custom_report3,model_project_revenue_custom_report3,,1,1,1,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
15 access_project_consultant_custom_report_user project_consultant_custom_report_user model_project_consultant_custom_report project.group_project_user 1 0 0 0
16 access_project_revenue_custom_report2_manager project_revenue_custom_report2_manager model_project_revenue_custom_report2 project.group_project_manager 1 1 1 1
17 access_project_revenue_custom_report2_user project_revenue_custom_report2_user model_project_revenue_custom_report2 project.group_project_user 1 0 0 0
18 access_project_revenue_custom_report3 access_project_revenue_custom_report3 model_project_revenue_custom_report3 1 1 1 0
19

View File

@ -8,10 +8,17 @@
<field name="inherit_id" ref="project.edit_project"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
<button class="oe_stat_button" type="object" name="action_view_allocation_custom_report3"
icon="fa-tasks"
attrs="{'invisible':['|',('pricing_type','!=','employee_rate'),('project_type','!=','hours_in_consultant')]}"
string="View Allocation" widget="statinfo">
</button>
</div>
<!--<div name="button_box" position="inside">
<button class="oe_stat_button" type="action" name="%(project_report.project_project_action_multi_create_project_expense)d" icon="fa-usd"
attrs="{'invisible': [('allow_billable','=',False)]}" string="Create Expense" widget="statinfo">
</button>
</div>
</div>-->
<!--<xpath expr="//field[@name='analytic_account_id']" position="after">
<field name="budgeted_hours" attrs="{'invisible': [('pricing_type','=','fixed_rate')], 'required': [('pricing_type','!=','fixed_rate')]}"/>
<field name="budgeted_revenue" attrs="{'invisible': [('pricing_type','=','fixed_rate')], 'required': [('pricing_type','!=','fixed_rate')]}"/>

View File

@ -0,0 +1,2 @@
from . import models
from . import wizard

View File

@ -0,0 +1,17 @@
{
'name': 'Project Revenue',
'version': '1.0.1',
'category': 'Project',
'author': 'SunArc Technologies',
'website': 'www.sunarctechnologies.com',
'license': 'LGPL-3',
'depends': ['base','hr','project'],
'data': [
'wizard/project_revenue_wizard.xml',
'security/ir.model.access.csv',
'views/custom_project.xml',
],
'installable': True,
'auto_install': False,
}

View File

@ -0,0 +1 @@
from . import custom_project

View File

@ -0,0 +1,58 @@
from odoo import api, fields, models
from odoo.exceptions import UserError, AccessError, ValidationError
from datetime import datetime
from dateutil import relativedelta
class CustomProject(models.Model):
_inherit = 'project.project'
revenue_amount_lines = fields.One2many('project.revenue.lines' , 'project_id')
class ProjectRevenueLines(models.Model):
_name = 'project.revenue.lines'
project_id = fields.Many2one('project.project')
# start_date = fields.Date(required=True)
# end_date = fields.Date(required=True)
date = fields.Date(required=True)
fixed_amount = fields.Float()
def edit_revenue_history_record(self):
context = dict(self.env.context)
context['form_view_initial_mode'] = 'edit'
return {
'name': ('Edit Revenue History'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'target': 'new',
'res_model': 'project.revenue.lines',
'res_id': self.id,
'context': context,
# 'view_id': view_id
}
"""@api.onchange('start_date')
def _onchange_start_date(self):
if self.project_id:
records = self.env['project.revenue.lines'].sudo().search([('project_id', '=', self.project_id.id)])
if self.start_date:
if self.start_date.day != 1:
raise AccessError('Please select first date of month')
else:
pass
for line in records:
if self.start_date >= line.start_date and self.start_date <= line.end_date:
raise AccessError('Date already exist')"""
def update_record(self):
return {
'type': 'ir.actions.act_window_close'}

View File

@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_project_revenue_lines,access.project.revenue.lines,model_project_revenue_lines,project.group_project_manager,1,1,1,1
access_project_revenue_wizard,access.project.revenue.wizard,model_project_revenue_wizard,project.group_project_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_project_revenue_lines access.project.revenue.lines model_project_revenue_lines project.group_project_manager 1 1 1 1
3 access_project_revenue_wizard access.project.revenue.wizard model_project_revenue_wizard project.group_project_manager 1 1 1 1

View File

@ -0,0 +1,50 @@
<odoo>
<record id="view_edit_project_inherit_revenue" model="ir.ui.view">
<field name="name"> project.edit.project.inherit</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.edit_project"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='settings']" position="after">
<page name="project_revenue_amount" string="Fixed Amount" groups="project.group_project_manager">
<button name="%(action_project_revenue_wizard)d" string="Add Revenue" type="action" class="oe_highlight"/>
<field name="revenue_amount_lines" context="{'project_id' : active_id}" readonly="1">
<tree editable="bottom">
<field name="project_id" invisible="1"/>
<!--<field name="start_date"/>
<field name="end_date"/>-->
<field name="date"/>
<field name="fixed_amount"/>
<button type="object" name="edit_revenue_history_record" string="Edit" class="oe_highlight"/>
<button name="unlink"
type="object"
icon="fa-trash-o"/>
</tree>
</field>
</page>
</xpath>
</field>
</record>
<record id="view_project_revenue_history_lines" model="ir.ui.view">
<field name="name"> project.edit.project.revenue.lines</field>
<field name="model">project.revenue.lines</field>
<field name="arch" type="xml">
<form string="Project Revenue History">
<group>
<group>
<field name="project_id" readonly="1"/>
<!--<field name="start_date"/>
<field name="end_date"/>-->
<field name="date"/>
<field name="fixed_amount"/>
</group>
</group>
<footer>
<button name="update_record" type="object" string="Update" class="oe_highlight"/>
<button special="cancel" string="Cancel"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@ -0,0 +1 @@
from . import project_revenue_wizard

View File

@ -0,0 +1,52 @@
from odoo import api, fields, models
from odoo.exceptions import UserError, AccessError, ValidationError
from datetime import datetime
from dateutil import relativedelta
class CustomProjectWizard(models.TransientModel):
_name = 'project.revenue.wizard'
project_id = fields.Many2one('project.project')
#start_date = fields.Date(required=True)
#end_date = fields.Datet(compute='_compute_end_date', store=True)
#end_date = fields.Date()
date = fields.Date(required=True)
fixed_amount = fields.Float()
# @api.depends('start_date')
# def _compute_end_date(self):
# for rec in self:
# if rec.start_date:
# rec.end_date = rec.start_date + relativedelta.relativedelta(months=1) - relativedelta.relativedelta(
# days=1)
"""@api.depends('start_date')
def _compute_end_date(self):
if self.start_date:
self.end_date = self.start_date + relativedelta.relativedelta(months=1) - relativedelta.relativedelta(days=1)"""
def action_set_revenue_lines(self):
values = {
'project_id':self.project_id.id,
# 'start_date':self.start_date,
# 'end_date':self.end_date,
'date':self.date,
'fixed_amount':self.fixed_amount
}
res = self.env['project.revenue.lines'].create(values)
return res
"""@api.onchange('start_date')
def _onchange_start_date(self):
active_id = self._context.get('active_ids', [])
project_object = self.env['project.project'].search([('id', '=', active_id[0])])
records = self.env['project.revenue.lines'].sudo().search([('project_id', '=', project_object.id)])
if self.start_date:
if self.start_date.day != 1:
raise AccessError('Please select first date of month')
else:
self.project_id = project_object.id
for line in records:
if self.start_date >= line.start_date and self.start_date <= line.end_date:
raise AccessError('Date already exist')"""

View File

@ -0,0 +1,32 @@
<odoo>
<record id="view_edit_project_wizard_revenue" model="ir.ui.view">
<field name="name"> project.edit.project.inherit.wizard</field>
<field name="model">project.revenue.wizard</field>
<field name="arch" type="xml">
<form string="Project Revenue Wizard">
<group>
<group>
<field name="project_id" readonly="1"/>
<!--<field name="start_date"/>
<field name="end_date"/>-->
<field name="date"/>
<field name="fixed_amount"/>
</group>
</group>
<footer>
<button string="OK" type="object" name="action_set_revenue_lines" class="btn-primary"/>
<button string="Cancel" class="btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_project_revenue_wizard" model="ir.actions.act_window">
<field name="name">Project Revenue wizard</field>
<field name="res_model">project.revenue.wizard</field>
<field name="view_id" ref="project_revenue_amount.view_edit_project_wizard_revenue"/>
<field name="target">new</field>
<field name="context">{ 'default_project_id': active_id, 'project_id': active_id } </field>
</record>
</odoo>

255
project_revenue_custom_report2.py Executable file
View File

@ -0,0 +1,255 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, tools
class ProjectRevenueCustomReport2(models.Model):
_name = "project.revenue.custom.report2"
_description = "Project Revenue Custom Analysis report2"
#_order = 'project_id'
_auto = False
project_id = fields.Many2one('project.project', string='Project', readonly=True)
parent_project = fields.Many2one('project.project', string='Parent Project', readonly=True)
partner_id = fields.Many2one('res.partner', string='Client', readonly=True)
pricing_type = fields.Selection([
('fixed_rate', 'Fixed rate'),
('employee_rate', 'Consultant rate')
], string="Pricing", readonly=True)
project_type = fields.Selection([
('hours_in_consultant', 'Hours are budgeted according to a consultant'),
('hours_no_limit', 'Total hours are budgeted without division to consultant'),
], string="Project Type", readonly=True)
employee_id = fields.Many2one('hr.employee', string='Consultant', readonly=True)
timesheet_sdatetime = fields.Datetime(string='Timesheet Start Time', readonly=True)
unit_amount = fields.Float('Timesheet Hours', digits=(16, 2))
timesheet_cost = fields.Float('Hourly Cost', digits=(16, 2))
profit_per = fields.Float(string='Profit (%)', digits=(16, 2))
profit_amt = fields.Float(string='Profit Amount', digits=(16, 2))
expenses_amt = fields.Float(string='Expenses Amount', digits=(16, 2))
pro_hourly_rate = fields.Float("Hourly Revenue", digits=(16, 2), group_operator="max")
budgeted_hours = fields.Float("Budgeted Hours", digits=(16, 2), readonly=True, group_operator="sum")
budgeted_revenue = fields.Float("Budgeted Revenue", digits=(16, 2), readonly=True, group_operator="sum")
actual_revenue = fields.Float("Actual Revenue", digits=(16, 2), readonly=True, group_operator="sum")
actual_cost = fields.Float("Actual Cost", digits=(16, 2), readonly=True, group_operator="sum")
overall_budgeted_revenue = fields.Float("Overall Budgeted Rev.", digits=(16, 2), readonly=True, group_operator="sum")
overall_hourly_rate = fields.Float("Overall Hourly Rate", digits=(16, 2), group_operator="sum")
def init(self):
'''Create the view'''
tools.drop_view_if_exists(self._cr, self._table)
self._cr.execute("""
CREATE OR REPLACE VIEW %s AS (
SELECT ROW_NUMBER() OVER() as id,
project_id,
parentproject as parent_project,
project_type,
pricing_type,
employee_id,
overall_budgeted_revenue,
partner_id,
budgeted_revenue,
budgeted_hours,
pro_hourly_rate,
overall_hourly_rate,
actual_revenue,
actual_cost,
expenses_amt,
profit_amt,
profit_per,
unit_amount,
timesheet_cost,
timesheet_sdatetime
from (
select pro.id AS project_id,
(select project_id from project_subproject_rel as par where pro.id=par.id limit 1) as parentproject,
pro.partner_id AS partner_id,
pro.project_type AS project_type,
pro.pricing_type as pricing_type,
AAL.employee_id AS employee_id,
0.0 AS overall_budgeted_revenue,
0.0 AS budgeted_revenue,
0.0 AS budgeted_hours,
0.0 AS overall_hourly_rate,
AAL.unit_amount,
0.0 AS pro_hourly_rate,
((AAL.amount * -1)/NULLIF(AAL.unit_amount, 0)) as timesheet_cost,
0.0 AS actual_revenue,
(AAL.amount * -1) AS actual_cost,
0.0 AS expenses_amt,
0.0 - (AAL.amount * -1) AS profit_amt,
0.0 AS profit_per,
AAL.start_datetime AS timesheet_sdatetime
FROM project_project PRO
RIGHT JOIN account_analytic_account AA ON PRO.analytic_account_id = AA.id
RIGHT JOIN account_analytic_line AAL ON AAL.account_id = AA.id and AAL.project_id = PRO.id
WHERE PRO.active = 't' and PRO.pricing_type = 'fixed_rate'
UNION
select
pro.id AS project_id,
(select project_id from project_subproject_rel as par where pro.id=par.id limit 1) as parentproject,
pro.partner_id AS partner_id,
pro.project_type AS project_type,
pro.pricing_type as pricing_type,
null::int AS employee_id,
0.0 AS overall_budgeted_revenue,
pro.budgeted_revenue AS budgeted_revenue,
pro.budgeted_hours2 AS budgeted_hours,
0.0 AS overall_hourly_rate,
0.0 as unit_amount,
0.0 AS pro_hourly_rate,
0.0 as timesheet_cost,
0.0 AS actual_revenue,
0.0 AS actual_cost,
pro.expenses_amt AS expenses_amt,
0.0 AS profit_amt,
0.0 AS profit_per,
null::timestamp as timesheet_sdatetime
FROM project_project PRO
WHERE PRO.active = 't' and PRO.pricing_type = 'employee_rate' and PRO.project_type='hours_no_limit'
UNION
select
pro.id AS project_id,
(select project_id from project_subproject_rel as par where pro.id=par.id limit 1) as parentproject,
pro.partner_id AS partner_id,
pro.project_type AS project_type,
pro.pricing_type as pricing_type,
AAL.employee_id AS employee_id,
0.0 AS overall_budgeted_revenue,
0.0 AS budgeted_revenue,
0.0 AS budgeted_hours,
0.0 AS overall_hourly_rate,
AAL.unit_amount,
pro.hourly_rate AS pro_hourly_rate,
((AAL.amount * -1)/NULLIF(AAL.unit_amount, 0)) as timesheet_cost,
(AAL.unit_amount * pro.hourly_rate) AS actual_revenue,
(AAL.amount * -1) AS actual_cost,
0.0 AS expenses_amt,
((AAL.unit_amount * pro.hourly_rate) - (AAL.amount * -1)) AS profit_amt,
((AAL.unit_amount * pro.hourly_rate) - (AAL.amount * -1))/NULLIF((AAL.unit_amount * pro.hourly_rate),0) AS profit_per,
AAL.start_datetime AS timesheet_sdatetime
FROM project_project PRO
RIGHT JOIN account_analytic_account AA ON PRO.analytic_account_id = AA.id
RIGHT JOIN account_analytic_line AAL ON AAL.account_id = AA.id and AAL.project_id = PRO.id
WHERE PRO.active = 't' and PRO.pricing_type = 'employee_rate' and PRO.project_type='hours_no_limit'
UNION
select
pro.id AS project_id,
(select project_id from project_subproject_rel as par where pro.id=par.id limit 1) as parentproject,
pro.partner_id AS partner_id,
pro.project_type AS project_type,
pro.pricing_type as pricing_type,
pro_emp.employee_id AS employee_id,
0.0 AS overall_budgeted_revenue,
pro_emp.cost AS budgeted_revenue,
pro_emp.budgeted_qty AS budgeted_hours,
0.0 AS overall_hourly_rate,
0.0 as unit_amount,
0.0 AS pro_hourly_rate,
0.0 as timesheet_cost,
0.0 AS actual_revenue,
0.0 AS actual_cost,
0.0 AS expenses_amt,
0.0 as profit_amt,
0.0 as profit_per,
null::timestamp as timesheet_sdatetime
FROM project_project PRO
Left JOIN project_sale_line_employee_map pro_emp ON pro_emp.project_id = pro.id
LEFT JOIN account_analytic_account AA ON PRO.analytic_account_id = AA.id
LEFT JOIN account_analytic_line AAL ON AAL.account_id = AA.id and AAL.project_id = PRO.id and AAL.employee_id = pro_emp.employee_id
WHERE PRO.active = 't' and PRO.pricing_type='employee_rate' and PRO.project_type='hours_in_consultant'
UNION
select
pro.id AS project_id,
(select project_id from project_subproject_rel as par where pro.id=par.id limit 1) as parentproject,
pro.partner_id AS partner_id,
pro.project_type AS project_type,
pro.pricing_type as pricing_type,
null::int AS employee_id,
pro.budgeted_revenue AS overall_budgeted_revenue,
0.0 AS budgeted_revenue,
0.0 AS budgeted_hours,
pro.hourly_rate AS overall_hourly_rate,
0.0 as unit_amount,
0.0 as pro_hourly_rate,
0.0 as timesheet_cost,
0.0 AS actual_revenue,
0.0 AS actual_cost,
pro.expenses_amt AS expenses_amt,
0.0 as profit_amt,
0.0 as profit_per,
null::timestamp as timesheet_sdatetime
FROM project_project PRO
WHERE PRO.active = 't' and PRO.pricing_type='employee_rate' and PRO.project_type='hours_in_consultant'
UNION
select
pro.id AS project_id,
(select project_id from project_subproject_rel as par where pro.id=par.id limit 1) as parentproject,
pro.partner_id AS partner_id,
pro.project_type AS project_type,
pro.pricing_type as pricing_type,
AAL.employee_id AS employee_id,
0.0 AS overall_budgeted_revenue,
0.0 AS budgeted_revenue,
0.0 AS budgeted_hours,
0.0 AS overall_hourly_rate,
AAL.unit_amount,
COALESCE(pro_emp.price_unit, 0) as pro_hourly_rate,
case when pro_emp.employee_price is null then ((AAL.amount * -1)/NULLIF(AAL.unit_amount, 0)) else pro_emp.employee_price end as timesheet_cost,
(AAL.unit_amount * COALESCE(pro_emp.price_unit, 0)) AS actual_revenue,
case when pro_emp.employee_price is null then (AAL.amount * -1) else (AAL.unit_amount * pro_emp.employee_price) end as actual_cost,
0.0 AS expenses_amt,
(AAL.unit_amount * COALESCE(pro_emp.price_unit, 0)) - case when pro_emp.employee_price is null then (AAL.amount * -1) else (AAL.unit_amount * pro_emp.employee_price) end
as profit_amt,
(((AAL.unit_amount * COALESCE(pro_emp.price_unit, 0)) - case when pro_emp.employee_price is null then (AAL.amount * -1) else (AAL.unit_amount * pro_emp.employee_price) end)
/ NULLIF((AAL.unit_amount * COALESCE(pro_emp.price_unit, 0)), 0)) * 100 as profit_per,
AAL.start_datetime AS timesheet_sdatetime
FROM project_project PRO
LEFT JOIN account_analytic_account AA ON PRO.analytic_account_id = AA.id
LEFT JOIN account_analytic_line AAL ON AAL.account_id = AA.id and AAL.project_id = PRO.id
Left JOIN project_sale_line_employee_map pro_emp ON pro_emp.project_id = pro.id and AAL.employee_id = pro_emp.employee_id
LEFT JOIN hr_employee EMP ON AAL.employee_id = EMP.id
WHERE PRO.active = 't' and PRO.pricing_type='employee_rate' and PRO.project_type='hours_in_consultant'
) res
)""" % (self._table,))
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
res = super(ProjectRevenueCustomReport2, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
hourly_rate = 0
actual_cost = 0
for line in res:
try:
if 'actual_cost' in line:
actual_cost = line['actual_cost']
"""if 'pro_hourly_rate' in line:
hourly_rate = line['pro_hourly_rate']
if 'unit_amount' in line: # and 'pro_hourly_rate' in line and 'id' in line
if hourly_rate != 0:
line['actual_revenue'] = hourly_rate * line['unit_amount']
if 'unit_amount' in line and 'timesheet_cost' in line:
line['actual_cost'] = line['timesheet_cost'] * line['unit_amount']"""
if 'overall_budgeted_revenue' in line and 'budgeted_hours' in line:
if line['budgeted_hours'] > 0:
line['overall_hourly_rate'] = line['overall_budgeted_revenue'] / line['budgeted_hours']
if 'pro_hourly_rate' in line and 'budgeted_hours' in line:
if line['budgeted_hours'] > 0:
line['pro_hourly_rate'] = line['budgeted_revenue'] / line['budgeted_hours']
if 'actual_revenue' in line and 'actual_cost' in line and 'expenses_amt' in line:
line['profit_amt'] = line['actual_revenue'] - line['actual_cost'] - line['expenses_amt']
if 'profit_amt' in line and 'actual_revenue' in line:
try:
line['profit_per'] = (line['profit_amt'] / line['actual_revenue']) * 100
except ZeroDivisionError:
pass
if 'unit_amount' in line:
try:
line['timesheet_cost'] = actual_cost / line['unit_amount']
except ZeroDivisionError:
pass
except Exception:
pass
return res

View File

@ -58,13 +58,13 @@
</button>
</div>
<xpath expr="//field[@name='allowed_internal_user_ids']" position="after">
<field name="is_sub_project" invisible="0"/>
<field name="is_sub_project" invisible="0" groups="project.group_project_manager"/>
</xpath>
<xpath expr="//field[@name='allowed_internal_user_ids']" position="after">
<field name="parent_project" attrs="{'invisible': [('is_sub_project', '=', False)]}"/>
<field name="parent_project" attrs="{'invisible': [('is_sub_project', '=', False)]}" groups="project.group_project_manager"/>
</xpath>
<xpath expr="//field[@name='allowed_internal_user_ids']" position="after">
<field name="sub_project" widget="many2many_tags"
<field name="sub_project" widget="many2many_tags" groups="project.group_project_manager"
attrs="{'invisible': [('is_sub_project', '=', True)]}"
options="{'no_open': True, 'no_create': True, 'no_create_edit': True}"/>
</xpath>

View File

@ -0,0 +1,2 @@
from . import models
from . import wizard

View File

@ -0,0 +1,20 @@
{
'name': 'Timesheet Block',
'version': '1.0.1',
'summary': 'This module is block to users to fill old timehseet entry',
'description': 'This module is block to users to fill old timehseet entry',
'category': 'Employee',
'author': 'SunArc Technologies',
'website': 'www.sunarctechnologies.com',
'license': 'LGPL-3',
'depends': ['base','hr','hr_timesheet','project'],
'data': [
'security/ir.model.access.csv',
'wizard/date_assign.xml',
'views/timesheet.xml',
'views/res_config_settings.xml',
],
'installable': True,
'auto_install': False,
}

View File

@ -0,0 +1,2 @@
from . import timesheet
from . import res_config_settings

View File

@ -0,0 +1,11 @@
from odoo import api, fields, models
class ResConfigSettingsTimesheet(models.TransientModel):
_inherit = 'res.config.settings'
timesheet_edit_create_limit = fields.Integer('No. of days',default=0, config_parameter='timesheet_block.timesheet_edit_create_limit')
# timesheet_edit_create_months = fields.Integer('No. of months',default=1, config_parameter='timesheet_block.timesheet_edit_create_months')

View File

@ -0,0 +1,120 @@
from odoo import api, fields, models
from odoo.exceptions import UserError, AccessError, ValidationError
from datetime import datetime
from dateutil import relativedelta
class EmployeeInherit(models.Model):
_inherit = 'hr.employee'
allow_to_edit = fields.Selection(selection=[('always', 'Always'), ('specific', 'Specific')],string="Allow to edit")
allow_to_create = fields.Selection(selection=[('always', 'Always'), ('specific', 'Specific')] )
permission_history = fields.One2many(comodel_name="timesheet.block.history", inverse_name="employee_id",
string="History", required=False )
class EmployeePublicInherit(models.Model):
_inherit = 'hr.employee.public'
allow_to_edit = fields.Selection(selection=[('always', 'Always'), ('specific', 'Specific')],string="Allow to edit")
allow_to_create = fields.Selection(selection=[('always', 'Always'), ('specific', 'Specific')] )
permission_history = fields.One2many(comodel_name="timesheet.block.history", inverse_name="employee_id",
string="History", required=False )
class TimesheetBlock(models.Model):
_name = 'timesheet.block.history'
start_date = fields.Date('Start Date')
end_date = fields.Date('End Date')
permission_type = fields.Selection(string="Permission Type", selection=[('edit', 'Edit'), ('create', 'Create')],
required=False)
reason = fields.Text('Reason')
user_id = fields.Many2one('res.users', string='Given By')
employee_id = fields.Many2one(comodel_name="hr.employee", string="Employee", required=False)
class TimehseetBlock(models.Model):
_inherit = 'account.analytic.line'
@api.model
def create(self, values):
current_date = datetime.today().date()
config = self.env['ir.config_parameter'].sudo()
config_days_limit = float(config.get_param('timesheet_block.timesheet_edit_create_limit'))
entry_start_date = datetime.strptime(str(values['start_datetime']),"%Y-%m-%d %H:%M:%S").date()
# entry_end_date = datetime.strptime(values['end_datetime'],"%Y-%m-%d %H:%M:%S").date()
days = (current_date - entry_start_date).days
employee_object = self.env['hr.employee'].search([('user_id', '=', self.env.uid)])
project_manager_group = self.env.ref('hr_timesheet.group_timesheet_manager')
if self.env.uid not in project_manager_group.users.ids:
if days >= config_days_limit:
if employee_object.allow_to_create != 'always' or employee_object.allow_to_create == False:
permission_object = employee_object.permission_history.search([('permission_type', '=', 'create'),('employee_id', '=', employee_object.id)] , order='create_date desc' , limit = 1)
if permission_object:
# if not (permission_object.start_date <= entry_start_date and permission_object.end_date >= entry_start_date) or not (permission_object.start_date <= entry_end_date and permission_object.end_date >= entry_end_date):
if not (permission_object.start_date <= entry_start_date and permission_object.end_date >= entry_start_date):
raise AccessError('You can not create the backdate entry please contact to HR')
else:
raise AccessError('You can not create the backdate entry please contact to HR')
return super(TimehseetBlock, self).create(values)
def write(self, record):
current_date = datetime.today().date()
if 'start_datetime' in record or 'end_datetime' in record or 'project_id' in record or 'sub_project' in record or 'task_id' in record or 'description' in record:
if 'start_datetime' in record:
entry_start_date = datetime.strptime(str(record['start_datetime']),"%Y-%m-%d %H:%M:%S").date()
# entry_end_date = datetime.strptime(record['end_datetime'],"%Y-%m-%d %H:%M:%S").date()
else:
res_start_date = str(self.start_datetime).split('.', 1)[0]
# res_end_date = str(self.end_datetime).split('.', 1)[0]
entry_start_date = datetime.strptime(str(res_start_date), '%Y-%m-%d %H:%M:%S').date()
# entry_end_date = datetime.strptime(str(res_end_date), '%Y-%m-%d %H:%M:%S').date()
if current_date != entry_start_date:
employee_object = self.env['hr.employee'].search([('user_id', '=', self.env.uid)])
if employee_object.allow_to_edit != 'always' or employee_object.allow_to_edit == False:
permission_object = employee_object.permission_history.search([('permission_type', '=', 'edit'),('employee_id', '=', employee_object.id)], order='create_date desc' , limit = 1)
if self.user_id.id == self.env.uid:
if permission_object:
# if not (permission_object.start_date <= entry_start_date and permission_object.end_date >= entry_start_date) or not (permission_object.start_date <= entry_end_date and permission_object.end_date >= entry_end_date):
if not (permission_object.start_date <= entry_start_date and permission_object.end_date >= entry_start_date):
raise AccessError(
'You can not edit your backdate entry please connect to HR/Department Head/Project Manager')
else:
raise AccessError(
'You can not edit your backdate entry please connect to HR/Department Head/Project Manager')
return super(TimehseetBlock, self).write(record)
# @api.onchange('end_datetime' ,'start_datetime')
# def _check_end_datetime(self):
# if self.end_datetime and self.start_datetime:
# current_date = datetime.today().date()
# end_date = datetime.strptime(str(self.end_datetime), "%Y-%m-%d %H:%M:%S").date()
# start_date = datetime.strptime(str(self.start_datetime), "%Y-%m-%d %H:%M:%S").date()
# if end_date > current_date:
# self.end_datetime = False
# self.start_datetime = False
# raise AccessError('End date cannot be a future date!')
# if start_date > end_date:
# self.end_datetime = False
# self.start_datetime = False
# raise AccessError('End date should be greater than start date!')
@api.constrains('unit_amount')
def _check_unit_amount(self):
for rec in self:
if rec.unit_amount < 0.0:
raise AccessError('End date should be greater than start date!')
@api.constrains('end_datetime')
def _check_end_datetime(self):
current_date = datetime.today().date()
for rec in self:
if rec.end_datetime:
end_date = datetime.strptime(str(self.end_datetime), "%Y-%m-%d %H:%M:%S").date()
if end_date > current_date:
raise AccessError('End date cannot be a future date!')

View File

@ -0,0 +1 @@
,sunarcmanish,sunarcmanish,20.07.2020 23:42,file:///home/sunarcmanish/.config/libreoffice/4;

View File

@ -0,0 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_timesheet_permission_history_manager,access.timesheet.block.history.manager,model_timesheet_block_history,hr_timesheet.group_timesheet_manager,1,1,1,1
access_timesheet_permission_history_user,access.timesheet.block.history.user,model_timesheet_block_history,hr_timesheet.group_hr_timesheet_user,1,1,1,0
access_back_date_assign_user,access.back.date.assign.user,model_back_date_assign,hr_timesheet.group_timesheet_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_timesheet_permission_history_manager access.timesheet.block.history.manager model_timesheet_block_history hr_timesheet.group_timesheet_manager 1 1 1 1
3 access_timesheet_permission_history_user access.timesheet.block.history.user model_timesheet_block_history hr_timesheet.group_hr_timesheet_user 1 1 1 0
4 access_back_date_assign_user access.back.date.assign.user model_back_date_assign hr_timesheet.group_timesheet_manager 1 1 1 1

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="res_config_settings_view_form_inherit_timesheet_block" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.timesheet.block</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="hr_timesheet.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='section_leaves']" position="after">
<div name="timesheet_block_limit">
<h2>Timesheet Create/Edit Configuration </h2>
<div class="row mt16 o_settings_container" name="timesheet_control">
<div class="col-12 col-lg-6 o_setting_box" id="timesheet_block_validation_setting">
<div class="o_setting_right_pane">
<label for="timesheet_edit_create_limit"/>
<div >
<field name="timesheet_edit_create_limit"/>
</div>
</div>
<!-- <div class="o_setting_right_pane">-->
<!-- <label for="timesheet_edit_create_months"/>-->
<!-- <div >-->
<!-- <field name="timesheet_edit_create_months"/>-->
<!-- </div>-->
<!-- </div>-->
</div>
</div>
</div>
</xpath>
</field>
</record>
<record id="timesheet_block_settings_action" model="ir.actions.act_window">
<field name="name">Timesheet Block Configuration</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">res.config.settings</field>
<field name="view_id" ref="timesheet_block.res_config_settings_view_form_inherit_timesheet_block"/>
<field name="view_mode">form</field>
<field name="target">inline</field>
<field name="context">{'module' : 'timesheet_block', 'bin_size': False}</field>
</record>
</odoo>

View File

@ -0,0 +1,60 @@
<!-- Inherit Form View to Modify it -->
<odoo>
<record id="view_employee_form_timehseet_block" model="ir.ui.view">
<field name="name">hr.employee.timesheet.block.inherit</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='personal_information']" position="after">
<page name="'timehseet_block" string="Timesheet Block">
<group groups="hr.group_hr_manager">
<group>
<field name="allow_to_edit"/>
</group>
<group>
<field name="allow_to_create"/>
</group>
</group>
<button name="%(timesheet_block.action_back_date_assign)d" string="Assign Date"
type="action" groups="hr.group_hr_manager"
class="oe_highlight"/>
<group/>
<field name="permission_history" readonly="1" groups="hr_timesheet.group_timesheet_manager">
<tree editable="bottom">
<field name="start_date"/>
<field name="end_date"/>
<field name="reason"/>
<field name="permission_type"/>
<field name="user_id"
options="{'no_create': True, 'no_create_edit':True, 'no_open': True}"/>
<field name="employee_id" invisible="1"
options="{'no_create': True, 'no_create_edit':True, 'no_open': True}"/>
</tree>
<form>
<sheet>
<group>
<group>
<field name="start_date"/>
<field name="end_date"/>
<field name="user_id"
options="{'no_create': True, 'no_create_edit':True, 'no_open': True}"/>
</group>
<group>
<field name="permission_type"/>
<field name="employee_id"
options="{'no_create': True, 'no_create_edit':True, 'no_open': True}"/>
</group>
</group>
<group>
<field name="reason"/>
</group>
</sheet>
</form>
</field>
</page>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,4 @@
from . import date_assign

View File

@ -0,0 +1,24 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class ProjectCloseDate(models.TransientModel):
_name = "back.date.assign"
_description = "Timesheet Back Date Assign"
start_date = fields.Date('Start Date')
end_date = fields.Date('End Date')
reason = fields.Text('Reason', required=True)
permission_type = fields.Selection(string="Permission Type", selection=[('edit', 'Edit'), ('create', 'Create')],
required=True)
def action_compute_project_close(self):
employee_obj = self.env['hr.employee'].browse(self._context.get('active_ids', []))
print("employee_obj", employee_obj)
employee_obj.permission_history.create({'employee_id': employee_obj.id,
'start_date': self.start_date,
'end_date': self.end_date,
'permission_type': self.permission_type,
'reason': self.reason,
'user_id': self.env.uid})

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_back_date_assign" model="ir.ui.view">
<field name="name">back.date.assign.from</field>
<field name="model">back.date.assign</field>
<field name="arch" type="xml">
<form string="Project Close">
<group>
<group>
<field name="start_date" required="1"/>
<field name="end_date" required="1"/>
</group>
<group>
<field name="permission_type" required="1"/>
</group>
</group>
<group>
<field name="reason"/>
</group>
<footer>
<button string="OK" type="object" name="action_compute_project_close" class="btn-primary"/>
<button string="Cancel" class="btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_back_date_assign" model="ir.actions.act_window">
<field name="name">Timesheet Date Assign</field>
<!-- <field name="type">ir.actions.act_window</field>-->
<field name="res_model">back.date.assign</field>
<!-- <field name="view_type">form</field>-->
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</openerp>

View File

@ -0,0 +1,2 @@
from . import models
from . import wizard

View File

@ -0,0 +1,18 @@
{
'name': 'Timesheet Entry Block',
'version': '1.0.1',
'summary': 'This module is block to users to fill timehseet entry',
'description': 'This module is block to users to fill timehseet entry',
'category': 'Employee',
'author': 'SunArc Technologies',
'website': 'www.sunarctechnologies.com',
'license': 'LGPL-3',
'depends': ['base','hr','hr_timesheet','project'],
'data': [
'security/ir.model.access.csv',
'wizard/date_assign.xml',
'views/timesheet.xml',
],
'installable': True,
'auto_install': False,
}

View File

@ -0,0 +1 @@
from . import timesheet

View File

@ -0,0 +1,103 @@
from odoo import api, fields, models
from odoo.exceptions import UserError, AccessError, ValidationError
from datetime import datetime
from dateutil import relativedelta
class EmployeeInherit(models.Model):
_inherit = 'hr.employee'
permission_block_history = fields.One2many(comodel_name="timesheet.block.history", inverse_name="employee_id",
string="History", required=False )
class EmployeePublicInherit(models.Model):
_inherit = 'hr.employee.public'
permission_block_history = fields.One2many(comodel_name="timesheet.block.history", inverse_name="employee_id",
string="History", required=False )
class TimesheetBlock(models.Model):
_name = 'timesheet.block.history'
start_date = fields.Date('Start Date')
end_date = fields.Date('End Date')
reason = fields.Text('Reason')
user_id = fields.Many2one('res.users', string='Given By')
employee_id = fields.Many2one(comodel_name="hr.employee", string="Employee", required=False)
class TimehseetBlock(models.Model):
_inherit = 'account.analytic.line'
@api.model
def create(self, values):
entry_start_date = datetime.strptime(str(values['start_datetime']),"%Y-%m-%d %H:%M:%S").date()
entry_end_date = datetime.strptime(values['end_datetime'],"%Y-%m-%d %H:%M:%S").date()
employee_object = self.env['hr.employee'].search([('user_id', '=', self.env.uid)])
# project_manager_group = self.env.ref('hr_timesheet.group_timesheet_manager')
permission_block_object = employee_object.permission_block_history.search(
[('employee_id', '=', employee_object.id)], order='create_date desc', limit=1)
if permission_block_object:
if (permission_block_object.start_date <= entry_start_date and permission_block_object.end_date >= entry_start_date) or (
permission_block_object.start_date <= entry_end_date and permission_block_object.end_date >= entry_end_date) or (
permission_block_object.start_date >= entry_start_date and permission_block_object.end_date <= entry_end_date
) or (
permission_block_object.end_date >= entry_start_date and permission_block_object.end_date <= entry_end_date
):
raise AccessError('You are not allowed to create entry for this date!')
else:
pass
return super(TimehseetBlock, self).create(values)
def write(self, record):
if 'start_datetime' in record or 'end_datetime' in record or 'project_id' in record or 'sub_project' in record or 'task_id' in record or 'description' in record:
if 'start_datetime' in record and 'end_datetime' not in record:
entry_start_date = datetime.strptime(str(record['start_datetime']),"%Y-%m-%d %H:%M:%S").date()
entry_end_date = datetime.strptime(str(self.end_datetime),"%Y-%m-%d %H:%M:%S").date()
elif 'start_datetime' not in record and 'end_datetime' in record:
entry_start_date = datetime.strptime(str(self.start_datetime), "%Y-%m-%d %H:%M:%S").date()
entry_end_date = datetime.strptime(str(record['end_datetime']), "%Y-%m-%d %H:%M:%S").date()
elif 'start_datetime' in record and 'end_datetime' in record:
entry_start_date = datetime.strptime(str(record['start_datetime']),"%Y-%m-%d %H:%M:%S").date()
entry_end_date = datetime.strptime(str(record['end_datetime']), "%Y-%m-%d %H:%M:%S").date()
else:
res_start_date = str(self.start_datetime).split('.', 1)[0]
res_end_date = str(self.end_datetime).split('.', 1)[0]
entry_start_date = datetime.strptime(str(res_start_date), '%Y-%m-%d %H:%M:%S').date()
entry_end_date = datetime.strptime(str(res_end_date), '%Y-%m-%d %H:%M:%S').date()
employee_object = self.env['hr.employee'].search([('user_id', '=', self.env.uid)])
# project_manager_group = self.env.ref('hr_timesheet.group_timesheet_manager')
if self.user_id.id == self.env.uid:
permission_block_object = employee_object.permission_block_history.search(
[('employee_id', '=', employee_object.id)], order='create_date desc', limit=1)
if permission_block_object:
if (
permission_block_object.start_date <= entry_start_date and permission_block_object.end_date >= entry_start_date) or (
permission_block_object.start_date <= entry_end_date and permission_block_object.end_date >= entry_end_date) or (
permission_block_object.start_date >= entry_start_date and permission_block_object.end_date <= entry_end_date
) or (
permission_block_object.end_date >= entry_start_date and permission_block_object.end_date <= entry_end_date
):
raise AccessError('You are not allowed to edit entry for this date!')
else:
pass
return super(TimehseetBlock, self).write(record)
@api.constrains('unit_amount')
def _check_unit_amount(self):
for rec in self:
if rec.unit_amount < 0.0:
raise AccessError('End date should be greater than start date!')
@api.constrains('end_datetime')
def _check_end_datetime(self):
current_date = datetime.today().date()
for rec in self:
if rec.end_datetime:
end_date = datetime.strptime(str(self.end_datetime), "%Y-%m-%d %H:%M:%S").date()
if end_date > current_date:
raise AccessError('End date cannot be a future date!')

View File

@ -0,0 +1 @@
,sunarcmanish,sunarcmanish,20.07.2020 23:42,file:///home/sunarcmanish/.config/libreoffice/4;

View File

@ -0,0 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_timesheet_permission_history_manager,access.timesheet.block.history.manager,model_timesheet_block_history,hr_timesheet.group_timesheet_manager,1,1,1,1
access_timesheet_permission_history_user,access.timesheet.block.history.user,model_timesheet_block_history,hr_timesheet.group_hr_timesheet_user,1,1,1,0
access_back_date_assign_user,access.back.date.assign.user,model_back_date_assign,hr_timesheet.group_timesheet_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_timesheet_permission_history_manager access.timesheet.block.history.manager model_timesheet_block_history hr_timesheet.group_timesheet_manager 1 1 1 1
3 access_timesheet_permission_history_user access.timesheet.block.history.user model_timesheet_block_history hr_timesheet.group_hr_timesheet_user 1 1 1 0
4 access_back_date_assign_user access.back.date.assign.user model_back_date_assign hr_timesheet.group_timesheet_manager 1 1 1 1

View File

@ -0,0 +1,50 @@
<!-- Inherit Form View to Modify it -->
<odoo>
<record id="view_employee_form_timehseet_block" model="ir.ui.view">
<field name="name">hr.employee.timesheet.block.inherit</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='personal_information']" position="after">
<page name="'timehseet_block" string="Timesheet Block">
<button name="%(timesheet_entry_block.action_back_date_assign)d" string="Assign Date"
type="action" groups="hr.group_hr_manager"
class="oe_highlight"/>
<group/>
<field name="permission_block_history" readonly="0" groups="hr_timesheet.group_timesheet_manager">
<tree editable="bottom" create="false">
<field name="start_date" required="1"/>
<field name="end_date" required="1"/>
<field name="reason"/>
<field name="user_id" readonly="1"
options="{'no_create': True, 'no_create_edit':True, 'no_open': True}"/>
<field name="employee_id" invisible="1"
options="{'no_create': True, 'no_create_edit':True, 'no_open': True}"/>
</tree>
<form>
<sheet>
<group>
<group>
<field name="start_date"/>
<field name="end_date"/>
<field name="user_id"
options="{'no_create': True, 'no_create_edit':True, 'no_open': True}"/>
</group>
<group>
<field name="employee_id"
options="{'no_create': True, 'no_create_edit':True, 'no_open': True}"/>
</group>
</group>
<group>
<field name="reason"/>
</group>
</sheet>
</form>
</field>
</page>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,4 @@
from . import date_assign

View File

@ -0,0 +1,22 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class ProjectCloseDate(models.TransientModel):
_name = "back.date.assign"
_description = "Timesheet Back Date Assign"
start_date = fields.Date('Start Date')
end_date = fields.Date('End Date')
reason = fields.Text('Reason', required=True)
def action_compute_project_close(self):
employee_obj = self.env['hr.employee'].browse(self._context.get('active_ids', []))
print("employee_obj", employee_obj)
employee_obj.permission_block_history.create({'employee_id': employee_obj.id,
'start_date': self.start_date,
'end_date': self.end_date,
'reason': self.reason,
'user_id': self.env.uid})

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_back_date_assign" model="ir.ui.view">
<field name="name">back.date.assign.from</field>
<field name="model">back.date.assign</field>
<field name="arch" type="xml">
<form string="Project Close">
<group>
<group>
<field name="start_date" required="1"/>
<field name="end_date" required="1"/>
</group>
</group>
<group>
<field name="reason"/>
</group>
<footer>
<button string="OK" type="object" name="action_compute_project_close" class="btn-primary"/>
<button string="Cancel" class="btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="action_back_date_assign" model="ir.actions.act_window">
<field name="name">Timesheet Date Assign</field>
<!-- <field name="type">ir.actions.act_window</field>-->
<field name="res_model">back.date.assign</field>
<!-- <field name="view_type">form</field>-->
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</openerp>