merge upstream

This commit is contained in:
Christophe Matthieu 2014-06-25 12:02:10 +02:00
commit f0ca6fbf75
15 changed files with 173 additions and 233 deletions

View File

@ -448,15 +448,31 @@ class account_bank_statement_line(osv.osv):
""" Used to instanciate a batch of reconciliations in a single request """
# Build a list of reconciliations data
ret = []
statement_line_done = {}
mv_line_ids_selected = []
for st_line in self.browse(cr, uid, ids, context=context):
# look for structured communication first
exact_match_id = self.search_structured_com(cr, uid, st_line, context=context)
if exact_match_id:
reconciliation_data = {
'st_line': self.get_statement_line_for_reconciliation(cr, uid, st_line.id, context),
'reconciliation_proposition': self.make_counter_part_lines(cr, uid, st_line, [exact_match_id], context=context)
}
for mv_line in reconciliation_data['reconciliation_proposition']:
mv_line_ids_selected.append(mv_line['id'])
statement_line_done[st_line.id] = reconciliation_data
for st_line_id in ids:
reconciliation_data = {
'st_line': self.get_statement_line_for_reconciliation(cr, uid, st_line_id, context),
'reconciliation_proposition': self.get_reconciliation_proposition(cr, uid, st_line_id, mv_line_ids_selected, context)
}
for mv_line in reconciliation_data['reconciliation_proposition']:
mv_line_ids_selected.append(mv_line['id'])
ret.append(reconciliation_data)
if statement_line_done.get(st_line_id):
ret.append(statement_line_done.get(st_line_id))
else:
reconciliation_data = {
'st_line': self.get_statement_line_for_reconciliation(cr, uid, st_line_id, context),
'reconciliation_proposition': self.get_reconciliation_proposition(cr, uid, st_line_id, mv_line_ids_selected, context)
}
for mv_line in reconciliation_data['reconciliation_proposition']:
mv_line_ids_selected.append(mv_line['id'])
ret.append(reconciliation_data)
# Check if, now that 'candidate' move lines were selected, there are moves left for statement lines
#for reconciliation_data in ret:
@ -529,23 +545,19 @@ class account_bank_statement_line(osv.osv):
if st_line.amount < 0:
sign = -1
# look for structured communication
exact_match_id = self.search_structured_com(cr, uid, st_line, context=context)
if exact_match_id:
return self.make_counter_part_lines(cr, uid, st_line, [exact_match_id], count=False, context=context)
#we don't propose anything if there is no partner detected
if not st_line.partner_id.id:
return []
# look for exact match
exact_match_id = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, limit=1, additional_domain=[(amount_field, '=', (sign * st_line.amount))])
exact_match_id = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, additional_domain=[(amount_field, '=', (sign * st_line.amount))])
if exact_match_id:
return exact_match_id
return exact_match_id[0]
# select oldest move lines
if sign == -1:
mv_lines = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, limit=50, additional_domain=[(amount_field, '<', 0)])
mv_lines = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, additional_domain=[(amount_field, '<', 0)])
else:
mv_lines = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, limit=50, additional_domain=[(amount_field, '>', 0)])
mv_lines = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, additional_domain=[(amount_field, '>', 0)])
ret = []
total = 0
# get_move_lines_counterparts inverts debit and credit
@ -558,11 +570,11 @@ class account_bank_statement_line(osv.osv):
break
return ret
def get_move_lines_counterparts_id(self, cr, uid, st_line_id, excluded_ids=[], filter_str="", offset=0, limit=None, count=False, additional_domain=[], context=None):
def get_move_lines_counterparts_id(self, cr, uid, st_line_id, excluded_ids=[], additional_domain=[], count=False, context=None):
st_line = self.browse(cr, uid, st_line_id, context=context)
return self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids, filter_str, offset, limit, count, additional_domain, context=context)
return self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids, additional_domain, count, context=context)
def get_move_lines_counterparts(self, cr, uid, st_line, excluded_ids=[], filter_str="", offset=0, limit=None, count=False, additional_domain=[], context=None):
def get_move_lines_counterparts(self, cr, uid, st_line, excluded_ids=[], additional_domain=[], count=False, context=None):
""" Find the move lines that could be used to reconcile a statement line and returns the counterpart that could be created to reconcile them
If count is true, only returns the count.
@ -576,10 +588,7 @@ class account_bank_statement_line(osv.osv):
"""
mv_line_pool = self.pool.get('account.move.line')
domain = additional_domain + [
('reconcile_id', '=', False),
('state', '=', 'valid'),
]
domain = additional_domain + [('reconcile_id', '=', False),('state', '=', 'valid')]
if st_line.partner_id.id:
domain += [('partner_id', '=', st_line.partner_id.id),
'|', ('account_id.type', '=', 'receivable'),
@ -589,11 +598,7 @@ class account_bank_statement_line(osv.osv):
#domain += [('account_id.reconcile', '=', True), ('account_id.type', '=', 'other')]
if excluded_ids:
domain.append(('id', 'not in', excluded_ids))
if filter_str:
if not st_line.partner_id:
domain += [ '|', ('partner_id.name', 'ilike', filter_str)]
domain += ['|', ('move_id.name', 'ilike', filter_str), ('move_id.ref', 'ilike', filter_str)]
line_ids = mv_line_pool.search(cr, uid, domain, offset=offset, limit=limit, order="date_maturity asc, id asc", context=context)
line_ids = mv_line_pool.search(cr, uid, domain, order="date_maturity asc, id asc", context=context)
return self.make_counter_part_lines(cr, uid, st_line, line_ids, count=count, context=context)
def make_counter_part_lines(self, cr, uid, st_line, line_ids, count=False, context=None):

View File

@ -37,7 +37,7 @@ openerp.account = function (instance) {
// We'll need to get the code of an account selected in a many2one (whose value is the id)
this.map_account_id_code = {};
// The same move line cannot be selected for multiple resolutions
this.excluded_move_lines_ids = {};
this.excluded_move_lines_ids = [];
// Description of the fields to initialize in the "create new line" form
// NB : for presets to work correctly, a field id must be the same string as a preset field
this.create_form_fields = {
@ -253,59 +253,34 @@ openerp.account = function (instance) {
});
}
},
// Adds move line ids to the list of move lines not to fetch for a given partner
// This is required because the same move line cannot be selected for multiple reconciliation
excludeMoveLines: function(source_child, partner_id, line_ids) {
excludeMoveLines: function(line_ids) {
var self = this;
var excluded_ids = this.excluded_move_lines_ids[partner_id];
var excluded_move_lines_changed = false;
_.each(line_ids, function(line_id){
if (excluded_ids.indexOf(line_id) === -1) {
excluded_ids.push(line_id);
excluded_move_lines_changed = true;
line_id = parseInt(line_id);
if (self.excluded_move_lines_ids.indexOf(line_id) === -1) {
self.excluded_move_lines_ids.push(line_id);
}
});
if (! excluded_move_lines_changed)
return;
// Function that finds if an array of line objects contains at least a line identified by its id
var contains_lines = function(lines_array, line_ids) {
for (var i = 0; i < lines_array.length; i++)
for (var j = 0; j < line_ids.length; j++)
if (lines_array[i].id === line_ids[j])
return true;
return false;
};
// Update children if needed
//update all children view
_.each(self.getChildren(), function(child){
if (child.partner_id === partner_id && child !== source_child) {
if (contains_lines(child.get("mv_lines_selected"), line_ids)) {
child.set("mv_lines_selected", _.filter(child.get("mv_lines_selected"), function(o){ return line_ids.indexOf(o.id) === -1 }));
} else if (contains_lines(child.mv_lines_deselected, line_ids)) {
child.mv_lines_deselected = _.filter(child.mv_lines_deselected, function(o){ return line_ids.indexOf(o.id) === -1 });
child.updateMatches();
} else if (contains_lines(child.get("mv_lines"), line_ids)) {
child.updateMatches();
}
}
child.render();
});
},
unexcludeMoveLines: function(source_child, partner_id, line_ids) {
unexcludeMoveLines: function(line_ids) {
var self = this;
var initial_excluded_lines_num = this.excluded_move_lines_ids[partner_id].length;
this.excluded_move_lines_ids[partner_id] = _.difference(this.excluded_move_lines_ids[partner_id], line_ids);
if (this.excluded_move_lines_ids[partner_id].length === initial_excluded_lines_num)
return;
// Update children if needed
var index = -1;
_.each(line_ids, function(line_id){
line_id = parseInt(line_id);
index = self.excluded_move_lines_ids.indexOf(line_id);
if (index > -1) {
self.excluded_move_lines_ids.splice(index,1);
}
});
//update all children view
_.each(self.getChildren(), function(child){
if (child.partner_id === partner_id && child !== source_child && (child.get("mode") === "match" || child.$el.hasClass("no_match")))
child.updateMatches();
child.render();
});
},
@ -490,9 +465,7 @@ openerp.account = function (instance) {
// Exclude selected move lines
var selected_line_ids = _(context.reconciliation_proposition).map(function(o){ return o.id });
if (this.getParent().excluded_move_lines_ids[this.partner_id] === undefined)
this.getParent().excluded_move_lines_ids[this.partner_id] = [];
this.getParent().excludeMoveLines(this, this.partner_id, selected_line_ids);
this.getParent().excludeMoveLines(selected_line_ids);
} else {
this.set("mv_lines_selected", []);
this.st_line = undefined;
@ -513,7 +486,6 @@ openerp.account = function (instance) {
this.presets = this.getParent().presets;
this.is_valid = true;
this.is_consistent = true; // Used to prevent bad server requests
this.total_move_lines_num = undefined; // Used for pagers
this.filter = "";
this.set("balance", undefined); // Debit is +, credit is -
@ -525,12 +497,14 @@ openerp.account = function (instance) {
// NB : mv_lines represent the counterpart that will be created to reconcile existing move lines, so debit and credit are inverted
this.set("mv_lines", []);
this.on("change:mv_lines", this, this.mvLinesChanged);
this.mv_lines_deselected = []; // deselected lines are displayed on top of the match table
this.on("change:mv_lines_selected", this, this.mvLinesSelectedChanged);
this.set("lines_created", []);
this.set("line_created_being_edited", [{'id': 0}]);
this.on("change:lines_created", this, this.createdLinesChanged);
this.on("change:line_created_being_edited", this, this.createdLinesChanged);
//all lines associated to current reconciliation
this.propositions_lines = undefined;
},
start: function() {
@ -553,9 +527,6 @@ openerp.account = function (instance) {
self.st_line = data;
self.decorateStatementLine(self.st_line);
self.partner_id = data.partner_id;
if (self.getParent().excluded_move_lines_ids[self.partner_id] === undefined)
self.getParent().excluded_move_lines_ids[self.partner_id] = [];
// load and display move lines
$.when(self.loadReconciliationProposition()).then(function(){
deferred_fetch_data.resolve();
});
@ -566,6 +537,15 @@ openerp.account = function (instance) {
// Display the widget
return $.when(deferred_fetch_data).then(function(){
//load all lines that can be usefull for counterparts
var deferred_total_move_lines_num = self.model_bank_statement_line
.call("get_move_lines_counterparts_id", [self.st_line.id, []])
.then(function(lines){
_(lines).each(self.decorateMoveLine.bind(self));
self.propositions_lines = lines;
});
return deferred_total_move_lines_num;
}).then(function(){
// Render template
var presets_array = [];
for (var id in self.presets)
@ -577,10 +557,9 @@ openerp.account = function (instance) {
self.$(".match").slideUp(0);
self.$(".create").slideUp(0);
if (self.st_line.no_match) self.$el.addClass("no_match");
if (self.context.mode !== "match") self.updateMatches();
if (self.context.mode !== "match") self.render();
self.bindPopoverTo(self.$(".line_info_button"));
self.createFormWidgets();
// Special case hack : no identified partner
if (self.st_line.has_no_partner) {
self.$el.css("opacity", "0");
@ -623,7 +602,7 @@ openerp.account = function (instance) {
_.each(self.getChildren(), function(o){ o.destroy() });
self.is_consistent = false;
return $.when(self.$el.animate({opacity: 0}, self.animation_speed)).then(function() {
self.getParent().unexcludeMoveLines(self, self.partner_id, _.map(self.get("mv_lines_selected"), function(o){ return o.id }));
self.getParent().unexcludeMoveLines(_.map(self.get("mv_lines_selected"), function(o){ return o.id }));
$.each(self.$(".bootstrap_popover"), function(){ $(this).popover('destroy') });
self.$el.empty();
self.$el.removeClass("no_partner");
@ -637,7 +616,6 @@ openerp.account = function (instance) {
self.set("pager_index", 0, {silent: true});
self.set("mv_lines", [], {silent: true});
self.set("mv_lines_selected", [], {silent: true});
self.mv_lines_deselected = [];
self.set("lines_created", [], {silent: true});
self.set("line_created_being_edited", [{'id': 0}], {silent: true});
// Rebirth
@ -836,68 +814,35 @@ openerp.account = function (instance) {
if (e.currentTarget.dataset.selected === "true") self.deselectMoveLine(e.currentTarget);
else self.selectMoveLine(e.currentTarget);
},
// Takes a move line from the match view and adds it to the mv_lines_selected array
selectMoveLine: function(mv_line) {
var self = this;
var line_id = mv_line.dataset.lineid;
// find the line in mv_lines or mv_lines_deselected
var line = _.find(self.get("mv_lines"), function(o){ return o.id == line_id });
if (! line) {
line = _.find(self.mv_lines_deselected, function(o){ return o.id == line_id });
self.mv_lines_deselected = _.filter(self.mv_lines_deselected, function(o) { return o.id != line_id });
}
// If no line found, the view is probably not up to date to the model (asynchronous fun)
if (! line) return;
// Warn the user if he's selecting lines from both a payable and a receivable account
var last_selected_line = _.last(self.get("mv_lines_selected"));
if (last_selected_line && last_selected_line.account_type != line.account_type) {
// TODO : web client API
alert(_.str.sprintf(_t("You are selecting transactions from both a payable and a receivable account.\n\nIn order to proceed, you first need to deselect the %s transactions."), last_selected_line.account_type));
return;
}
var line = _.find(self.propositions_lines, function(o){ return o.id == line_id});
$(mv_line).attr('data-selected','true');
self.getParent().excludeMoveLines([line_id]);
self.set("mv_lines_selected", self.get("mv_lines_selected").concat(line));
},
// Removes a move line from the mv_lines_selected array
deselectMoveLine: function(mv_line) {
var self = this;
var line_id = mv_line.dataset.lineid;
var line = _.find(self.get("mv_lines_selected"), function(o) { return o.id == line_id });
// If no line found, the view is probably not up to date to the model (asynchronous fun)
if (! line) return;
// add the line to mv_lines_deselected and remove it from mv_lines_selected
self.mv_lines_deselected.unshift(line);
var mv_lines_selected = _.filter(self.get("mv_lines_selected"), function(o) { return o.id != line_id });
// remove partial reconciliation stuff if necessary
if (line.partial_reconcile === true) self.unpartialReconcileLine(line);
if (line.propose_partial_reconcile === true) line.propose_partial_reconcile = false;
self.$el.removeClass("no_match");
self.set("mode", "match");
self.set("mv_lines_selected", mv_lines_selected);
var line = _.find(self.propositions_lines, function(o){ return o.id == line_id});
$(mv_line).attr('data-selected','false');
self.getParent().unexcludeMoveLines([line_id]);
self.set("mv_lines_selected",_.filter(self.get("mv_lines_selected"), function(o) { return o.id != line_id }));
},
/** Matches pagination */
pagerControlLeftHandler: function() {
var self = this;
if (self.$(".pager_control_left").hasClass("disabled")) { return; /* shouldn't happen, anyway*/ }
if (self.total_move_lines_num < 0) { return; }
self.set("pager_index", self.get("pager_index")-1 );
},
pagerControlRightHandler: function() {
var self = this;
var new_index = self.get("pager_index")+1;
if (self.$(".pager_control_right").hasClass("disabled")) { return; /* shouldn't happen, anyway*/ }
if ((new_index * self.max_move_lines_displayed) >= self.total_move_lines_num) { return; }
self.set("pager_index", new_index );
},
@ -905,11 +850,9 @@ openerp.account = function (instance) {
var self = this;
self.set("pager_index", 0);
self.filter = self.$(".filter").val();
window.clearTimeout(self.apply_filter_timeout);
self.apply_filter_timeout = window.setTimeout(self.proxy('updateMatches'), 200);
self.render();
},
/** Creating */
initializeCreateForm: function() {
@ -1041,20 +984,16 @@ openerp.account = function (instance) {
table.empty();
var slice_start = self.get("pager_index") * self.max_move_lines_displayed;
var slice_end = (self.get("pager_index")+1) * self.max_move_lines_displayed;
_( _.filter(self.mv_lines_deselected, function(o){
return o.q_label.indexOf(self.filter) !== -1 || (o.ref && o.ref.indexOf(self.filter) !== -1)
})
.slice(slice_start, slice_end)).each(function(line){
var $line = $(QWeb.render("bank_statement_reconciliation_move_line", {line: line, selected: false}));
self.bindPopoverTo($line.find(".line_info_button"));
table.append($line);
nothing_displayed = false;
});
var visible = 0
_(self.get("mv_lines")).each(function(line){
var $line = $(QWeb.render("bank_statement_reconciliation_move_line", {line: line, selected: false}));
self.bindPopoverTo($line.find(".line_info_button"));
table.append($line);
nothing_displayed = false;
if (visible >= slice_start && visible < slice_end) {
var $line = $(QWeb.render("bank_statement_reconciliation_move_line", {line: line, selected: false}));
self.bindPopoverTo($line.find(".line_info_button"));
table.append($line);
nothing_displayed = false;
}
visible = visible + 1;
});
if (nothing_displayed)
table.append(QWeb.render("filter_no_match", {filter_str: self.filter}));
@ -1066,7 +1005,7 @@ openerp.account = function (instance) {
self.$(".pager_control_left").addClass("disabled");
else
self.$(".pager_control_left").removeClass("disabled");
if (self.total_move_lines_num <= ((self.get("pager_index")+1) * self.max_move_lines_displayed))
if (self.get('mv_lines').length <= ((self.get("pager_index")+1) * self.max_move_lines_displayed))
self.$(".pager_control_right").addClass("disabled");
else
self.$(".pager_control_right").removeClass("disabled");
@ -1136,7 +1075,7 @@ openerp.account = function (instance) {
self.el.dataset.mode = "inactive";
} else if (self.get("mode") === "match") {
return $.when(self.updateMatches()).then(function() {
return $.when(self.render()).then(function() {
if (self.$el.hasClass("no_match")) {
self.set("mode", "inactive");
return;
@ -1156,18 +1095,14 @@ openerp.account = function (instance) {
pagerChanged: function() {
var self = this;
self.updateMatches();
self.render();
},
mvLinesChanged: function() {
var self = this;
// If pager_index is out of range, set it to display the last page
if (self.get("pager_index") !== 0 && self.total_move_lines_num <= (self.get("pager_index") * self.max_move_lines_displayed)) {
self.set("pager_index", Math.ceil(self.total_move_lines_num/self.max_move_lines_displayed)-1);
}
// If there is no match to display, disable match view and pass in mode inactive
if (self.total_move_lines_num + self.mv_lines_deselected.length === 0 && self.filter === "") {
if (self.get("mv_lines").length === 0 && self.filter === "") {
self.$el.addClass("no_match");
if (self.get("mode") === "match") {
self.set("mode", "inactive");
@ -1186,13 +1121,11 @@ openerp.account = function (instance) {
var added_lines_ids = _.map(_.difference(val.newValue, val.oldValue), function(o){ return o.id });
var removed_lines_ids = _.map(_.difference(val.oldValue, val.newValue), function(o){ return o.id });
self.getParent().excludeMoveLines(self, self.partner_id, added_lines_ids);
self.getParent().unexcludeMoveLines(self, self.partner_id, removed_lines_ids);
self.getParent().excludeMoveLines(added_lines_ids);
self.getParent().unexcludeMoveLines(removed_lines_ids);
$.when(self.updateMatches()).then(function(){
self.updateAccountingViewMatchedLines();
self.updateBalance();
});
self.updateAccountingViewMatchedLines();
self.updateBalance();
},
// Generic function for updating the line_created_being_edited
@ -1339,47 +1272,23 @@ openerp.account = function (instance) {
loadReconciliationProposition: function() {
var self = this;
return self.model_bank_statement_line
.call("get_reconciliation_proposition", [self.st_line.id, self.getParent().excluded_move_lines_ids[self.partner_id]])
.call("get_reconciliation_proposition", [self.st_line.id, self.getParent().excluded_move_lines_ids])
.then(function (lines) {
_(lines).each(self.decorateMoveLine.bind(self));
self.set("mv_lines_selected", self.get("mv_lines_selected").concat(lines));
});
},
// Loads move lines according to the widget's state
updateMatches: function() {
render: function() {
var self = this;
var deselected_lines_num = self.mv_lines_deselected.length;
var move_lines = {};
var move_lines_num = 0;
var offset = self.get("pager_index") * self.max_move_lines_displayed - deselected_lines_num;
if (offset < 0) offset = 0;
var limit = (self.get("pager_index")+1) * self.max_move_lines_displayed - deselected_lines_num;
if (limit > self.max_move_lines_displayed) limit = self.max_move_lines_displayed;
var excluded_ids = _.collect(self.get("mv_lines_selected").concat(self.mv_lines_deselected), function(o){ return o.id });
excluded_ids = excluded_ids.concat(self.getParent().excluded_move_lines_ids[self.partner_id]);
var deferred_move_lines;
if (limit > 0) {
// Load move lines
deferred_move_lines = self.model_bank_statement_line
.call("get_move_lines_counterparts_id", [self.st_line.id, excluded_ids, self.filter, offset, limit])
.then(function (lines) {
_(lines).each(self.decorateMoveLine.bind(self));
move_lines = lines;
});
}
// Fetch the number of move lines corresponding to this statement line and this filter
var deferred_total_move_lines_num = self.model_bank_statement_line
.call("get_move_lines_counterparts_id", [self.st_line.id, excluded_ids, self.filter, 0, undefined, true])
.then(function(num){
move_lines_num = num;
});
return $.when(deferred_move_lines, deferred_total_move_lines_num).then(function(){
self.total_move_lines_num = move_lines_num + deselected_lines_num;
self.set("mv_lines", move_lines);
var lines_to_show = [];
_.each(self.propositions_lines, function(line){
var filter = (line.q_label.toLowerCase().indexOf(self.filter.toLowerCase()) > -1 || line.account_code.toLowerCase().indexOf(self.filter.toLowerCase()) > -1);
if (self.getParent().excluded_move_lines_ids.indexOf(line.id) === -1 && filter) {
lines_to_show.push(line);
}
});
self.set("mv_lines", lines_to_show);
},
// Changes the partner_id of the statement_line in the DB and reloads the widget
@ -1439,8 +1348,6 @@ openerp.account = function (instance) {
var self = this;
if (! self.is_consistent) return;
self.getParent().unexcludeMoveLines(self, self.partner_id, _.map(self.get("mv_lines_selected"), function(o){ return o.id }));
// Prepare data
var mv_line_dicts = [];
_.each(self.get("mv_lines_selected"), function(o) { mv_line_dicts.push(self.prepareSelectedMoveLineForPersisting(o)) });

View File

@ -128,7 +128,7 @@
<td><span class="glyphicon glyphicon-add-remove"></span></td>
<td><t t-esc="line.account_code"/></td>
<td><t t-esc="line.q_due_date"/></td>
<td><t t-esc="line.q_label"/></td>
<td class="js_qlabel"><t t-esc="line.q_label"/></td>
<td><t t-if="line.debit !== 0">
<t t-if="line.propose_partial_reconcile" t-call="icon_do_partial_reconciliation"></t>

View File

@ -87,7 +87,7 @@ class crm_claim(osv.osv):
('object_id.model', '=', 'crm.claim')]"),
'priority': fields.selection([('0','Low'), ('1','Normal'), ('2','High')], 'Priority'),
'type_action': fields.selection([('correction','Corrective Action'),('prevention','Preventive Action')], 'Action Type'),
'user_id': fields.many2one('res.users', 'Responsible'),
'user_id': fields.many2one('res.users', 'Responsible', track_visibility='always'),
'user_fault': fields.char('Trouble Responsible', size=64),
'section_id': fields.many2one('crm.case.section', 'Sales Team', \
select=True, help="Responsible sales team."\

View File

@ -253,14 +253,6 @@
</p>
</field>
</record>
<menuitem name="Documents" id="menu_document_doc" parent="knowledge.menu_document" sequence="0"/>
<menuitem
name="Documents"
action="action_document_file_form"
id="menu_document_files"
parent="menu_document_doc"
sequence="0"
/>
<record model="ir.actions.act_window" id="action_document_file_directory_form">
<field name="type">ir.actions.act_window</field>

View File

@ -16,8 +16,9 @@
<record model="ir.actions.act_window" id="hr_applicant_resumes">
<field name="name">Resumes and Letters</field>
<field name="res_model">ir.attachment</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="document.view_document_file_tree"/>
<field name="view_type">form</field>
<field name="view_mode">kanban,tree,form</field>
<field name="view_id" ref="mail.view_document_file_kanban"/>
<field name="domain">[('res_model','=','hr.applicant')]</field>
<field name="help" type="html">
<p>

View File

@ -100,13 +100,21 @@
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
<div t-attf-class="oe_attachment" t-if="record.file_type_icon.value != 'webimage'">
<img t-att-src="'/mail/static/src/img/mimetypes/' + record.file_type_icon.value + '.png'"></img>
<div class='oe_name'><t t-raw='record.name.value' />bb</div>
</div>
<div t-attf-class="oe_attachment oe_preview" t-if="record.file_type_icon.value == 'webimage'">
<img t-att-src="kanban_image('ir.attachment', 'datas', record.id.value)" class="oe_kanban_image"/>
<div class='oe_name'><t t-raw='record.name.value' />aa</div>
<div class="oe_kanban_vignette">
<div class="oe_attachment">
<div t-if="record.file_type_icon.value != 'webimage'">
<img t-att-src="'/mail/static/src/img/mimetypes/' + record.file_type_icon.value + '.png'"></img>
</div>
<div t-if="record.file_type_icon.value == 'webimage' and !record.url.value">
<img t-att-src="kanban_image('ir.attachment', 'datas', record.id.value)"/>
</div>
<div t-if="record.file_type_icon.value and record.url.value">
<img t-att-src="record.url.value"/>
</div>
<div class="oe_name">
<field name="name"/>
</div>
</div>
</div>
</div>
</t>

View File

@ -10,7 +10,7 @@ import werkzeug.routing
import openerp
from openerp.addons.base import ir
from openerp.addons.base.ir import ir_qweb
from openerp.addons.website.models.website import slug, url_for
from openerp.addons.website.models.website import slug, url_for, _UNSLUG_RE
from openerp.http import request
from openerp.osv import orm
@ -202,7 +202,7 @@ class ModelConverter(ir.ir_http.ModelConverter):
def __init__(self, url_map, model=False, domain='[]'):
super(ModelConverter, self).__init__(url_map, model)
self.domain = domain
self.regex = r'(?:[A-Za-z0-9-_]+?-)?(\d+)(?=$|/)'
self.regex = _UNSLUG_RE.pattern
def to_url(self, value):
return slug(value)
@ -211,7 +211,7 @@ class ModelConverter(ir.ir_http.ModelConverter):
m = re.match(self.regex, value)
_uid = RequestUID(value=value, match=m, converter=self)
return request.registry[self.model].browse(
request.cr, _uid, int(m.group(1)), context=request.context)
request.cr, _uid, int(m.group(2)), context=request.context)
def generate(self, cr, uid, query=None, args=None, context=None):
obj = request.registry[self.model]

View File

@ -118,11 +118,23 @@ def slug(value):
else:
# assume name_search result tuple
id, name = value
slugname = slugify(name or '')
slugname = slugify(name or '').strip().strip('-')
if not slugname:
return str(id)
return "%s-%d" % (slugname, id)
_UNSLUG_RE = re.compile(r'(?:(\w{1,2}|\w[a-z0-9-_]+?\w)-)?(-?\d+)(?=$|/)', re.I)
def unslug(s):
"""Extract slug and id from a string.
Always return un 2-tuple (str|None, int|None)
"""
m = _UNSLUG_RE.match(s)
if not m:
return None, None
return m.group(1), int(m.group(2))
def urlplus(url, params):
return werkzeug.Href(url)(params or None)

View File

@ -9,11 +9,32 @@ from lxml.builder import E
from openerp.tests import common
from openerp.addons.base.ir import ir_qweb
from openerp.addons.website.models.ir_qweb import html_to_text
from openerp.addons.website.models.website import slugify
from openerp.addons.website.models.website import slugify, unslug
impl = getDOMImplementation()
document = impl.createDocument(None, None, None)
class TestUnslug(unittest2.TestCase):
def test_unslug(self):
tests = {
'': (None, None),
'foo': (None, None),
'foo-': (None, None),
'-': (None, None),
'foo-1': ('foo', 1),
'foo-bar-1': ('foo-bar', 1),
'foo--1': ('foo', -1),
'1': (None, 1),
'1-1': ('1', 1),
'--1': (None, None),
'foo---1': (None, None),
'foo1': (None, None),
}
for slug, expected in tests.iteritems():
self.assertEqual(unslug(slug), expected)
class TestHTMLToText(unittest2.TestCase):
def test_rawstring(self):
self.assertEqual(

View File

@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
import re
import werkzeug
from openerp import SUPERUSER_ID
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.addons.website.models.website import slug
from openerp.addons.website.models.website import slug, unslug
from openerp.tools.translate import _
@ -131,7 +130,7 @@ class WebsiteCrmPartnerAssign(http.Controller):
# Do not use semantic controller due to SUPERUSER_ID
@http.route(['/partners/<partner_id>'], type='http', auth="public", website=True)
def partners_detail(self, partner_id, partner_name='', **post):
mo = re.search('([-0-9]+)$', str(partner_id))
_, partner_id = unslug(partner_id)
current_grade, current_country = None, None
grade_id = post.get('grade_id')
country_id = post.get('country_id')
@ -143,8 +142,7 @@ class WebsiteCrmPartnerAssign(http.Controller):
country_ids = request.registry['res.country'].exists(request.cr, request.uid, int(country_id), context=request.context)
if country_ids:
current_country = request.registry['res.country'].browse(request.cr, request.uid, country_ids[0], context=request.context)
if mo:
partner_id = int(mo.group(1))
if partner_id:
partner = request.registry['res.partner'].browse(request.cr, SUPERUSER_ID, partner_id, context=request.context)
if partner.exists() and partner.website_published:
values = {

View File

@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
import re
import openerp
from openerp import SUPERUSER_ID
from openerp.addons.web import http
from openerp.addons.website.models.website import unslug
from openerp.tools.translate import _
from openerp.addons.web.http import request
import werkzeug.urls
@ -84,9 +83,8 @@ class WebsiteCustomer(http.Controller):
# Do not use semantic controller due to SUPERUSER_ID
@http.route(['/customers/<partner_id>'], type='http', auth="public", website=True)
def partners_detail(self, partner_id, **post):
mo = re.search('([-0-9]+)$', str(partner_id))
if mo:
partner_id = int(mo.group(1))
_, partner_id = unslug(partner_id)
if partner_id:
partner = request.registry['res.partner'].browse(request.cr, SUPERUSER_ID, partner_id, context=request.context)
if partner.exists() and partner.website_published:
values = {}

View File

@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
import re
from openerp import SUPERUSER_ID
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.addons.website.models.website import unslug
from openerp.tools.translate import _
import werkzeug.urls
@ -105,9 +104,8 @@ class WebsiteMembership(http.Controller):
# Do not use semantic controller due to SUPERUSER_ID
@http.route(['/members/<partner_id>'], type='http', auth="public", website=True)
def partners_detail(self, partner_id, **post):
mo = re.search('([-0-9]+)$', str(partner_id))
if mo:
partner_id = int(mo.group(1))
_, partner_id = unslug(partner_id)
if partner_id:
partner = request.registry['res.partner'].browse(request.cr, SUPERUSER_ID, partner_id, context=request.context)
if partner.exists() and partner.website_published:
values = {}

View File

@ -3860,7 +3860,7 @@ class BaseModel(object):
parents_changed = []
parent_order = self._parent_order or self._order
if self._parent_store and (self._parent_name in vals):
if self._parent_store and (self._parent_name in vals) and not context.get('defer_parent_store_computation'):
# The parent_left/right computation may take up to
# 5 seconds. No need to recompute the values if the
# parent is the same.

View File

@ -470,7 +470,7 @@ class PreforkServer(CommonServer):
cmd = nargs[0]
cmd = os.path.join(os.path.dirname(cmd), "openerp-gevent")
nargs[0] = cmd
popen = subprocess.Popen(nargs)
popen = subprocess.Popen([sys.executable] + nargs)
self.long_polling_pid = popen.pid
def worker_pop(self, pid):