mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-11 17:52:15 +01:00
Compare commits
1 Commits
v0.15.1
...
feature/cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
819dfd5037 |
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
|||||||
namespace App\Service\Import\Importers;
|
namespace App\Service\Import\Importers;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Support\Carbon;
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use League\Csv\Exception as CsvException;
|
use League\Csv\Exception as CsvException;
|
||||||
use League\Csv\Reader;
|
use League\Csv\Reader;
|
||||||
@@ -25,7 +24,6 @@ class ClockifyProjectsImporter extends DefaultImporter
|
|||||||
$header = $reader->getHeader();
|
$header = $reader->getHeader();
|
||||||
$this->validateHeader($header);
|
$this->validateHeader($header);
|
||||||
$billableRateKey = $this->getBillableRateKey($header);
|
$billableRateKey = $this->getBillableRateKey($header);
|
||||||
$tasksKey = $this->getTasksKey($header);
|
|
||||||
$records = $reader->getRecords();
|
$records = $reader->getRecords();
|
||||||
foreach ($records as $record) {
|
foreach ($records as $record) {
|
||||||
$clientId = null;
|
$clientId = null;
|
||||||
@@ -46,12 +44,11 @@ class ClockifyProjectsImporter extends DefaultImporter
|
|||||||
'is_billable' => $record['Billability'] === 'Yes',
|
'is_billable' => $record['Billability'] === 'Yes',
|
||||||
'billable_rate' => $billableRateKey !== null && $record[$billableRateKey] !== '' ? (int) (((float) $record[$billableRateKey]) * 100) : null,
|
'billable_rate' => $billableRateKey !== null && $record[$billableRateKey] !== '' ? (int) (((float) $record[$billableRateKey]) * 100) : null,
|
||||||
'estimated_time' => $record['Estimated (h)'] !== '' && is_numeric($record['Estimated (h)']) ? (int) ($record['Estimated (h)'] * 3600) : null,
|
'estimated_time' => $record['Estimated (h)'] !== '' && is_numeric($record['Estimated (h)']) ? (int) ($record['Estimated (h)'] * 3600) : null,
|
||||||
'archived_at' => $record['Status'] === 'Archived' ? Carbon::now() : null,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($record[$tasksKey] !== '') {
|
if ($record['Task'] !== '') {
|
||||||
$tasks = explode(', ', $record[$tasksKey]);
|
$tasks = explode(', ', $record['Task']);
|
||||||
foreach ($tasks as $task) {
|
foreach ($tasks as $task) {
|
||||||
$this->taskImportHelper->getKey([
|
$this->taskImportHelper->getKey([
|
||||||
'name' => $task,
|
'name' => $task,
|
||||||
@@ -84,26 +81,13 @@ class ClockifyProjectsImporter extends DefaultImporter
|
|||||||
'Status',
|
'Status',
|
||||||
'Visibility',
|
'Visibility',
|
||||||
'Billability',
|
'Billability',
|
||||||
|
'Task',
|
||||||
];
|
];
|
||||||
foreach ($requiredFields as $requiredField) {
|
foreach ($requiredFields as $requiredField) {
|
||||||
if (! in_array($requiredField, $header, true)) {
|
if (! in_array($requiredField, $header, true)) {
|
||||||
throw new ImportException('Invalid CSV header, missing field: '.$requiredField);
|
throw new ImportException('Invalid CSV header, missing field: '.$requiredField);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Clockify renamed the "Task" column to "Tasks" in newer exports; accept either.
|
|
||||||
if (! in_array('Task', $header, true) && ! in_array('Tasks', $header, true)) {
|
|
||||||
throw new ImportException('Invalid CSV header, missing field: Tasks');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clockify renamed the "Task" column to "Tasks" in newer exports.
|
|
||||||
*
|
|
||||||
* @param array<string> $header
|
|
||||||
*/
|
|
||||||
private function getTasksKey(array $header): string
|
|
||||||
{
|
|
||||||
return in_array('Tasks', $header, true) ? 'Tasks' : 'Task';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ declare(strict_types=1);
|
|||||||
return [
|
return [
|
||||||
'clockify_time_entries' => [
|
'clockify_time_entries' => [
|
||||||
'name' => 'Clockify Time Entries',
|
'name' => 'Clockify Time Entries',
|
||||||
'description' => '<strong>Important:</strong> If you also want to import your projects use the "Clockify Projects" importer before this one, since that export contains more details such as billable status, billable rates and estimated time.<br><br>'.
|
'description' => '1. First make sure that you set the Date format to "MM/DD/YYYY" and the Time format to "12-hour" in the user settings.<br>'.
|
||||||
'1. First make sure that you set the Date format to "MM/DD/YYYY" and the Time format to "12-hour" in the user settings.<br>'.
|
|
||||||
'2. In the same preferences page change the language of Clockfiy to English.<br>'.
|
'2. In the same preferences page change the language of Clockfiy to English.<br>'.
|
||||||
'3. Go to REPORTS -> TIME -> Detailed in the navigation on the left. <br>'.
|
'3. Go to REPORTS -> TIME -> Detailed in the navigation on the left. <br>'.
|
||||||
'4. Now select the date range that you want to export in the right top. '.
|
'4. Now select the date range that you want to export in the right top. '.
|
||||||
@@ -62,8 +61,7 @@ return [
|
|||||||
],
|
],
|
||||||
'harvest_time_entries' => [
|
'harvest_time_entries' => [
|
||||||
'name' => 'Harvest Time Entries',
|
'name' => 'Harvest Time Entries',
|
||||||
'description' => '<strong>Important:</strong> If you also want to import your projects use the "Harvest Projects" importer before this one, since that export contains more details such as billable status and estimated time.<br><br>'.
|
'description' => '1. Go to Settings (right top corner)<br>2. Click on "Import/Export" in the left navigation'.
|
||||||
'1. Go to Settings (right top corner)<br>2. Click on "Import/Export" in the left navigation'.
|
|
||||||
'<br>3. Now click on "Export all time" '.
|
'<br>3. Now click on "Export all time" '.
|
||||||
'<br><br>Before you import make sure that the Timezone settings in Harvest are the same as in solidtime.',
|
'<br><br>Before you import make sure that the Timezone settings in Harvest are the same as in solidtime.',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ body {
|
|||||||
--muted-foreground: var(--color-text-tertiary);
|
--muted-foreground: var(--color-text-tertiary);
|
||||||
--accent: var(--color-bg-tertiary);
|
--accent: var(--color-bg-tertiary);
|
||||||
--accent-foreground: var(--color-text-primary);
|
--accent-foreground: var(--color-text-primary);
|
||||||
--destructive: 0 72% 60%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: var(--color-text-primary);
|
--destructive-foreground: var(--color-text-primary);
|
||||||
--border: var(--color-border-primary);
|
--border: var(--color-border-primary);
|
||||||
--input: var(--color-border-tertiary);
|
--input: var(--color-border-tertiary);
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
"Project","Client","Status","Visibility","Billability","Tasks","Tracked (h)","Estimated (h)","Remaining (h)","Overage (h)","Tracked (USD)","Estimated (USD)","Remaining (USD)","Overage (USD)","Progress(%)","Recurring estimate","Billable (h)","Non-billable (h)","Billable Rate (USD)","Amount (USD)","Cost Rate (USD)","Expenses (USD)","Billable expenses (USD)","Non-billable expenses (USD)","Additional fields","Project members","Project manager","Note"
|
|
||||||
"Active Project","Big Company","Active","Public","Yes","Task 1, Task 2","0.00","100.00","","","","","","","","","0.00","0.00","100.01","0.00","","0.00","0.00","0.00","","Constantin Graf","",""
|
|
||||||
"Archived Project","","Archived","Public","Yes","","0.00","","","","","","","","","","0.00","0.00","","0.00","","0.00","0.00","0.00","","Constantin Graf","",""
|
|
||||||
|
@@ -5,8 +5,6 @@ declare(strict_types=1);
|
|||||||
namespace Tests\Unit\Service\Import\Importers;
|
namespace Tests\Unit\Service\Import\Importers;
|
||||||
|
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
use App\Models\Project;
|
|
||||||
use App\Models\Task;
|
|
||||||
use App\Service\Import\Importers\ClockifyProjectsImporter;
|
use App\Service\Import\Importers\ClockifyProjectsImporter;
|
||||||
use App\Service\Import\Importers\DefaultImporter;
|
use App\Service\Import\Importers\DefaultImporter;
|
||||||
use App\Service\Import\Importers\ImportException;
|
use App\Service\Import\Importers\ImportException;
|
||||||
@@ -52,47 +50,4 @@ class ClockifyProjectsImporterTest extends ImporterTestAbstract
|
|||||||
// Assert
|
// Assert
|
||||||
$this->checkTestScenarioProjectsOnlyAfterImport();
|
$this->checkTestScenarioProjectsOnlyAfterImport();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_import_sets_archived_at_based_on_status_column(): void
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
$organization = Organization::factory()->create();
|
|
||||||
$timezone = 'Europe/Vienna';
|
|
||||||
$importer = new ClockifyProjectsImporter;
|
|
||||||
$importer->init($organization);
|
|
||||||
$data = Storage::disk('testfiles')->get('clockify_projects_import_test_2.csv');
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$importer->importData($data, $timezone);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
$activeProject = Project::query()->where('organization_id', $organization->id)->where('name', 'Active Project')->firstOrFail();
|
|
||||||
$this->assertNull($activeProject->archived_at);
|
|
||||||
$this->assertFalse($activeProject->is_archived);
|
|
||||||
|
|
||||||
$archivedProject = Project::query()->where('organization_id', $organization->id)->where('name', 'Archived Project')->firstOrFail();
|
|
||||||
$this->assertNotNull($archivedProject->archived_at);
|
|
||||||
$this->assertTrue($archivedProject->is_archived);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_import_supports_renamed_tasks_column(): void
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
$organization = Organization::factory()->create();
|
|
||||||
$timezone = 'Europe/Vienna';
|
|
||||||
$importer = new ClockifyProjectsImporter;
|
|
||||||
$importer->init($organization);
|
|
||||||
// Newer Clockify exports rename the "Task" column to "Tasks".
|
|
||||||
$data = Storage::disk('testfiles')->get('clockify_projects_import_test_2.csv');
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$importer->importData($data, $timezone);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
$activeProject = Project::query()->where('organization_id', $organization->id)->where('name', 'Active Project')->firstOrFail();
|
|
||||||
$this->assertEqualsCanonicalizing(
|
|
||||||
['Task 1', 'Task 2'],
|
|
||||||
Task::query()->where('project_id', $activeProject->id)->pluck('name')->all(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user