Implement delete handling and do more checks to make sure we dont get corrupted ical files

This commit is contained in:
Troels Liebe Bentsen 2014-10-20 20:59:46 +02:00
parent 0b79b69d41
commit ae42a5a37a
3 changed files with 98 additions and 34 deletions

View File

@ -39,10 +39,12 @@ namespace {
* @property integer $public_id
* @property string $custom_value1
* @property string $custom_value2
* @property string $vat_number
* @property-read \Account $account
* @property-read \Illuminate\Database\Eloquent\Collection|\Invoice[] $invoices
* @property-read \Illuminate\Database\Eloquent\Collection|\Payment[] $payments
* @property-read \Illuminate\Database\Eloquent\Collection|\Contact[] $contacts
* @property-read \Illuminate\Database\Eloquent\Collection|\Project[] $projects
* @property-read \Country $country
* @property-read \Currency $currency
* @property-read \Size $size
@ -74,6 +76,7 @@ namespace {
* @method static \Illuminate\Database\Query\Builder|\Client wherePublicId($value)
* @method static \Illuminate\Database\Query\Builder|\Client whereCustomValue1($value)
* @method static \Illuminate\Database\Query\Builder|\Client whereCustomValue2($value)
* @method static \Illuminate\Database\Query\Builder|\Client whereVatNumber($value)
* @method static \EntityModel scope($publicId = false, $accountId = false)
*/
class Client {}
@ -104,6 +107,7 @@ namespace {
* @property integer $public_id
* @property boolean $force_pdfjs
* @property string $remember_token
* @property integer $news_feed_id
* @property-read \Account $account
* @property-read \Theme $theme
* @method static \Illuminate\Database\Query\Builder|\User whereId($value)
@ -127,6 +131,7 @@ namespace {
* @method static \Illuminate\Database\Query\Builder|\User wherePublicId($value)
* @method static \Illuminate\Database\Query\Builder|\User whereForcePdfjs($value)
* @method static \Illuminate\Database\Query\Builder|\User whereRememberToken($value)
* @method static \Illuminate\Database\Query\Builder|\User whereNewsFeedId($value)
*/
class User {}
}
@ -441,6 +446,7 @@ namespace {
* @property-read \Client $client
* @property-read \Illuminate\Database\Eloquent\Collection|\InvoiceItem[] $invoice_items
* @property-read \InvoiceStatus $invoice_status
* @property-read \InvoiceDesign $invoice_design
* @property-read \Illuminate\Database\Eloquent\Collection|\Invitation[] $invitations
* @method static \Illuminate\Database\Query\Builder|\Invoice whereId($value)
* @method static \Illuminate\Database\Query\Builder|\Invoice whereClientId($value)
@ -806,8 +812,10 @@ namespace {
*
* @property integer $id
* @property string $name
* @property string $javascript
* @method static \Illuminate\Database\Query\Builder|\InvoiceDesign whereId($value)
* @method static \Illuminate\Database\Query\Builder|\InvoiceDesign whereName($value)
* @method static \Illuminate\Database\Query\Builder|\InvoiceDesign whereJavascript($value)
*/
class InvoiceDesign {}
}
@ -987,14 +995,18 @@ namespace {
* @property string $end_date
* @property float $hours
* @property float $discount
* @property boolean $manualedit
* @property string $org_code
* @property string $org_created_at
* @property string $org_updated_at
* @property string $org_deleted_at
* @property string $org_start_date_timezone
* @property string $org_end_date_timezone
* @property string $org_data
* @property string $import_error
* @property string $import_warning
* @property string $updated_data
* @property string $updated_data_at
* @property-read \Account $account
* @property-read \User $user
* @property-read \TimesheetEventSource $source
@ -1020,14 +1032,18 @@ namespace {
* @method static \Illuminate\Database\Query\Builder|\TimesheetEvent whereEndDate($value)
* @method static \Illuminate\Database\Query\Builder|\TimesheetEvent whereHours($value)
* @method static \Illuminate\Database\Query\Builder|\TimesheetEvent whereDiscount($value)
* @method static \Illuminate\Database\Query\Builder|\TimesheetEvent whereManualedit($value)
* @method static \Illuminate\Database\Query\Builder|\TimesheetEvent whereOrgCode($value)
* @method static \Illuminate\Database\Query\Builder|\TimesheetEvent whereOrgCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\TimesheetEvent whereOrgUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\TimesheetEvent whereOrgDeletedAt($value)
* @method static \Illuminate\Database\Query\Builder|\TimesheetEvent whereOrgStartDateTimezone($value)
* @method static \Illuminate\Database\Query\Builder|\TimesheetEvent whereOrgEndDateTimezone($value)
* @method static \Illuminate\Database\Query\Builder|\TimesheetEvent whereOrgData($value)
* @method static \Illuminate\Database\Query\Builder|\TimesheetEvent whereImportError($value)
* @method static \Illuminate\Database\Query\Builder|\TimesheetEvent whereImportWarning($value)
* @method static \Illuminate\Database\Query\Builder|\TimesheetEvent whereUpdatedData($value)
* @method static \Illuminate\Database\Query\Builder|\TimesheetEvent whereUpdatedDataAt($value)
*/
class TimesheetEvent {}
}
@ -1039,6 +1055,7 @@ namespace {
* @property integer $id
* @property integer $user_id
* @property integer $account_id
* @property integer $client_id
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property \Carbon\Carbon $deleted_at
@ -1046,10 +1063,12 @@ namespace {
* @property string $description
* @property-read \Account $account
* @property-read \User $user
* @property-read \Client $client
* @property-read \Illuminate\Database\Eloquent\Collection|\ProjectCode[] $codes
* @method static \Illuminate\Database\Query\Builder|\Project whereId($value)
* @method static \Illuminate\Database\Query\Builder|\Project whereUserId($value)
* @method static \Illuminate\Database\Query\Builder|\Project whereAccountId($value)
* @method static \Illuminate\Database\Query\Builder|\Project whereClientId($value)
* @method static \Illuminate\Database\Query\Builder|\Project whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Project whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Project whereDeletedAt($value)
@ -1109,6 +1128,12 @@ namespace {
* @property boolean $custom_invoice_taxes1
* @property boolean $custom_invoice_taxes2
* @property string $vat_number
* @property string $invoice_design
* @property string $invoice_number_prefix
* @property integer $invoice_number_counter
* @property string $quote_number_prefix
* @property integer $quote_number_counter
* @property boolean $share_counter
* @property-read \Illuminate\Database\Eloquent\Collection|\User[] $users
* @property-read \Illuminate\Database\Eloquent\Collection|\Client[] $clients
* @property-read \Illuminate\Database\Eloquent\Collection|\Invoice[] $invoices
@ -1167,6 +1192,12 @@ namespace {
* @method static \Illuminate\Database\Query\Builder|\Account whereCustomInvoiceTaxes1($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereCustomInvoiceTaxes2($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereVatNumber($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereInvoiceDesign($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereInvoiceNumberPrefix($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereInvoiceNumberCounter($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereQuoteNumberPrefix($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereQuoteNumberCounter($value)
* @method static \Illuminate\Database\Query\Builder|\Account whereShareCounter($value)
*/
class Account {}
}

View File

@ -1,7 +1,5 @@
<?php
//TODO: Add support for recurring event : https://github.com/tplaner/When
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
@ -95,16 +93,28 @@ class ImportTimesheetData extends Command {
$this->info("Find events in " . $event_source->name);
file_put_contents("/tmp/" . $event_source->name . ".ical", $icalresponses[$i]); // FIXME: Remove
$T->lap("Split on events for ".$event_source->name);
// Check if the file is complete
if(!preg_match("/^\s*BEGIN:VCALENDAR/", $icalresponses[$i]) || !preg_match("/END:VCALENDAR\s*$/", $icalresponses[$i])) {
$this->error("Missing start or end of ical file");
continue;
}
// Extract all events from ical file
if (preg_match_all('/BEGIN:VEVENT\r?\n(.+?)\r?\nEND:VEVENT/s', $icalresponses[$i], $icalmatches)) {
$this->info("Found ".(count($icalmatches[1])-1)." events");
$T->lap("Fetch all uids so we can do a quick lookup ".$event_source->name);
$T->lap("Fetch all uids and last updated at so we can do a quick lookup to find out if the event needs to be updated in the database".$event_source->name);
$uids = [];
$event_source->events()->get(['uid', 'org_updated_at', 'updated_data_at'])->map(function($item) use(&$uids) {
$org_deleted = []; // Create list of events we know are deleted on the source, but still have in the db
$event_source->events()->withTrashed()->get(['uid', 'org_updated_at', 'updated_data_at', 'org_deleted_at'])->map(function($item) use(&$uids, &$org_deleted) {
if($item->org_updated_at > $item->updated_data_at) {
$uids[$item->uid] = $item->org_updated_at;
} else {
$uids[$item->uid] = $item->updated_data_at;
}
if($item->org_deleted_at > '0000-00-00 00:00:00') {
$org_deleted[$item->uid] = $item->updated_data_at;
}
});
$deleted = $uids;
@ -124,14 +134,36 @@ class ImportTimesheetData extends Command {
list($codename, $tags, $title) = TimesheetUtils::parseEventSummary($data['summary']);
if ($codename != null) {
$event = TimesheetEvent::createNew($user);
// Copy data to new object
$event->uid = $data['uid'];
$event->summary = $title;
$event->org_data = $eventstr;
$event->org_code = $codename;
if(isset($data['description'])) {
$event->description = $data['description'];
}
$event->owner = $event_source->owner;
$event->timesheet_event_source_id = $event_source->id;
if (isset($codes[$codename])) {
$event->project_id = $codes[$codename]->project_id;
$event->project_code_id = $codes[$codename]->id;
}
if (isset($data['location'])) {
$event->location = $data['location'];
}
# Add RECURRENCE-ID to the UID to make sure the event is unique
if (isset($data['recurrence-id'])) {
$event->uid .= "::".$data['recurrence-id'];
}
//FIXME: Bail on RRULE as we don't support that
//TODO: Add support for recurring event, make limit on number of events created : https://github.com/tplaner/When
// Bail on RRULE as we don't support that
if(isset($event['rrule'])) {
die("Recurring event not supported: {$event['summary']} - {$event['dtstart']}");
}
// Convert to DateTime objects
foreach (['dtstart', 'dtend', 'created', 'last-modified'] as $key) {
@ -142,10 +174,11 @@ class ImportTimesheetData extends Command {
if ($dt == null || $dt < $unix_epoch) {
if ($key == 'created' || $key == 'last-modified') {
$dt = $unix_epoch; // Default to UNIX epoch
$event->import_error = "Could not parse date for $key: '" . $data[$key] . "' so default to UNIX Epoc\n";
$event->import_warning = "Could not parse date for $key: '" . $data[$key] . "' so default to UNIX Epoc\n";
} else {
echo "Could not parse date for $key: '" . $data[$key] . "'\n";
exit(255); // TODO: Bail on this event or write to error table
$event->import_error = "Could not parse date for $key: '" . $data[$key] . "' so default to UNIX Epoc\n";
// TODO: Bail on this event or write to error table
die("Could not parse date for $key: '" . $data[$key] . "'\n");
}
}
@ -186,23 +219,6 @@ class ImportTimesheetData extends Command {
$di = $event->end_date->diff($event->start_date);
$event->hours = $di->h + $di->i / 60;
// Copy data to new object
$event->org_data = $eventstr;
$event->summary = $title;
if(isset($data['description'])) {
$event->description = $data['description'];
}
$event->org_code = $codename;
$event->owner = $event_source->owner;
$event->timesheet_event_source_id = $event_source->id;
if (isset($codes[$codename])) {
$event->project_id = $codes[$codename]->project_id;
$event->project_code_id = $codes[$codename]->id;
}
if (isset($data['location'])) {
$event->location = $data['location'];
}
// Check for events we already have
if (isset($uids[$event->uid])) {
// Remove from deleted list
@ -211,7 +227,7 @@ class ImportTimesheetData extends Command {
// See if the event has been updated compared to the one in the database
$db_event_org_updated_at = new DateTime($uids[$event->uid], new DateTimeZone('UTC'));
// Check if same or older version of new event so skip
// Check if same or older version of new event then skip
if($event->org_updated_at <= $db_event_org_updated_at) {
// SKIP
@ -224,7 +240,7 @@ class ImportTimesheetData extends Command {
// Make sure it's more than the org_updated_at that has been changed
if (count($changes) > 1) {
// Check if we have changed the event in the database or used it in a timesheet
// Check if we have manually changed the event in the database or used it in a timesheet
if ($db_event->manualedit || $db_event->timesheet) {
$this->info("Updated Data");
$db_event->updated_data = $event->org_data;
@ -268,18 +284,34 @@ class ImportTimesheetData extends Command {
}
}
}
$this->info("Deleted ".count($deleted). " events");
// Delete events in database that no longer exists in the source
foreach($deleted as $uid => $lastupdated_date) {
// FIXME: Delete events in database
echo "$uid: $lastupdated_date\n";
// Skip we already marked this a deleted
if(isset($org_deleted[$uid])) {
unset($deleted[$uid]);
continue;
}
// Delete or update event in db
$db_event = $event_source->events()->where('uid', $uid)->firstOrFail();
if($db_event->timesheet_id === null && !$db_event->manualedit) {
// Hard delete if this event has not been assigned to a timesheet or have been manually edited
$db_event->forceDelete();
} else {
// Mark as deleted in source
$db_event->org_deleted_at = new DateTime('now', new DateTimeZone('UTC'));
$db_event->save();
}
}
$this->info("Deleted ".count($deleted). " events");
} else {
// Parse error
// TODO: Parse error
}
} else {
// Curl Error
// TODO: Curl Error
}
}

View File

@ -116,6 +116,7 @@ class AddTimesheets extends Migration {
$t->string('org_code');
$t->timeStamp('org_created_at');
$t->timeStamp('org_updated_at');
$t->timeStamp('org_deleted_at')->default('0000-00-00T00:00:00');
$t->string('org_start_date_timezone')->nullable();
$t->string('org_end_date_timezone')->nullable();
$t->text('org_data');