Procházet zdrojové kódy

duplicate detection improved

master
patrick před 6 roky
rodič
revize
0946619c2a
5 změnil soubory, kde provedl 213 přidání a 105 odebrání
  1. +0
    -1
      Xero.php
  2. +19
    -4
      css/bts_office.css
  3. +5
    -1
      html/jobv1.html
  4. +1
    -1
      html/jobv1_editor.html
  5. +188
    -98
      js/bts_office.js

+ 0
- 1
Xero.php Zobrazit soubor

$msg= "Xero API resource not found rate limit exceeded, please try again later, existing sync within 600 seconds will by passed automatically\n"; $msg= "Xero API resource not found rate limit exceeded, please try again later, existing sync within 600 seconds will by passed automatically\n";
$this->logConsole($msg); $this->logConsole($msg);
} }
} }
private function sync_clients(){ private function sync_clients(){
$contacts = $this->getClients($this->clientgroup); $contacts = $this->getClients($this->clientgroup);

+ 19
- 4
css/bts_office.css Zobrazit soubor

cursor: pointer; cursor: pointer;
} }


div.jobTable.to_be_deleted_duplicate div.bsave span.ticon-save,
div.jobTable.saved div.bsave span.ticon-save { div.jobTable.saved div.bsave span.ticon-save {
display: none; display: none;
} }
animation-name: blinker; animation-name: blinker;
} }


div.bdelete span.ticon-trash,
div.bedit span.ticon-edit {
div.workspace div.bdelete span.ticon-trash,
div.workspace div.bedit span.ticon-edit {
display: inline-block; display: inline-block;
border: 3px solid lightgrey; border: 3px solid lightgrey;
padding: 5px; padding: 5px;
color: red; color: red;
cursor: pointer; cursor: pointer;
} }
div.bedit span.ticon-edit{
div.workspace div.bedit span.ticon-edit{
color:blue; color:blue;
} }


{ {
animation: blinker 0.3s linear infinite; animation: blinker 0.3s linear infinite;
} }

div.jobTable.to_be_deleted_duplicate span.ticon-copy, div.jobTable.to_be_deleted_duplicate span.ticon-copy,
div.jobTable.to_be_deleted_duplicate span.ticon-edit{ div.jobTable.to_be_deleted_duplicate span.ticon-edit{
display:none !important; display:none !important;
} }
div.jobTable:not(.to_be_deleted_duplicate) span.ticon-check-circle{
display:none !important;
}
div.jobTable.to_be_deleted_duplicate span.ticon-check-circle{
display: inline-block;
border: 3px solid green;
padding: 5px;
border-radius: 10px;
color: green;
cursor: pointer;
font-size: 1.1em;
}


div.divTableHeading div > span:hover {
div.divTableHeading div > span.ticon-trash:hover,
div.divTableHeading div > span.ticon-search:hover {
cursor: pointer; cursor: pointer;
animation: blinker 0.3s linear infinite; animation: blinker 0.3s linear infinite;
} }

+ 5
- 1
html/jobv1.html Zobrazit soubor

