Merge branch 'development' into 'master'
Development See merge request prakash.jain/cor-odoo!47
|
@ -0,0 +1,3 @@
|
|||
# compiled python files
|
||||
*.py[co]
|
||||
__pycache__/
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
|
@ -0,0 +1,32 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "Dashboard per user",
|
||||
|
||||
'summary': """
|
||||
This module helps you to see other's user dashboard""",
|
||||
|
||||
'description': """
|
||||
Long description of module's purpose
|
||||
""",
|
||||
|
||||
'license': 'AGPL-3',
|
||||
|
||||
'author': "Apanda",
|
||||
'support': "ryantsoa@gmail.com",
|
||||
|
||||
'category': 'Uncategorized',
|
||||
'version': '1.0',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['base', 'board'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
'security/board_users.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/board_users_view.xml',
|
||||
'views/assets_backend.xml',
|
||||
],
|
||||
'qweb': ['static/src/xml/board.xml'],
|
||||
'images': ['static/description/cover.gif'],
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import board_users
|
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import tools
|
||||
from odoo import models, fields, api
|
||||
|
||||
class board_user(models.Model):
|
||||
_name = 'board.users'
|
||||
_auto = False
|
||||
|
||||
user_id = fields.Many2one('res.users', string='User', readonly=True)
|
||||
|
||||
def init(self):
|
||||
tools.drop_view_if_exists(self.env.cr, self._table)
|
||||
self.env.cr.execute("""
|
||||
create or replace view board_users as (
|
||||
select
|
||||
min(i.id) as id,
|
||||
i.user_id as user_id
|
||||
from
|
||||
ir_ui_view_custom as i
|
||||
group by
|
||||
i.user_id
|
||||
)
|
||||
""")
|
||||
|
||||
class Board(models.AbstractModel):
|
||||
_inherit = 'board.board'
|
||||
|
||||
@api.model
|
||||
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
|
||||
user_dashboard = self.env.context.get('user_dashboard')
|
||||
if user_dashboard:
|
||||
self = self.with_env(self.env(user=user_dashboard))
|
||||
res = super(Board, self).fields_view_get(view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu)
|
||||
return res
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="all_dashboards" model="res.groups">
|
||||
<field name="name">All dashboards </field>
|
||||
<field name="comment">the user will have an access to the the menu all dashboards</field>
|
||||
<field name="category_id" ref="base.module_category_usability"/>
|
||||
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
|
||||
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
|
||||
</record>
|
||||
</odoo>
|
|
@ -0,0 +1,2 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_board_user_board_user,board_user.board_user,model_board_users,board_user.all_dashboards,1,0,0,0
|
|
After Width: | Height: | Size: 189 KiB |
After Width: | Height: | Size: 784 KiB |
After Width: | Height: | Size: 4.3 KiB |
|
@ -0,0 +1,25 @@
|
|||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">See all dashboards of each user</h2>
|
||||
<h3 class="oe_slogan">Get the list of users who sets up a dashboard.</h3>
|
||||
<div class="oe_demo oe_picture oe_screenshot">
|
||||
<img src="all_dashboards.gif">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan" style="color:#875A7B;">Settings</h2>
|
||||
<div class="oe_span6 text-justify oe_mt32">
|
||||
<span class="fa fa-cog fa-2x pull-left"/>
|
||||
<p class="oe_mb32" style="margin-left:48px;">
|
||||
Set which users can see others dashboards.
|
||||
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
<img class="oe_picture oe_screenshot" src="settings.gif">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
After Width: | Height: | Size: 85 KiB |
|
@ -0,0 +1,46 @@
|
|||
odoo.define('web.AbstractControllerBoardUser', function (require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractController = require('web.AbstractController');
|
||||
|
||||
AbstractController.include({
|
||||
|
||||
_onOpenRecord: function (event) {
|
||||
event.stopPropagation();
|
||||
var record = this.model.get(event.data.id, {raw: true});
|
||||
var self = this;
|
||||
var res_id = record.res_id;
|
||||
var model = this.modelName;
|
||||
if (model == "board.users"){
|
||||
self._rpc({
|
||||
model: 'ir.model.data',
|
||||
method: 'xmlid_to_res_model_res_id',
|
||||
args:["board.board_my_dash_view"],
|
||||
})
|
||||
.then(function (data) {
|
||||
self.do_action({
|
||||
name: "Dashboard",
|
||||
type: "ir.actions.act_window",
|
||||
res_model: "board.board",
|
||||
views: [[data[1], 'form']],
|
||||
usage: 'menu',
|
||||
context: {'user_dashboard': record.data.user_id},
|
||||
view_type: 'form',
|
||||
view_mode: 'form',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
else{
|
||||
self.trigger_up('switch_view', {
|
||||
view_type: 'form',
|
||||
res_id: res_id,
|
||||
mode: event.data.mode || 'readonly',
|
||||
model: model
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,78 @@
|
|||
odoo.define('web.BoardBoardUser', function (require) {
|
||||
"use strict";
|
||||
|
||||
var BoardView = require('board.BoardView');
|
||||
var core = require('web.core');
|
||||
var QWeb = core.qweb;
|
||||
var Domain = require('web.Domain');
|
||||
BoardView.prototype.config.Renderer.include({
|
||||
_renderTagBoard: function (node) {
|
||||
var self = this;
|
||||
// we add the o_dashboard class to the renderer's $el. This means that
|
||||
// this function has a side effect. This is ok because we assume that
|
||||
// once we have a '<board>' tag, we are in a special dashboard mode.
|
||||
this.$el.addClass('o_dashboard');
|
||||
this.trigger_up('enable_dashboard');
|
||||
|
||||
var hasAction = _.detect(node.children, function (column) {
|
||||
return _.detect(column.children,function (element){
|
||||
return element.tag === "action"? element: false;
|
||||
});
|
||||
});
|
||||
if (!hasAction) {
|
||||
return $(QWeb.render('DashBoard.NoContent'));
|
||||
}
|
||||
|
||||
// We should start with three columns available
|
||||
node = $.extend(true, {}, node);
|
||||
|
||||
// no idea why master works without this, but whatever
|
||||
if (!('layout' in node.attrs)) {
|
||||
node.attrs.layout = node.attrs.style;
|
||||
}
|
||||
for (var i = node.children.length; i < 3; i++) {
|
||||
node.children.push({
|
||||
tag: 'column',
|
||||
attrs: {},
|
||||
children: []
|
||||
});
|
||||
}
|
||||
|
||||
// register actions, alongside a generated unique ID
|
||||
_.each(node.children, function (column, column_index) {
|
||||
_.each(column.children, function (action, action_index) {
|
||||
action.attrs.id = 'action_' + column_index + '_' + action_index;
|
||||
self.actionsDescr[action.attrs.id] = action.attrs;
|
||||
});
|
||||
});
|
||||
var state_context = self.state.context;
|
||||
if (state_context.hasOwnProperty('user_dashboard')){
|
||||
node.perm_close = self.state.context.uid === self.state.context.user_dashboard;
|
||||
}
|
||||
else{
|
||||
node.perm_close = true;
|
||||
}
|
||||
var $html = $('<div>').append($(QWeb.render('DashBoard', {node: node})));
|
||||
|
||||
// render each view
|
||||
_.each(this.actionsDescr, function (action) {
|
||||
self.defs.push(self._createController({
|
||||
$node: $html.find('.oe_action[data-id=' + action.id + '] .oe_content'),
|
||||
actionID: _.str.toNumber(action.name),
|
||||
context: action.context,
|
||||
domain: Domain.prototype.stringToArray(action.domain, {}),
|
||||
viewType: action.view_mode,
|
||||
}));
|
||||
});
|
||||
$html.find('.oe_dashboard_column').sortable({
|
||||
connectWith: '.oe_dashboard_column',
|
||||
handle: '.oe_header',
|
||||
scroll: false
|
||||
}).bind('sortstop', function () {
|
||||
self.trigger_up('save_dashboard');
|
||||
});
|
||||
return $html;
|
||||
},
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<template>
|
||||
<t t-extend="DashBoard">
|
||||
<t t-jquery=".oe_dashboard_links" t-operation="attributes">
|
||||
<attribute name="t-if">node.perm_close</attribute>
|
||||
</t>
|
||||
<t t-jquery="t[t-call='DashBoard.action']" t-operation="replace">
|
||||
<t t-if="node.perm_close">
|
||||
<t t-foreach="column.children" t-as="action" t-if="action.tag == 'action'" t-call="DashBoard.action"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-foreach="column.children" t-as="action" t-if="action.tag == 'action'" t-call="DashBoard.action.no_close"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
<t t-name="DashBoard.action.no_close">
|
||||
<div t-att-data-id="action.attrs.id" class="oe_action">
|
||||
<h2 t-attf-class="oe_header #{action.attrs.string ? '' : 'oe_header_empty'}">
|
||||
<span class="oe_header_txt"> <t t-esc="action.attrs.string"/> </span>
|
||||
<input class = "oe_header_text" type="text"/>
|
||||
<t t-if="!action.attrs.string">&nbsp;</t>
|
||||
</h2>
|
||||
<div class="oe_content"/>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="assets_backend" name="board_users_backend" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/board_user/static/src/js/abstract_controller_inherit.js"></script>
|
||||
<script type="text/javascript" src="/board_user/static/src/js/board_view_inherit.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
|
@ -0,0 +1,28 @@
|
|||
<odoo>
|
||||
<data>
|
||||
<!-- explicit list view definition -->
|
||||
|
||||
<record model="ir.ui.view" id="board_user_list">
|
||||
<field name="name">board_user list</field>
|
||||
<field name="model">board.users</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree edit="false">
|
||||
<field name="user_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- actions opening views on models -->
|
||||
|
||||
<record model="ir.actions.act_window" id="board_user_action">
|
||||
<field name="name">All dashboards per user</field>
|
||||
<field name="res_model">board.users</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="All dashboards" id="board_user_menu" parent="base.menu_board_root"
|
||||
action="board_user_action"/>
|
||||
|
||||
</data>
|
||||
</odoo>
|
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
from . import wizard
|
||||
#from . import report
|
|
@ -0,0 +1,45 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "cor_custom",
|
||||
|
||||
'summary': """
|
||||
Short (1 phrase/line) summary of the module's purpose, used as
|
||||
subtitle on modules listing or apps.openerp.com""",
|
||||
|
||||
'description': """
|
||||
Long description of module's purpose
|
||||
""",
|
||||
|
||||
'author': "My Company",
|
||||
'website': "http://www.yourcompany.com",
|
||||
|
||||
# Categories can be used to filter modules in modules listing
|
||||
# Check https://github.com/odoo/odoo/blob/14.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||
# for the full list
|
||||
'category': 'Uncategorized',
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['base', 'crm', 'sale_timesheet', 'analytic', 'hr'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
#'security/ir.model.access.csv',
|
||||
#'security/cor_custom_security.xml',
|
||||
'views/crm_view.xml',
|
||||
'views/sale_views.xml',
|
||||
'views/project_view.xml',
|
||||
'views/hr_employee_views.xml',
|
||||
'views/hr_timesheet_templates.xml',
|
||||
'views/analytic_view.xml',
|
||||
'report/project_profitability_report_analysis_views.xml',
|
||||
'views/views.xml',
|
||||
'views/templates.xml',
|
||||
#'views/menu_show_view.xml',
|
||||
'wizard/project_create_sale_order_views.xml',
|
||||
],
|
||||
# only loaded in demonstration mode
|
||||
'demo': [
|
||||
'demo/demo.xml',
|
||||
],
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import controllers
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# from odoo import http
|
||||
|
||||
|
||||
# class CorCustom(http.Controller):
|
||||
# @http.route('/cor_custom/cor_custom/', auth='public')
|
||||
# def index(self, **kw):
|
||||
# return "Hello, world"
|
||||
|
||||
# @http.route('/cor_custom/cor_custom/objects/', auth='public')
|
||||
# def list(self, **kw):
|
||||
# return http.request.render('cor_custom.listing', {
|
||||
# 'root': '/cor_custom/cor_custom',
|
||||
# 'objects': http.request.env['cor_custom.cor_custom'].search([]),
|
||||
# })
|
||||
|
||||
# @http.route('/cor_custom/cor_custom/objects/<model("cor_custom.cor_custom"):obj>/', auth='public')
|
||||
# def object(self, obj, **kw):
|
||||
# return http.request.render('cor_custom.object', {
|
||||
# 'object': obj
|
||||
# })
|
|
@ -0,0 +1,30 @@
|
|||
<odoo>
|
||||
<data>
|
||||
<!--
|
||||
<record id="object0" model="cor_custom.cor_custom">
|
||||
<field name="name">Object 0</field>
|
||||
<field name="value">0</field>
|
||||
</record>
|
||||
|
||||
<record id="object1" model="cor_custom.cor_custom">
|
||||
<field name="name">Object 1</field>
|
||||
<field name="value">10</field>
|
||||
</record>
|
||||
|
||||
<record id="object2" model="cor_custom.cor_custom">
|
||||
<field name="name">Object 2</field>
|
||||
<field name="value">20</field>
|
||||
</record>
|
||||
|
||||
<record id="object3" model="cor_custom.cor_custom">
|
||||
<field name="name">Object 3</field>
|
||||
<field name="value">30</field>
|
||||
</record>
|
||||
|
||||
<record id="object4" model="cor_custom.cor_custom">
|
||||
<field name="name">Object 4</field>
|
||||
<field name="value">40</field>
|
||||
</record>
|
||||
-->
|
||||
</data>
|
||||
</odoo>
|
|
@ -0,0 +1,510 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * cor_custom
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 14.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-12-28 06:13+0000\n"
|
||||
"PO-Revision-Date: 2020-12-28 06:13+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: cor_custom
|
||||
#: model_terms:ir.ui.view,arch_db:cor_custom.inherit_timesheet_plan
|
||||
msgid "<b>Total</b>"
|
||||
msgstr "סך הכל"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead__act_project_manager_id
|
||||
msgid "Actual Project Manager"
|
||||
msgstr "מנהל פרויקטים בפועל"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead__admin_user
|
||||
msgid "Admin User"
|
||||
msgstr "משתמש מנהל"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields.selection,name:cor_custom.selection__project_project__privacy_visibility__employees
|
||||
msgid "All internal users"
|
||||
msgstr "כל המשתמשים הפנימיים"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model,name:cor_custom.model_account_analytic_line
|
||||
msgid "Analytic Line"
|
||||
msgstr "שורה אנליטית"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_quotation_partner__lead_id
|
||||
msgid "Associated Lead"
|
||||
msgstr "ליד משויך"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_sale_line_employee_map__budgeted_hour_week
|
||||
msgid "Budgeted Hours per week"
|
||||
msgstr "שעות מתוקצבות לשבוע"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_create_sale_order_line__budgeted_qty
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_sale_line_employee_map__budgeted_qty
|
||||
msgid "Budgeted Qty"
|
||||
msgstr "כמות מתוקצבת"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_create_sale_order_line__budgeted_uom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_sale_line_employee_map__budgeted_uom
|
||||
msgid "Budgeted UOM"
|
||||
msgstr "UOM מתוקצב"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead__partner_id
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_quotation_partner__partner_id
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_create_sale_order__partner_id
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_sale_order__partner_id
|
||||
#: model_terms:ir.ui.view,arch_db:cor_custom.crm_lead_form_cor_custom
|
||||
#: model_terms:ir.ui.view,arch_db:cor_custom.inherit_view_task_form2
|
||||
#: model_terms:ir.ui.view,arch_db:cor_custom.project_project_analytic_view_form
|
||||
msgid "Client"
|
||||
msgstr "לָקוּחַ"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead__client_folder
|
||||
msgid "Client Folder"
|
||||
msgstr "תיקיית לקוח"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_product_product__taxes_id
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_product_template__taxes_id
|
||||
msgid "Client Taxes"
|
||||
msgstr "מיסי לקוח"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_project__bill_type
|
||||
msgid "Client Type"
|
||||
msgstr "סוג לקוח"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,help:cor_custom.field_project_create_sale_order__partner_id
|
||||
msgid "Client of the sales order"
|
||||
msgstr "לקוח של הזמנת המכירה"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead__close_date
|
||||
msgid "Close Date"
|
||||
msgstr "תאריך קרוב"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_create_sale_order_line__employee_id
|
||||
#: model_terms:ir.ui.view,arch_db:cor_custom.inherit_project_project_view_form
|
||||
msgid "Consultant"
|
||||
msgstr "יוֹעֵץ"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_create_sale_order_line__employee_price
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_sale_line_employee_map__employee_price
|
||||
msgid "Consultant Price"
|
||||
msgstr "מחיר יועץ"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields.selection,name:cor_custom.selection__project_project__pricing_type__employee_rate
|
||||
msgid "Consultant rate"
|
||||
msgstr "תעריף יועץ"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,help:cor_custom.field_project_create_sale_order_line__employee_id
|
||||
msgid "Consultant that has timesheets on the project."
|
||||
msgstr "יועץ שיש לו לוחות זמנים על הפרויקט"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead__contract
|
||||
msgid "Contract"
|
||||
msgstr "חוֹזֶה"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model_terms:ir.ui.view,arch_db:cor_custom.project_project_analytic_view_form
|
||||
msgid "Cost/Revenue"
|
||||
msgstr "עלות / הכנסה"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model,name:cor_custom.model_project_create_sale_order_line
|
||||
msgid "Create SO Line from project"
|
||||
msgstr "צור שורת הזמנת לקוח מפרויקט"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model,name:cor_custom.model_project_create_sale_order
|
||||
msgid "Create SO from project"
|
||||
msgstr "צור הזמנת לקוח מפרויקט"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields.selection,name:cor_custom.selection__crm_quotation_partner__action__create
|
||||
msgid "Create a new client"
|
||||
msgstr "צור לקוח חדש"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model,name:cor_custom.model_crm_quotation_partner
|
||||
msgid "Create new or use existing Customer on new Quotation"
|
||||
msgstr "צור לקוח חדש או השתמש בקיים בהצעת מחיר חדשה"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model_terms:ir.ui.view,arch_db:cor_custom.inherit_project_project_view_form
|
||||
msgid "Default Sales Order Item"
|
||||
msgstr "פריט הזמנת מכר המוגדר כברירת מחדל"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model_terms:ir.ui.view,arch_db:cor_custom.inherit_project_project_view_form
|
||||
msgid "Default Service"
|
||||
msgstr "שירות ברירת מחדל"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,help:cor_custom.field_product_product__taxes_id
|
||||
#: model:ir.model.fields,help:cor_custom.field_product_template__taxes_id
|
||||
msgid "Default taxes used when selling the product."
|
||||
msgstr "מיסים ברירת מחדל המשמשים בעת מכירת המוצר."
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,help:cor_custom.field_project_create_sale_order_line__budgeted_uom
|
||||
msgid "Default unit of measure used for all stock operations."
|
||||
msgstr "חידת מידה המוגדרת כברירת מחדל המשמשת לכל פעולות המניות"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,help:cor_custom.field_project_project__privacy_visibility
|
||||
msgid ""
|
||||
"Defines the visibility of the tasks of the project:\n"
|
||||
"- Invited internal users: employees may only see the followed project and tasks.\n"
|
||||
"- All internal users: employees may see all project and tasks.\n"
|
||||
"- Invited portal and all internal users: employees may see everything. Portal users may see project and tasks followed by\n"
|
||||
" them or by someone of their company."
|
||||
msgstr ""
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_account_analytic_line__display_name
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead__display_name
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_quotation_partner__display_name
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_hr_employee__display_name
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_hr_employee_public__display_name
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_product_product__display_name
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_product_template__display_name
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_create_sale_order__display_name
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_create_sale_order_line__display_name
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_project__display_name
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_sale_line_employee_map__display_name
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_sale_order__display_name
|
||||
msgid "Display Name"
|
||||
msgstr "הצג שם"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields.selection,name:cor_custom.selection__crm_quotation_partner__action__nothing
|
||||
msgid "Do not link to a client"
|
||||
msgstr "אל תקשר לקוח"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.constraint,message:cor_custom.constraint_crm_lead_email_from_uniq
|
||||
msgid "Email ID already exists !"
|
||||
msgstr "Email ID already exists !"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model,name:cor_custom.model_hr_employee
|
||||
msgid "Employee"
|
||||
msgstr "עובד"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_account_analytic_line__end_time
|
||||
msgid "End Time"
|
||||
msgstr "זמן סיום"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.constraint,message:cor_custom.constraint_account_analytic_line_check_end_time_positive
|
||||
msgid "End hour must be a positive number"
|
||||
msgstr "שעת סיום חייבת להיות מספר חיובי"
|
||||
|
||||
#. module: cor_custom
|
||||
#: code:addons/cor_custom/models/analytic.py:0
|
||||
#: code:addons/cor_custom/models/analytic.py:0
|
||||
#, python-format
|
||||
msgid "End time cannot be earlier than Start time"
|
||||
msgstr "זמן הסיום לא יכול להיות מוקדם יותר משעת ההתחלה"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields.selection,name:cor_custom.selection__project_project__pricing_type__fixed_rate
|
||||
msgid "Fixed rate"
|
||||
msgstr "שער קבוע"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model_terms:ir.ui.view,arch_db:cor_custom.inherit_timesheet_plan
|
||||
msgid "Hourly Rate"
|
||||
msgstr "תעריף לשעה"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields.selection,name:cor_custom.selection__project_project__project_type__hours_in_consultant
|
||||
msgid "Hours are budgeted according to a consultant"
|
||||
msgstr "השעות מתוקצבות על פי יועץ"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_account_analytic_line__id
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead__id
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_quotation_partner__id
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_hr_employee__id
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_hr_employee_public__id
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_product_product__id
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_product_template__id
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_create_sale_order__id
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_create_sale_order_line__id
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_project__id
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_sale_line_employee_map__id
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_sale_order__id
|
||||
msgid "ID"
|
||||
msgstr "תעודה מזהה"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields.selection,name:cor_custom.selection__project_project__privacy_visibility__followers
|
||||
msgid "Invited internal users"
|
||||
msgstr "משתמשים פנימיים מוזמנים"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields.selection,name:cor_custom.selection__project_project__privacy_visibility__portal
|
||||
msgid "Invited portal users and all internal users"
|
||||
msgstr "משתמשי פורטל מוזמנים וכל המשתמשים הפנימיים"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields.selection,name:cor_custom.selection__project_project__bill_type__customer_project
|
||||
msgid "Invoice all tasks to a single client"
|
||||
msgstr "חשב את כל המשימות ללקוח יחיד"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields.selection,name:cor_custom.selection__project_project__bill_type__customer_task
|
||||
msgid "Invoice tasks separately to different clients"
|
||||
msgstr "משימות חשבונית בנפרד ללקוחות שונים"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model_terms:ir.ui.view,arch_db:cor_custom.inherit_project_project_view_form
|
||||
msgid "Invoicing"
|
||||
msgstr "חשבונית"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_account_analytic_line____last_update
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead____last_update
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_quotation_partner____last_update
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_hr_employee____last_update
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_hr_employee_public____last_update
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_product_product____last_update
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_product_template____last_update
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_create_sale_order____last_update
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_create_sale_order_line____last_update
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_project____last_update
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_sale_line_employee_map____last_update
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_sale_order____last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr "שינוי אחרון ב"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead__lead_no
|
||||
msgid "Lead ID"
|
||||
msgstr "תעודת זהות"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model,name:cor_custom.model_crm_lead
|
||||
msgid "Lead/Opportunity"
|
||||
msgstr "ליד/הזדמנות"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields.selection,name:cor_custom.selection__crm_quotation_partner__action__exist
|
||||
msgid "Link to an existing client"
|
||||
msgstr "קישור ללקוח קיים"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,help:cor_custom.field_crm_lead__partner_id
|
||||
msgid ""
|
||||
"Linked partner (optional). Usually created when converting the lead. You can"
|
||||
" find a partner by its Name, TIN, Email or Internal Reference."
|
||||
msgstr ""
|
||||
"לקוח/ספק מקושר (אופציונלי). נוצר בדרך כלל בעת המרת הליד. אתה יכול למצוא לקוח"
|
||||
" לפי שם, דוא\"ל או מזהה פנימי."
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields.selection,name:cor_custom.selection__crm_lead__contract__no
|
||||
msgid "No"
|
||||
msgstr "לא"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.constraint,message:cor_custom.constraint_crm_lead_phone_uniq
|
||||
msgid "Phone No already exists !"
|
||||
msgstr "טלפון לא קיים כבר!"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_project__pricing_type
|
||||
msgid "Pricing"
|
||||
msgstr "תמחור"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model,name:cor_custom.model_product_product
|
||||
msgid "Product"
|
||||
msgstr "מוצר"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model,name:cor_custom.model_product_template
|
||||
msgid "Product Template"
|
||||
msgstr "תבנית מוצר "
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead__professional_support
|
||||
msgid "Professional Support"
|
||||
msgstr "תמיכה מקצועית"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead__project_manager_id
|
||||
#: model_terms:ir.ui.view,arch_db:cor_custom.project_project_analytic_view_form
|
||||
msgid "Project Manager"
|
||||
msgstr "מנהל פרוייקט"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead__project_name
|
||||
msgid "Project Name"
|
||||
msgstr "שם הפרוייקט"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model,name:cor_custom.model_project_sale_line_employee_map
|
||||
msgid "Project Sales line, employee mapping"
|
||||
msgstr "קו מכירות פרויקטים, מיפוי עובדים"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_project__project_type
|
||||
msgid "Project Type"
|
||||
msgstr "סוג הפרויקט"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields.selection,name:cor_custom.selection__project_project__project_type__no_limit
|
||||
msgid "Projects that have no time limit"
|
||||
msgstr "פרויקטים שאין להם מגבלת זמן"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model,name:cor_custom.model_hr_employee_public
|
||||
msgid "Public Employee"
|
||||
msgstr "עובד ציבור"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model_terms:ir.ui.view,arch_db:cor_custom.project_create_sale_order_inherit1_view_form
|
||||
msgid "Qty"
|
||||
msgstr "כמות"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_quotation_partner__action
|
||||
msgid "Quotation Client"
|
||||
msgstr "לקוח הצעת מחיר"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead__ref_summary_status
|
||||
msgid "Referral Summary status"
|
||||
msgstr "מצב סיכום הפניות"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model,name:cor_custom.model_sale_order
|
||||
msgid "Sales Order"
|
||||
msgstr "הזמנת לקוח"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead__scope
|
||||
msgid "Scope (NIS)"
|
||||
msgstr "Scope (NIS)"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model_terms:ir.ui.view,arch_db:cor_custom.inherit_project_project_view_form
|
||||
msgid "Service"
|
||||
msgstr "שֵׁרוּת"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead__start_date
|
||||
msgid "Start Date"
|
||||
msgstr "תאריך התחלה"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_account_analytic_line__start_time
|
||||
msgid "Start Time"
|
||||
msgstr "שעת התחלה"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.constraint,message:cor_custom.constraint_account_analytic_line_check_start_time_positive
|
||||
msgid "Start hour must be a positive number"
|
||||
msgstr "שעת ההתחלה חייבת להיות מספר חיובי"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model,name:cor_custom.model_project_project
|
||||
msgid "Sub Project"
|
||||
msgstr "פרויקט משנה"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,help:cor_custom.field_project_project__pricing_type
|
||||
msgid ""
|
||||
"The fixed rate is perfect if you bill a service at a fixed rate per hour or "
|
||||
"day worked regardless of the consultant who performed it. The consultant "
|
||||
"rate is preferable if your employees deliver the same service at a different"
|
||||
" rate. For instance, junior and senior consultants would deliver the same "
|
||||
"service (= consultancy), but at a different rate because of their level of "
|
||||
"seniority."
|
||||
msgstr ""
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_crm_lead__project_scope
|
||||
msgid "The scope of the project"
|
||||
msgstr "היקף הפרויקט"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_sale_line_employee_map__timesheet_hour
|
||||
msgid "Timesheet Hour"
|
||||
msgstr "שעת לוח הזמנים"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields.selection,name:cor_custom.selection__project_project__project_type__hours_no_limit
|
||||
msgid "Total hours are budgeted without division to consultant"
|
||||
msgstr "Total hours are budgeted without division to consultant"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_project_project__privacy_visibility
|
||||
msgid "Visibility"
|
||||
msgstr "יוצג ל:"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,help:cor_custom.field_project_project__bill_type
|
||||
msgid ""
|
||||
"When billing tasks individually, a Sales Order will be created from each task. It is perfect if you would like to bill different services to different clients at different rates. \n"
|
||||
" When billing the whole project, a Sales Order will be created from the project instead. This option is better if you would like to bill all the tasks of a given project to a specific client either at a fixed rate, or at an employee rate."
|
||||
msgstr ""
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_hr_employee__budgeted_hour_week
|
||||
#: model:ir.model.fields,field_description:cor_custom.field_hr_employee_public__budgeted_hour_week
|
||||
msgid "Working days per week"
|
||||
msgstr "ימי עבודה בשבוע"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.fields.selection,name:cor_custom.selection__crm_lead__contract__yes
|
||||
msgid "Yes"
|
||||
msgstr "כן"
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.constraint,message:cor_custom.constraint_account_analytic_line_check_end_time_lower_than_24
|
||||
msgid "You cannot have a end hour greater than 24"
|
||||
msgstr "24 אינך יכול לקבל שעת סיום גדולה מ- "
|
||||
|
||||
#. module: cor_custom
|
||||
#: model:ir.model.constraint,message:cor_custom.constraint_account_analytic_line_check_start_time_lower_than_24
|
||||
msgid "You cannot have a start hour greater than 24"
|
||||
msgstr "24 אינך יכול לקבל שעת התחלה גדולה מ- "
|
||||
|
||||
#. module: cor_custom
|
||||
#: code:addons/cor_custom/models/analytic.py:0
|
||||
#: code:addons/cor_custom/models/analytic.py:0
|
||||
#, python-format
|
||||
msgid "Your can not fill 0.0 hour entry"
|
||||
msgstr "לא ניתן למלא כניסה של 0.0 שעות"
|
||||
|
||||
#. module: cor_custom
|
||||
#: code:addons/cor_custom/models/analytic.py:0
|
||||
#, python-format
|
||||
msgid "Your can not fill entry more than Budgeted hours"
|
||||
msgstr "אינך יכול למלא ערך יותר משעות שתוקצבו"
|
|
@ -0,0 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import crm_lead
|
||||
from . import models
|
||||
from . import project
|
||||
from . import project_overview
|
||||
from . import analytic
|
||||
from . import product
|
||||
from . import hr_employee
|
||||
from . import sale
|
|
@ -0,0 +1,58 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details
|
||||
|
||||
from odoo import api, exceptions, fields, models, _
|
||||
from odoo.exceptions import UserError, AccessError, ValidationError
|
||||
from odoo.osv import expression
|
||||
|
||||
class AccountAnalyticLine(models.Model):
|
||||
_inherit = 'account.analytic.line'
|
||||
|
||||
start_time = fields.Float(string='Start Time', digits=(16, 2))
|
||||
end_time = fields.Float(string='End Time', digits=(16, 2))
|
||||
|
||||
|
||||
_sql_constraints = [
|
||||
('check_start_time_lower_than_24', 'CHECK(start_time <= 24)', 'You cannot have a start hour greater than 24'),
|
||||
('check_start_time_positive', 'CHECK(start_time >= 0)', 'Start hour must be a positive number'),
|
||||
('check_end_time_lower_than_24', 'CHECK(end_time <= 24)', 'You cannot have a end hour greater than 24'),
|
||||
('check_end_time_positive', 'CHECK(end_time >= 0)', 'End hour must be a positive number'),
|
||||
]
|
||||
|
||||
@api.constrains('start_time', 'end_time')
|
||||
def _check_validity_start_time_end_time(self):
|
||||
for rec in self:
|
||||
if rec.start_time and rec.end_time:
|
||||
if rec.end_time < rec.start_time:
|
||||
raise exceptions.ValidationError(_('End time cannot be earlier than Start time'))
|
||||
|
||||
@api.onchange('start_time', 'end_time')
|
||||
def _onchange_start_end_time(self):
|
||||
if self.start_time > 0 and self.end_time > 0:
|
||||
res = self.end_time - self.start_time
|
||||
if res <= 0:
|
||||
raise ValidationError(_("End time cannot be earlier than Start time"))
|
||||
self.unit_amount = res
|
||||
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if vals.get('unit_amount') == 0.0:
|
||||
raise ValidationError(_("Your can not fill 0.0 hour entry"))
|
||||
if vals.get('employee_id') and vals.get('project_id'):
|
||||
project = self.env['project.project'].search([('id', '=', vals.get('project_id'))])
|
||||
if project:
|
||||
for rec in project.sale_line_employee_ids:
|
||||
if rec.employee_id.id == vals.get('employee_id'):
|
||||
remain_hour = rec.budgeted_qty - rec.timesheet_hour
|
||||
if vals.get('unit_amount') > remain_hour:
|
||||
raise ValidationError(_("Your can not fill entry more than Budgeted hours"))
|
||||
value = super(AccountAnalyticLine, self).create(vals)
|
||||
return value
|
||||
|
||||
|
||||
|
||||
def write(self, vals):
|
||||
if vals.get('unit_amount') == 0.0:
|
||||
raise ValidationError(_("Your can not fill 0.0 hour entry"))
|
||||
return super().write(vals)
|
|
@ -0,0 +1,49 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class Lead(models.Model):
|
||||
_inherit = 'crm.lead'
|
||||
|
||||
partner_id = fields.Many2one(
|
||||
'res.partner', string='Client', index=True, tracking=10,
|
||||
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",
|
||||
help="Linked partner (optional). Usually created when converting the lead. You can find a partner by its Name, TIN, Email or Internal Reference.")
|
||||
lead_no = fields.Char(string='Lead ID')
|
||||
scope = fields.Char(string='Scope (NIS)')
|
||||
professional_support = fields.Char(string='Professional Support')
|
||||
ref_summary_status = fields.Char(string='Referral Summary status')
|
||||
contract = fields.Selection([('Yes','Yes'),('No','No')], string='Contract')
|
||||
project_name = fields.Char(string='Project Name')
|
||||
project_scope = fields.Char(string='The scope of the project')
|
||||
act_project_manager_id = fields.Many2one('res.users', string='Actual Project Manager')
|
||||
project_manager_id = fields.Many2one('res.users', string='Project Manager')
|
||||
client_folder = fields.Char(string='Client Folder')
|
||||
start_date = fields.Date(string='Start Date')
|
||||
close_date = fields.Date(string='Close Date')
|
||||
admin_user = fields.Boolean(compute='get_admin_login')
|
||||
#user_id = fields.Many2one('res.users', string='Salesperson', index=True, tracking=True, default=lambda self: self.env.user)
|
||||
#set_readonly = fields.Boolean('Set Readonly', compute='_domain_check_user_group')
|
||||
|
||||
_sql_constraints = [
|
||||
('lead_no_uniq', 'unique(lead_no)', "Lead ID already exists !"),
|
||||
('phone_uniq', 'unique(phone)', "Phone No already exists !"),
|
||||
('email_from_uniq', 'unique(email_from)', "Email ID already exists !"),
|
||||
]
|
||||
@api.onchange('type')
|
||||
def get_admin_login(self):
|
||||
#print('11111111111', self.env.context.get('uid'))
|
||||
group_list = self.env.ref('base.group_erp_manager')
|
||||
if (self.env.context.get('uid') in group_list.users.ids):
|
||||
self.admin_user = True
|
||||
else:
|
||||
self.admin_user = False
|
||||
|
||||
"""def _domain_check_user_group(self):
|
||||
print(self.user_has_groups('sales_team.group_sale_salesman'), self.user_has_groups('sales_team.group_sale_salesman_all_leads'), self.user_has_groups('group_sale_manager'))
|
||||
if self.user_has_groups('sales_team.group_sale_salesman') and \
|
||||
(not self.user_has_groups('sales_team.group_sale_salesman_all_leads') or not self.user_has_groups('group_sale_manager')):
|
||||
self.set_readonly = True
|
||||
return domain"""
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class HrEmployee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
|
||||
budgeted_hour_week = fields.Integer("Working days per week", default=5)
|
||||
|
||||
@api.onchange('resource_calendar_id')
|
||||
def onchange_(self):
|
||||
if self.resource_calendar_id and self.resource_calendar_id.attendance_ids:
|
||||
total_week = [val.dayofweek for val in self.resource_calendar_id.attendance_ids]
|
||||
res = list(set(total_week))
|
||||
self.budgeted_hour_week = len(res)
|
||||
|
||||
class EmployeePublic(models.Model):
|
||||
_inherit = 'hr.employee.public'
|
||||
|
||||
budgeted_hour_week = fields.Integer("Working days per week")
|
|
@ -0,0 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# from odoo import models, fields, api
|
||||
|
||||
|
||||
# class cor_custom(models.Model):
|
||||
# _name = 'cor_custom.cor_custom'
|
||||
# _description = 'cor_custom.cor_custom'
|
||||
|
||||
# name = fields.Char()
|
||||
# value = fields.Integer()
|
||||
# value2 = fields.Float(compute="_value_pc", store=True)
|
||||
# description = fields.Text()
|
||||
#
|
||||
# @api.depends('value')
|
||||
# def _value_pc(self):
|
||||
# for record in self:
|
||||
# record.value2 = float(record.value) / 100
|
|
@ -0,0 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = "product.template"
|
||||
|
||||
taxes_id = fields.Many2many('account.tax', 'product_taxes_rel', 'prod_id', 'tax_id', help="Default taxes used when selling the product.", string='Client Taxes',
|
||||
domain=[('type_tax_use', '=', 'sale')], default=lambda self: self.env.company.account_sale_tax_id)
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = "product.product"
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class Project(models.Model):
|
||||
_inherit = 'project.project'
|
||||
|
||||
bill_type = fields.Selection([
|
||||
('customer_task', 'Invoice tasks separately to different clients'),
|
||||
('customer_project', 'Invoice all tasks to a single client')
|
||||
], string="Client Type", default="customer_project",
|
||||
help='When billing tasks individually, a Sales Order will be created from each task. It is perfect if you would like to bill different services to different clients at different rates. \n When billing the whole project, a Sales Order will be created from the project instead. This option is better if you would like to bill all the tasks of a given project to a specific client either at a fixed rate, or at an employee rate.')
|
||||
|
||||
pricing_type = fields.Selection([
|
||||
('fixed_rate', 'Fixed rate'),
|
||||
('employee_rate', 'Consultant rate')
|
||||
], string="Pricing", default="fixed_rate",
|
||||
help='The fixed rate is perfect if you bill a service at a fixed rate per hour or day worked regardless of the consultant who performed it. The consultant rate is preferable if your employees deliver the same service at a different rate. For instance, junior and senior consultants would deliver the same service (= consultancy), but at a different rate because of their level of seniority.')
|
||||
|
||||
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", default="hours_in_consultant")
|
||||
|
||||
privacy_visibility = fields.Selection([
|
||||
('followers', 'Invited internal users'),
|
||||
('employees', 'All internal users'),
|
||||
('portal', 'Invited portal users and all internal users'),
|
||||
],
|
||||
string='Visibility', required=True,
|
||||
default='followers',
|
||||
help="Defines the visibility of the tasks of the project:\n"
|
||||
"- Invited internal users: employees may only see the followed project and tasks.\n"
|
||||
"- All internal users: employees may see all project and tasks.\n"
|
||||
"- Invited portal and all internal users: employees may see everything."
|
||||
" Portal users may see project and tasks followed by\n"
|
||||
" them or by someone of their company.")
|
||||
allow_billable = fields.Boolean("Billable", default=True, help="Invoice your time and material from tasks.")
|
||||
|
||||
budgeted_hours = fields.Float(string='Total Budgeted Hours', digits=(16, 2))
|
||||
budgeted_revenue = fields.Float(string='Budgeted Revenue', digits=(16, 2))
|
||||
expenses_per = fields.Float(string='Expenses (%)', digits=(16, 2))
|
||||
expenses_amt = fields.Float(string='Expenses Amount', digits=(16, 2))
|
||||
cost = fields.Float("Sum of Cost", compute='onchange_compute_values', store=True)
|
||||
hourly_rate = fields.Float("Sum of Hourly rate", compute='onchange_compute_values', store=True)
|
||||
budgeted_hour_week = fields.Float("Budgeted Hours(per week)", compute='onchange_compute_values', store=True)
|
||||
total_expenses = fields.Float(string='Total Expenses', digits=(16, 2), compute='_compute_calc', store=True)
|
||||
profit_amt = fields.Float(string='Profit Amount', digits=(16, 2), compute='_compute_calc', store=True)
|
||||
profit_per = fields.Float(string='Porfit Percentage', digits=(16, 2), compute='_compute_calc', store=True)
|
||||
|
||||
@api.onchange('budgeted_revenue', 'expenses_per')
|
||||
def onchange_expenses_per(self):
|
||||
if self.budgeted_revenue > 0 and self.expenses_per > 0:
|
||||
expense_amount = self.budgeted_revenue * (self.expenses_per / 100)
|
||||
self.expenses_amt = expense_amount
|
||||
|
||||
@api.depends('cost', 'expenses_amt', 'budgeted_revenue')
|
||||
def _compute_calc(self):
|
||||
for record in self:
|
||||
total_exp = record.cost + record.expenses_amt
|
||||
record.total_expenses = total_exp
|
||||
profit_amt = record.budgeted_revenue - total_exp
|
||||
record.profit_amt = profit_amt
|
||||
if record.profit_amt > 0 and record.budgeted_revenue > 0:
|
||||
record.profit_per = (record.profit_amt / record.budgeted_revenue) * 100
|
||||
|
||||
@api.depends('sale_line_employee_ids')
|
||||
def onchange_compute_values(self):
|
||||
for record in self:
|
||||
if record.project_type == 'hours_in_consultant':
|
||||
val = 0.0
|
||||
cost = 0.0
|
||||
budgeted_hour_week = 0.0
|
||||
for rec in record.sale_line_employee_ids:
|
||||
val = val + rec.budgeted_qty
|
||||
cost = cost + rec.cost
|
||||
budgeted_hour_week = budgeted_hour_week + rec.budgeted_hour_week
|
||||
record.budgeted_hours = val
|
||||
record.cost = cost
|
||||
if val > 0.0:
|
||||
record.hourly_rate = (cost/val)
|
||||
record.budgeted_hour_week = budgeted_hour_week
|
||||
|
||||
|
||||
class InheritProjectProductEmployeeMap(models.Model):
|
||||
_inherit = 'project.sale.line.employee.map'
|
||||
|
||||
employee_price = fields.Monetary(string="Consultant Price", related="employee_id.timesheet_cost", readonly=True)
|
||||
budgeted_qty = fields.Float(string='Budgeted Hours', store=True)
|
||||
budgeted_uom = fields.Many2one('uom.uom', string='Budgeted UOM', related='sale_line_id.product_uom', readonly=True)
|
||||
# budgeted_uom = fields.Many2one('uom.uom', string='Budgeted UOM', related='timesheet_product_id.uom_id', readonly=True)
|
||||
timesheet_hour = fields.Float("Timesheet Hour", compute='_compute_timesheet_hour', default=0.0)
|
||||
budgeted_hour_week = fields.Float("Budgeted Hours per week", compute='_compute_budgeted_hour_week')
|
||||
price_unit = fields.Float("Hourly rate")
|
||||
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)])
|
||||
cost = fields.Float("Cost", compute='_compute_total_cost')
|
||||
|
||||
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, })
|
||||
res = self._cr.fetchone()
|
||||
if res and res[2]:
|
||||
val.timesheet_hour = res[2]
|
||||
else:
|
||||
val.timesheet_hour = 0.0
|
||||
|
||||
def _compute_budgeted_hour_week(self):
|
||||
for val in self:
|
||||
if val.employee_id and val.employee_id.budgeted_hour_week > 0 and val.budgeted_qty:
|
||||
val.budgeted_hour_week = (val.budgeted_qty / val.employee_id.budgeted_hour_week)
|
||||
else:
|
||||
val.budgeted_hour_week = 0
|
||||
|
||||
def _compute_total_cost(self):
|
||||
for val in self:
|
||||
val.cost = val.budgeted_qty * val.price_unit
|
||||
|
||||
@api.depends('sale_line_id', 'sale_line_id.price_unit', 'timesheet_product_id')
|
||||
def _compute_price_unit(self):
|
||||
for line in self:
|
||||
if line.sale_line_id:
|
||||
line.price_unit = line.sale_line_id.price_unit
|
||||
line.currency_id = line.sale_line_id.currency_id
|
||||
elif line.timesheet_product_id:
|
||||
line.price_unit = line.timesheet_product_id.lst_price
|
||||
line.currency_id = line.timesheet_product_id.currency_id
|
||||
else:
|
||||
#line.price_unit = 0
|
||||
line.currency_id = False
|
|
@ -0,0 +1,192 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import babel.dates
|
||||
from dateutil.relativedelta import relativedelta
|
||||
import itertools
|
||||
import json
|
||||
|
||||
from odoo import fields, _, models
|
||||
from odoo.osv import expression
|
||||
from odoo.tools import float_round
|
||||
from odoo.tools.misc import get_lang
|
||||
|
||||
from odoo.addons.web.controllers.main import clean_action
|
||||
|
||||
DEFAULT_MONTH_RANGE = 3
|
||||
|
||||
|
||||
class Project(models.Model):
|
||||
_inherit = 'project.project'
|
||||
|
||||
|
||||
|
||||
|
||||
def _plan_prepare_values(self):
|
||||
currency = self.env.company.currency_id
|
||||
uom_hour = self.env.ref('uom.product_uom_hour')
|
||||
company_uom = self.env.company.timesheet_encode_uom_id
|
||||
is_uom_day = company_uom == self.env.ref('uom.product_uom_day')
|
||||
hour_rounding = uom_hour.rounding
|
||||
billable_types = ['non_billable', 'non_billable_project', 'billable_time', 'non_billable_timesheet', 'billable_fixed']
|
||||
|
||||
values = {
|
||||
'projects': self,
|
||||
'currency': currency,
|
||||
'timesheet_domain': [('project_id', 'in', self.ids)],
|
||||
'profitability_domain': [('project_id', 'in', self.ids)],
|
||||
'stat_buttons': self._plan_get_stat_button(),
|
||||
'is_uom_day': is_uom_day,
|
||||
}
|
||||
|
||||
#
|
||||
# Hours, Rates and Profitability
|
||||
#
|
||||
dashboard_values = {
|
||||
'time': dict.fromkeys(billable_types + ['total'], 0.0),
|
||||
'rates': dict.fromkeys(billable_types + ['total'], 0.0),
|
||||
'profit': {
|
||||
'invoiced': 0.0,
|
||||
'to_invoice': 0.0,
|
||||
'cost': 0.0,
|
||||
'total': 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
# hours from non-invoiced timesheets that are linked to canceled so
|
||||
canceled_hours_domain = [('project_id', 'in', self.ids), ('timesheet_invoice_type', '!=', False), ('so_line.state', '=', 'cancel')]
|
||||
total_canceled_hours = sum(self.env['account.analytic.line'].search(canceled_hours_domain).mapped('unit_amount'))
|
||||
canceled_hours = float_round(total_canceled_hours, precision_rounding=hour_rounding)
|
||||
if is_uom_day:
|
||||
# convert time from hours to days
|
||||
canceled_hours = round(uom_hour._compute_quantity(canceled_hours, company_uom, raise_if_failure=False), 2)
|
||||
dashboard_values['time']['canceled'] = canceled_hours
|
||||
dashboard_values['time']['total'] += canceled_hours
|
||||
|
||||
# hours (from timesheet) and rates (by billable type)
|
||||
dashboard_domain = [('project_id', 'in', self.ids), ('timesheet_invoice_type', '!=', False), '|', ('so_line', '=', False), ('so_line.state', '!=', 'cancel')] # force billable type
|
||||
dashboard_data = self.env['account.analytic.line'].read_group(dashboard_domain, ['unit_amount', 'timesheet_invoice_type'], ['timesheet_invoice_type'])
|
||||
dashboard_total_hours = sum([data['unit_amount'] for data in dashboard_data]) + total_canceled_hours
|
||||
for data in dashboard_data:
|
||||
billable_type = data['timesheet_invoice_type']
|
||||
amount = float_round(data.get('unit_amount'), precision_rounding=hour_rounding)
|
||||
if is_uom_day:
|
||||
# convert time from hours to days
|
||||
amount = round(uom_hour._compute_quantity(amount, company_uom, raise_if_failure=False), 2)
|
||||
dashboard_values['time'][billable_type] = amount
|
||||
dashboard_values['time']['total'] += amount
|
||||
# rates
|
||||
rate = round(data.get('unit_amount') / dashboard_total_hours * 100, 2) if dashboard_total_hours else 0.0
|
||||
dashboard_values['rates'][billable_type] = rate
|
||||
dashboard_values['rates']['total'] += rate
|
||||
dashboard_values['time']['total'] = round(dashboard_values['time']['total'], 2)
|
||||
|
||||
# rates from non-invoiced timesheets that are linked to canceled so
|
||||
dashboard_values['rates']['canceled'] = float_round(100 * total_canceled_hours / (dashboard_total_hours or 1), precision_rounding=hour_rounding)
|
||||
|
||||
other_revenues = self.env['account.analytic.line'].read_group([
|
||||
('account_id', 'in', self.analytic_account_id.ids),
|
||||
('amount', '>=', 0),
|
||||
('project_id', '=', False)], ['amount'], [])[0].get('amount', 0)
|
||||
|
||||
# profitability, using profitability SQL report
|
||||
profit = dict.fromkeys(['invoiced', 'to_invoice', 'cost', 'expense_cost', 'expense_amount_untaxed_invoiced', 'total'], 0.0)
|
||||
profitability_raw_data = self.env['project.profitability.report'].read_group([('project_id', 'in', self.ids)], ['project_id', 'amount_untaxed_to_invoice', 'amount_untaxed_invoiced', 'timesheet_cost', 'expense_cost', 'expense_amount_untaxed_invoiced'], ['project_id'])
|
||||
for data in profitability_raw_data:
|
||||
profit['invoiced'] += data.get('amount_untaxed_invoiced', 0.0)
|
||||
profit['to_invoice'] += data.get('amount_untaxed_to_invoice', 0.0)
|
||||
profit['cost'] += data.get('timesheet_cost', 0.0)
|
||||
profit['expense_cost'] += data.get('expense_cost', 0.0)
|
||||
profit['expense_amount_untaxed_invoiced'] += data.get('expense_amount_untaxed_invoiced', 0.0)
|
||||
profit['other_revenues'] = other_revenues or 0
|
||||
profit['total'] = sum([profit[item] for item in profit.keys()])
|
||||
|
||||
# Profit Percentage added in COR Project
|
||||
try:
|
||||
profit_amount = profit['total']
|
||||
revenues_amount = profit['invoiced'] + profit['to_invoice'] + profit['other_revenues']
|
||||
profit_percent = round(profit_amount / revenues_amount * 100, 2) if revenues_amount else 0.0
|
||||
profit['profit_percent'] = profit_percent
|
||||
except Exception:
|
||||
profit['profit_percent'] = 0
|
||||
# End
|
||||
|
||||
dashboard_values['profit'] = profit
|
||||
|
||||
|
||||
values['dashboard'] = dashboard_values
|
||||
|
||||
|
||||
#
|
||||
# Time Repartition (per employee per billable types)
|
||||
#
|
||||
user_ids = self.env['project.task'].sudo().read_group([('project_id', 'in', self.ids), ('user_id', '!=', False)], ['user_id'], ['user_id'])
|
||||
user_ids = [user_id['user_id'][0] for user_id in user_ids]
|
||||
employee_ids = self.env['res.users'].sudo().search_read([('id', 'in', user_ids)], ['employee_ids'])
|
||||
# flatten the list of list
|
||||
employee_ids = list(itertools.chain.from_iterable([employee_id['employee_ids'] for employee_id in employee_ids]))
|
||||
|
||||
aal_employee_ids = self.env['account.analytic.line'].read_group([('project_id', 'in', self.ids), ('employee_id', '!=', False)], ['employee_id'], ['employee_id'])
|
||||
employee_ids.extend(list(map(lambda x: x['employee_id'][0], aal_employee_ids)))
|
||||
|
||||
# Retrieve the employees for which the current user can see theirs timesheets
|
||||
employee_domain = expression.AND([[('company_id', 'in', self.env.companies.ids)], self.env['account.analytic.line']._domain_employee_id()])
|
||||
employees = self.env['hr.employee'].sudo().browse(employee_ids).filtered_domain(employee_domain)
|
||||
repartition_domain = [('project_id', 'in', self.ids), ('employee_id', '!=', False), ('timesheet_invoice_type', '!=', False)] # force billable type
|
||||
# repartition data, without timesheet on cancelled so
|
||||
repartition_data = self.env['account.analytic.line'].read_group(repartition_domain + ['|', ('so_line', '=', False), ('so_line.state', '!=', 'cancel')], ['employee_id', 'timesheet_invoice_type', 'unit_amount'], ['employee_id', 'timesheet_invoice_type'], lazy=False)
|
||||
# read timesheet on cancelled so
|
||||
cancelled_so_timesheet = self.env['account.analytic.line'].read_group(repartition_domain + [('so_line.state', '=', 'cancel')], ['employee_id', 'unit_amount'], ['employee_id'], lazy=False)
|
||||
repartition_data += [{**canceled, 'timesheet_invoice_type': 'canceled'} for canceled in cancelled_so_timesheet]
|
||||
|
||||
# set repartition per type per employee
|
||||
repartition_employee = {}
|
||||
for employee in employees:
|
||||
repartition_employee[employee.id] = dict(
|
||||
employee_id=employee.id,
|
||||
employee_name=employee.name,
|
||||
employee_price=employee.timesheet_cost,
|
||||
non_billable_project=0.0,
|
||||
non_billable=0.0,
|
||||
billable_time=0.0,
|
||||
non_billable_timesheet=0.0,
|
||||
billable_fixed=0.0,
|
||||
canceled=0.0,
|
||||
total=0.0,
|
||||
)
|
||||
for data in repartition_data:
|
||||
employee_id = data['employee_id'][0]
|
||||
repartition_employee.setdefault(employee_id, dict(
|
||||
employee_id=data['employee_id'][0],
|
||||
employee_name=data['employee_id'][1],
|
||||
employee_price=data['employee_id'][1],
|
||||
non_billable_project=0.0,
|
||||
non_billable=0.0,
|
||||
billable_time=0.0,
|
||||
non_billable_timesheet=0.0,
|
||||
billable_fixed=0.0,
|
||||
canceled=0.0,
|
||||
total=0.0,
|
||||
))[data['timesheet_invoice_type']] = float_round(data.get('unit_amount', 0.0), precision_rounding=hour_rounding)
|
||||
repartition_employee[employee_id]['__domain_' + data['timesheet_invoice_type']] = data['__domain']
|
||||
# compute total
|
||||
for employee_id, vals in repartition_employee.items():
|
||||
repartition_employee[employee_id]['total'] = sum([vals[inv_type] for inv_type in [*billable_types, 'canceled']])
|
||||
if is_uom_day:
|
||||
# convert all times from hours to days
|
||||
for time_type in ['non_billable_project', 'non_billable', 'billable_time', 'non_billable_timesheet', 'billable_fixed', 'canceled', 'total']:
|
||||
if repartition_employee[employee_id][time_type]:
|
||||
repartition_employee[employee_id][time_type] = round(uom_hour._compute_quantity(repartition_employee[employee_id][time_type], company_uom, raise_if_failure=False), 2)
|
||||
hours_per_employee = [repartition_employee[employee_id]['total'] for employee_id in repartition_employee]
|
||||
values['repartition_employee_max'] = (max(hours_per_employee) if hours_per_employee else 1) or 1
|
||||
values['repartition_employee'] = repartition_employee
|
||||
|
||||
#
|
||||
# Table grouped by SO / SOL / Employees
|
||||
#
|
||||
timesheet_forecast_table_rows = self._table_get_line_values(employees)
|
||||
if timesheet_forecast_table_rows:
|
||||
values['timesheet_forecast_table'] = timesheet_forecast_table_rows
|
||||
return values
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
class SaleInherit(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
partner_id = fields.Many2one(
|
||||
'res.partner', string='Client', readonly=True,
|
||||
states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
|
||||
required=True, change_default=True, index=True, tracking=1,
|
||||
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",)
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import project_profitability_report_analysis
|
|
@ -0,0 +1,360 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models, tools
|
||||
|
||||
|
||||
class ProfitabilityAnalysis(models.Model):
|
||||
|
||||
_name = "project.profitability.report"
|
||||
_description = "Project Profitability Report"
|
||||
_order = 'project_id, sale_line_id'
|
||||
_auto = False
|
||||
|
||||
analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', readonly=True)
|
||||
project_id = fields.Many2one('project.project', string='Project', readonly=True)
|
||||
currency_id = fields.Many2one('res.currency', string='Project Currency', readonly=True)
|
||||
company_id = fields.Many2one('res.company', string='Project Company', readonly=True)
|
||||
user_id = fields.Many2one('res.users', string='Project Manager', readonly=True)
|
||||
partner_id = fields.Many2one('res.partner', string='Customer', readonly=True)
|
||||
line_date = fields.Date("Date", readonly=True)
|
||||
# cost
|
||||
#budgeted_unit_amount = fields.Float("Budgeted Duration", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
timesheet_unit_amount = fields.Float("Timesheet Duration", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
timesheet_cost = fields.Float("Timesheet Cost", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
expense_cost = fields.Float("Other Costs", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
# sale revenue
|
||||
order_confirmation_date = fields.Datetime('Sales Order Confirmation Date', readonly=True)
|
||||
sale_line_id = fields.Many2one('sale.order.line', string='Sale Order Line', readonly=True)
|
||||
sale_order_id = fields.Many2one('sale.order', string='Sale Order', readonly=True)
|
||||
product_id = fields.Many2one('product.product', string='Product', readonly=True)
|
||||
|
||||
amount_untaxed_to_invoice = fields.Float("Untaxed Amount to Invoice", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
amount_untaxed_invoiced = fields.Float("Untaxed Amount Invoiced", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
expense_amount_untaxed_to_invoice = fields.Float("Untaxed Amount to Re-invoice", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
expense_amount_untaxed_invoiced = fields.Float("Untaxed Amount Re-invoiced", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
other_revenues = fields.Float("Other Revenues", digits=(16, 2), readonly=True, group_operator="sum",
|
||||
help="All revenues that are not from timesheets and that are linked to the analytic account of the project.")
|
||||
margin = fields.Float("Margin", digits=(16, 2), readonly=True, group_operator="sum")
|
||||
|
||||
def init(self):
|
||||
tools.drop_view_if_exists(self._cr, self._table)
|
||||
query = """
|
||||
CREATE VIEW %s AS (
|
||||
SELECT
|
||||
sub.id as id,
|
||||
sub.project_id as project_id,
|
||||
sub.user_id as user_id,
|
||||
sub.sale_line_id as sale_line_id,
|
||||
sub.analytic_account_id as analytic_account_id,
|
||||
sub.partner_id as partner_id,
|
||||
sub.company_id as company_id,
|
||||
sub.currency_id as currency_id,
|
||||
sub.sale_order_id as sale_order_id,
|
||||
sub.order_confirmation_date as order_confirmation_date,
|
||||
sub.product_id as product_id,
|
||||
sub.sale_qty_delivered_method as sale_qty_delivered_method,
|
||||
sub.expense_amount_untaxed_to_invoice as expense_amount_untaxed_to_invoice,
|
||||
sub.expense_amount_untaxed_invoiced as expense_amount_untaxed_invoiced,
|
||||
sub.amount_untaxed_to_invoice as amount_untaxed_to_invoice,
|
||||
sub.amount_untaxed_invoiced as amount_untaxed_invoiced,
|
||||
sub.timesheet_unit_amount as timesheet_unit_amount,
|
||||
sub.timesheet_cost as timesheet_cost,
|
||||
sub.expense_cost as expense_cost,
|
||||
sub.other_revenues as other_revenues,
|
||||
sub.line_date as line_date,
|
||||
(sub.expense_amount_untaxed_to_invoice + sub.expense_amount_untaxed_invoiced + sub.amount_untaxed_to_invoice +
|
||||
sub.amount_untaxed_invoiced + sub.other_revenues + sub.timesheet_cost + sub.expense_cost)
|
||||
as margin
|
||||
FROM (
|
||||
SELECT
|
||||
ROW_NUMBER() OVER (ORDER BY P.id, SOL.id) AS id,
|
||||
P.id AS project_id,
|
||||
P.user_id AS user_id,
|
||||
SOL.id AS sale_line_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
P.partner_id AS partner_id,
|
||||
C.id AS company_id,
|
||||
C.currency_id AS currency_id,
|
||||
S.id AS sale_order_id,
|
||||
S.date_order AS order_confirmation_date,
|
||||
SOL.product_id AS product_id,
|
||||
SOL.qty_delivered_method AS sale_qty_delivered_method,
|
||||
COST_SUMMARY.expense_amount_untaxed_to_invoice AS expense_amount_untaxed_to_invoice,
|
||||
COST_SUMMARY.expense_amount_untaxed_invoiced AS expense_amount_untaxed_invoiced,
|
||||
COST_SUMMARY.amount_untaxed_to_invoice AS amount_untaxed_to_invoice,
|
||||
COST_SUMMARY.amount_untaxed_invoiced AS amount_untaxed_invoiced,
|
||||
COST_SUMMARY.timesheet_unit_amount AS timesheet_unit_amount,
|
||||
COST_SUMMARY.timesheet_cost AS timesheet_cost,
|
||||
COST_SUMMARY.expense_cost AS expense_cost,
|
||||
COST_SUMMARY.other_revenues AS other_revenues,
|
||||
COST_SUMMARY.line_date::date AS line_date
|
||||
FROM project_project P
|
||||
JOIN res_company C ON C.id = P.company_id
|
||||
LEFT JOIN (
|
||||
-- Each costs and revenues will be retrieved individually by sub-requests
|
||||
-- This is required to able to get the date
|
||||
SELECT
|
||||
project_id,
|
||||
analytic_account_id,
|
||||
sale_line_id,
|
||||
SUM(timesheet_unit_amount) AS timesheet_unit_amount,
|
||||
SUM(timesheet_cost) AS timesheet_cost,
|
||||
SUM(expense_cost) AS expense_cost,
|
||||
SUM(other_revenues) AS other_revenues,
|
||||
SUM(downpayment_invoiced) AS downpayment_invoiced,
|
||||
SUM(expense_amount_untaxed_to_invoice) AS expense_amount_untaxed_to_invoice,
|
||||
SUM(expense_amount_untaxed_invoiced) AS expense_amount_untaxed_invoiced,
|
||||
SUM(amount_untaxed_to_invoice) AS amount_untaxed_to_invoice,
|
||||
SUM(amount_untaxed_invoiced) AS amount_untaxed_invoiced,
|
||||
line_date AS line_date
|
||||
FROM (
|
||||
-- Get the timesheet costs
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
TS.so_line AS sale_line_id,
|
||||
TS.unit_amount AS timesheet_unit_amount,
|
||||
TS.amount AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced,
|
||||
0.0 AS expense_amount_untaxed_to_invoice,
|
||||
0.0 AS expense_amount_untaxed_invoiced,
|
||||
0.0 AS amount_untaxed_to_invoice,
|
||||
0.0 AS amount_untaxed_invoiced,
|
||||
TS.date AS line_date
|
||||
FROM account_analytic_line TS, project_project P
|
||||
WHERE TS.project_id IS NOT NULL AND P.id = TS.project_id AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Get the other revenues
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
AAL.so_line AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
AAL.amount AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced,
|
||||
0.0 AS expense_amount_untaxed_to_invoice,
|
||||
0.0 AS expense_amount_untaxed_invoiced,
|
||||
0.0 AS amount_untaxed_to_invoice,
|
||||
0.0 AS amount_untaxed_invoiced,
|
||||
AAL.date AS line_date
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account AA ON P.analytic_account_id = AA.id
|
||||
LEFT JOIN account_analytic_line AAL ON AAL.account_id = AA.id
|
||||
WHERE AAL.amount > 0.0 AND AAL.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Get the expense costs from account analytic line
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
AAL.so_line AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
AAL.amount AS expense_cost,
|
||||
0.0 AS downpayment_invoiced,
|
||||
0.0 AS expense_amount_untaxed_to_invoice,
|
||||
0.0 AS expense_amount_untaxed_invoiced,
|
||||
0.0 AS amount_untaxed_to_invoice,
|
||||
0.0 AS amount_untaxed_invoiced,
|
||||
AAL.date AS line_date
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account AA ON P.analytic_account_id = AA.id
|
||||
LEFT JOIN account_analytic_line AAL ON AAL.account_id = AA.id
|
||||
WHERE AAL.amount < 0.0 AND AAL.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Get the invoiced downpayments
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
MY_SOLS.id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
CASE WHEN MY_SOLS.invoice_status = 'invoiced' THEN MY_SOLS.price_reduce ELSE 0.0 END AS downpayment_invoiced,
|
||||
0.0 AS expense_amount_untaxed_to_invoice,
|
||||
0.0 AS expense_amount_untaxed_invoiced,
|
||||
0.0 AS amount_untaxed_to_invoice,
|
||||
0.0 AS amount_untaxed_invoiced,
|
||||
MY_S.date_order AS line_date
|
||||
FROM project_project P
|
||||
LEFT JOIN sale_order_line MY_SOL ON P.sale_line_id = MY_SOL.id
|
||||
LEFT JOIN sale_order MY_S ON MY_SOL.order_id = MY_S.id
|
||||
LEFT JOIN sale_order_line MY_SOLS ON MY_SOLS.order_id = MY_S.id
|
||||
WHERE MY_SOLS.is_downpayment = 't'
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Get the expense costs from sale order line
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
OLIS.id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
OLIS.price_reduce AS expense_cost,
|
||||
0.0 AS downpayment_invoiced,
|
||||
0.0 AS expense_amount_untaxed_to_invoice,
|
||||
0.0 AS expense_amount_untaxed_invoiced,
|
||||
0.0 AS amount_untaxed_to_invoice,
|
||||
0.0 AS amount_untaxed_invoiced,
|
||||
ANLI.date AS line_date
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account ANAC ON P.analytic_account_id = ANAC.id
|
||||
LEFT JOIN account_analytic_line ANLI ON ANAC.id = ANLI.account_id
|
||||
LEFT JOIN sale_order_line OLI ON P.sale_line_id = OLI.id
|
||||
LEFT JOIN sale_order ORD ON OLI.order_id = ORD.id
|
||||
LEFT JOIN sale_order_line OLIS ON ORD.id = OLIS.order_id
|
||||
WHERE OLIS.product_id = ANLI.product_id AND OLIS.is_downpayment = 't' AND ANLI.amount < 0.0 AND ANLI.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Get the following values: expense amount untaxed to invoice/invoiced, amount untaxed to invoice/invoiced
|
||||
-- These values have to be computed from all the records retrieved just above but grouped by project and sale order line
|
||||
SELECT
|
||||
AMOUNT_UNTAXED.project_id AS project_id,
|
||||
AMOUNT_UNTAXED.analytic_account_id AS analytic_account_id,
|
||||
AMOUNT_UNTAXED.sale_line_id AS sale_line_id,
|
||||
0.0 AS timesheet_unit_amount,
|
||||
0.0 AS timesheet_cost,
|
||||
0.0 AS other_revenues,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced,
|
||||
CASE
|
||||
WHEN SOL.qty_delivered_method = 'analytic' THEN (SOL.untaxed_amount_to_invoice / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
|
||||
ELSE 0.0
|
||||
END AS expense_amount_untaxed_to_invoice,
|
||||
CASE
|
||||
WHEN SOL.qty_delivered_method = 'analytic' AND SOL.invoice_status != 'no'
|
||||
THEN
|
||||
CASE
|
||||
WHEN T.expense_policy = 'sales_price'
|
||||
THEN (SOL.price_reduce / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END) * SOL.qty_invoiced
|
||||
ELSE -AMOUNT_UNTAXED.expense_cost
|
||||
END
|
||||
ELSE 0.0
|
||||
END AS expense_amount_untaxed_invoiced,
|
||||
CASE
|
||||
WHEN SOL.qty_delivered_method IN ('timesheet', 'manual') THEN (SOL.untaxed_amount_to_invoice / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
|
||||
ELSE 0.0
|
||||
END AS amount_untaxed_to_invoice,
|
||||
CASE
|
||||
WHEN SOL.qty_delivered_method IN ('timesheet', 'manual') THEN (COALESCE(SOL.untaxed_amount_invoiced, AMOUNT_UNTAXED.downpayment_invoiced) / CASE COALESCE(S.currency_rate, 0) WHEN 0 THEN 1.0 ELSE S.currency_rate END)
|
||||
ELSE 0.0
|
||||
END AS amount_untaxed_invoiced,
|
||||
S.date_order AS line_date
|
||||
FROM project_project P
|
||||
JOIN res_company C ON C.id = P.company_id
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
AAL.so_line AS sale_line_id,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM account_analytic_line AAL, project_project P
|
||||
WHERE AAL.project_id IS NOT NULL AND P.id = AAL.project_id AND P.active = 't'
|
||||
GROUP BY P.id, AAL.so_line
|
||||
|
||||
UNION
|
||||
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
AAL.so_line AS sale_line_id,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account AA ON P.analytic_account_id = AA.id
|
||||
LEFT JOIN account_analytic_line AAL ON AAL.account_id = AA.id
|
||||
WHERE AAL.amount > 0.0 AND AAL.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, AA.id, AAL.so_line
|
||||
UNION
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
AAL.so_line AS sale_line_id,
|
||||
SUM(AAL.amount) AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account AA ON P.analytic_account_id = AA.id
|
||||
LEFT JOIN account_analytic_line AAL ON AAL.account_id = AA.id
|
||||
WHERE AAL.amount < 0.0 AND AAL.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, AA.id, AAL.so_line
|
||||
UNION
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
MY_SOLS.id AS sale_line_id,
|
||||
0.0 AS expense_cost,
|
||||
CASE WHEN MY_SOLS.invoice_status = 'invoiced' THEN MY_SOLS.price_reduce ELSE 0.0 END AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
LEFT JOIN sale_order_line MY_SOL ON P.sale_line_id = MY_SOL.id
|
||||
LEFT JOIN sale_order MY_S ON MY_SOL.order_id = MY_S.id
|
||||
LEFT JOIN sale_order_line MY_SOLS ON MY_SOLS.order_id = MY_S.id
|
||||
WHERE MY_SOLS.is_downpayment = 't'
|
||||
GROUP BY P.id, MY_SOLS.id
|
||||
UNION
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
OLIS.id AS sale_line_id,
|
||||
OLIS.price_reduce AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM project_project P
|
||||
LEFT JOIN account_analytic_account ANAC ON P.analytic_account_id = ANAC.id
|
||||
LEFT JOIN account_analytic_line ANLI ON ANAC.id = ANLI.account_id
|
||||
LEFT JOIN sale_order_line OLI ON P.sale_line_id = OLI.id
|
||||
LEFT JOIN sale_order ORD ON OLI.order_id = ORD.id
|
||||
LEFT JOIN sale_order_line OLIS ON ORD.id = OLIS.order_id
|
||||
WHERE OLIS.product_id = ANLI.product_id AND OLIS.is_downpayment = 't' AND ANLI.amount < 0.0 AND ANLI.project_id IS NULL AND P.active = 't' AND P.allow_timesheets = 't'
|
||||
GROUP BY P.id, OLIS.id
|
||||
UNION
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
SOL.id AS sale_line_id,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM sale_order_line SOL
|
||||
INNER JOIN project_project P ON SOL.project_id = P.id
|
||||
WHERE P.active = 't' AND P.allow_timesheets = 't'
|
||||
UNION
|
||||
SELECT
|
||||
P.id AS project_id,
|
||||
P.analytic_account_id AS analytic_account_id,
|
||||
SOL.id AS sale_line_id,
|
||||
0.0 AS expense_cost,
|
||||
0.0 AS downpayment_invoiced
|
||||
FROM sale_order_line SOL
|
||||
INNER JOIN project_task T ON SOL.task_id = T.id
|
||||
INNER JOIN project_project P ON P.id = T.project_id
|
||||
WHERE P.active = 't' AND P.allow_timesheets = 't'
|
||||
) AMOUNT_UNTAXED ON AMOUNT_UNTAXED.project_id = P.id
|
||||
LEFT JOIN sale_order_line SOL ON AMOUNT_UNTAXED.sale_line_id = SOL.id
|
||||
LEFT JOIN sale_order S ON SOL.order_id = S.id
|
||||
LEFT JOIN product_product PP on (SOL.product_id = PP.id)
|
||||
LEFT JOIN product_template T on (PP.product_tmpl_id = T.id)
|
||||
WHERE P.active = 't' AND P.analytic_account_id IS NOT NULL
|
||||
) SUB_COST_SUMMARY
|
||||
GROUP BY project_id, analytic_account_id, sale_line_id, line_date
|
||||
) COST_SUMMARY ON COST_SUMMARY.project_id = P.id
|
||||
LEFT JOIN sale_order_line SOL ON COST_SUMMARY.sale_line_id = SOL.id
|
||||
LEFT JOIN sale_order S ON SOL.order_id = S.id
|
||||
WHERE P.active = 't' AND P.analytic_account_id IS NOT NULL
|
||||
) AS sub
|
||||
)
|
||||
""" % self._table
|
||||
self._cr.execute(query)
|
|
@ -0,0 +1,76 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="sale_timesheet.project_profitability_report_view_pivot" model="ir.ui.view">
|
||||
<field name="name">project.profitability.report.pivot</field>
|
||||
<field name="model">project.profitability.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Profitability Analysis" display_quantity="true" disable_linking="True" sample="1">
|
||||
<field name="project_id" type="row"/>
|
||||
<field name="amount_untaxed_to_invoice" type="measure"/>
|
||||
<field name="amount_untaxed_invoiced" type="measure"/>
|
||||
<field name="timesheet_cost" type="measure"/>
|
||||
<!--<field name="budgeted_unit_amount" type="measure"/>-->
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sale_timesheet.project_profitability_report_view_graph" model="ir.ui.view">
|
||||
<field name="name">project.profitability.report.graph</field>
|
||||
<field name="model">project.profitability.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Profitability Analysis" type="bar" sample="1" disable_linking="1">
|
||||
<field name="project_id" type="row"/>
|
||||
<field name="product_id" type="col"/>
|
||||
<field name="amount_untaxed_to_invoice" type="measure"/>
|
||||
<field name="amount_untaxed_invoiced" type="measure"/>
|
||||
<field name="timesheet_cost" type="measure"/>
|
||||
<field name="other_revenues" type="measure"/>
|
||||
<field name="margin" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="sale_timesheet.project_profitability_report_view_search" model="ir.ui.view">
|
||||
<field name="name">project.profitability.report.search</field>
|
||||
<field name="model">project.profitability.report</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Profitability Analysis">
|
||||
<field name="project_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="product_id"/>
|
||||
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<filter string="My Projects" name="my_project" domain="[('user_id','=', uid)]"/>
|
||||
<group expand="1" string="Group By">
|
||||
<filter string="Project" name="group_by_project" context="{'group_by':'project_id'}"/>
|
||||
<filter string="Project Manager" name="group_by_user_id" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Customer" name="group_by_partner_id" context="{'group_by':'partner_id'}"/>
|
||||
<filter string="Company" name="group_by_company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
|
||||
<filter string="Date" name="group_by_line_date" context="{'group_by':'line_date'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sale_timesheet.project_profitability_report_action" model="ir.actions.act_window">
|
||||
<field name="name">Project Costs and Revenues</field>
|
||||
<field name="res_model">project.profitability.report</field>
|
||||
<field name="view_mode">pivot,graph</field>
|
||||
<field name="search_view_id" ref="sale_timesheet.project_profitability_report_view_search"/>
|
||||
<field name="context">{
|
||||
'group_by_no_leaf':1,
|
||||
'group_by':[],
|
||||
'sale_show_order_product_name': 1,
|
||||
}</field>
|
||||
<field name="help">This report allows you to analyse the profitability of your projects: compare the amount to invoice, the ones already invoiced and the project cost (via timesheet cost of your employees).</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="sale_timesheet.menu_project_profitability_analysis"
|
||||
parent="project.menu_project_report"
|
||||
action="sale_timesheet.project_profitability_report_action"
|
||||
name="Project Costs and Revenues"
|
||||
sequence="50"/>
|
||||
|
||||
</odoo>
|
|
@ -0,0 +1,2 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_cor_custom_cor_custom,cor_custom.cor_custom,model_cor_custom_cor_custom,base.group_user,1,1,1,1
|
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!--<record id="view_account_analytic_line_inherit_percentage" model="ir.ui.view">
|
||||
<field name="name">Percentage Analytic</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="analytic.view_account_analytic_line_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='currency_id']" position="after">
|
||||
<separator string="Percentage"/>
|
||||
<field name="ispercentage"/>
|
||||
<field name="percentage_rate" attrs="{'invisible':[('ispercentage','=',False)]}"/>
|
||||
<field name="per_from_amt" attrs="{'invisible':[('ispercentage','=',False)]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>-->
|
||||
|
||||
<record id="hr_timesheet_line_tree_inherit1" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.tree.hr_timesheet_inherit1</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='name']" position="after">
|
||||
<field name="start_time" widget="float_time"/>
|
||||
<field name="end_time" widget="float_time"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!--<record id="timesheet_view_tree_user_inherit1" model="ir.ui.view">
|
||||
<field name="name">account.analytic.line.tree.hr_timesheet_user_inherit1</field>
|
||||
<field name="model">account.analytic.line</field>
|
||||
<field name="inherit_id" ref="hr_timesheet.timesheet_view_tree_user"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='name']" position="after">
|
||||
<field name="start_time"/>
|
||||
<field name="end_time"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>-->
|
||||
|
||||
</odoo>
|
|
@ -0,0 +1,88 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="crm_lead_form_cor_custom" model="ir.ui.view">
|
||||
<field name="name">crm.lead.cor.inherit1</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="inherit_id" ref="crm.crm_lead_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@name='opportunity_partner']//field[@name='partner_id']" position="replace">
|
||||
<field name="partner_id"
|
||||
widget="res_partner_many2one"
|
||||
string="Client"
|
||||
context="{'res_partner_search_mode': type == 'opportunity' and 'customer' or False,
|
||||
'default_name': contact_name or partner_name,
|
||||
'default_street': street,
|
||||
'default_is_company': type == 'opportunity' and contact_name == False,
|
||||
'default_company_name': type == 'opportunity' and partner_name,
|
||||
'default_street2': street2,
|
||||
'default_city': city,
|
||||
'default_title': title,
|
||||
'default_state_id': state_id,
|
||||
'default_zip': zip,
|
||||
'default_country_id': country_id,
|
||||
'default_function': function,
|
||||
'default_phone': phone,
|
||||
'default_mobile': mobile,
|
||||
'default_email': email_from,
|
||||
'default_user_id': user_id,
|
||||
'default_team_id': team_id,
|
||||
'default_website': website,
|
||||
'show_vat': True,
|
||||
}"
|
||||
/>
|
||||
</xpath>
|
||||
<!--<xpath expr="//field[@name='user_id']" position="replace">
|
||||
<field name="set_readonly" invisible="1" />
|
||||
<field name="user_id" domain="[('share', '=', False)]" attrs="{'readonly': [('set_readonly','!=',True)]}"
|
||||
context="{'default_sales_team_id': team_id}" widget="many2one_avatar_user"/>
|
||||
</xpath>-->
|
||||
<xpath expr="//group[@name='Misc']" position="after">
|
||||
<group colspan="2" col="4">
|
||||
<field name="lead_no"/>
|
||||
<field name="scope"/>
|
||||
<field name="professional_support"/>
|
||||
<field name="ref_summary_status"/>
|
||||
<field name="contract"/>
|
||||
<field name="client_folder"/>
|
||||
<field name="start_date"/>
|
||||
<field name="close_date"/>
|
||||
<field name="project_name"/>
|
||||
<field name="project_scope"/>
|
||||
<field name="act_project_manager_id"/>
|
||||
<field name="project_manager_id"/>
|
||||
</group>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='user_id']" position="replace">
|
||||
<field name="admin_user" invisible="1"/>
|
||||
<field name="user_id" domain="[('share', '=', False)]" attrs="{'readonly':[('admin_user','=',False)]}"
|
||||
context="{'default_sales_team_id': team_id}" widget="many2one_avatar_user"/>
|
||||
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- <record id="view_salesperson_readonly_custom" model="ir.ui.view">
|
||||
<field name="name">crm.lead.cor.inherit2</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="inherit_id" ref="cor_custom.crm_lead_form_cor_custom"/>
|
||||
<field name="groups_id" eval="[(6, 0, [ref('base.group_erp_manager')])]"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='user_id']" position="attributes">
|
||||
<attribute name="readonly">False</attribute>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
-->
|
||||
<record id="crm_case_kanban_view_leads" model="ir.ui.view">
|
||||
<field name="name">crm.lead.kanban.lead</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="inherit_id" ref="crm.crm_case_kanban_view_leads"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//kanban" position="attributes">
|
||||
<attribute name="on_create"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="inherit_view_employee_form" model="ir.ui.view">
|
||||
<field name="name">hr.employee.form.inherit</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="inherit_id" ref="hr.view_employee_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='resource_calendar_id']" position="after">
|
||||
<field name="budgeted_hour_week"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!--<record id="inherit_view_employee_form" model="ir.ui.view">
|
||||
<field name="name">hr.employee.form.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='hr_settings']" position="inside">
|
||||
<group>
|
||||
<label for="budgeted_hour_week"/>
|
||||
<div>
|
||||
<field name="budgeted_hour_week" class="oe_inline"/>
|
||||
per week
|
||||
</div>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>-->
|
||||
|
||||
</odoo>
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="inherit_timesheet_plan" model="ir.ui.view">
|
||||
<field name="name">Timesheet Plan</field>
|
||||
<field name="type">qweb</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="inherit_id" ref="sale_timesheet.timesheet_plan"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('o_profitability_wrapper')]//div[hasclass('o_profitability_section')][2]//tr[7]"
|
||||
position="replace">
|
||||
<tr>
|
||||
<td class="o_timesheet_plan_dashboard_total">
|
||||
<b>
|
||||
<t t-esc="dashboard['profit']['total']"
|
||||
t-options='{"widget": "monetary", "display_currency": currency}'/>
|
||||
</b>
|
||||
</td>
|
||||
<td class="o_timesheet_plan_dashboard_cell">
|
||||
(<t t-esc="dashboard['profit']['profit_percent']"/> %)
|
||||
</td>
|
||||
<td>
|
||||
<b>Total</b>
|
||||
</td>
|
||||
</tr>
|
||||
</xpath>
|
||||
<xpath expr="//div[@class='o_timesheet_plan_sale_timesheet_people_time']/t/div/table/thead/tr/th[2]"
|
||||
position="after">
|
||||
<th>Hourly Rate</th>
|
||||
</xpath>
|
||||
<xpath expr="//div[@class='o_timesheet_plan_sale_timesheet_people_time']/t/div/table/tbody/t/tr/td[2]"
|
||||
position="after">
|
||||
<td style="width: 15%; vertical-align: middle;">
|
||||
<t t-esc="employee['employee_price']"/>
|
||||
</td>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="mail.menu_root_discuss" model="ir.ui.menu">
|
||||
<field name="groups_id" eval="[(6, 0, [ref('cor_custom.group_show_hr_discuss_group')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="contacts.menu_contacts" model="ir.ui.menu">
|
||||
<field name="groups_id" eval="[(6, 0, [ref('cor_custom.group_show_hr_contact_group')])]"/>
|
||||
</record>
|
||||
|
||||
<record id="hr.menu_hr_root" model="ir.ui.menu">
|
||||
<field name="groups_id" eval="[(6, 0, [ref('cor_custom.group_show_hr_menu_group')])]"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
|
@ -0,0 +1,139 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="project_project_analytic_view_form" model="ir.ui.view">
|
||||
<field name="name">Project Analytic</field>
|
||||
<field name="model">project.project</field>
|
||||
<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_account_analytic_line" icon="fa-usd"
|
||||
attrs="{'invisible': [('allow_billable','=',False)]}" string="Cost/Revenue" widget="statinfo">
|
||||
</button>
|
||||
</div>
|
||||
<xpath expr="//field[@name='user_id']" position="replace">
|
||||
<field name="user_id" string="Project Manager" widget="many2one_avatar_user" required="0"
|
||||
attrs="{'readonly':[('active','=',False)]}" domain="[('share', '=', False)]"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='partner_id']" position="replace">
|
||||
<field name="partner_id" string="Client" required="0"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="inherit_project_project_view_form" model="ir.ui.view">
|
||||
<field name="name">project.project.form.inherit</field>
|
||||
<field name="model">project.project</field>
|
||||
<field name="inherit_id" ref="sale_timesheet.project_project_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page[@name='billing_employee_rate']" position="replace">
|
||||
<page name="billing_employee_rate" string="Invoicing"
|
||||
attrs="{'invisible': [('allow_billable', '=', False)]}">
|
||||
<group>
|
||||
<field name="display_create_order" invisible="1"/>
|
||||
<!--<field name="project_type" widget="radio"/>-->
|
||||
<field name="bill_type" widget="radio" invisible="1"/>
|
||||
<field name="pricing_type"
|
||||
attrs="{'invisible': ['|', ('allow_billable', '=', False), ('bill_type', '!=', 'customer_project')], 'required': ['&', ('allow_billable', '=', True), ('allow_timesheets', '=', True)]}"
|
||||
widget="radio"/>
|
||||
<field name="project_type" attrs="{'invisible': [('pricing_type', '=', 'fixed_rate')]}"
|
||||
widget="radio"/>
|
||||
<field name="budgeted_revenue"
|
||||
attrs="{'invisible': [('pricing_type','=','fixed_rate')], 'required': [('pricing_type','!=','fixed_rate')]}"/>
|
||||
<field name="expenses_per" attrs="{'invisible': [('pricing_type','=','fixed_rate')]}"/>
|
||||
<field name="expenses_amt" attrs="{'invisible': [('pricing_type','=','fixed_rate')]}"/>
|
||||
<div class="o_td_label"
|
||||
attrs="{'invisible': ['|', '|', ('allow_timesheets', '=', False), ('sale_order_id', '!=', False), '&', ('pricing_type', '!=', 'fixed_rate'), ('bill_type', '!=', 'customer_task')]}">
|
||||
<label for="timesheet_product_id" string="Default Service"
|
||||
attrs="{'invisible': [('bill_type', '!=', 'customer_task')]}"/>
|
||||
<label for="timesheet_product_id" string="Service"
|
||||
attrs="{'invisible': [('bill_type', '=', 'customer_task')]}"/>
|
||||
</div>
|
||||
<field name="timesheet_product_id" nolabel="1"
|
||||
attrs="{'invisible': ['|', '|', ('allow_timesheets', '=', False), ('sale_order_id', '!=', False), '&', ('pricing_type', '!=', 'fixed_rate'), ('bill_type', '!=', 'customer_task')], 'required': ['&', ('allow_billable', '=', True), ('allow_timesheets', '=', True)]}"
|
||||
context="{'default_type': 'service', 'default_service_policy': 'delivered_timesheet', 'default_service_type': 'timesheet'}"/>
|
||||
<field name="sale_order_id" invisible="1"/>
|
||||
<field name="sale_line_id" string="Default Sales Order Item"
|
||||
attrs="{'invisible': ['|', '|', ('sale_order_id', '=', False), ('bill_type', '!=', 'customer_project'), ('pricing_type', '!=', 'fixed_rate')], 'readonly': [('sale_order_id', '=', False)]}"
|
||||
options="{'no_create': True, 'no_edit': True, 'delete': False}"/>
|
||||
</group>
|
||||
<field name="sale_line_employee_ids"
|
||||
attrs="{'invisible': ['|', ('bill_type', '!=', 'customer_project'), ('pricing_type', '!=', 'employee_rate')]}">
|
||||
<tree editable="top">
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="project_id" invisible="1"/>
|
||||
<field name="employee_id" options="{'no_create': True}" string="Consultant Name"/>
|
||||
<!--<field name="sale_line_id" options="{'no_create': True}"
|
||||
attrs="{'column_invisible': [('parent.sale_order_id', '=', False)]}"
|
||||
domain="[('order_id','=',parent.sale_order_id), ('is_service', '=', True)]"/>
|
||||
<field name="price_unit" widget="monetary" options="{'currency_field': 'currency_id'}"
|
||||
attrs="{'readonly': [('parent.sale_order_id', '!=', False)]}"/>-->
|
||||
<field name="price_unit" readonly="0"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="budgeted_qty"
|
||||
attrs="{'readonly': [('parent.project_type', '=', 'hours_no_limit')]}"/>
|
||||
<field name="cost"/>
|
||||
<field name="timesheet_hour"/>
|
||||
<field name="budgeted_hour_week"/>
|
||||
<!--<field name="timesheet_product_id"
|
||||
attrs="{'column_invisible': [('parent.sale_order_id', '!=', False)]}"/>-->
|
||||
<!--<field name="budgeted_uom"
|
||||
attrs="{'column_invisible': [('parent.sale_order_id', '=', False)]}"/>-->
|
||||
<!--<field name="budgeted_qty"
|
||||
attrs="{'column_invisible': [('parent.sale_order_id', '=', False)]}"/>-->
|
||||
<!--<field name="employee_price" widget="monetary" options="{'currency_field': 'currency_id'}"/>-->
|
||||
</tree>
|
||||
</field>
|
||||
<group attrs="{'invisible': [('pricing_type','=','fixed_rate')]}">
|
||||
<group>
|
||||
<field name="budgeted_hours"
|
||||
attrs="{'invisible': [('pricing_type','=','fixed_rate')], 'readonly': [('project_type','=','hours_in_consultant')],
|
||||
'required': [('pricing_type','!=','fixed_rate')]}"/>
|
||||
<field name="total_expenses"/>
|
||||
<field name="profit_amt"/>
|
||||
<field name="profit_per"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="cost"/>
|
||||
<field name="hourly_rate"/>
|
||||
<field name="budgeted_hour_week"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="project.open_view_project_all" model="ir.actions.act_window">
|
||||
<field name="name">Projects</field>
|
||||
<field name="res_model">project.project</field>
|
||||
<field name="domain">[]</field>
|
||||
<field name="view_mode">tree,kanban,form</field>
|
||||
<field name="view_id" ref="project.view_project"/>
|
||||
<field name="search_view_id" ref="project.view_project_project_filter"/>
|
||||
<field name="target">main</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No projects found. Let's create one!
|
||||
</p>
|
||||
<p>
|
||||
Projects regroup tasks on the same topic and each have their own dashboard.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="inherit_view_task_form2" model="ir.ui.view">
|
||||
<field name="name">project.task.form.inherit</field>
|
||||
<field name="model">project.task</field>
|
||||
<field name="inherit_id" ref="project.view_task_form2"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='partner_id']" position="replace">
|
||||
<field name="partner_id" class="o_task_customer_field" string="Client"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="inherit_view_order_form" model="ir.ui.view">
|
||||
<field name="name">sale.order.form.inherit</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<!--<xpath expr="//field[@name='partner_id']" position="replace">
|
||||
<field name="partner_id" widget="res_partner_many2one" string="Client" required="1"
|
||||
context="{'res_partner_search_mode': 'customer', 'show_address': 1, 'show_vat': True}"
|
||||
options='{"always_reload": True}'/>
|
||||
</xpath>-->
|
||||
<xpath expr="//field[@name='require_signature']" position="replace">
|
||||
<field name="require_signature" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='require_payment']" position="replace">
|
||||
<field name="require_payment" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='payment_term_id']" position="replace">
|
||||
<field name="payment_term_id" invisible="1" options="{'no_open':True,'no_create': True}"/>
|
||||
</xpath>
|
||||
<page name="customer_signature" position="attributes">
|
||||
<attribute name='invisible'>1</attribute>
|
||||
</page>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- <record id="inherit_view_quotation_tree" model="ir.ui.view">
|
||||
<field name="name">sale.order.tree.inherit</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale.view_quotation_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='partner_id']" position="replace">
|
||||
<field name="partner_id" readonly="1" string="Client"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="inherit_view_order_tree" model="ir.ui.view">
|
||||
<field name="name">sale.order.tree.inherit</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale.view_order_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='partner_id']" position="replace">
|
||||
<field name="partner_id" readonly="1" string="Client"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record> -->
|
||||
|
||||
</odoo>
|
|
@ -0,0 +1,24 @@
|
|||
<odoo>
|
||||
<data>
|
||||
<!--
|
||||
<template id="listing">
|
||||
<ul>
|
||||
<li t-foreach="objects" t-as="object">
|
||||
<a t-attf-href="#{ root }/objects/#{ object.id }">
|
||||
<t t-esc="object.display_name"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<template id="object">
|
||||
<h1><t t-esc="object.display_name"/></h1>
|
||||
<dl>
|
||||
<t t-foreach="object._fields" t-as="field">
|
||||
<dt><t t-esc="field"/></dt>
|
||||
<dd><t t-esc="object[field]"/></dd>
|
||||
</t>
|
||||
</dl>
|
||||
</template>
|
||||
-->
|
||||
</data>
|
||||
</odoo>
|
|
@ -0,0 +1,60 @@
|
|||
<odoo>
|
||||
<data>
|
||||
<!-- explicit list view definition -->
|
||||
<!--
|
||||
<record model="ir.ui.view" id="cor_custom.list">
|
||||
<field name="name">cor_custom list</field>
|
||||
<field name="model">cor_custom.cor_custom</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="value"/>
|
||||
<field name="value2"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
-->
|
||||
|
||||
<!-- actions opening views on models -->
|
||||
<!--
|
||||
<record model="ir.actions.act_window" id="cor_custom.action_window">
|
||||
<field name="name">cor_custom window</field>
|
||||
<field name="res_model">cor_custom.cor_custom</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
-->
|
||||
|
||||
<!-- server action to the one above -->
|
||||
<!--
|
||||
<record model="ir.actions.server" id="cor_custom.action_server">
|
||||
<field name="name">cor_custom server</field>
|
||||
<field name="model_id" ref="model_cor_custom_cor_custom"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">
|
||||
action = {
|
||||
"type": "ir.actions.act_window",
|
||||
"view_mode": "tree,form",
|
||||
"res_model": model._name,
|
||||
}
|
||||
</field>
|
||||
</record>
|
||||
-->
|
||||
|
||||
<!-- Top menu item -->
|
||||
<!--
|
||||
<menuitem name="cor_custom" id="cor_custom.menu_root"/>
|
||||
-->
|
||||
<!-- menu categories -->
|
||||
<!--
|
||||
<menuitem name="Menu 1" id="cor_custom.menu_1" parent="cor_custom.menu_root"/>
|
||||
<menuitem name="Menu 2" id="cor_custom.menu_2" parent="cor_custom.menu_root"/>
|
||||
-->
|
||||
<!-- actions -->
|
||||
<!--
|
||||
<menuitem name="List" id="cor_custom.menu_1_list" parent="cor_custom.menu_1"
|
||||
action="cor_custom.action_window"/>
|
||||
<menuitem name="Server to list" id="cor_custom" parent="cor_custom.menu_2"
|
||||
action="cor_custom.action_server"/>
|
||||
-->
|
||||
</data>
|
||||
</odoo>
|
|
@ -0,0 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import project_create_sale_order
|
||||
from . import crm_opportunity_to_quotation
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class Opportunity2Quotation(models.TransientModel):
|
||||
_inherit = 'crm.quotation.partner'
|
||||
|
||||
action = fields.Selection([
|
||||
('create', 'Create a new client'),
|
||||
('exist', 'Link to an existing client'),
|
||||
('nothing', 'Do not link to a client')
|
||||
], string='Quotation Client', required=True)
|
||||
lead_id = fields.Many2one('crm.lead', "Associated Lead", required=True)
|
||||
partner_id = fields.Many2one('res.partner', 'Client')
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class ProjectCreateSalesOrder(models.TransientModel):
|
||||
_inherit = 'project.create.sale.order'
|
||||
|
||||
|
||||
partner_id = fields.Many2one('res.partner', string="Client", required=True, help="Client of the sales order")
|
||||
|
||||
def _make_billable_at_project_rate(self, sale_order):
|
||||
self.ensure_one()
|
||||
task_left = self.project_id.tasks.filtered(lambda task: not task.sale_line_id)
|
||||
ticket_timesheet_ids = self.env.context.get('ticket_timesheet_ids', [])
|
||||
budgeted_qty = 0
|
||||
for wizard_line in self.line_ids:
|
||||
task_ids = self.project_id.tasks.filtered(lambda task: not task.sale_line_id and task.timesheet_product_id == wizard_line.product_id)
|
||||
task_left -= task_ids
|
||||
# trying to simulate the SO line created a task, according to the product configuration
|
||||
# To avoid, generating a task when confirming the SO
|
||||
task_id = False
|
||||
if task_ids and wizard_line.product_id.service_tracking in ['task_in_project', 'task_global_project']:
|
||||
task_id = task_ids.ids[0]
|
||||
|
||||
# create SO line
|
||||
sale_order_line = self.env['sale.order.line'].create({
|
||||
'order_id': sale_order.id,
|
||||
'product_id': wizard_line.product_id.id,
|
||||
'price_unit': wizard_line.price_unit,
|
||||
'project_id': self.project_id.id, # prevent to re-create a project on confirmation
|
||||
'task_id': task_id,
|
||||
'product_uom_qty': wizard_line.budgeted_qty,
|
||||
})
|
||||
budgeted_qty += wizard_line.budgeted_qty
|
||||
if ticket_timesheet_ids and not self.project_id.sale_line_id and not task_ids:
|
||||
# With pricing = "project rate" in project. When the user wants to create a sale order from a ticket in helpdesk
|
||||
# The project cannot contain any tasks. Thus, we need to give the first sale_order_line created to link
|
||||
# the timesheet to this first sale order line.
|
||||
# link the project to the SO line
|
||||
self.project_id.write({
|
||||
'sale_order_id': sale_order.id,
|
||||
'sale_line_id': sale_order_line.id,
|
||||
'partner_id': self.partner_id.id,
|
||||
})
|
||||
|
||||
# link the tasks to the SO line
|
||||
task_ids.write({
|
||||
'sale_line_id': sale_order_line.id,
|
||||
'partner_id': sale_order.partner_id.id,
|
||||
'email_from': sale_order.partner_id.email,
|
||||
})
|
||||
|
||||
# assign SOL to timesheets
|
||||
search_domain = [('task_id', 'in', task_ids.ids), ('so_line', '=', False)]
|
||||
if ticket_timesheet_ids:
|
||||
search_domain = [('id', 'in', ticket_timesheet_ids), ('so_line', '=', False)]
|
||||
|
||||
self.env['account.analytic.line'].search(search_domain).write({
|
||||
'so_line': sale_order_line.id
|
||||
})
|
||||
#sale_order_line.with_context({'no_update_planned_hours': True}).write({
|
||||
#'product_uom_qty': sale_order_line.qty_delivered
|
||||
#})
|
||||
|
||||
if ticket_timesheet_ids and self.project_id.sale_line_id and not self.project_id.tasks and len(self.line_ids) > 1:
|
||||
# Then, we need to give to the project the last sale order line created
|
||||
self.project_id.write({
|
||||
'sale_line_id': sale_order_line.id
|
||||
})
|
||||
else: # Otherwise, we are in the normal behaviour
|
||||
# link the project to the SO line
|
||||
self.project_id.write({
|
||||
'sale_order_id': sale_order.id,
|
||||
'sale_line_id': sale_order_line.id, # we take the last sale_order_line created
|
||||
'partner_id': self.partner_id.id,
|
||||
})
|
||||
if self.project_id.budgeted_hours <= 0:
|
||||
self.project_id.write({
|
||||
'budgeted_hours': budgeted_qty,
|
||||
})
|
||||
|
||||
if task_left:
|
||||
task_left.sale_line_id = False
|
||||
|
||||
|
||||
def _make_billable_at_employee_rate(self, sale_order):
|
||||
# trying to simulate the SO line created a task, according to the product configuration
|
||||
# To avoid, generating a task when confirming the SO
|
||||
task_id = self.env['project.task'].search([('project_id', '=', self.project_id.id)], order='create_date DESC', limit=1).id
|
||||
project_id = self.project_id.id
|
||||
|
||||
lines_already_present = dict([(l.employee_id.id, l) for l in self.project_id.sale_line_employee_ids])
|
||||
|
||||
non_billable_tasks = self.project_id.tasks.filtered(lambda task: not task.sale_line_id)
|
||||
non_allow_billable_tasks = self.project_id.tasks.filtered(lambda task: task.non_allow_billable)
|
||||
|
||||
map_entries = self.env['project.sale.line.employee.map']
|
||||
EmployeeMap = self.env['project.sale.line.employee.map'].sudo()
|
||||
|
||||
# create SO lines: create on SOL per product/price. So many employee can be linked to the same SOL
|
||||
map_product_price_sol = {} # (product_id, price) --> SOL
|
||||
budgeted_qty = 0
|
||||
for wizard_line in self.line_ids:
|
||||
map_key = (wizard_line.product_id.id, wizard_line.price_unit)
|
||||
if map_key not in map_product_price_sol:
|
||||
values = {
|
||||
'order_id': sale_order.id,
|
||||
'product_id': wizard_line.product_id.id,
|
||||
'price_unit': wizard_line.price_unit,
|
||||
'product_uom_qty': wizard_line.budgeted_qty,
|
||||
}
|
||||
budgeted_qty += wizard_line.budgeted_qty
|
||||
if wizard_line.product_id.service_tracking in ['task_in_project', 'task_global_project']:
|
||||
values['task_id'] = task_id
|
||||
if wizard_line.product_id.service_tracking in ['task_in_project', 'project_only']:
|
||||
values['project_id'] = project_id
|
||||
sale_order_line = self.env['sale.order.line'].create(values)
|
||||
map_product_price_sol[map_key] = sale_order_line
|
||||
|
||||
if wizard_line.employee_id.id not in lines_already_present:
|
||||
map_entries |= EmployeeMap.create({
|
||||
'project_id': self.project_id.id,
|
||||
'sale_line_id': map_product_price_sol[map_key].id,
|
||||
'employee_id': wizard_line.employee_id.id,
|
||||
})
|
||||
else:
|
||||
map_entries |= lines_already_present[wizard_line.employee_id.id]
|
||||
lines_already_present[wizard_line.employee_id.id].write({
|
||||
'sale_line_id': map_product_price_sol[map_key].id
|
||||
})
|
||||
|
||||
# link the project to the SO
|
||||
self.project_id.write({
|
||||
'sale_order_id': sale_order.id,
|
||||
'sale_line_id': sale_order.order_line[0].id,
|
||||
'partner_id': self.partner_id.id,
|
||||
})
|
||||
if self.project_id.budgeted_hours <= 0:
|
||||
self.project_id.write({
|
||||
'budgeted_hours': budgeted_qty,
|
||||
})
|
||||
non_billable_tasks.write({
|
||||
'partner_id': sale_order.partner_id.id,
|
||||
'email_from': sale_order.partner_id.email,
|
||||
})
|
||||
non_allow_billable_tasks.sale_line_id = False
|
||||
|
||||
tasks = self.project_id.tasks.filtered(lambda t: not t.non_allow_billable)
|
||||
# assign SOL to timesheets
|
||||
for map_entry in map_entries:
|
||||
search_domain = [('employee_id', '=', map_entry.employee_id.id), ('so_line', '=', False)]
|
||||
ticket_timesheet_ids = self.env.context.get('ticket_timesheet_ids', [])
|
||||
if ticket_timesheet_ids:
|
||||
search_domain.append(('id', 'in', ticket_timesheet_ids))
|
||||
else:
|
||||
search_domain.append(('task_id', 'in', tasks.ids))
|
||||
self.env['account.analytic.line'].search(search_domain).write({
|
||||
'so_line': map_entry.sale_line_id.id
|
||||
})
|
||||
#map_entry.sale_line_id.with_context({'no_update_planned_hours': True}).write({
|
||||
# 'product_uom_qty': map_entry.sale_line_id.qty_delivered
|
||||
#})
|
||||
|
||||
return map_entries
|
||||
|
||||
|
||||
class ProjectCreateSalesOrderLine(models.TransientModel):
|
||||
_inherit= 'project.create.sale.order.line'
|
||||
|
||||
|
||||
employee_id = fields.Many2one('hr.employee', string="Consultant", help="Consultant that has timesheets on the project.")
|
||||
budgeted_qty = fields.Float(string='Budgeted Hour', digits='Product Unit of Measure', default=1.0)
|
||||
budgeted_uom = fields.Many2one('uom.uom', string='Budgeted UOM', related='product_id.uom_id', readonly=True)
|
||||
employee_price = fields.Float("Consultant Price")
|
||||
price_unit = fields.Float("Hourly rate", help="Unit price of the sales order item.")
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="project_create_sale_order_inherit1_view_form" model="ir.ui.view">
|
||||
<field name="name">project.create.sale.order.wizard.form.budgeted</field>
|
||||
<field name="model">project.create.sale.order</field>
|
||||
<field name="inherit_id" ref="sale_timesheet.project_create_sale_order_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='line_ids']//tree//field[@name='product_id']" position="after">
|
||||
<field name="budgeted_qty" string="Budgeted Hour"/> <!-- attrs="{'column_invisible': [('parent.pricing_type','=','fixed_rate')]}" -->
|
||||
<!--<field name="budgeted_uom" attrs="{'column_invisible': [('parent.pricing_type','=','fixed_rate')]}"/>-->
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
|
@ -0,0 +1,103 @@
|
|||
============
|
||||
Mail Debrand
|
||||
============
|
||||
|
||||
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png
|
||||
:target: https://odoo-community.org/page/development-status
|
||||
:alt: Production/Stable
|
||||
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
|
||||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||
:alt: License: AGPL-3
|
||||
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github
|
||||
:target: https://github.com/OCA/social/tree/13.0/mail_debrand
|
||||
:alt: OCA/social
|
||||
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
|
||||
:target: https://translation.odoo-community.org/projects/social-13-0/social-13-0-mail_debrand
|
||||
:alt: Translate me on Weblate
|
||||
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
|
||||
:target: https://runbot.odoo-community.org/runbot/205/13.0
|
||||
:alt: Try me on Runbot
|
||||
|
||||
|badge1| |badge2| |badge3| |badge4| |badge5|
|
||||
|
||||
This module modifies the functionality of emails to remove the Odoo branding,
|
||||
specifically the 'using Odoo' of notifications or the 'Powered by Odoo'
|
||||
|
||||
**Table of contents**
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
Usage
|
||||
=====
|
||||
|
||||
To use this module, you need to:
|
||||
|
||||
* Install it.
|
||||
* Send an email.
|
||||
* Nobody will know it comes from Odoo.
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
12.0.1.0.0 (2018-11-06)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* [NEW] Initial V12 version. Complete rewrite from v11.
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/social/issues>`_.
|
||||
In case of trouble, please check there if your issue has already been reported.
|
||||
If you spotted it first, help us smashing it by providing a detailed and welcomed
|
||||
`feedback <https://github.com/OCA/social/issues/new?body=module:%20mail_debrand%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||
|
||||
Do not contact contributors directly about support or help with technical issues.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
||||
Authors
|
||||
~~~~~~~
|
||||
|
||||
* Tecnativa
|
||||
* Eficent
|
||||
* Onestein
|
||||
|
||||
Contributors
|
||||
~~~~~~~~~~~~
|
||||
|
||||
* Pedro M. Baeza <pedro.baeza@tecnativa.com>
|
||||
* Lois Rilo <lois.rilo@eficent.com>
|
||||
* Graeme Gellatly <graeme@o4sb.com>
|
||||
|
||||
Maintainers
|
||||
~~~~~~~~~~~
|
||||
|
||||
This module is maintained by the OCA.
|
||||
|
||||
.. image:: https://odoo-community.org/logo.png
|
||||
:alt: Odoo Community Association
|
||||
:target: https://odoo-community.org
|
||||
|
||||
OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
mission is to support the collaborative development of Odoo features and
|
||||
promote its widespread use.
|
||||
|
||||
.. |maintainer-pedrobaeza| image:: https://github.com/pedrobaeza.png?size=40px
|
||||
:target: https://github.com/pedrobaeza
|
||||
:alt: pedrobaeza
|
||||
|
||||
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|
||||
|
||||
|maintainer-pedrobaeza|
|
||||
|
||||
This module is part of the `OCA/social <https://github.com/OCA/social/tree/13.0/mail_debrand>`_ project on GitHub.
|
||||
|
||||
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
|
|
@ -0,0 +1 @@
|
|||
from . import models
|
|
@ -0,0 +1,19 @@
|
|||
# Copyright 2016 Tecnativa - Jairo Llopis
|
||||
# Copyright 2017 Tecnativa - Pedro M. Baeza
|
||||
# Copyright 2019 Eficent Business and IT Consulting Services S.L.
|
||||
# - Lois Rilo <lois.rilo@eficent.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
{
|
||||
"name": "Mail Debrand",
|
||||
"summary": "Remove Odoo branding in sent emails",
|
||||
"version": "13.0.2.0.1",
|
||||
"category": "Social Network",
|
||||
"website": "https://github.com/OCA/social/",
|
||||
"author": "Tecnativa, Eficent, Onestein, Odoo Community Association (OCA)",
|
||||
"license": "AGPL-3",
|
||||
"installable": True,
|
||||
"depends": ["mail"],
|
||||
"development_status": "Production/Stable",
|
||||
"maintainers": ["pedrobaeza"],
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * mail_debrand
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 13.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2020-08-12 17:59+0000\n"
|
||||
"Last-Translator: Weblate Admin <weblate@odoo-community.org>\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 3.10\n"
|
||||
|
||||
#. module: mail_debrand
|
||||
#: model:ir.model,name:mail_debrand.model_mail_template
|
||||
msgid "Email Templates"
|
||||
msgstr "Modèles d'emails"
|
||||
|
||||
#. module: mail_debrand
|
||||
#: model:ir.model,name:mail_debrand.model_mail_thread
|
||||
msgid "Email Thread"
|
||||
msgstr "Discussion par email"
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "Odoo"
|
||||
msgstr "Odoo"
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "Powered by"
|
||||
msgstr "Propulsé par"
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "using"
|
||||
msgstr "utilisant"
|
|
@ -0,0 +1,42 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * mail_debrand
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 13.0\n"
|
||||
"Report-Msgid-Bugs-To: \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: mail_debrand
|
||||
#: model:ir.model,name:mail_debrand.model_mail_template
|
||||
msgid "Email Templates"
|
||||
msgstr ""
|
||||
|
||||
#. module: mail_debrand
|
||||
#: model:ir.model,name:mail_debrand.model_mail_thread
|
||||
msgid "Email Thread"
|
||||
msgstr ""
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "Odoo"
|
||||
msgstr ""
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "Powered by"
|
||||
msgstr ""
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "using"
|
||||
msgstr ""
|
|
@ -0,0 +1,45 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * mail_debrand
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 13.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2020-04-30 11:19+0000\n"
|
||||
"Last-Translator: Raf Ven <raf.ven@dynapps.be>\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: nl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 3.10\n"
|
||||
|
||||
#. module: mail_debrand
|
||||
#: model:ir.model,name:mail_debrand.model_mail_template
|
||||
msgid "Email Templates"
|
||||
msgstr ""
|
||||
|
||||
#. module: mail_debrand
|
||||
#: model:ir.model,name:mail_debrand.model_mail_thread
|
||||
msgid "Email Thread"
|
||||
msgstr ""
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "Odoo"
|
||||
msgstr ""
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "Powered by"
|
||||
msgstr "Aangeboden door"
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "using"
|
||||
msgstr ""
|
|
@ -0,0 +1,45 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * mail_debrand
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 13.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2020-03-02 17:13+0000\n"
|
||||
"Last-Translator: Pedro Castro Silva <pedrocs@exo.pt>\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: pt\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=2; plural=n > 1;\n"
|
||||
"X-Generator: Weblate 3.10\n"
|
||||
|
||||
#. module: mail_debrand
|
||||
#: model:ir.model,name:mail_debrand.model_mail_template
|
||||
msgid "Email Templates"
|
||||
msgstr "Modelos de Email"
|
||||
|
||||
#. module: mail_debrand
|
||||
#: model:ir.model,name:mail_debrand.model_mail_thread
|
||||
msgid "Email Thread"
|
||||
msgstr "Thread do email"
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "Odoo"
|
||||
msgstr ""
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "Powered by"
|
||||
msgstr ""
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "using"
|
||||
msgstr ""
|
|
@ -0,0 +1,46 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * mail_debrand
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 13.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2020-03-19 10:13+0000\n"
|
||||
"Last-Translator: Matjaz Mozetic <matjaz@matmoz.si>\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: sl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n"
|
||||
"%100==4 ? 2 : 3;\n"
|
||||
"X-Generator: Weblate 3.10\n"
|
||||
|
||||
#. module: mail_debrand
|
||||
#: model:ir.model,name:mail_debrand.model_mail_template
|
||||
msgid "Email Templates"
|
||||
msgstr "Predloge e-pošte"
|
||||
|
||||
#. module: mail_debrand
|
||||
#: model:ir.model,name:mail_debrand.model_mail_thread
|
||||
msgid "Email Thread"
|
||||
msgstr "E-poštna nit"
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "Odoo"
|
||||
msgstr "Odoo"
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "Powered by"
|
||||
msgstr "Powered by"
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "using"
|
||||
msgstr "z uporabo"
|
|
@ -0,0 +1,44 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * mail_debrand
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 13.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: sr_Latn\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
|
||||
|
||||
#. module: mail_debrand
|
||||
#: model:ir.model,name:mail_debrand.model_mail_template
|
||||
msgid "Email Templates"
|
||||
msgstr ""
|
||||
|
||||
#. module: mail_debrand
|
||||
#: model:ir.model,name:mail_debrand.model_mail_thread
|
||||
msgid "Email Thread"
|
||||
msgstr ""
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "Odoo"
|
||||
msgstr ""
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "Powered by"
|
||||
msgstr ""
|
||||
|
||||
#. module: mail_debrand
|
||||
#: code:addons/mail_debrand/models/mail_template.py:0
|
||||
#, python-format
|
||||
msgid "using"
|
||||
msgstr ""
|
|
@ -0,0 +1,2 @@
|
|||
from . import mail_template
|
||||
from . import mail_thread
|
|
@ -0,0 +1,51 @@
|
|||
# Copyright 2019 O4SB - Graeme Gellatly
|
||||
# Copyright 2019 Tecnativa - Ernesto Tejeda
|
||||
# Copyright 2020 Onestein - Andrea Stirpe
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
import re
|
||||
|
||||
from lxml import html as htmltree
|
||||
|
||||
from odoo import _, api, models
|
||||
|
||||
|
||||
class MailTemplate(models.Model):
|
||||
_inherit = "mail.template"
|
||||
|
||||
@api.model
|
||||
def _debrand_translated_words(self):
|
||||
def _get_translated(word):
|
||||
return self.env["ir.translation"]._get_source(
|
||||
"ir.ui.view,arch_db", "model_terms", self.env.lang, word
|
||||
)
|
||||
|
||||
odoo_word = _get_translated("Odoo") or _("Odoo")
|
||||
powered_by = _get_translated("Powered by") or _("Powered by")
|
||||
using_word = _get_translated("using") or _("using")
|
||||
return odoo_word, powered_by, using_word
|
||||
|
||||
@api.model
|
||||
def _debrand_body(self, html):
|
||||
odoo_word, powered_by, using_word = self._debrand_translated_words()
|
||||
html = re.sub(using_word + "(.*)[\r\n]*(.*)>" + odoo_word + r"</a>", "", html)
|
||||
if powered_by not in html:
|
||||
return html
|
||||
root = htmltree.fromstring(html)
|
||||
powered_by_elements = root.xpath("//*[text()[contains(.,'%s')]]" % powered_by)
|
||||
for elem in powered_by_elements:
|
||||
# make sure it isn't a spurious powered by
|
||||
if any(
|
||||
[
|
||||
"www.odoo.com" in child.get("href", "")
|
||||
for child in elem.getchildren()
|
||||
]
|
||||
):
|
||||
for child in elem.getchildren():
|
||||
elem.remove(child)
|
||||
elem.text = None
|
||||
return htmltree.tostring(root).decode("utf-8")
|
||||
|
||||
@api.model
|
||||
def render_post_process(self, html):
|
||||
html = super().render_post_process(html)
|
||||
return self._debrand_body(html)
|
|
@ -0,0 +1,14 @@
|
|||
# Copyright 2019 Eficent Business and IT Consulting Services S.L.
|
||||
# Lois Rilo <lois.rilo@eficent.com>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo import models
|
||||
|
||||
|
||||
class MailThread(models.AbstractModel):
|
||||
_inherit = "mail.thread"
|
||||
|
||||
def _replace_local_links(self, html, base_url=None):
|
||||
html = super()._replace_local_links(html, base_url=base_url)
|
||||
html_debranded = self.env["mail.template"]._debrand_body(html)
|
||||
return html_debranded
|
|
@ -0,0 +1,3 @@
|
|||
* Pedro M. Baeza <pedro.baeza@tecnativa.com>
|
||||
* Lois Rilo <lois.rilo@eficent.com>
|
||||
* Graeme Gellatly <graeme@o4sb.com>
|
|
@ -0,0 +1,2 @@
|
|||
This module modifies the functionality of emails to remove the Odoo branding,
|
||||
specifically the 'using Odoo' of notifications or the 'Powered by Odoo'
|
|
@ -0,0 +1,4 @@
|
|||
12.0.1.0.0 (2018-11-06)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* [NEW] Initial V12 version. Complete rewrite from v11.
|
|
@ -0,0 +1,5 @@
|
|||
To use this module, you need to:
|
||||
|
||||
* Install it.
|
||||
* Send an email.
|
||||
* Nobody will know it comes from Odoo.
|
After Width: | Height: | Size: 5.2 KiB |
|
@ -0,0 +1,248 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 128 128"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="icon.svg"
|
||||
width="128"
|
||||
height="128"
|
||||
inkscape:export-filename="icon.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<metadata
|
||||
id="metadata128">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1855"
|
||||
inkscape:window-height="1176"
|
||||
id="namedview126"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.4583592"
|
||||
inkscape:cx="-40.114514"
|
||||
inkscape:cy="5.0469316"
|
||||
inkscape:window-x="65"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
id="linearGradient5060">
|
||||
<stop
|
||||
offset="0"
|
||||
id="stop7" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-opacity="0"
|
||||
id="stop9" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient5027"
|
||||
x2="0"
|
||||
y1="-150.69685"
|
||||
y2="327.6604">
|
||||
<stop
|
||||
offset="0"
|
||||
stop-opacity="0"
|
||||
id="stop12" />
|
||||
<stop
|
||||
offset=".5"
|
||||
id="stop14" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-opacity="0"
|
||||
id="stop16" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="matrix(2.9186598,0,0,2.6345737,-4.8428991,-17.303404)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient6738"
|
||||
x1="16.25"
|
||||
x2="31.5"
|
||||
y1="12.25"
|
||||
y2="36.625">
|
||||
<stop
|
||||
offset="0"
|
||||
stop-color="#fff"
|
||||
id="stop19" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#ddd"
|
||||
id="stop21" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient6954"
|
||||
x2="0"
|
||||
y1="29.22382"
|
||||
y2="35.47382">
|
||||
<stop
|
||||
offset="0"
|
||||
stop-color="#fff"
|
||||
id="stop24" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#c9c9c9"
|
||||
id="stop26" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient6956"
|
||||
x1="26.504271"
|
||||
x2="28.364229"
|
||||
xlink:href="#linearGradient5060"
|
||||
y1="35.819832"
|
||||
y2="36.569832"
|
||||
gradientTransform="translate(5.9124945,0.99230678)" />
|
||||
<linearGradient
|
||||
gradientTransform="matrix(-0.97769,0.21008,0.21008,0.97769,61.56807,-4.44833)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient6958"
|
||||
x1="22.686769"
|
||||
x2="21.408461"
|
||||
xlink:href="#linearGradient5060"
|
||||
y1="36.3904"
|
||||
y2="35.739632" />
|
||||
<radialGradient
|
||||
cx="55"
|
||||
cy="125"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient278"
|
||||
r="14.375">
|
||||
<stop
|
||||
offset="0"
|
||||
stop-color="#fff"
|
||||
id="stop31" />
|
||||
<stop
|
||||
offset=".5"
|
||||
stop-color="#fff520"
|
||||
stop-opacity=".89109"
|
||||
id="stop33" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#fff300"
|
||||
stop-opacity="0"
|
||||
id="stop35" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
cx="16.214741"
|
||||
cy="19.83647"
|
||||
gradientTransform="matrix(1,0,0,0.68192,19.76231,14.9041)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient6948"
|
||||
r="13.56536">
|
||||
<stop
|
||||
offset="0"
|
||||
stop-color="#727e0a"
|
||||
id="stop38" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#5b6508"
|
||||
id="stop40" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
cx="29.344931"
|
||||
cy="17.064079"
|
||||
gradientTransform="matrix(0.788,0,0,0.788,6.2212,3.61763)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient6952"
|
||||
r="9.1620598">
|
||||
<stop
|
||||
offset="0"
|
||||
stop-color="#e9b15e"
|
||||
id="stop43" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="#966416"
|
||||
id="stop45" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
cx="605.71429"
|
||||
cy="486.64789"
|
||||
gradientTransform="matrix(-2.77439,0,0,1.96971,112.7623,-872.8854)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient5031"
|
||||
r="117.14286"
|
||||
xlink:href="#linearGradient5060" />
|
||||
<radialGradient
|
||||
cx="605.71429"
|
||||
cy="486.64789"
|
||||
gradientTransform="matrix(2.77439,0,0,1.96971,-1891.633,-872.8854)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient5029"
|
||||
r="117.14286"
|
||||
xlink:href="#linearGradient5060" />
|
||||
<radialGradient
|
||||
cx="31.1127"
|
||||
cy="19.008619"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient6950"
|
||||
r="8.6620598"
|
||||
xlink:href="#linearGradient5060" />
|
||||
</defs>
|
||||
<g
|
||||
transform="matrix(2.1385661,0,0,2.1385661,-36.355623,89.820203)"
|
||||
inkscape:label="Calque 1"
|
||||
id="layer1">
|
||||
<path
|
||||
transform="translate(13,-50)"
|
||||
d="m 10,8 c -3.3137,0 -6,2.6822 -6,6 0.074392,9.0983 1.952e-4,18.727 0,28 0,3.1932 2.4839,5.7776 5.625,5.9688 l 44.625,0.031 c 3.199,-0.129 5.75,-2.765 5.75,-6 l 0,-29.031 c -0.485,-2.829 -2.941,-4.969 -5.906,-4.969 -14.69,0.0067 -29.398,1e-4 -44.094,0 z m -0.09375,3.1562 44.375,0 -21.469,21.469 c -0.39174,0.39174 -1.0145,0.39174 -1.4062,0 l -21.5,-21.469 z m 47.094,1.532 0,29.093 c 0,1.7334 -0.6245,2.7125 -1.875,3.0625 l -15.125,-15.156 17,-17 z m -49.656,0.124 16.718,16.719 -15.281,15.281 c -1.046,-0.353 -1.4101,-1.195 -1.4372,-2.343 l 0,-29.656 z m 18.844,18.812 5.2188,5.25 c 0.39174,0.39174 1.0145,0.39174 1.4062,0 l 5.062,-5.062 13.187,13.188 -38.25,0 13.375,-13.375 z"
|
||||
sodipodi:nodetypes="ccscccccccccccccccccccccccccccc"
|
||||
style="fill:#000000"
|
||||
id="rect3174"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(0.26759984,0,0,-0.26759984,62.366861,118.02764)"
|
||||
inkscape:label="ink_ext_XXXXXX"
|
||||
id="g10">
|
||||
<path
|
||||
d="m 104.00014,165.45424 c -8.313043,0 -16.263246,-1.57861 -23.850744,-4.72609 -7.594015,-3.15718 -14.181554,-7.52151 -19.768717,-13.10966 -5.587026,-5.58537 -10.031894,-12.10596 -13.321854,-19.55278 -3.297028,-7.45376 -4.942836,-15.61837 -4.942836,-24.4966 0,-8.312905 1.645808,-16.26269 4.942836,-23.850746 3.28996,-7.594012 7.734828,-14.256255 13.321854,-19.984093 5.587163,-5.733797 12.174702,-10.246299 19.768717,-13.536809 7.587498,-3.297027 15.537701,-4.941729 23.850744,-4.941729 8.30583,0 16.2577,1.644702 23.85171,4.941729 7.5875,3.29051 14.18128,7.803012 19.76942,13.536809 5.58675,5.727838 10.02454,12.390081 13.32171,19.984093 3.29023,7.588056 4.94089,15.537841 4.94089,23.850746 -0.85928,16.90258 -6.87567,31.15898 -18.04779,42.76184 -11.17419,11.60314 -25.6454,17.97571 -43.40561,19.12329 l -0.43033,0 z M 104.43047,208 c 14.03437,0 27.28859,-2.79405 39.75226,-8.38083 12.46244,-5.58534 23.4211,-13.10687 32.87602,-22.5618 9.45355,-9.45492 16.97508,-20.48705 22.56183,-33.09085 C 205.20733,131.3558 208,117.89258 208,103.56911 208,89.239815 205.27385,75.85019 199.83679,63.387487 194.39,50.925336 187.01124,39.966662 177.70323,30.511325 168.38966,21.056678 157.431,13.602838 144.82719,8.1646355 132.21703,2.726419 118.61203,0 104.00014,0 89.671258,0 76.208041,2.5780813 63.603558,7.7347705 50.993109,12.892 39.96777,20.123798 30.512431,29.437215 21.058202,38.744259 13.605015,49.702934 8.1651761,62.313379 2.7204595,74.917448 0,88.668665 0,103.56911 c 0,14.32347 2.6462976,27.71892 7.9502436,40.1826 5.2990814,12.46381 12.6781754,23.34765 22.1335154,32.66119 9.453538,9.30801 20.553026,16.76025 33.305391,22.347 12.744188,5.58675 26.429298,8.66218 41.04132,9.2401"
|
||||
style="fill:#a2478a;fill-opacity:1;fill-rule:nonzero;stroke:none"
|
||||
id="path14"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="g4250"
|
||||
transform="matrix(0.16801262,0,0,0.16801262,85.012954,66.157096)">
|
||||
<ellipse
|
||||
style="fill:none;stroke:#ff0000;stroke-width:50.00000381"
|
||||
id="path3315"
|
||||
cx="30.856556"
|
||||
cy="143.08545"
|
||||
rx="199.9995"
|
||||
ry="199.99942" />
|
||||
<path
|
||||
d="m -114.84995,20.228255 297.14,260.000005"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ff0000;stroke:#ff0000;stroke-width:50"
|
||||
id="path4089" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.9 KiB |
|
@ -0,0 +1,449 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" />
|
||||
<title>Mail Debrand</title>
|
||||
<style type="text/css">
|
||||
|
||||
/*
|
||||
:Author: David Goodger (goodger@python.org)
|
||||
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
|
||||
:Copyright: This stylesheet has been placed in the public domain.
|
||||
|
||||
Default cascading style sheet for the HTML output of Docutils.
|
||||
|
||||
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
|
||||
customize this style sheet.
|
||||
*/
|
||||
|
||||
/* used to remove borders from tables and images */
|
||||
.borderless, table.borderless td, table.borderless th {
|
||||
border: 0 }
|
||||
|
||||
table.borderless td, table.borderless th {
|
||||
/* Override padding for "table.docutils td" with "! important".
|
||||
The right padding separates the table cells. */
|
||||
padding: 0 0.5em 0 0 ! important }
|
||||
|
||||
.first {
|
||||
/* Override more specific margin styles with "! important". */
|
||||
margin-top: 0 ! important }
|
||||
|
||||
.last, .with-subtitle {
|
||||
margin-bottom: 0 ! important }
|
||||
|
||||
.hidden {
|
||||
display: none }
|
||||
|
||||
.subscript {
|
||||
vertical-align: sub;
|
||||
font-size: smaller }
|
||||
|
||||
.superscript {
|
||||
vertical-align: super;
|
||||
font-size: smaller }
|
||||
|
||||
a.toc-backref {
|
||||
text-decoration: none ;
|
||||
color: black }
|
||||
|
||||
blockquote.epigraph {
|
||||
margin: 2em 5em ; }
|
||||
|
||||
dl.docutils dd {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Uncomment (and remove this text!) to get bold-faced definition list terms
|
||||
dl.docutils dt {
|
||||
font-weight: bold }
|
||||
*/
|
||||
|
||||
div.abstract {
|
||||
margin: 2em 5em }
|
||||
|
||||
div.abstract p.topic-title {
|
||||
font-weight: bold ;
|
||||
text-align: center }
|
||||
|
||||
div.admonition, div.attention, div.caution, div.danger, div.error,
|
||||
div.hint, div.important, div.note, div.tip, div.warning {
|
||||
margin: 2em ;
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.admonition p.admonition-title, div.hint p.admonition-title,
|
||||
div.important p.admonition-title, div.note p.admonition-title,
|
||||
div.tip p.admonition-title {
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
div.attention p.admonition-title, div.caution p.admonition-title,
|
||||
div.danger p.admonition-title, div.error p.admonition-title,
|
||||
div.warning p.admonition-title, .code .error {
|
||||
color: red ;
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
/* Uncomment (and remove this text!) to get reduced vertical space in
|
||||
compound paragraphs.
|
||||
div.compound .compound-first, div.compound .compound-middle {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
div.compound .compound-last, div.compound .compound-middle {
|
||||
margin-top: 0.5em }
|
||||
*/
|
||||
|
||||
div.dedication {
|
||||
margin: 2em 5em ;
|
||||
text-align: center ;
|
||||
font-style: italic }
|
||||
|
||||
div.dedication p.topic-title {
|
||||
font-weight: bold ;
|
||||
font-style: normal }
|
||||
|
||||
div.figure {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
div.footer, div.header {
|
||||
clear: both;
|
||||
font-size: smaller }
|
||||
|
||||
div.line-block {
|
||||
display: block ;
|
||||
margin-top: 1em ;
|
||||
margin-bottom: 1em }
|
||||
|
||||
div.line-block div.line-block {
|
||||
margin-top: 0 ;
|
||||
margin-bottom: 0 ;
|
||||
margin-left: 1.5em }
|
||||
|
||||
div.sidebar {
|
||||
margin: 0 0 0.5em 1em ;
|
||||
border: medium outset ;
|
||||
padding: 1em ;
|
||||
background-color: #ffffee ;
|
||||
width: 40% ;
|
||||
float: right ;
|
||||
clear: right }
|
||||
|
||||
div.sidebar p.rubric {
|
||||
font-family: sans-serif ;
|
||||
font-size: medium }
|
||||
|
||||
div.system-messages {
|
||||
margin: 5em }
|
||||
|
||||
div.system-messages h1 {
|
||||
color: red }
|
||||
|
||||
div.system-message {
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.system-message p.system-message-title {
|
||||
color: red ;
|
||||
font-weight: bold }
|
||||
|
||||
div.topic {
|
||||
margin: 2em }
|
||||
|
||||
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
|
||||
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
|
||||
margin-top: 0.4em }
|
||||
|
||||
h1.title {
|
||||
text-align: center }
|
||||
|
||||
h2.subtitle {
|
||||
text-align: center }
|
||||
|
||||
hr.docutils {
|
||||
width: 75% }
|
||||
|
||||
img.align-left, .figure.align-left, object.align-left, table.align-left {
|
||||
clear: left ;
|
||||
float: left ;
|
||||
margin-right: 1em }
|
||||
|
||||
img.align-right, .figure.align-right, object.align-right, table.align-right {
|
||||
clear: right ;
|
||||
float: right ;
|
||||
margin-left: 1em }
|
||||
|
||||
img.align-center, .figure.align-center, object.align-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
table.align-center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left }
|
||||
|
||||
.align-center {
|
||||
clear: both ;
|
||||
text-align: center }
|
||||
|
||||
.align-right {
|
||||
text-align: right }
|
||||
|
||||
/* reset inner alignment in figures */
|
||||
div.align-right {
|
||||
text-align: inherit }
|
||||
|
||||
/* div.align-center * { */
|
||||
/* text-align: left } */
|
||||
|
||||
.align-top {
|
||||
vertical-align: top }
|
||||
|
||||
.align-middle {
|
||||
vertical-align: middle }
|
||||
|
||||
.align-bottom {
|
||||
vertical-align: bottom }
|
||||
|
||||
ol.simple, ul.simple {
|
||||
margin-bottom: 1em }
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal }
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha }
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha }
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman }
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman }
|
||||
|
||||
p.attribution {
|
||||
text-align: right ;
|
||||
margin-left: 50% }
|
||||
|
||||
p.caption {
|
||||
font-style: italic }
|
||||
|
||||
p.credits {
|
||||
font-style: italic ;
|
||||
font-size: smaller }
|
||||
|
||||
p.label {
|
||||
white-space: nowrap }
|
||||
|
||||
p.rubric {
|
||||
font-weight: bold ;
|
||||
font-size: larger ;
|
||||
color: maroon ;
|
||||
text-align: center }
|
||||
|
||||
p.sidebar-title {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold ;
|
||||
font-size: larger }
|
||||
|
||||
p.sidebar-subtitle {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
p.topic-title {
|
||||
font-weight: bold }
|
||||
|
||||
pre.address {
|
||||
margin-bottom: 0 ;
|
||||
margin-top: 0 ;
|
||||
font: inherit }
|
||||
|
||||
pre.literal-block, pre.doctest-block, pre.math, pre.code {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
pre.code .ln { color: grey; } /* line numbers */
|
||||
pre.code, code { background-color: #eeeeee }
|
||||
pre.code .comment, code .comment { color: #5C6576 }
|
||||
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
|
||||
pre.code .literal.string, code .literal.string { color: #0C5404 }
|
||||
pre.code .name.builtin, code .name.builtin { color: #352B84 }
|
||||
pre.code .deleted, code .deleted { background-color: #DEB0A1}
|
||||
pre.code .inserted, code .inserted { background-color: #A3D289}
|
||||
|
||||
span.classifier {
|
||||
font-family: sans-serif ;
|
||||
font-style: oblique }
|
||||
|
||||
span.classifier-delimiter {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
span.interpreted {
|
||||
font-family: sans-serif }
|
||||
|
||||
span.option {
|
||||
white-space: nowrap }
|
||||
|
||||
span.pre {
|
||||
white-space: pre }
|
||||
|
||||
span.problematic {
|
||||
color: red }
|
||||
|
||||
span.section-subtitle {
|
||||
/* font-size relative to parent (h1..h6 element) */
|
||||
font-size: 80% }
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docinfo {
|
||||
margin: 2em 4em }
|
||||
|
||||
table.docutils {
|
||||
margin-top: 0.5em ;
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
table.footnote {
|
||||
border-left: solid 1px black;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docutils td, table.docutils th,
|
||||
table.docinfo td, table.docinfo th {
|
||||
padding-left: 0.5em ;
|
||||
padding-right: 0.5em ;
|
||||
vertical-align: top }
|
||||
|
||||
table.docutils th.field-name, table.docinfo th.docinfo-name {
|
||||
font-weight: bold ;
|
||||
text-align: left ;
|
||||
white-space: nowrap ;
|
||||
padding-left: 0 }
|
||||
|
||||
/* "booktabs" style (no vertical lines) */
|
||||
table.docutils.booktabs {
|
||||
border: 0px;
|
||||
border-top: 2px solid;
|
||||
border-bottom: 2px solid;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table.docutils.booktabs * {
|
||||
border: 0px;
|
||||
}
|
||||
table.docutils.booktabs th {
|
||||
border-bottom: thin solid;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
|
||||
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
|
||||
font-size: 100% }
|
||||
|
||||
ul.auto-toc {
|
||||
list-style-type: none }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="document" id="mail-debrand">
|
||||
<h1 class="title">Mail Debrand</h1>
|
||||
|
||||
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Production/Stable" src="https://img.shields.io/badge/maturity-Production%2FStable-green.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/social/tree/13.0/mail_debrand"><img alt="OCA/social" src="https://img.shields.io/badge/github-OCA%2Fsocial-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/social-13-0/social-13-0-mail_debrand"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/205/13.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
|
||||
<p>This module modifies the functionality of emails to remove the Odoo branding,
|
||||
specifically the ‘using Odoo’ of notifications or the ‘Powered by Odoo’</p>
|
||||
<p><strong>Table of contents</strong></p>
|
||||
<div class="contents local topic" id="contents">
|
||||
<ul class="simple">
|
||||
<li><a class="reference internal" href="#usage" id="id2">Usage</a></li>
|
||||
<li><a class="reference internal" href="#changelog" id="id3">Changelog</a><ul>
|
||||
<li><a class="reference internal" href="#id1" id="id4">12.0.1.0.0 (2018-11-06)</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#bug-tracker" id="id5">Bug Tracker</a></li>
|
||||
<li><a class="reference internal" href="#credits" id="id6">Credits</a><ul>
|
||||
<li><a class="reference internal" href="#authors" id="id7">Authors</a></li>
|
||||
<li><a class="reference internal" href="#contributors" id="id8">Contributors</a></li>
|
||||
<li><a class="reference internal" href="#maintainers" id="id9">Maintainers</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="usage">
|
||||
<h1><a class="toc-backref" href="#id2">Usage</a></h1>
|
||||
<p>To use this module, you need to:</p>
|
||||
<ul class="simple">
|
||||
<li>Install it.</li>
|
||||
<li>Send an email.</li>
|
||||
<li>Nobody will know it comes from Odoo.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="changelog">
|
||||
<h1><a class="toc-backref" href="#id3">Changelog</a></h1>
|
||||
<div class="section" id="id1">
|
||||
<h2><a class="toc-backref" href="#id4">12.0.1.0.0 (2018-11-06)</a></h2>
|
||||
<ul class="simple">
|
||||
<li>[NEW] Initial V12 version. Complete rewrite from v11.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
<h1><a class="toc-backref" href="#id5">Bug Tracker</a></h1>
|
||||
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/social/issues">GitHub Issues</a>.
|
||||
In case of trouble, please check there if your issue has already been reported.
|
||||
If you spotted it first, help us smashing it by providing a detailed and welcomed
|
||||
<a class="reference external" href="https://github.com/OCA/social/issues/new?body=module:%20mail_debrand%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
|
||||
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
||||
</div>
|
||||
<div class="section" id="credits">
|
||||
<h1><a class="toc-backref" href="#id6">Credits</a></h1>
|
||||
<div class="section" id="authors">
|
||||
<h2><a class="toc-backref" href="#id7">Authors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Tecnativa</li>
|
||||
<li>Eficent</li>
|
||||
<li>Onestein</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="contributors">
|
||||
<h2><a class="toc-backref" href="#id8">Contributors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Pedro M. Baeza <<a class="reference external" href="mailto:pedro.baeza@tecnativa.com">pedro.baeza@tecnativa.com</a>></li>
|
||||
<li>Lois Rilo <<a class="reference external" href="mailto:lois.rilo@eficent.com">lois.rilo@eficent.com</a>></li>
|
||||
<li>Graeme Gellatly <<a class="reference external" href="mailto:graeme@o4sb.com">graeme@o4sb.com</a>></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="maintainers">
|
||||
<h2><a class="toc-backref" href="#id9">Maintainers</a></h2>
|
||||
<p>This module is maintained by the OCA.</p>
|
||||
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
|
||||
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
mission is to support the collaborative development of Odoo features and
|
||||
promote its widespread use.</p>
|
||||
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
|
||||
<p><a class="reference external" href="https://github.com/pedrobaeza"><img alt="pedrobaeza" src="https://github.com/pedrobaeza.png?size=40px" /></a></p>
|
||||
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/social/tree/13.0/mail_debrand">OCA/social</a> project on GitHub.</p>
|
||||
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1 @@
|
|||
from . import test_mail_debrand
|
|
@ -0,0 +1,38 @@
|
|||
# Copyright 2017 Tecnativa - Pedro M. Baeza
|
||||
# Copyright 2020 Onestein - Andrea Stirpe
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from odoo.tests import common
|
||||
from odoo.tools.misc import mute_logger
|
||||
|
||||
|
||||
class TestMailDebrand(common.TransactionCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.default_arch = self.env.ref("mail.message_notification_email").arch
|
||||
self.paynow_arch = self.env.ref("mail.mail_notification_paynow").arch
|
||||
|
||||
def test_default_debrand(self):
|
||||
self.assertIn("using", self.default_arch)
|
||||
res = self.env["mail.template"]._debrand_body(self.default_arch)
|
||||
self.assertNotIn("using", res)
|
||||
|
||||
def test_paynow_debrand(self):
|
||||
self.assertIn("Powered by", self.paynow_arch)
|
||||
res = self.env["mail.template"]._debrand_body(self.paynow_arch)
|
||||
self.assertNotIn("Powered by", res)
|
||||
|
||||
def test_lang_paynow_debrand(self):
|
||||
with mute_logger("odoo.addons.base.models.ir_translation"):
|
||||
self.env["base.language.install"].create(
|
||||
{"lang": "nl_NL", "overwrite": True}
|
||||
).lang_install()
|
||||
with mute_logger("odoo.tools.translate"):
|
||||
self.env["base.update.translations"].create({"lang": "nl_NL"}).act_update()
|
||||
|
||||
ctx = dict(lang="nl_NL")
|
||||
paynow_template = self.env.ref("mail.mail_notification_paynow")
|
||||
paynow_arch = paynow_template.with_context(ctx).arch
|
||||
self.assertIn("Aangeboden door", paynow_arch)
|
||||
res = self.env["mail.template"].with_context(ctx)._debrand_body(paynow_arch)
|
||||
self.assertNotIn("Aangeboden door", res)
|
|
@ -0,0 +1 @@
|
|||
from . import controllers
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
'name': 'odoo debranding',
|
||||
'version': '14.0',
|
||||
'summary': 'odoo debranding',
|
||||
'description': 'odoo debranding',
|
||||
'category': '',
|
||||
'author': '',
|
||||
'website': '',
|
||||
'license': '',
|
||||
'depends': ['base_setup', 'web'],
|
||||
'data': ['views/webclient_templates.xml'],
|
||||
'demo': [''],
|
||||
'qweb': ["static/src/xml/base.xml"],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from . import main
|
|
@ -0,0 +1,33 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import jinja2
|
||||
import json
|
||||
|
||||
import odoo
|
||||
from odoo import http
|
||||
from odoo.addons.web.controllers.main import DBNAME_PATTERN, db_monodb,\
|
||||
Database as DB
|
||||
|
||||
loader = jinja2.PackageLoader('odoo.addons.odoo-debranding',
|
||||
"views")
|
||||
env = jinja2.Environment(loader=loader, autoescape=True)
|
||||
env.filters["json"] = json.dumps
|
||||
|
||||
|
||||
class Database(DB):
|
||||
|
||||
def _render_template(self, **d):
|
||||
d.setdefault('manage', True)
|
||||
d['insecure'] = odoo.tools.config['admin_passwd'] == 'admin'
|
||||
d['list_db'] = odoo.tools.config['list_db']
|
||||
d['langs'] = odoo.service.db.exp_list_lang()
|
||||
d['countries'] = odoo.service.db.exp_list_countries()
|
||||
d['pattern'] = DBNAME_PATTERN
|
||||
# databases list
|
||||
d['databases'] = []
|
||||
try:
|
||||
d['databases'] = http.db_list()
|
||||
except odoo.exceptions.AccessDenied:
|
||||
monodb = db_monodb()
|
||||
if monodb:
|
||||
d['databases'] = [monodb]
|
||||
return env.get_template("database_manager.html").render(d)
|
|
@ -0,0 +1 @@
|
|||
from . import odoodebrand
|
|
@ -0,0 +1,17 @@
|
|||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import Warning
|
||||
|
||||
class WebsiteConfig(models.Model):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
company_logo = fields.Binary(related='website_id.company_logo',
|
||||
string="Company Logo",
|
||||
help="This field holds the image"
|
||||
" used for the Company Logo",
|
||||
readonly=False)
|
||||
company_name = fields.Char(related='website_id.company_name',
|
||||
string="Company Name",
|
||||
readonly=False)
|
||||
company_website = fields.Char(related='website_id.company_website',
|
||||
readonly=False)
|
||||
|
After Width: | Height: | Size: 24 KiB |
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
<t t-extend="mail.client_action">
|
||||
<t t-jquery=".o_mail_request_permission" t-operation="inner">
|
||||
Your permission is required to <a href="#"> enable desktop notifications</a>.
|
||||
</t>
|
||||
</t>
|
||||
<t t-extend="UserMenu.Actions">
|
||||
<t t-jquery="a[data-menu='documentation']" t-operation="replace">
|
||||
|
||||
</t>
|
||||
<t t-jquery="a[data-menu='support']" t-operation="replace">
|
||||
|
||||
</t>
|
||||
<!-- <t t-jquery="a[data-menu='about']" t-operation="replace"></t> -->
|
||||
<t t-jquery="a[data-menu='account']" t-operation="replace">
|
||||
|
||||
</t>
|
||||
<t t-jquery="li.divider" t-operation="replace">
|
||||
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="res_config_edition">
|
||||
<t t-jquery="div[id='edition']" t-operation="replace">
|
||||
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="res_config_dev_tool">
|
||||
<t t-jquery="div[id='developer_tool']" t-operation="replace"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
|
@ -0,0 +1,422 @@
|
|||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||
<title>Odoo</title>
|
||||
<link rel="shortcut icon" href="/odoo-debranding/static/src/img/image.png" type="image/x-icon">
|
||||
|
||||
<link rel="stylesheet" href="/web/static/lib/fontawesome/css/font-awesome.css">
|
||||
<link rel="stylesheet" href="/web/static/lib/bootstrap/css/bootstrap.css">
|
||||
|
||||
<script src="/web/static/lib/jquery/jquery.js" type="text/javascript"></script>
|
||||
|
||||
<script type="text/javascript" src="/web/static/lib/popper/popper.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/web/static/lib/bootstrap/js/index.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/bootstrap/js/util.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/bootstrap/js/alert.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/bootstrap/js/button.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/bootstrap/js/carousel.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/bootstrap/js/collapse.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/bootstrap/js/dropdown.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/bootstrap/js/modal.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/bootstrap/js/tooltip.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/bootstrap/js/popover.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/bootstrap/js/scrollspy.js"></script>
|
||||
<script type="text/javascript" src="/web/static/lib/bootstrap/js/tab.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
// Little eye
|
||||
$('body').on('mousedown', '.o_little_eye', function (ev) {
|
||||
$(ev.target).closest('.input-group').find('.form-control').prop("type",
|
||||
(i, old) => { return old === "text" ? "password" : "text"; }
|
||||
);
|
||||
});
|
||||
// db modal
|
||||
$('body').on('click', '.o_database_action', function (ev) {
|
||||
ev.preventDefault();
|
||||
var db = $(ev.currentTarget).data('db');
|
||||
var target = $(ev.currentTarget).data('target');
|
||||
$(target).find('input[name=name]').val(db);
|
||||
$(target).modal();
|
||||
});
|
||||
// close modal on submit
|
||||
$('.modal').on('submit', 'form', function (ev) {
|
||||
var form = $(this).closest('form')[0];
|
||||
if (form && form.checkValidity && !form.checkValidity()) {
|
||||
return;
|
||||
}
|
||||
var modal = $(this).parentsUntil('body', '.modal');
|
||||
if (modal.hasClass('o_database_backup')) {
|
||||
$(modal).modal('hide');
|
||||
if (!$('.alert-backup-long').length) {
|
||||
$('.list-group').before("<div class='alert alert-info alert-backup-long'>The backup may take some time before being ready</div>");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// generate a random master password
|
||||
// removed l1O0 to avoid confusions
|
||||
var charset = "abcdefghijkmnpqrstuvwxyz23456789";
|
||||
var password = "";
|
||||
for (var i = 0, n = charset.length; i < 12; ++i) {
|
||||
password += charset.charAt(Math.floor(Math.random() * n));
|
||||
if (i === 3 || i === 7) {
|
||||
password += "-";
|
||||
}
|
||||
}
|
||||
var master_pwds = document.getElementsByClassName("generated_master_pwd");
|
||||
for (var i=0, len=master_pwds.length|0; i<len; i=i+1|0) {
|
||||
master_pwds[i].innerText = password;
|
||||
}
|
||||
var master_pwd_inputs = document.getElementsByClassName("generated_master_pwd_input");
|
||||
for (var i=0, len=master_pwd_inputs.length|0; i<len; i=i+1|0) {
|
||||
master_pwd_inputs[i].value = password;
|
||||
master_pwd_inputs[i].setAttribute('autocomplete', 'new-password');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
{% macro master_input(set_master_pwd=False) -%}
|
||||
|
||||
{% set input_class = "form-control" %}
|
||||
{% if insecure %}
|
||||
{% if set_master_pwd %}
|
||||
<input type="hidden" name="master_pwd" class="form-control" value="admin"/>
|
||||
{% else %}
|
||||
<div class="alert alert-warning">
|
||||
<p>Warning, your Odoo database manager is not protected. To secure it, we have generated the following master password for it:</p>
|
||||
<p style="text-align: center;"><strong class="generated_master_pwd"></strong></p>
|
||||
<p>You can change it below but be sure to remember it, it will be asked for future operations on databases.</p>
|
||||
</div>
|
||||
{% set input_class = "form-control generated_master_pwd_input" %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if not insecure or not set_master_pwd %}
|
||||
<div class="form-group row">
|
||||
<label for="master_pwd" class="col-md-4 col-form-label">Master Password</label>
|
||||
<div class="col-md-8 input-group">
|
||||
<input name="master_pwd"
|
||||
class="{{ input_class}}"
|
||||
required="required"
|
||||
autofocus="autofocus"
|
||||
type="password"
|
||||
autocomplete="current-password" />
|
||||
<div class="input-group-append">
|
||||
<span class="fa fa-eye o_little_eye input-group-text" aria-hidden="true" style="cursor: pointer;"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro create_form() -%}
|
||||
{{ master_input() }}
|
||||
<div class="form-group row">
|
||||
<label for="name" class="col-md-4 col-form-label">Database Name</label>
|
||||
<div class="col-md-8">
|
||||
<input id="dbname" type="text" name="name" class="form-control" required="required" autocomplete="off" pattern="{{ pattern }}" title="Only alphanumerical characters, underscore, hyphen and dot are allowed"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="login" class="col-md-4 col-form-label">Email</label>
|
||||
<div class="col-md-8">
|
||||
<input id="login" type="text" name="login" class="form-control" required="required" autocomplete="off"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="password" class="col-md-4 col-form-label">Password</label>
|
||||
<div class="col-md-8 input-group">
|
||||
<input id="password" type="password" name="password" class="form-control" required="required" autocomplete="off"/>
|
||||
<div class="input-group-append">
|
||||
<span class="fa fa-eye o_little_eye input-group-text" aria-hidden="true" style="cursor: pointer;"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="phone" class="col-md-4 col-form-label">Phone number</label>
|
||||
<div class="col-md-8 input-group">
|
||||
<input id="phone" type="tel" name="phone" class="form-control" autocomplete="off"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="lang" class="col-md-4 col-form-label">Language</label>
|
||||
<div class="col-md-8">
|
||||
<select id="lang" name="lang" class="form-control" required="required" autocomplete="off">
|
||||
{% for lang in langs %}
|
||||
<option {% if lang[0] == "en_US" %}selected="selected" {% endif %}value="{{ lang[0] }}">{{ lang[1] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="country" class="col-md-4 col-form-label ">Country</label>
|
||||
<div class="col-md-8">
|
||||
<select id="country" name="country_code" class="form-control" autocomplete="off">
|
||||
<option value=""></option>
|
||||
{% for country in countries %}
|
||||
<option value="{{ country[0] }}">{{ country[1] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="demo" class="col-md-4 col-form-label">Demo data</label>
|
||||
<div class="col-md-8">
|
||||
<input type="checkbox" id="load_demo_checkbox" class="form-control-sm" name="demo" value="1">
|
||||
</div>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- Database List -->
|
||||
<div class="row">
|
||||
<div class="col-lg-6 offset-lg-3 o_database_list">
|
||||
<img src="/odoo-debranding/static/src/img/image.png" class="img-fluid d-block mx-auto"/>
|
||||
{% if not list_db %}
|
||||
<div class="alert alert-danger text-center">The database manager has been disabled by the administrator</div>
|
||||
{% elif insecure and databases %}
|
||||
<div class="alert alert-warning">
|
||||
Warning, your Odoo database manager is not protected.<br/>
|
||||
Please <a href="#" data-toggle="modal" data-target=".o_database_master">set a master password</a> to secure it.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if error %}
|
||||
<div class="alert alert-danger">{{ error }}</div>
|
||||
{% endif %}
|
||||
{% if list_db and databases %}
|
||||
<div class="list-group">
|
||||
{% for db in databases %}
|
||||
<div class="list-group-item d-flex align-items-center">
|
||||
<a href="/web?db={{ db }}" class="d-block flex-grow-1">
|
||||
{% if db in incompatible_databases %}
|
||||
<i class="icon fa fa-warning float-right text-warning" title="This database may not be compatible"></i>
|
||||
{% endif %}
|
||||
{{ db }}
|
||||
</a>
|
||||
{% if manage %}
|
||||
<div class="btn-group btn-group-sm float-right">
|
||||
<button type="button" data-db="{{ db }}" data-target=".o_database_backup" class="o_database_action btn btn-primary">
|
||||
<i class="fa fa-floppy-o fa-fw"></i> Backup
|
||||
</button>
|
||||
<button type="button" data-db="{{ db }}" data-target=".o_database_duplicate" class="o_database_action btn btn-secondary">
|
||||
<i class="fa fa-files-o fa-fw"></i> Duplicate
|
||||
</button>
|
||||
<button type="button" data-db="{{ db }}" data-target=".o_database_delete" class="o_database_action btn btn-danger">
|
||||
<i class="fa fa-trash-o fa-fw"></i> Delete
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if manage %}
|
||||
<div class="d-flex mt-2">
|
||||
<button type="button" data-toggle="modal" data-target=".o_database_create" class="btn btn-primary flex-grow-1">Create Database</button>
|
||||
<button type="button" data-toggle="modal" data-target=".o_database_restore" class="btn btn-primary flex-grow-1 ml-2">Restore Database</button>
|
||||
<button type="button" data-toggle="modal" data-target=".o_database_master" class="btn btn-primary flex-grow-1 ml-2">Set Master Password</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center mt-2">
|
||||
<a href="/web/database/manager">Manage databases</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% elif list_db %}
|
||||
<form role="form" action="/web/database/create" method="post">
|
||||
{{ create_form() }}
|
||||
<input type="submit" value="Create database" class="btn btn-primary float-left"/>
|
||||
</form>
|
||||
<a role="button" data-toggle="modal" data-target=".o_database_restore" class="btn btn-link">or restore a database</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create -->
|
||||
<div class="modal fade o_database_create" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form role="form" action="/web/database/create" method="post">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Create Database</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{ create_form() }}
|
||||
<small class="text-muted">
|
||||
To enhance your experience, some data may be sent to Odoo online services. See our <a href="https://www.odoo.com/privacy">Privacy Policy</a>.
|
||||
</small>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="submit" value="Continue" class="btn btn-primary float-right"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Restore -->
|
||||
<div class="modal fade o_database_restore" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Restore Database</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
<form id="form_restore_db" role="form" action="/web/database/restore" method="post" enctype="multipart/form-data">
|
||||
<div class="modal-body">
|
||||
{{ master_input() }}
|
||||
<div class="form-group row">
|
||||
<label for="backup_file" class="col-md-4 col-form-label">File</label>
|
||||
<div class="col-md-8">
|
||||
<input id="backup_file" type="file" name="backup_file" class="required"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="name" class="col-md-4 col-form-label">Database Name</label>
|
||||
<div class="col-md-8">
|
||||
<input id="dbname_restore" type="text" name="name" class="form-control" required="required" pattern="{{ pattern }}" title="Only alphanumerical characters, underscore, hyphen and dot are allowed"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="copy">This database might have been moved or copied.</label>
|
||||
<p class="form-text">
|
||||
In order to avoid conflicts between databases, Odoo needs to know if this database was moved or copied.<br/>
|
||||
If you don't know, answer "This database is a copy".
|
||||
</p>
|
||||
<div class="custom-control custom-radio">
|
||||
<input id="radio_copy_true" name="copy" type="radio" class="custom-control-input" value="true" checked="1">
|
||||
<label for="radio_copy_true" class="custom-control-label">This database is a copy</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio">
|
||||
<input id="radio_copy_false" name="copy" type="radio" class="custom-control-input" value="false">
|
||||
<label for="radio_copy_false" class="custom-control-label">This database was moved</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="submit" value="Continue" class="btn btn-primary float-right"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Master password -->
|
||||
<div class="modal fade o_database_master" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Set Master Password</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
<form id="form_change_pwd" role="form" action="/web/database/change_password" method="post">
|
||||
<div class="modal-body">
|
||||
<p>The master password is required to create, delete, dump or restore databases.</p>
|
||||
{{ master_input(set_master_pwd=True) }}
|
||||
<div class="form-group">
|
||||
<label for="master_pwd_new" class="col-form-label">New Master Password</label>
|
||||
<div class="input-group">
|
||||
<input id="master_pwd_new" type="password" name="master_pwd_new" class="form-control" required="required" autocomplete="new-password"/>
|
||||
<div class="input-group-append">
|
||||
<span class="fa fa-eye o_little_eye input-group-text" aria-hidden="true" style="cursor: pointer;"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="submit" value="Continue" class="btn btn-primary float-right"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Duplicate DB -->
|
||||
<div class="modal fade o_database_duplicate" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Duplicate Database</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
<form id="form-duplicate-db" role="form" action="/web/database/duplicate" method="post">
|
||||
<div class="modal-body">
|
||||
{{ master_input() }}
|
||||
<div class="form-group">
|
||||
<label for="name" class="col-form-label">Database Name</label>
|
||||
<input id="dbname_duplicate" type="text" name="name" class="form-control" required="required" readonly="readonly"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="new_name" class="col-form-label">New Name</label>
|
||||
<input id="new_name" type="text" name="new_name" class="form-control" required="required" pattern="{{ pattern }}" title="Only alphanumerical characters, underscore, hyphen and dot are allowed"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="submit" value="Continue" class="btn btn-primary float-right"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Drop DB -->
|
||||
<div class="modal fade o_database_delete" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Delete Database</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
<form id="form_drop_db" role="form" action="/web/database/drop" method="post">
|
||||
<div class="modal-body">
|
||||
{{ master_input() }}
|
||||
<div class="form-group">
|
||||
<label for="name" class="col-form-label">Database</label>
|
||||
<input id="dbname_delete" type="text" name="name" class="form-control" required="required" readonly="readonly"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="submit" value="Delete" class="btn btn-primary float-right"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Backup DB -->
|
||||
<div class="modal fade o_database_backup" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Backup Database</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
<form id="form_backup_db" role="form" action="/web/database/backup" method="post">
|
||||
<div class="modal-body">
|
||||
{{ master_input() }}
|
||||
<div class="form-group">
|
||||
<label for="name" class="col-form-label">Database Name</label>
|
||||
<input id="dbname_backup" type="text" name="name" class="form-control" required="required" readonly="readonly"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="backup_format" class="col-form-label">Backup Format</label>
|
||||
<select id="backup_format" name="backup_format" class="form-control" required="required">
|
||||
<option value="zip">zip (includes filestore)</option>
|
||||
<option value="dump">pg_dump custom format (without filestore)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<input type="submit" value="Backup" class="btn btn-primary float-right"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,65 @@
|
|||
<odoo>
|
||||
|
||||
<template id="web.layout" name="Web layout"><!DOCTYPE html>
|
||||
<html t-att="html_data or {}">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
|
||||
|
||||
<title t-esc="title or 'COR'"/>
|
||||
<link type="image/x-icon" rel="shortcut icon" t-att-href="x_icon or '/odoo-debranding/static/src/img/image.png'"/>
|
||||
|
||||
<script type="text/javascript">
|
||||
var odoo = {
|
||||
csrf_token: "<t t-esc="request.csrf_token(None)"/>",
|
||||
debug: "<t t-esc="debug"/>",
|
||||
};
|
||||
</script>
|
||||
|
||||
<t t-raw="head or ''"/>
|
||||
</head>
|
||||
<body t-att-class="body_classname">
|
||||
<t t-raw="0"/>
|
||||
</body>
|
||||
</html>
|
||||
</template>
|
||||
|
||||
<template id="web.login_layout" name="Login Layout">
|
||||
<t t-call="web.frontend_layout">
|
||||
<t t-set="html_data" t-value="{'style': 'height: 100%;'}"/>
|
||||
<t t-set="body_classname" t-value="'bg-100'"/>
|
||||
<t t-set="no_header" t-value="True"/>
|
||||
<t t-set="no_footer" t-value="True"/>
|
||||
|
||||
<div class="container py-5">
|
||||
<div t-attf-class="card border-0 mx-auto bg-100 {{login_card_classes}} o_database_list" style="max-width: 300px;">
|
||||
<div class="card-body">
|
||||
<div t-attf-class="text-center pb-3 border-bottom {{'mb-3' if form_small else 'mb-4'}}">
|
||||
<img t-attf-src="/web/binary/company_logo{{ '?dbname='+db if db else '' }}" alt="Logo" style="max-height:120px; max-width: 100%; width:auto"/>
|
||||
</div>
|
||||
<t t-raw="0"/>
|
||||
<div class="text-center small mt-4 pt-3 border-top" t-if="not disable_footer">
|
||||
<!--<t t-if="not disable_database_manager">
|
||||
<a class="border-right pr-2 mr-1" href="/web/database/manager">Manage Databases</a>
|
||||
</t>-->
|
||||
<!-- <a href="https://www.odoo.com?utm_source=db&utm_medium=auth" target="_blank">Powered by <span>Odoo</span></a>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
|
||||
<record id="res_config_settings_view_form_inherit" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.remove.about</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<div id="about" position="replace"/>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
</odoo>
|
|
@ -0,0 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import controllers
|
||||
from . import wizard
|
||||
from . import report
|
|
@ -0,0 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': "Planning",
|
||||
'summary': """Manage your employees' schedule""",
|
||||
'description': """
|
||||
Schedule your teams and employees with shift.
|
||||
""",
|
||||
'category': 'Human Resources/Planning',
|
||||
'version': '1.0',
|
||||
'depends': ['hr', 'web_gantt'],
|
||||
'data': [
|
||||
'security/planning_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'wizard/planning_send_views.xml',
|
||||
'views/assets.xml',
|
||||
'views/hr_views.xml',
|
||||
'report/planning_report_views.xml',
|
||||
'views/planning_template_views.xml',
|
||||
'views/planning_views.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/planning_templates.xml',
|
||||
'data/planning_cron.xml',
|
||||
'data/mail_data.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/planning_demo.xml',
|
||||
],
|
||||
'application': True,
|
||||
'license': 'OEEL-1',
|
||||
'qweb': [
|
||||
'static/src/xml/planning_gantt.xml',
|
||||
'static/src/xml/field_colorpicker.xml',
|
||||
]
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import main
|
|
@ -0,0 +1,127 @@
|
|||
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licens
|
||||
|
||||
from odoo import http, fields, _
|
||||
from odoo.http import request
|
||||
from odoo.osv import expression
|
||||
|
||||
import pytz
|
||||
from werkzeug.utils import redirect
|
||||
import babel
|
||||
from werkzeug.exceptions import Forbidden
|
||||
|
||||
from odoo import tools
|
||||
|
||||
|
||||
class ShiftController(http.Controller):
|
||||
|
||||
@http.route(['/planning/<string:planning_token>/<string:employee_token>'], type='http', auth="public", website=True)
|
||||
def planning(self, planning_token, employee_token, message=False, **kwargs):
|
||||
""" Displays an employee's calendar and the current list of open shifts """
|
||||
employee_sudo = request.env['hr.employee'].sudo().search([('employee_token', '=', employee_token)], limit=1)
|
||||
if not employee_sudo:
|
||||
return request.not_found()
|
||||
|
||||
planning_sudo = request.env['planning.planning'].sudo().search([('access_token', '=', planning_token)], limit=1)
|
||||
if not planning_sudo:
|
||||
return request.not_found()
|
||||
|
||||
employee_tz = pytz.timezone(employee_sudo.tz or 'UTC')
|
||||
employee_fullcalendar_data = []
|
||||
open_slots = []
|
||||
planning_slots = []
|
||||
|
||||
# domain of slots to display
|
||||
domain = planning_sudo._get_domain_slots()[planning_sudo.id]
|
||||
if planning_sudo.include_unassigned:
|
||||
domain = expression.AND([domain, ['|', ('employee_id', '=', employee_sudo.id), ('employee_id', '=', False)]])
|
||||
else:
|
||||
domain = expression.AND([domain, [('employee_id', '=', employee_sudo.id)]])
|
||||
|
||||
planning_slots = request.env['planning.slot'].sudo().search(domain)
|
||||
|
||||
# filter and format slots
|
||||
for slot in planning_slots:
|
||||
if slot.employee_id:
|
||||
employee_fullcalendar_data.append({
|
||||
'title': '%s%s' % (slot.role_id.name, u' \U0001F4AC' if slot.name else ''),
|
||||
'start': str(pytz.utc.localize(slot.start_datetime).astimezone(employee_tz).replace(tzinfo=None)),
|
||||
'end': str(pytz.utc.localize(slot.end_datetime).astimezone(employee_tz).replace(tzinfo=None)),
|
||||
'color': self._format_planning_shifts(slot.role_id.color),
|
||||
'alloc_hours': slot.allocated_hours,
|
||||
'slot_id': slot.id,
|
||||
'note': slot.name,
|
||||
'allow_self_unassign': slot.allow_self_unassign
|
||||
})
|
||||
else:
|
||||
open_slots.append(slot)
|
||||
|
||||
return request.render('planning.period_report_template', {
|
||||
'employee_slots_fullcalendar_data': employee_fullcalendar_data,
|
||||
'open_slots_ids': open_slots,
|
||||
'planning_slots_ids': planning_slots,
|
||||
'planning_planning_id': planning_sudo,
|
||||
'employee': employee_sudo,
|
||||
'format_datetime': lambda dt, dt_format: tools.format_datetime(request.env, dt, dt_format=dt_format),
|
||||
'message_slug': message,
|
||||
})
|
||||
|
||||
@http.route('/planning/<string:token_planning>/<string:token_employee>/assign/<int:slot_id>', type="http", auth="public", methods=['post'], website=True)
|
||||
def planning_self_assign(self, token_planning, token_employee, slot_id, **kwargs):
|
||||
slot_sudo = request.env['planning.slot'].sudo().browse(slot_id)
|
||||
if not slot_sudo.exists():
|
||||
return request.not_found()
|
||||
|
||||
if slot_sudo.employee_id:
|
||||
raise Forbidden(_('You can not assign yourself to this shift.'))
|
||||
|
||||
employee_sudo = request.env['hr.employee'].sudo().search([('employee_token', '=', token_employee)], limit=1)
|
||||
if not employee_sudo:
|
||||
return request.not_found()
|
||||
|
||||
planning_sudo = request.env['planning.planning'].sudo().search([('access_token', '=', token_planning)], limit=1)
|
||||
if not planning_sudo or slot_sudo.id not in planning_sudo.slot_ids._ids:
|
||||
return request.not_found()
|
||||
|
||||
slot_sudo.write({'employee_id': employee_sudo.id})
|
||||
return redirect('/planning/%s/%s?message=%s' % (token_planning, token_employee, 'assign'))
|
||||
|
||||
@http.route('/planning/<string:token_planning>/<string:token_employee>/unassign/<int:shift_id>', type="http", auth="public", methods=['post'], website=True)
|
||||
def planning_self_unassign(self, token_planning, token_employee, shift_id, **kwargs):
|
||||
slot_sudo = request.env['planning.slot'].sudo().search([('id', '=', shift_id)], limit=1)
|
||||
if not slot_sudo or not slot_sudo.allow_self_unassign:
|
||||
return request.not_found()
|
||||
|
||||
employee_sudo = request.env['hr.employee'].sudo().search([('employee_token', '=', token_employee)], limit=1)
|
||||
if not employee_sudo or employee_sudo.id != slot_sudo.employee_id.id:
|
||||
return request.not_found()
|
||||
|
||||
planning_sudo = request.env['planning.planning'].sudo().search([('access_token', '=', token_planning)], limit=1)
|
||||
if not planning_sudo or slot_sudo.id not in planning_sudo.slot_ids._ids:
|
||||
return request.not_found()
|
||||
|
||||
slot_sudo.write({'employee_id': False})
|
||||
|
||||
return redirect('/planning/%s/%s?message=%s' % (token_planning, token_employee, 'unassign'))
|
||||
|
||||
@staticmethod
|
||||
def _format_planning_shifts(color_code):
|
||||
"""Take a color code from Odoo's Kanban view and returns an hex code compatible with the fullcalendar library"""
|
||||
|
||||
switch_color = {
|
||||
0: '#008784', # No color (doesn't work actually...)
|
||||
1: '#EE4B39', # Red
|
||||
2: '#F29648', # Orange
|
||||
3: '#F4C609', # Yellow
|
||||
4: '#55B7EA', # Light blue
|
||||
5: '#71405B', # Dark purple
|
||||
6: '#E86869', # Salmon pink
|
||||
7: '#008784', # Medium blue
|
||||
8: '#267283', # Dark blue
|
||||
9: '#BF1255', # Fushia
|
||||
10: '#2BAF73', # Green
|
||||
11: '#8754B0' # Purple
|
||||
}
|
||||
|
||||
return switch_color[color_code]
|
|
@ -0,0 +1,118 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="email_template_slot_single" model="mail.template">
|
||||
<field name="name">Planning: new schedule (single shift)</field>
|
||||
<field name="email_from">${(object.company_id.email or '')}</field>
|
||||
<field name="subject">Planning: new schedule (single shift)</field>
|
||||
<field name="email_to">${object.employee_id.work_email}</field>
|
||||
<field name="model_id" ref="model_planning_slot"/>
|
||||
<field name="auto_delete" eval="False"/>
|
||||
<field name="body_html" type="html">
|
||||
<div>
|
||||
<p>Dear ${object.employee_id.name or ''},</p><br/>
|
||||
<p>You have been assigned the following schedule:</p><br/>
|
||||
<table style="table-layout: fixed; width: 80%; margin: auto;">
|
||||
<tr>
|
||||
<th style="padding: 5px;text-align: left; width: 15%;">From</th>
|
||||
<td style="padding: 5px;">${format_datetime(object.start_datetime, tz=object.employee_id.tz)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="padding: 5px;text-align: left; width: 15%;">To</th>
|
||||
<td style="padding: 5px;">${format_datetime(object.end_datetime, tz=object.employee_id.tz)}</td>
|
||||
</tr>
|
||||
% if object.role_id
|
||||
<tr>
|
||||
<th style="padding: 5px;text-align: left; width: 15%;">Role</th>
|
||||
<td style="padding: 5px;">${object.role_id.name or ''}</td>
|
||||
</tr>
|
||||
% endif
|
||||
% if object.project_id
|
||||
<tr>
|
||||
<th style="padding: 5px;text-align: left; width: 15%;">Project</th>
|
||||
<td style="padding: 5px;">${object.project_id.name or ''}</td>
|
||||
</tr>
|
||||
% endif
|
||||
% if object.name
|
||||
<tr>
|
||||
<th style="padding: 5px;text-align: left; width: 15%;">Note</th>
|
||||
<td style="padding: 5px;">${object.name or ''}</td>
|
||||
</tr>
|
||||
% endif
|
||||
</table>
|
||||
<div style="text-align: center">
|
||||
% if ctx.get('render_link')
|
||||
<div style="display: inline-block; margin: 15px; text-align: center">
|
||||
<a href="${ctx.unavailable_path}${object.employee_id.employee_token}" target="_blank"
|
||||
style="padding: 5px 10px; color: #875A7B; text-decoration: none; background-color: #FFFFFF; border: 1px solid #FFFFFF; border-radius: 3px"
|
||||
>I am unavailable</a>
|
||||
</div>
|
||||
% endif
|
||||
% if ctx.get('render_link')
|
||||
<div style="display: inline-block; margin: 15px; text-align: center">
|
||||
<a href="/web?#action=${ctx.get('action_id')}&model=planning.slot&menu_id=${ctx.get('menu_id')}&db=${'dbname' in ctx and ctx['dbname'] or '' }" target="_blank"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px"
|
||||
>View Planning</a>
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="email_template_planning_planning" model="mail.template">
|
||||
<field name="name">Planning: new schedule (multiple shifts)</field>
|
||||
<field name="email_from">${(object.company_id.email or '')}</field>
|
||||
<field name="subject">Your planning from ${format_datetime(object.start_datetime, tz=ctx.get('employee').tz or 'UTC', dt_format='short')} to ${format_datetime(object.end_datetime, tz=employee.tz if employee else 'UTC', dt_format='short')}</field>
|
||||
<field name="email_to"></field><!-- Set in the code -->
|
||||
<field name="model_id" ref="model_planning_planning"/>
|
||||
<field name="auto_delete" eval="False"/><!-- TODO JEM change this as we are testing -->
|
||||
<field name="body_html" type="html">
|
||||
<div>
|
||||
% if ctx.get('employee'):
|
||||
<p>Dear ${ctx['employee'].name},</p>
|
||||
% else:
|
||||
<p>Hello,</p>
|
||||
% endif
|
||||
<p>
|
||||
You have been assigned new shifts:
|
||||
</p>
|
||||
|
||||
<table style="table-layout: fixed; width: 80%; margin: auto;">
|
||||
<tr>
|
||||
<th style="padding: 5px;text-align: left; width: 15%;">From</th>
|
||||
<td style="padding: 5px;">${format_datetime(object.start_datetime, tz=ctx.get('employee').tz or 'UTC', dt_format='short')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="padding: 5px;text-align: left; width: 15%;">To</th>
|
||||
<td style="padding: 5px;">${format_datetime(object.end_datetime, tz=ctx.get('employee').tz or 'UTC', dt_format='short')}</td>
|
||||
</tr>
|
||||
% if object.project_id
|
||||
<tr>
|
||||
<th style="padding: 5px;text-align: left; width: 15%;">Project</th>
|
||||
<td style="padding: 5px;">${object.project_id.name or ''}</td>
|
||||
</tr>
|
||||
% endif
|
||||
</table>
|
||||
|
||||
% if ctx.get('planning_url'):
|
||||
<div style="margin: 15px;">
|
||||
<a href="${ctx.get('planning_url')}" target="_blank"
|
||||
style="padding: 5px 10px; color: #FFFFFF; text-decoration: none; background-color: #875A7B; border: 1px solid #875A7B; border-radius: 3px">View Your Planning</a>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
% if ctx.get('slot_unassigned_count'):
|
||||
<p>There are new open shifts available. Please assign yourself if you are available.</p>
|
||||
% endif
|
||||
|
||||
% if ctx.get('message'):
|
||||
<p>${ctx['message']}</p>
|
||||
% endif
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</odoo>
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="ir_cron_forecast_schedule" model="ir.cron">
|
||||
<field name="name">Planning: generate next recurring shifts</field>
|
||||
<field name="model_id" ref="model_planning_recurrency"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._cron_schedule_next()</field>
|
||||
<field name="interval_type">weeks</field>
|
||||
<field name="numbercall">-1</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
|
@ -0,0 +1,547 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- Project forecast will warn if the user assigned to a forecast doesn't have a timezone -->
|
||||
<record id="base.user_demo" model="res.users">
|
||||
<field name="tz">Europe/Brussels</field>
|
||||
<field name="groups_id" eval="[(4,ref('planning.group_planning_user'))]"/>
|
||||
</record>
|
||||
|
||||
<!-- Roles -->
|
||||
<record id="planning_role_bartender" model="planning.role">
|
||||
<field name="name">Bartender</field>
|
||||
<field name="color">2</field>
|
||||
</record>
|
||||
<record id="planning_role_waiter" model="planning.role">
|
||||
<field name="name">Waiter</field>
|
||||
<field name="color">3</field>
|
||||
</record>
|
||||
<record id="planning_role_chef" model="planning.role">
|
||||
<field name="name">Chef</field>
|
||||
<field name="color">4</field>
|
||||
</record>
|
||||
|
||||
<!-- Shift templates (morning shifts) -->
|
||||
<record id="planning_template_chef_morning" model="planning.slot.template">
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="start_time" eval="8"/>
|
||||
<field name="duration" eval="6"/>
|
||||
</record>
|
||||
<record id="planning_template_bartender_morning" model="planning.slot.template">
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="start_time" eval="8"/>
|
||||
<field name="duration" eval="8"/>
|
||||
</record>
|
||||
<record id="planning_template_waiter_morning" model="planning.slot.template">
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="start_time" eval="8"/>
|
||||
<field name="duration" eval="8"/>
|
||||
</record>
|
||||
|
||||
<!-- Shift templates (evening shifts) -->
|
||||
<record id="planning_template_chef_evening" model="planning.slot.template">
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="start_time" eval="14"/>
|
||||
<field name="duration" eval="8"/>
|
||||
</record>
|
||||
<record id="planning_template_bartender_evening" model="planning.slot.template">
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="start_time" eval="16"/>
|
||||
<field name="duration" eval="8"/>
|
||||
</record>
|
||||
<record id="planning_template_waiter_evening" model="planning.slot.template">
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="start_time" eval="16"/>
|
||||
<field name="duration" eval="8"/>
|
||||
</record>
|
||||
|
||||
<!-- Recurrencies -->
|
||||
<record id="planning_recurrency_1" model="planning.recurrency">
|
||||
<field name="repeat_interval" eval="1"/>
|
||||
<field name="repeat_type">forever</field>
|
||||
</record>
|
||||
<record id="planning_recurrency_2" model="planning.recurrency">
|
||||
<field name="repeat_interval" eval="1"/>
|
||||
<field name="repeat_type">forever</field>
|
||||
</record>
|
||||
<record id="planning_recurrency_3" model="planning.recurrency">
|
||||
<field name="repeat_interval" eval="1"/>
|
||||
<field name="repeat_type">forever</field>
|
||||
</record>
|
||||
|
||||
<!-- Week 1 -->
|
||||
|
||||
<!-- Monday morning shifts -->
|
||||
<record id="planning_slot_111" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_qdp"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_112" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 12:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_stw"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_113" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_mit"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Monday evening shifts -->
|
||||
<record id="planning_slot_114" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 20:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_jth"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_115" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_chs"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_116" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday())).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_niv"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Tuesday morning shifts -->
|
||||
<record id="planning_slot_121" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_qdp"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_122" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 12:00:00')"/>
|
||||
<field name="employee_id" eval="False"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="name" eval="'Check with Katy from the employment agency if they have someone to replace Randall this day'"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_123" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_jep"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Tuesday evening shifts -->
|
||||
<record id="planning_slot_124" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 20:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_jth"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_125" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_chs"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_126" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 1)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_niv"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Wednesday morning shifts -->
|
||||
<record id="planning_slot_131" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_qdp"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_132" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 12:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_stw"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_133" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_mit"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Wednesday evening shifts -->
|
||||
<record id="planning_slot_134" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 20:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_jth"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_135" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_admin"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="name" eval="'Mitchel is replacing Jennie until the end of the week'"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_136" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 2)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_niv"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Thursday morning shifts -->
|
||||
<record id="planning_slot_141" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_qdp"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_142" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 12:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_stw"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_143" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_mit"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Thursday evening shifts -->
|
||||
<record id="planning_slot_144" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 20:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_jth"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_145" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_admin"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_146" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 3)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_niv"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Friday morning shifts -->
|
||||
<record id="planning_slot_151" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" eval="False"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_152" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 12:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_stw"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_153" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_mit"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Friday evening shifts -->
|
||||
<record id="planning_slot_154" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 20:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_jth"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_155" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_admin"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_156" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 4)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_niv"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Week 2 -->
|
||||
|
||||
<!-- Monday morning shifts -->
|
||||
<record id="planning_slot_211" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_qdp"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_212" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 12:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_stw"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_213" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_mit"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Monday evening shifts -->
|
||||
<record id="planning_slot_214" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 20:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_jth"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_215" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_chs"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_216" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_niv"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Tuesday morning shifts -->
|
||||
<record id="planning_slot_221" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_qdp"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_222" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 12:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_stw"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_223" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_jep"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Tuesday evening shifts -->
|
||||
<record id="planning_slot_224" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 20:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_jth"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_225" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_chs"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_226" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 1)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" eval="False"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Wednesday morning shifts -->
|
||||
<record id="planning_slot_231" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_qdp"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_232" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 12:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_stw"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_233" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_mit"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Wednesday evening shifts -->
|
||||
<record id="planning_slot_234" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 20:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_jth"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_235" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_chs"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_236" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 2)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" eval="False"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Thursday morning shifts -->
|
||||
<record id="planning_slot_241" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_qdp"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_242" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 12:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_stw"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_243" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_mit"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Thursday evening shifts -->
|
||||
<record id="planning_slot_244" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 20:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_admin"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="name" eval="'Ask Toni if she can show Mitchel how things are done in the kitchen because it is her first time working as a chef'"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_245" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_chs"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_246" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 3)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_niv"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Friday morning shifts -->
|
||||
<record id="planning_slot_251" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_qdp"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="recurrency_id" ref="planning_recurrency_1"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_252" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 12:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_stw"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="recurrency_id" ref="planning_recurrency_2"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_253" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 06:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="employee_id" eval="False"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Friday evening shifts -->
|
||||
<record id="planning_slot_254" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 20:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_admin"/>
|
||||
<field name="role_id" ref="planning_role_chef"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_255" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_chs"/>
|
||||
<field name="role_id" ref="planning_role_waiter"/>
|
||||
<field name="recurrency_id" ref="planning_recurrency_3"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
<record id="planning_slot_256" model="planning.slot">
|
||||
<field name="start_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 14:00:00')"/>
|
||||
<field name="end_datetime" eval="(datetime.today() - timedelta(days=datetime.today().weekday() - 7 - 4)).strftime('%Y-%m-%d 22:00:00')"/>
|
||||
<field name="employee_id" ref="hr.employee_niv"/>
|
||||
<field name="role_id" ref="planning_role_bartender"/>
|
||||
<field name="publication_warning" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Duplicate shifts that have a recurrence set -->
|
||||
<function model="planning.recurrency" name="_repeat_slot" eval="[[ref('planning_recurrency_1'), ref('planning_recurrency_2'), ref('planning_recurrency_3')]]"/>
|
||||
|
||||
</data>
|
||||
</odoo>
|