%# Never underestimate the bandwidth of a station wagon full of tapes.
%# 		-- Dr. Warren Jackson, Director, UTCS
<%PERL>
return unless !$r or $RT::Sync104HRMS or $Force;
return if $r and $r->uri =~ /401/ and !$Force;

use strict;
my $key = '104_HRMS_SYSTEM_SYNCHRONIZE_LOCK';
$RT::Handle->dbh->do("SELECT GET_LOCK('$key', 3600);");

my $ROOT = RT::CurrentUser->new;
$ROOT->LoadByName('root');
return unless $ROOT->Id;

my $Outs = sub {
    return unless $Force;
    if ($m) { $m->print(" " x 100, $_[0]) } else { print $_[0] }
};
my $Info = sub {
    $RT::Logger->info("[Sync104HRMS] $_[0]");
#    $Outs->("$_[0]<br>\n");
};

# Check for updates {{{

my (%groups, %users, %bosses, %orgs);
my $driver = ($^O eq 'MSWin32') ? 'ODBC' : 'mysql';
my $default_dsn = ($^O eq 'MSWin32') ? 'dsn=rt' : '89390142';
my @conn = (
    ($DSN || "DBI:$driver:$default_dsn"),
    ($DBUser || (($^O eq 'MSWin32') ? '' : "root")),
    $DBPass,
);
my $dbh = RT::User->dbh || DBI->connect(@conn)
    or ($RT::Handle->dbh->do("SELECT RELEASE_LOCK('$key');"), return $Outs->("@conn: $DBI::errstr"));

my ($u1) = $dbh->selectrow_array(qq(
    select UPDATE_DATE from hrms_sys_flowchar ORDER BY UPDATE_DATE DESC;
));
my ($u2) = $dbh->selectrow_array(qq(
    select UPDATE_DATE from hrms_employee ORDER BY UPDATE_DATE DESC;
));

my $time_current = "$u1 / $u2";

my ($time_previous) = $RT::Handle->dbh->selectrow_array(qq(
    select a_session from sessions where id = '$key';
));

local @RT::Record::ISA = qw(RT::Base DBIx::SearchBuilder::Record); # defeat caching

my $UpgradeLevel = 1;
if (eval { $ROOT->Attribute('UpgradeLevel') } < $UpgradeLevel
    and eval { require RTx::Reports; require RTx::ReportSources; 1 })
{
    require RT::CompanySpecific;
    local $RT::CompanySpecific = 0;
    my $Reports = RTx::Reports->new($RT::SystemUser);
    foreach my $Report (@{$Reports->ItemsArrayRef}) {
	$Report->UpdateCompany;
    }
    my $ReportSources = RTx::ReportSources->new($RT::SystemUser);
    foreach my $ReportSource (@{$ReportSources->ItemsArrayRef}) {
	$ReportSource->UpdateCompany;
    }
    $ROOT->SetAttribute('UpgradeLevel' => $UpgradeLevel);
}