{{#jobs}} {{#jobs}}
<div class="divTable jobTable blueTable {{#saved}} saved {{/saved}} {{^saved}} dirty {{/saved}} {{#is_week1}}week1job{{/is_week1}} {{#is_week2}} week2job {{/is_week2}}" <div class="divTable jobTable blueTable {{#saved}} saved {{/saved}} {{^saved}} dirty {{/saved}} {{#is_week1}}week1job{{/is_week1}} {{#is_week2}} week2job {{/is_week2}}"
{{#id}} id="job_{{id}}" {{/id}} {{^id}} id="{{newjob_id}}" {{/id}} {{#id}} id="job_{{id}}" {{/id}} {{^id}} id="{{newjob_id}}" {{/id}}
data-id="{{id}}" {{^id}} data-newjob_id="{{newjob_id}}" {{/id}} data-tos="{{tos}}" data-rate="{{rate}}" data-staff="{{staff}}" data-client="{{client}}">
data-id="{{id}}" {{^id}} data-newjob_id="{{newjob_id}}" {{/id}}
data-tos="{{tos}}" data-rate="{{rate}}" data-staff="{{staff}}" data-client="{{client}}"
data-start="{{start}}" data-finish="{{finish}}">
<div class="divTableBody"> <div class="divTableBody">
<div class="divTableRow"> <div class="divTableRow">
<div class="divTableCell btos {{#tos_err}}error{{/tos_err}}" title="{{tos_err}}"> <div class="divTableCell btos {{#tos_err}}error{{/tos_err}}" title="{{tos_err}}">
<div class="divTableCell bconfirmed"><input name="ack" type=checkbox {{#is_confirmed}}checked{{/is_confirmed}} onclick="return false;"></div> <div class="divTableCell bconfirmed"><input name="ack" type=checkbox {{#is_confirmed}}checked{{/is_confirmed}} onclick="return false;"></div>
<div class="divTableCell brating {{#rating_err}} error {{/rating_err}}">{{rating}}</div> <div class="divTableCell brating {{#rating_err}} error {{/rating_err}}">{{rating}}</div>
<div class="divTableCell bedit"> <div class="divTableCell bedit">
<span class="ticon ticon-check-circle"></span>
<span class="ticon ticon-edit"></span> <span class="ticon ticon-edit"></span>
</div> </div>
<div class="divTableCell bdelete"> <div class="divTableCell bdelete">
</div> </div>
<div class="divTableCell bsave "> <div class="divTableCell bsave ">
<span class="ticon ticon-copy"></span> <span class="ticon ticon-copy"></span>
<span class="ticon ticon-save"></span>
</div> </div>
</div> </div>
<div class="divTableRow errmsg"> <div class="divTableRow errmsg">

+ 1
- 1
html/jobv1_editor.html Zobrazit soubor

</select> </select>
</div> </div>
<div class="divTableCell bdelete"> <div class="divTableCell bdelete">
<span class="ticon ticon-plus"></span>
<span class="ticon ticon-plus-not-exist"></span>
</div> </div>
<div class="divTableCell bsave "> <div class="divTableCell bsave ">
<span class="ticon ticon-save"></span> <span class="ticon ticon-save"></span>

+ 188
- 98
js/bts_office.js Zobrazit soubor

}); });
$(document).on('click', 'div.divTableHead.bdelete span.ticon-trash', function(){ $(document).on('click', 'div.divTableHead.bdelete span.ticon-trash', function(){
var len = $('div.jobTable.to_be_deleted_duplicate').length;
if (len <= 0){
if (confirm("No duplicates to delete, trying to find duplicates?")){
check_duplicate();
}
return;
}else{
if (!confirm("Delete " + len + " duplicates? "))
return;
}
var ids = [];
$('div.jobTable.to_be_deleted_duplicate').each(function(){
var id = $(this).attr('data-id');
ids.push(id);
});

$.post(bts().ajax_url, { // POST request
_ajax_nonce: bts().nonce, // nonce
action: "delete_jobv1", // action
jobs: ids, //delete multiple ids
}, function(response, status, xhr){
if (response.status=='success'){
$('div.jobTable.to_be_deleted_duplicate').each(function(){
var id = $(this).attr('data-id');
delete bts().job_map[id];
$(this).get(0).scrollIntoView();
fade_and_delete(this);
});
debounced_calculate();
}else{
alert( 'error deleting duplicates:\n\nError:\n\n' + response.error + "\n\n");
}
});

check_duplicate();
setTimeout(do_delete_duplicate, 200);//200ms make GUI refresh
}); });


function fade_and_delete(el) function fade_and_delete(el)
add_new_empty_job(); add_new_empty_job();
}); });
function remove_job_from_gui(el)
{
el = $(el);
var newjob_id = el.data().newjob_id;
var id = el.data().id;
if (typeof newjob_id != 'undefined' && newjob_id !=''){
delete bts().job_map_new[newjob_id];
}else if (typeof id != 'undefined' && id != ''){
delete bts().job_map[id];
}
el.addClass('blink_me');
el.fadeOut(500);
setTimeout(function(){
el.remove();
debounced_calculate();
}, 500);
}
$(document).on('click', 'div.divTableCell.bdelete', function(){ $(document).on('click', 'div.divTableCell.bdelete', function(){
var el = $(this).closest('div.jobTable'); var el = $(this).closest('div.jobTable');
var data = el.data(); var data = el.data();
if (typeof data.id =='undefined' || data.id == '')
el.remove();
else{
if (typeof data.id =='undefined' || data.id == ''){//not saved
remove_job_from_gui(el);//remove direclty
}else{
var id = data.id; var id = data.id;
if (confirm('delete this job?')){ if (confirm('delete this job?')){
$.post(bts().ajax_url, { // POST request $.post(bts().ajax_url, { // POST request
jobid: id, jobid: id,
}, function(response, status, xhr){ }, function(response, status, xhr){
if (response.status=='success'){ if (response.status=='success'){
var id = el.attr('data-id');
delete bts().job_map[id];
//console.log("delete %s , job_map[%s]=%o ", id, id, bts().job_map[id]);
el.addClass('blink_me');
el.fadeOut(900);
setTimeout(function(){
el.remove();
}, 900);
remove_job_from_gui(el);
}else{ }else{
alert( 'error saving data, please check your network');
alert( 'error deleting job, please check your network');
} }
}); });
} }
} }
debounced_calculate();
}); });
$(document).on('mouseenter', 'div.divTableCell', function(){ $(document).on('mouseenter', 'div.divTableCell', function(){
}); });
$(document).on('click', 'div.workspace span.ticon.ticon-save', function(){ $(document).on('click', 'div.workspace span.ticon.ticon-save', function(){
var table = $(this).closest('div.divTable');
table.data().job.do_save_record();
var table = $(this).closest('div.jobTable');
var data = table.data();
if (data.id == "" && data.newjob_id != "" && data.newjob_id.substring(0,4) == "new_" ){
job = bts().job_map_new[data.newjob_id];
console.assert(typeof job != 'undefined');
do_save_new_job(job.get_record(), table);
return;
}
alert("Error occured");
$(this).fadeOut();
}); });
$(document).on('click', '.divTableHeading span.ticon.ticon-save', function(){ $(document).on('click', '.divTableHeading span.ticon.ticon-save', function(){
//save all //save all
$('div.workspace span.ticon.ticon-save').each(function (i,e){ $('div.workspace span.ticon.ticon-save').each(function (i,e){
add_new_empty_job(newj); add_new_empty_job(newj);
});
});
function do_save_new_job(job_tobe_saved, table){
$.post(bts().ajax_url, { // POST request
_ajax_nonce: bts().nonce, // nonce
action: "save_job", // action
record: job_tobe_saved,
}, function(response, status, xhr){
if (response.status=='success'){
var job = new Job(response.newdata);
job.saved = true;
job.is_new = response.isNew;
bts().job_map[job.id] = job;
//delete that old job and display the new one
var jobs=[job];
var html = Mustache.render($('#jobv1_item').html(), {jobs:jobs});
delete bts().job_map_new[table.data().newjob_id];
table.after(html);
table.remove();
}else{
console.error("error saving job %o, response=%o", job_tobe_saved, response);
alert( 'error saving data, please check your network');
}
});
}
function mark_highlight_me(el, ms){ function mark_highlight_me(el, ms){
el.addClass('blink_me'); el.addClass('blink_me');
$(s).data(); $(s).data();
} }

// setTimeout(function(){
// set_modal_title('warning', 'suck title');
// set_modal_content('warning', 'fucking details');
// //open_modal('warning');
// }, 1000);
//
// setTimeout(function(){
// set_modal_title('error', 'error title');
// set_modal_content('error', 'error details');
// //open_modal('error');
// }, 5000);
$(document).on('mouseenter', 'div.week1 div', function(){
$(this).addClass('blink_me');
get_week2_partner(this).addClass('blink_me');
blink_same_date_by_div(this);
var blink_by_date_timer;
$(document).on('mouseenter', 'div.week1 > div, div.week2 > div', function(){
var self = this;
blink_by_date_timer = setTimeout (function(){
$(self).addClass('blink_me');
get_week2_partner(self).addClass('blink_me');
blink_same_date_by_div(self);
}, 1500);
}); });
$(document).on('mouseleave', 'div.week1 div', function(){
$(document).on('mouseleave', 'div.week1 div, div.week2 > div', function(){
clearTimeout(blink_by_date_timer);
$(this).removeClass('blink_me'); $(this).removeClass('blink_me');
get_week2_partner(this).removeClass('blink_me'); get_week2_partner(this).removeClass('blink_me');
unblink_all_date(); unblink_all_date();


$('div.week1 > div').click(function(e){ $('div.week1 > div').click(function(e){
e.stopPropagation(); e.stopPropagation();
blink_same_date_by_div(this);
if ($('div.bstart.blink_me').length == 0){ if ($('div.bstart.blink_me').length == 0){
alert("nothing to copy"); alert("nothing to copy");
return; return;
var els=[]; var els=[];
unblink_all_date(); unblink_all_date();
var first_into_view = false; //make sure first row in match is visible var first_into_view = false; //make sure first row in match is visible
$('div.bstart').each(function(i,e){
if ( $(e).is(":visible") ){
var value = $(e).html();
if( -1 != value.indexOf(strDate) ) //found
{
if ( !first_into_view ){ //scroll to top
first_into_view = true;
$(e).get(0).scrollIntoView();
}
els.push(e);
$(e).addClass('blink_me');
}
$('div.workspace div.bstart:visible').each(function(i,e){
var value = $(e).html();
if( -1 != value.indexOf(strDate) ) //found
{
if ( !first_into_view ){ //scroll to top
first_into_view = true;
ensure_visible(e);
}
els.push($(e));
$(e).addClass('blink_me');
} }
}); });
} }
function ensure_visible(el){
$(el).get(0).scrollIntoView();
}
function unblink_all_date(){ function unblink_all_date(){
$('div.bstart').removeClass('blink_me'); $('div.bstart').removeClass('blink_me');
//clear datetime picker //clear datetime picker
$('div.xdsoft_datetimepicker').remove(); $('div.xdsoft_datetimepicker').remove();
// //
bts().job_map = {};
bts().job_map_new = {};
show_loading_jobs(); show_loading_jobs();
} }
return false; return false;
} }
$('button[name="confirmschedule"]').click(function(){//TODO: check error before confirm
$('button[name="confirmschedule"]').click(function(){
if( check_workspace_error() ){ if( check_workspace_error() ){
return; return;
} }
}); });
} }
function do_delete_duplicate()
{
var len = $('div.jobTable.to_be_deleted_duplicate').length;
if (len <= 0){
return;
}
if (!confirm("Delete " + len + " duplicates? ")){
return;
}
$('div.jobTable.to_be_deleted_duplicate:not(.saved)').each(function(){
remove_job_from_gui(this);
});
var ids = [];
$('div.jobTable.to_be_deleted_duplicate.saved').each(function(){
var id = $(this).attr('data-id');
if (id != '')
ids.push(id);
});
if ( ids.length >0 ){
$.post(bts().ajax_url, { // POST request
_ajax_nonce: bts().nonce, // nonce
action: "delete_jobv1", // action
jobs: ids, //delete multiple ids
}, function(response, status, xhr){
if (response.status=='success'){
$('div.jobTable.to_be_deleted_duplicate.saved').each(function(){
remove_job_from_gui(this);
});
debounced_calculate();
}else{
alert( 'error deleting duplicates:\n\nError:\n\n' + response.error + "\n\n");
}
});
}
}


function check_duplicate() function check_duplicate()
{ {
var to_be_deleted=[]; var to_be_deleted=[];
//make a copy of all jobs
var alljobs = {};
$('div.jobTable:visible').each(function(e){
alljobs[$(this).attr('id')] = $.extend({}, $(this).data());
});
//loop through jobs //loop through jobs
for(var id1 in bts().job_map){
var job1 = bts().job_map[id1];
for(var id1 in alljobs){
var job1 = alljobs[id1];
if (typeof job1.parent != 'undefined') if (typeof job1.parent != 'undefined')
continue; //bypass it it has already found parents continue; //bypass it it has already found parents


job1.duplicates={}; job1.duplicates={};
//console.log('investigating %s' , job1.id); //console.log('investigating %s' , job1.id);
//match job2 //match job2
for(var id2 in bts().job_map){
var job2 = bts().job_map[id2];
for(var id2 in alljobs){
if (id1 == id2)
continue;
var job2 = alljobs[id2];
if (typeof job2.compared_as_master != 'undefined') if (typeof job2.compared_as_master != 'undefined')
continue; continue;
alert("No duplicate found!"); alert("No duplicate found!");
return; return;
} }
bts().to_be_deleted_duplicate = to_be_deleted;
//console.log('all-done, found %d duplicates: %o', to_be_deleted.length, to_be_deleted); //console.log('all-done, found %d duplicates: %o', to_be_deleted.length, to_be_deleted);
mark_duplicate(to_be_deleted); mark_duplicate(to_be_deleted);
return to_be_deleted;
} }
function is_same_job(job1, job2) function is_same_job(job1, job2)
{ {
var s1 = new Date(job1.start);
var s2 = new Date(job2.start);
var f1 = new Date(job1.finish);
var f2 = new Date(job2.finish);
if ( (job1.tos == job2.tos) && if ( (job1.tos == job2.tos) &&
(job1.staff == job2.staff) && (job1.staff == job2.staff) &&
(job1.client == job2.client) && (job1.client == job2.client) &&
(job1.start == job2.start) &&
(job1.finish == job2.finish) )
(s1 - s2 == 0) &&
(f1 - f2 == 0) )
{ {
return true; return true;
} }
function mark_duplicate(ids) function mark_duplicate(ids)
{ {
ids.forEach(function(id){ ids.forEach(function(id){
var selector = '#job_' +id;
$(selector).get(0).scrollIntoView();
var selector = '#' + id;
ensure_visible(selector);
$(selector).addClass('to_be_deleted_duplicate'); $(selector).addClass('to_be_deleted_duplicate');
}); });
} }
}); });
init_ts(); init_ts();
$('div.divTableHeading div.bsave span.ticon-search').mouseenter(function(){//highlight unsaved
var el = $('div.jobTable.dirty .bsave');
if (el.length > 0){
el.addClass('blink_me');
el.get(0).scrollIntoView();
}
})
$('div.divTableHeading div.bsave span.ticon-search').mouseleave(function(){//highlight unsaved
var el = $('div.jobTable.dirty .bsave');
if (el.length > 0 ) {
el.removeClass('blink_me');
}
})
$('div.divTableHeading div.bsave span.ticon-search').click(do_test);
function do_test(){ //TODO: remove this search function
open_modal('editor');
set_modal_title('editor', "title");
set_modal_content('editor', $('div.workspace').html());
$(document).on('click', 'div.jobTable.to_be_deleted_duplicate div.bedit span.ticon-check-circle',function(){//highlight unsaved
var el = $(this).closest('div.jobTable');
if (!confirm ("Mark this is none duplicate?")){
return;
}
el.removeClass('to_be_deleted_duplicate');
});
$('div.divTableHeading div.bsave span.ticon-search').click(do_delete_unsaved_copy);
function do_delete_unsaved_copy()
{ //TODO: remove this search function
var num = $('div.jobTable.dirty').length;
if (num > 0){
if ( !confirm('delete all '+ num + ' unsaved?')){
return;
}
$('div.jobTable.dirty').each(function(){
var newjob_id = $(this).data().newjob_id;
delete bts().job_map_new[newjob_id]
$(this).remove();
})
}else{
alert("nothing to clean up");
}
} }
/*________________________________________________________________________*/ /*________________________________________________________________________*/

Načítá se…
Zrušit
Uložit