This commit is contained in:
root 2022-08-25 14:16:45 +05:30
commit 79a3e34f90
16 changed files with 862 additions and 56 deletions

View File

@ -419,6 +419,25 @@ class InheritProjectProductEmployeeMap(models.Model):
# line.price_unit = 0
line.currency_id = False
def edit_sale_line_employee_record(self):
context = dict(self.env.context)
context['form_view_initial_mode'] = 'edit'
return {
'name': ('Edit Resource History'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'target': 'new',
'res_model': 'project.sale.line.employee.map',
'res_id': self.id,
'context': context,
# 'view_id': view_id
}
def update_record(self):
return {
'type': 'ir.actions.act_window_close'}
class CustomProjectTags(models.Model):
""" Tags of project's tasks """

View File

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

0
month_filter/__init__.py Normal file
View File

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -10,9 +10,14 @@
<field name="revenue_amount_lines" context="{'project_id' : active_id}" readonly="1">
<tree editable="bottom">
<field name="project_id" invisible="1"/>
<field name="start_date"/>
<field name="end_date"/>
<!--<field name="start_date"/>
<field name="end_date"/>-->
<field name="date"/>
<field name="fixed_amount"/>
<button type="object" name="edit_revenue_history_record" string="Edit" class="oe_highlight"/>
<button name="unlink"
type="object"
icon="fa-trash-o"/>
</tree>
</field>
</page>
@ -20,4 +25,26 @@
</field>
</record>
<record id="view_project_revenue_history_lines" model="ir.ui.view">
<field name="name"> project.edit.project.revenue.lines</field>
<field name="model">project.revenue.lines</field>
<field name="arch" type="xml">
<form string="Project Revenue History">
<group>
<group>
<field name="project_id" readonly="1"/>
<!--<field name="start_date"/>
<field name="end_date"/>-->
<field name="date"/>
<field name="fixed_amount"/>
</group>
</group>
<footer>
<button name="update_record" type="object" string="Update" class="oe_highlight"/>
<button special="cancel" string="Cancel"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@ -8,9 +8,10 @@ class CustomProjectWizard(models.TransientModel):
_name = 'project.revenue.wizard'
project_id = fields.Many2one('project.project')
start_date = fields.Date(required=True)
#start_date = fields.Date(required=True)
#end_date = fields.Datet(compute='_compute_end_date', store=True)
end_date = fields.Date()
#end_date = fields.Date()
date = fields.Date(required=True)
fixed_amount = fields.Float()
# @api.depends('start_date')
@ -20,22 +21,23 @@ class CustomProjectWizard(models.TransientModel):
# rec.end_date = rec.start_date + relativedelta.relativedelta(months=1) - relativedelta.relativedelta(
# days=1)
@api.depends('start_date')
"""@api.depends('start_date')
def _compute_end_date(self):
if self.start_date:
self.end_date = self.start_date + relativedelta.relativedelta(months=1) - relativedelta.relativedelta(days=1)
self.end_date = self.start_date + relativedelta.relativedelta(months=1) - relativedelta.relativedelta(days=1)"""
def action_set_revenue_lines(self):
values = {
'project_id':self.project_id.id,
'start_date':self.start_date,
'end_date':self.end_date,
# 'start_date':self.start_date,
# 'end_date':self.end_date,
'date':self.date,
'fixed_amount':self.fixed_amount
}
res = self.env['project.revenue.lines'].create(values)
return res
@api.onchange('start_date')
"""@api.onchange('start_date')
def _onchange_start_date(self):
active_id = self._context.get('active_ids', [])
project_object = self.env['project.project'].search([('id', '=', active_id[0])])
@ -47,4 +49,4 @@ class CustomProjectWizard(models.TransientModel):
self.project_id = project_object.id
for line in records:
if self.start_date >= line.start_date and self.start_date <= line.end_date:
raise AccessError('Date already exist')
raise AccessError('Date already exist')"""

View File

@ -1,21 +1,23 @@
<odoo>
<record id="view_edit_project_wizard_revenue" model="ir.ui.view">
<field name="name"> project.edit.project.inherit,wizard</field>
<field name="name"> project.edit.project.inherit.wizard</field>
<field name="model">project.revenue.wizard</field>
<field name="arch" type="xml">
<form string="Project Revenue Wizard">
<group>
<group>
<field name="project_id" readonly="1"/>
<field name="start_date"/>
<field name="end_date"/>
<!--<field name="start_date"/>
<field name="end_date"/>-->
<field name="date"/>
<field name="fixed_amount"/>
</group>
</group>
<footer>
<button string="OK" type="object" name="action_set_revenue_lines" class="btn-primary"/>
<button string="Cancel" class="btn-default" special="cancel"/>
</footer>
<button string="OK" type="object" name="action_set_revenue_lines" class="btn-primary"/>
<button string="Cancel" class="btn-default" special="cancel"/>
</footer>
</form>
</field>
</record>

View File

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

View File

@ -5,7 +5,7 @@ class ResConfigSettingsTimesheet(models.TransientModel):
timesheet_edit_create_limit = fields.Integer('No. of days',default=0, config_parameter='timesheet_block.timesheet_edit_create_limit')
timesheet_edit_create_months = fields.Integer('No. of months',default=1, config_parameter='timesheet_block.timesheet_edit_create_months')
# timesheet_edit_create_months = fields.Integer('No. of months',default=1, config_parameter='timesheet_block.timesheet_edit_create_months')

View File

@ -31,6 +31,7 @@ class TimehseetBlock(models.Model):
def create(self, values):
current_date = datetime.today().date()
entry_date = datetime.strptime(values['date'], "%Y-%m-%d").date()
# values['date'] = datetime.strptime(values['date'], "%Y-%m-%d %H:%M:%S")
months_diff = current_date.month - entry_date.month
employee_object = self.env['hr.employee'].sudo().search([('user_id', '=', self.env.uid)])
project_manager_group = self.env.ref('hr_timesheet.group_timesheet_manager')
@ -38,44 +39,75 @@ class TimehseetBlock(models.Model):
config_days_limit = float(config.get_param('timesheet_block.timesheet_edit_create_limit'))
if entry_date <= current_date:
if self.env.uid not in project_manager_group.users.ids:
if months_diff == 0:
res = super(TimehseetBlock, self).create(values)
elif months_diff == 1:
if employee_object.allow_to_create == 'always' and current_date.day <= config_days_limit:
if employee_object.allow_to_create == False:
if months_diff == 0:
res = super(TimehseetBlock, self).create(values)
return res
else:
raise AccessError('You can not create your backdate entry please connect to Hr/Department Head')
raise AccessError('You can not create your backdate entry')
elif employee_object.allow_to_create == 'always':
res = super(TimehseetBlock, self).create(values)
return res
else:
raise AccessError('You can not create your backdate entry please connect to Hr/Department Head')
if months_diff == 0:
res = super(TimehseetBlock, self).create(values)
return res
if months_diff == 1:
if current_date.day <= config_days_limit:
res = super(TimehseetBlock, self).create(values)
return res
else:
raise AccessError('You can not create your backdate entry ')
else:
raise AccessError('You can not create your backdate entry ')
else:
res = super(TimehseetBlock, self).create(values)
return res
else:
raise AccessError('You can not create future entry')
return res
def write(self, values):
current_date = datetime.today().date()
if 'date' in values:
entry_date = datetime.strptime(values['date'], "%Y-%m-%d").date()
# values['date'] = datetime.strptime(values['date'], "%Y-%m-%d %H:%M:%S")
else:
res_date = str(self.date).split('.', 1)[0]
entry_date = datetime.strptime(str(res_date), '%Y-%m-%d %H:%M:%S').date()
entry_date = datetime.strptime(str(res_date), '%Y-%m-%d').date()
months_diff = current_date.month - entry_date.month
employee_object = self.env['hr.employee'].sudo().search([('user_id', '=', self.env.uid)])
project_manager_group = self.env.ref('hr_timesheet.group_timesheet_manager')
config = self.env['ir.config_parameter'].sudo()
config_days_limit = float(config.get_param('timesheet_block.timesheet_edit_create_limit'))
if self.env.uid not in project_manager_group.users.ids:
if months_diff == 0:
res = super(TimehseetBlock, self).write(values)
elif months_diff == 1:
if employee_object.allow_to_edit == 'always' and current_date.day <= config_days_limit:
if entry_date <= current_date:
if self.env.uid not in project_manager_group.users.ids:
if employee_object.allow_to_edit == False:
if months_diff == 0:
res = super(TimehseetBlock, self).write(values)
return res
else:
raise AccessError('You can not edit your backdate entry ')
elif employee_object.allow_to_edit == 'always':
res = super(TimehseetBlock, self).write(values)
return res
else:
raise AccessError('You can not edit your backdate entry please connect to Hr/Department Head')
if months_diff == 0:
res = super(TimehseetBlock, self).write(values)
return res
if months_diff == 1:
if current_date.day <= config_days_limit:
res = super(TimehseetBlock, self).write(values)
return res
else:
raise AccessError(
'You can not edit your backdate entry ')
else:
raise AccessError('You can not edit your backdate entry ')
else:
raise AccessError('You can not edit your backdate entry please connect to Hr/Department Head')
res = super(TimehseetBlock, self).write(values)
return res
else:
res = super(TimehseetBlock, self).write(values)
return res
raise AccessError('You can not create future entry')

View File

@ -17,12 +17,12 @@
<field name="timesheet_edit_create_limit"/>
</div>
</div>
<div class="o_setting_right_pane">
<label for="timesheet_edit_create_months"/>
<div >
<field name="timesheet_edit_create_months"/>
</div>
</div>
<!-- <div class="o_setting_right_pane">-->
<!-- <label for="timesheet_edit_create_months"/>-->
<!-- <div >-->
<!-- <field name="timesheet_edit_create_months"/>-->
<!-- </div>-->
<!-- </div>-->
</div>
</div>
</div>