if (!length(eval { $ROOT->Attribute('Company') })) {
    require RT::CompanySpecific;
    local $RT::CompanySpecific = 0;

    $ROOT->SetAttribute('Company' => 0);
    my $Users = RT::Users->new($RT::SystemUser);
    $Users->Limit( FIELD => 'ExternalAuthId', OPERATOR => '!=', VALUE=> '0' );
    foreach my $User (@{$Users->ItemsArrayRef}) {
	$User->UpdateCompany;
    }

    my $Queues = RT::Queues->new($RT::SystemUser);
    $Queues->UnLimit;
    foreach my $Queue (@{$Queues->ItemsArrayRef}) {
	$Queue->UpdateCompany;
    }

    $Queues->SetCategories( [ split("\n", $ROOT->UserObj->FreeformContactInfo) ] ) unless $Queues->Categories;
    $ROOT->UserObj->SetFreeformContactInfo( '' );

    my $Roles = RT::Groups->new($RT::SystemUser);
    $Roles->LimitToUserDefinedGroups();
    $Roles->Limit(
	FIELD => 'Description',
	VALUE => '0',
	OPERATOR => '<'
    );
    $Roles->Limit(
	FIELD => 'Name',
	VALUE => 'NULL',
	OPERATOR => 'IS NOT'
    );

    while (my $Role = $Roles->Next) {
	$Role->SetDomain('RT::Group-Role');
	$Role->SetType('Owner');
    }

    my $Groups = RT::Groups->new($RT::SystemUser);
    $Groups->LimitToUserDefinedGroups;
    while (my $Group = $Groups->Next) {
	my $org_id = $Group->Description or next;
	my ($company) = $dbh->selectrow_array("
	    SELECT company_id
	    FROM hrms_department
	    WHERE department_id = '$org_id';
	");
	($company) = $dbh->selectrow_array("
	    SELECT company_id
	    FROM hrms_company
	    WHERE company_id = '$org_id';
	") unless $company;
	$Group->UpdateCompany($company) if $company;
    }

    require YAML;
    my $RoleMap = eval { YAML::Load($ROOT->UserObj->Signature) } || {};
    $Groups->SetRoleMap($RoleMap) unless $Groups->RoleMap;
    $ROOT->UserObj->SetSignature('');
}

if (!$Force and ($time_previous eq $time_current or !$RT::Sync104HRMS)) {
    $RT::Handle->dbh->do("SELECT RELEASE_LOCK('$key');");
    return if $m;
}

my $rv = $RT::Handle->dbh->do(qq(
    update sessions set a_session = '$time_current' where id = '$key';
));

eval {
    $RT::Handle->dbh->do("DELETE FROM sessions WHERE id = '$key';");
    $RT::Handle->dbh->do("INSERT INTO sessions (id, a_session) VALUES ('$key', '$time_current')");
} unless $rv == 1;

# }}}

# Defining $walk_group {{{
my $walk_group;
$walk_group = sub {
    my ($n, $i, $parent, $level, $parent_touched) = @_;
    my ($id, $type, $org_id) = map $n->{"FLOWCHAR_$_"}, qw(ID TYPE ORG_ID);
    my $org_type = [ '', qw(org company department) ]->[$type];

    my $company_sql = ', company_id' unless $org_type eq 'org';
    my ($name, $company) = $dbh->selectrow_array("
	SELECT ${org_type}_cname$company_sql
	  FROM hrms_$org_type
	 WHERE ${org_type}_id = '$org_id';
    ");

    return unless defined($name) and length($name);

    $name =~ s/^\s+//; $name =~ s/\s+$//;
    $name = Encode::decode(big5 => $name);
    $name =~ s/&#([1-9]\d\d+);/chr($1)/eg;  # anti-escapism

    my $boss = $i->{BOSS_EMPLOYEE_ID};
    my $prefix = ( 3 - $type ) * 100000;
    my $g = RT::Group->new($RT::SystemUser);
    $g->LoadByCols(
	Domain		=> 'UserDefined',
	Description	=> $org_id + $prefix,
    );

    my $touch = 0;
    if (!$g->Id) {
	if ($prefix) {
	    $g->LoadByCols( Domain => 'UserDefined', Description => $org_id);
	    if ($g->Id) {
		$Info->(loc("Group redescribed from [_1] to [_2]"), $org_id, ($org_id + $prefix));
		$g->SetDescription($org_id + $prefix);
		$touch++;
	    }
	}
	if (!$touch) {
	    $Info->(loc("Group created: [_1]", $name));
	    $g->CreateUserDefinedGroup( Name => $name, Description => $org_id + $prefix );
	    $touch++;
	}
    }
    elsif ($g->Name ne $name) {
	$Info->(loc("Group renamed from [_1] to [_2]", $g->Name, $name));
	$g->SetName($name);
	$touch++;
    }

    $g->SetAttribute( Company => $company ) if $company and $touch;
    if ($parent and ($touch or $parent_touched)) {
	RT::GroupMember->new($session{CurrentUser})->Create(
	    Member => $g->PrincipalObj,
	    Group => $parent->PrincipalObj,
	);
    }

    $orgs{$org_type}{$org_id} = $g;
    $groups{$g->Id}++;
    push @{$bosses{$boss}}, $g if $boss;

    $Outs->('.'); my $c = keys %groups;
    $Outs->("($c)<br>\n") unless $c % 100;

    foreach my $child (
        sort {
            $a->{FLOWCHAR_ID} <=> $b->{FLOWCHAR_ID}
        } grep {
            (($type >= 3) or ($_->{FLOWCHAR_TYPE} == ($type + 1)))
                and
            $_->{PARENT_ID} eq $id
        } values %$i
    ) {
	$walk_group->($child, $i, $g, $level, $touch);
    }
};

# }}}
# Select groups and insert them {{{

my $h = $dbh->selectrow_hashref(qq(
    SELECT *
    FROM hrms_sys_flowchar_ver
    WHERE flowchar_finished='1'
    ORDER BY flowchar_date DESC
)) or ($RT::Handle->dbh->do("SELECT RELEASE_LOCK('$key');"), return $Outs->($dbh->errstr));

my $all_groups = $dbh->selectall_hashref(qq(
    SELECT *
    FROM hrms_sys_flowchar
    WHERE flowchar_ver_id='$h->{FLOWCHAR_VER_ID}'
), 'FLOWCHAR_ID');

my $count = scalar keys %$all_groups;
$Outs->("<hr>" . loc("Phase 1: Create/Rename Groups ([_1])", $count) . "<br>\n");
foreach my $root_group ( grep !$_->{PARENT_ID}, values %$all_groups ) {
    $walk_group->($root_group, $all_groups);
}

# }}}

# Disable 'disappeared' groups {{{

my $all_rt_groups = $RT::Handle->dbh->selectall_arrayref(qq(
SELECT Groups.Id, Principals.Disabled
  FROM Groups, Principals
 WHERE Groups.Id = Principals.ObjectId
   AND Groups.Domain = 'UserDefined'
   AND Groups.Description > 0;
));

$count = @$all_rt_groups;
$Outs->("<hr>" . loc("Phase 2: Disable/Enable Groups ([_1])", $count) . "<br>\n");

my $c = 0;
foreach my $g (@$all_rt_groups) {
    $Outs->('.'); $Outs->("($c)<br>\n") unless ++$c % 100;
    my $disabled = $groups{$g->[0]} ? 0 : 1;
    next unless ($disabled xor $g->[1]);

    # so they are unequal... amend it...
    $Info->(loc("Setting [_1]'s 'Disabled' property to [_2]", $g->[0], $disabled));
    my $GroupObj = RT::Group->new($RT::SystemUser);
    $GroupObj->Load($g->[0]);
    $GroupObj->SetDisabled($disabled);
}
# }}}

# Defining $walk_user {{{

my ($walk_user, $check_name);

$check_name = sub {
    my ($name, $no, $all_users) = @_;
    my $u = RT::User->new($RT::SystemUser);
    $u->LoadByCol(Name => $name);
    $u->LoadByCol(RealName => $name) unless $u->Id;
    if ($u->Id and $u->Name eq $name) {
	my $old_no = $all_users->{$u->ExternalAuthId}{EMPLOYEE_NO};
	if ($old_no) {
	    my $old_name = "$name-$old_no";
	    $Info->(loc("Existing user renamed from [_1] to [_2]", $name, $old_name));
	    $u->SetName($old_name);
	    return "$name-$no";
	}
	else {
	    $Info->(loc("Purging stale data: [_1]", $name));
	    $u->DBIx::SearchBuilder::Record::Delete;
	}
    }
    return $name;
};

$walk_user = sub {
    my ($user, $all_users) = @_;
    my ($id, $name, $company, $dept, $no, $phone, $email)
	= @{$user}{qw(EMPLOYEE_ID EMPLOYEE_CNAME COMPANY_ID DEPARTMENT_ID EMPLOYEE_NO EMPLOYEE_OFFICE_TEL_1 EMPLOYEE_EMAIL_1)};

    return unless defined($name) and length($name);

    $name =~ s/^\s+//; $name =~ s/\s+$//;
    $name = Encode::decode(big5 => $name);
    $name =~ s/&#([1-9]\d\d+);/chr($1)/eg;  # anti-escapism

    my $u = RT::User->new($RT::SystemUser);
    $u->LoadByCol( ExternalAuthId => $id );

    my $touch = 0;
    if (!$u->Id) {
	my $real_name = $name;
	$name = $check_name->($name, $no, $all_users);
	if ($name eq $real_name) {
	    $Info->(loc("User created: [_1]", $name));
	}
	else {
	    $Info->(loc("User created: [_1] ([_2])", $name, $real_name));
	}
        $u->Create(
	    Privileged => 1,
	    Name => $name,
	    RealName => $real_name,
	    Gecos => $no,
	    Organization => $dept,
	    WorkPhone => $phone,
	    EmailAddress => $email,
	);
	$touch++;
    }
    else {
	if ($u->RealName ne $name) {
	    $Info->(loc("User renamed from [_1] to [_2]", $u->RealName, $name));
	    $u->SetRealName($name);
	    $touch++;
	}
	if ($u->Name ne $name and $u->Name ne "$name-$no") {
	    $name = $check_name->($name, $no, $all_users);
	    $Info->(loc("User renamed from from [_1] to [_2]", $u->Name, $name));
            $u->SetName($name);
	    $touch++;
	}
	if ($u->Gecos ne $no) {
            $u->SetGecos($no);
	}
	if ($u->Organization ne $dept) {
            $u->SetOrganization($dept);
	}
	if ($u->WorkPhone ne $phone) {
            $u->SetWorkPhone($phone);
	}
	if ($u->EmailAddress ne $email) {
            $u->SetEmailAddress($email);
	}
    }


    if ($touch) {
	$u->SetExternalAuthId($id);
	$u->SetAttribute( Company => $company ) if $company;
	$u->SetPrivileged(1);
    }

    $users{$u->Id}++;

    if ($dept and my $dg = $orgs{department}{$dept}) {
	$dg->AddMember($u->PrincipalId);
    }
    elsif ($company and my $cg = $orgs{company}{$company}) {
	$cg->AddMember($u->PrincipalId);
    }

    # find out which groups have me as the boss
    if (my $bg = $bosses{$id}) {
	my $principal = RT::Principal->new($RT::SystemUser);
	$principal->Load($u->PrincipalId);

	foreach my $g (@{$bg}) {
	    $principal->GrantRight( Object => $g, Right => 'AdminGroup' );
	}
    }

    $Outs->('.'); my $c = keys %users;
    $Outs->("($c)<br>\n") unless $c % 100;
};

# }}}

# Select users and insert them {{{

my $all_users = $dbh->selectall_hashref(qq(
    SELECT *
    FROM hrms_employee
    WHERE EMPLOYEE_WORK_STATUS < 2
), 'EMPLOYEE_ID');

$count = scalar keys %$all_users;
$Outs->("<hr>" . loc("Phase 3: Create/Rename Users ([_1])", $count) . "<br>\n");
foreach my $user (values %{$all_users}) {
    $walk_user->($user, $all_users);
}

# }}}

# Disable 'disappeared' users {{{

my $all_rt_users = $RT::Handle->dbh->selectall_arrayref(qq(
SELECT Users.Id, Disabled
  FROM Users, Principals
 WHERE Users.Id = Principals.ObjectId
   AND ExternalAuthId > 0;
));

$count = @$all_rt_users;
$Outs->("<hr>" . loc("Phase 4: Disable/Enable Users ([_1])", $count) . "<br>\n");

$c = 0;
foreach my $u (@$all_rt_users) {
    $Outs->('.'); $Outs->("($c)<br>\n") unless ++$c % 100;
    my $disabled = $users{$u->[0]} ? 0 : 1;
    next unless ($disabled xor $u->[1]);

    # so they are unequal... amend it...
    $Info->(loc("Setting [_1]'s 'Disabled' property to [_2]", $u->[0], $disabled));
    my $UserObj = RT::User->new($RT::SystemUser);
    $UserObj->Load($u->[0]);
    $UserObj->SetDisabled($disabled);
}

# }}}

$RT::Handle->dbh->do("SELECT RELEASE_LOCK('$key');");

return;

1;

</%PERL>
<%ARGS>
$DSN	=> $RT::ExternalDatabaseDSN
$DBUser	=> $RT::ExternalDatabaseUser
$DBPass	=> $RT::ExternalDatabasePass
$Force	=> 0
</%ARGS>
