389 lines
12 KiB
PHP
389 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Google;
|
|
|
|
use App\Core\DB;
|
|
use DateTime;
|
|
use PDO;
|
|
|
|
class GoogleImportService
|
|
{
|
|
public function import(): array
|
|
{
|
|
$pdo = DB::connection();
|
|
|
|
$stmt = $pdo->query("
|
|
SELECT *
|
|
FROM board_sources
|
|
WHERE source_type = 'google_sheet'
|
|
AND is_active = 1
|
|
AND sync_mode IN ('import', 'import_export')
|
|
");
|
|
$sources = $stmt->fetchAll();
|
|
|
|
$totalCreated = 0;
|
|
$totalUpdated = 0;
|
|
$totalSkipped = 0;
|
|
$totalRows = 0;
|
|
|
|
foreach ($sources as $source) {
|
|
$result = $this->importBoard($pdo, $source);
|
|
|
|
$totalCreated += $result['created'];
|
|
$totalUpdated += $result['updated'];
|
|
$totalSkipped += $result['skipped'];
|
|
$totalRows += $result['total'];
|
|
}
|
|
|
|
return [
|
|
'created' => $totalCreated,
|
|
'updated' => $totalUpdated,
|
|
'skipped' => $totalSkipped,
|
|
'total' => $totalRows,
|
|
];
|
|
}
|
|
|
|
private function importBoard(PDO $pdo, array $source): array
|
|
{
|
|
$boardId = (int)$source['board_id'];
|
|
$spreadsheetId = (string)$source['source_key'];
|
|
$sheetName = (string)$source['sheet_name'];
|
|
$notificationService = new \App\Services\NotificationService();
|
|
$userIds = $this->getAllUserIds($pdo);
|
|
|
|
$stmt = $pdo->prepare("
|
|
SELECT target_type, target_key, source_column_name
|
|
FROM board_source_mappings
|
|
WHERE board_source_id = ?
|
|
AND is_active = 1
|
|
");
|
|
$stmt->execute([$source['id']]);
|
|
$mappings = $stmt->fetchAll();
|
|
|
|
if (empty($mappings)) {
|
|
return ['created' => 0, 'updated' => 0, 'skipped' => 0, 'total' => 0];
|
|
}
|
|
|
|
$sheets = new GoogleSheetsService($spreadsheetId, $sheetName);
|
|
$rows = $sheets->getRowsRaw();
|
|
|
|
$created = 0;
|
|
$updated = 0;
|
|
$skipped = 0;
|
|
|
|
$publisher = new \App\Services\WebSocket\EventPublisher();
|
|
$stmt = $pdo->prepare("SELECT code FROM boards WHERE id = ? LIMIT 1");
|
|
$stmt->execute([$boardId]);
|
|
$boardCode = (string)($stmt->fetchColumn() ?: '');
|
|
|
|
foreach ($rows as $row) {
|
|
$rowNumber = (int)$row['_row_number'];
|
|
|
|
$base = [
|
|
'name' => '',
|
|
'creator_name' => null,
|
|
'assignee_name' => null,
|
|
'description' => null,
|
|
'status' => 'NEW',
|
|
'priority' => 'MEDIUM',
|
|
'crm_id' => null,
|
|
'completed_flag' => 0,
|
|
'task_created_at' => date('Y-m-d H:i:s'),
|
|
'planned_at' => null,
|
|
'completed_at' => null,
|
|
];
|
|
|
|
$custom = [];
|
|
|
|
foreach ($mappings as $map) {
|
|
$col = strtoupper((string)$map['source_column_name']);
|
|
$value = $row[$col] ?? null;
|
|
|
|
if ($map['target_type'] === 'base') {
|
|
$this->applyBase($base, (string)$map['target_key'], $value);
|
|
}
|
|
|
|
if ($map['target_type'] === 'custom') {
|
|
$custom[(string)$map['target_key']] = trim((string)$value);
|
|
}
|
|
}
|
|
|
|
$normalizedName = mb_strtolower(trim((string)$base['name']));
|
|
if (
|
|
$normalizedName === '' ||
|
|
$normalizedName === 'наименование детали' ||
|
|
$normalizedName === 'наименование'
|
|
) {
|
|
$skipped++;
|
|
continue;
|
|
}
|
|
|
|
$base['status'] = ((int)$base['completed_flag'] === 1) ? 'DONE' : 'IN_PROGRESS';
|
|
|
|
$existing = $this->findByRow($pdo, $boardId, $rowNumber);
|
|
|
|
// if (!$existing) {
|
|
// $existing = $this->findByName($pdo, $boardId, $base['name']);
|
|
// }
|
|
|
|
if ($existing) {
|
|
$taskId = (int)$existing['id'];
|
|
|
|
$stmt = $pdo->prepare("
|
|
UPDATE tasks SET
|
|
google_row_id = ?,
|
|
name = ?,
|
|
description = ?,
|
|
creator_name = ?,
|
|
assignee_name = ?,
|
|
priority = ?,
|
|
completed_flag = ?,
|
|
task_created_at = ?,
|
|
planned_at = ?,
|
|
completed_at = ?,
|
|
updated_at = NOW()
|
|
WHERE id = ?
|
|
");
|
|
|
|
$stmt->execute([
|
|
$rowNumber,
|
|
$base['name'],
|
|
$base['description'],
|
|
$base['creator_name'],
|
|
$base['assignee_name'],
|
|
$base['status'],
|
|
$base['priority'],
|
|
$base['completed_flag'],
|
|
$base['task_created_at'],
|
|
$base['planned_at'],
|
|
$base['completed_at'],
|
|
$taskId
|
|
]);
|
|
|
|
$this->saveCustom($pdo, $taskId, $boardId, $custom);
|
|
|
|
$updated++;
|
|
|
|
$publisher->publish([
|
|
'type' => 'task_updated',
|
|
'task_id' => $taskId,
|
|
'board_id' => $boardId,
|
|
'board_code' => $boardCode,
|
|
'name' => $base['name'],
|
|
'status' => $base['status'],
|
|
'updated_at' => date('Y-m-d H:i:s'),
|
|
]);
|
|
} else {
|
|
$crmId = !empty($base['crm_id'])
|
|
? (string)$base['crm_id']
|
|
: $this->generateCrmId();
|
|
|
|
$stmt = $pdo->prepare("
|
|
INSERT INTO tasks (
|
|
crm_id, board_id, google_row_id, source_type,
|
|
name, description, creator_name, assignee_name,
|
|
status, priority, completed_flag,
|
|
task_created_at, planned_at, completed_at,
|
|
created_at, updated_at
|
|
) VALUES (?, ?, ?, 'google', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
|
");
|
|
|
|
$stmt->execute([
|
|
$crmId,
|
|
$boardId,
|
|
$rowNumber,
|
|
$base['name'],
|
|
$base['description'],
|
|
$base['creator_name'],
|
|
$base['assignee_name'],
|
|
$base['status'],
|
|
$base['priority'],
|
|
$base['completed_flag'],
|
|
$base['task_created_at'],
|
|
$base['planned_at'],
|
|
$base['completed_at'],
|
|
]);
|
|
|
|
$taskId = (int)$pdo->lastInsertId();
|
|
|
|
$this->saveCustom($pdo, $taskId, $boardId, $custom);
|
|
|
|
$created++;
|
|
|
|
$notificationService->createForUsers(
|
|
$userIds,
|
|
'task_created',
|
|
'Новая задача',
|
|
$base['name'],
|
|
[
|
|
'task_id' => $taskId,
|
|
'board_id' => $boardId,
|
|
'board_code' => $boardCode,
|
|
'crm_id' => $crmId,
|
|
]
|
|
);
|
|
|
|
$publisher->publish([
|
|
'type' => 'task_created',
|
|
'task_id' => $taskId,
|
|
'board_id' => $boardId,
|
|
'board_code' => $boardCode,
|
|
'name' => $base['name'],
|
|
'crm_id' => $crmId,
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
]);
|
|
}
|
|
}
|
|
|
|
$publisher->publish([
|
|
'type' => 'import_finished',
|
|
'board_id' => $boardId,
|
|
'board_code' => $boardCode,
|
|
'created' => $created,
|
|
'updated' => $updated,
|
|
'skipped' => $skipped,
|
|
'total' => count($rows),
|
|
'finished_at' => date('Y-m-d H:i:s'),
|
|
]);
|
|
|
|
// if ($created > 0 || $updated > 0) {
|
|
// $notificationService->createForUsers(
|
|
// $userIds,
|
|
// 'import_finished',
|
|
// 'Импорт завершен',
|
|
// 'Новых: ' . $created . ', обновлено: ' . $updated,
|
|
// [
|
|
// 'board_id' => $boardId,
|
|
// 'board_code' => $boardCode,
|
|
// 'created' => $created,
|
|
// 'updated' => $updated,
|
|
// ]
|
|
// );
|
|
// }
|
|
|
|
return [
|
|
'created' => $created,
|
|
'updated' => $updated,
|
|
'skipped' => $skipped,
|
|
'total' => count($rows)
|
|
];
|
|
}
|
|
|
|
private function applyBase(array &$base, string $key, mixed $value): void
|
|
{
|
|
$value = trim((string)$value);
|
|
|
|
switch ($key) {
|
|
case 'name': $base['name'] = $value; break;
|
|
case 'description': $base['description'] = $value ?: null; break;
|
|
case 'creator_name': $base['creator_name'] = $value ?: null; break;
|
|
case 'assignee_name': $base['assignee_name'] = $value ?: null; break;
|
|
case 'crm_id': $base['crm_id'] = $value ?: null; break;
|
|
case 'completed_flag': $base['completed_flag'] = $this->toBool($value); break;
|
|
case 'task_created_at':
|
|
$base['task_created_at'] = $this->normalizeImportDateTime($value, true);
|
|
break;
|
|
|
|
case 'planned_at':
|
|
$base['planned_at'] = $this->normalizeImportDateTime($value, false);
|
|
break;
|
|
|
|
case 'completed_at':
|
|
$base['completed_at'] = $this->normalizeImportDateTime($value, false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private function saveCustom(PDO $pdo, int $taskId, int $boardId, array $data): void
|
|
{
|
|
if (!$data) return;
|
|
|
|
$stmt = $pdo->prepare("
|
|
SELECT id, code FROM board_fields WHERE board_id = ?
|
|
");
|
|
$stmt->execute([$boardId]);
|
|
$fields = $stmt->fetchAll();
|
|
|
|
$map = [];
|
|
foreach ($fields as $f) $map[$f['code']] = $f['id'];
|
|
|
|
foreach ($data as $code=>$val) {
|
|
|
|
if (!isset($map[$code])) continue;
|
|
|
|
$stmt = $pdo->prepare("
|
|
INSERT INTO task_field_values (task_id, field_id, value_text)
|
|
VALUES (?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE value_text = VALUES(value_text)
|
|
");
|
|
|
|
$stmt->execute([$taskId, $map[$code], $val]);
|
|
}
|
|
}
|
|
|
|
private function findByRow(PDO $pdo, int $boardId, int $row): array|false
|
|
{
|
|
$stmt = $pdo->prepare("
|
|
SELECT id FROM tasks WHERE board_id=? AND google_row_id=?
|
|
");
|
|
$stmt->execute([$boardId, $row]);
|
|
return $stmt->fetch();
|
|
}
|
|
|
|
private function findByName(PDO $pdo, int $boardId, string $name): array|false
|
|
{
|
|
$stmt = $pdo->prepare("
|
|
SELECT id FROM tasks WHERE board_id=? AND name=? LIMIT 1
|
|
");
|
|
$stmt->execute([$boardId, $name]);
|
|
return $stmt->fetch();
|
|
}
|
|
|
|
private function generateCrmId(): string
|
|
{
|
|
return 'CRM-' . date('Ymd-His') . '-' . bin2hex(random_bytes(2));
|
|
}
|
|
|
|
private function toBool($v): int
|
|
{
|
|
$v = mb_strtolower(trim((string)$v));
|
|
return in_array($v,['1','true','да']) ? 1 : 0;
|
|
}
|
|
private function getAllUserIds(PDO $pdo): array
|
|
{
|
|
$stmt = $pdo->query("SELECT id FROM users");
|
|
return array_map('intval', array_column($stmt->fetchAll(), 'id'));
|
|
}
|
|
private function normalizeImportDateTime(mixed $value, bool $useNowIfEmpty = false): ?string
|
|
{
|
|
$value = trim((string)$value);
|
|
|
|
if ($value === '') {
|
|
return $useNowIfEmpty ? date('Y-m-d H:i:s') : null;
|
|
}
|
|
|
|
$formats = [
|
|
'd.m.Y H:i:s',
|
|
'd.m.Y H:i',
|
|
'Y-m-d H:i:s',
|
|
'Y-m-d H:i',
|
|
'd.m.Y',
|
|
'Y-m-d',
|
|
];
|
|
|
|
foreach ($formats as $format) {
|
|
$date = \DateTime::createFromFormat($format, $value);
|
|
if ($date instanceof \DateTime) {
|
|
if ($format === 'd.m.Y' || $format === 'Y-m-d') {
|
|
return $date->format('Y-m-d') . ' 00:00:00';
|
|
}
|
|
|
|
return $date->format('Y-m-d H:i:s');
|
|
}
|
|
}
|
|
|
|
return $useNowIfEmpty ? date('Y-m-d H:i:s') : null;
|
|
}
|
|
} |