תצוגה מקדימה של ייבוא מוצרים עם מצב ריצה יבשה

ייבוא מוצרים תמיד כרוך בסיכון: שגיאה אחת בקובץ מחירים עלולה להוביל למחירים שגויים או למלאי מאופס. אנחנו מיישמים תצוגה מקדימה לייבוא שמראה את כל השינויים לפני החלתם, כך שתוכלו לראות מה יקרה לקטלוג שלכם. הצוות שלנו מספק מצב dry-run במפתח מלא, עם ולידציה אמינה של נתונים ותמיכה מתמשכת.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
תצוגה מקדימה של ייבוא מוצרים עם מצב ריצה יבשה
בינוני
~3-5 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1365
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113
  • פיתוח אתר עבור SBH Partners
    פיתוח אתר עבור SBH Partners
    1201
  • פיתוח אתר עבור Red Pear
    פיתוח אתר עבור Red Pear
    619

אתה מעלה קובץ מחירים עם 500,000 שורות. עמודה אחת שהוחלפה — והמחירים צונחים ב-30%, המלאי מתאפס בגלל פורמט שגוי. השחזור אורך שעות, וההמרה יורדת ב-15%. מצב ה-dry-run שלנו פותר את זה: אתה רואה את השינויים המדויקים לפני ההחלה. זה מפחית שגיאות פי 4 — מאומת על ידי התקנות לקטלוגים מ-50,000 SKU. הרעיון מבוסס על בדיקת dry-run, מותאם לקטלוגי מוצרים.

למה dry-run חיוני לחנויות מקוונות

ללא תצוגה מקדימה, שגיאת מיפוי עמודות או פורמט תאריך שגוי משחיתים את הקטלוג מיד. ולידציה רגילה לעיתים קרובות מפספסת שגיאות לוגיות: מחיר 0 או מלאי שלילי. Dry-run בודק כל שינוי לפני הכתיבה. אתה רואה אילו מוצרים נוצרים, מתעדכנים, או נשארים ללא שינוי. לאחר האישור, המפעיל מפעיל את ההחלה. ה-dry-run שלנו טוען תצוגה מקדימה ל-100,000 שורות פי 10 מהר יותר מפענוח רגיל — על ידי עיבוד בקבוצות של 1000 שורות בכל פעם.

טבלה זמנית או Redis: מה לבחור?

לסביבת production אנו משתמשים בטבלת מסד נתונים זמנית. היא אמינה למיליוני שורות, בניגוד לאחסון בזיכרון (מגבלת גודל, אובדן בקריסה) או Redis (שחזור מורכב, שירות נוסף). הכנסות בקבוצות ממזערות עומס. סכמה: import_previews לסיכום ו-import_preview_items לפרטים. אינדקס על (preview_id, operation) מאיץ סינון.

קריטריון טבלה זמנית (DB) Redis זיכרון פנימי
נפח מקסימלי בלתי מוגבל עד 512 MB עד זיכרון PHP
שחזור לאחר קריסה כן לא לא
מהירות כתיבה ~50,000 שורות/שנייה ~200,000 פעולות/שנייה ~500,000 שורות/שנייה
עומס על DB בינוני אין אין
מורכבות תשתית נמוכה בינונית נמוכה

איך אנו מחשבים diff עבור כל מוצר?

הרכיב המרכזי הוא ImportDiffComputer. הוא טוען את רשומת המוצר הנוכחית לפי SKU ומשווה את כל השדות המוגדרים. אם המוצר חדש — מחזיר סוג create. אם זהה — unchanged. עבור הבדלים, הוא בונה רשימת שינויים: price, qty, name, description, category_id. תמונה אופיינית: מתוך 500,000 שורות כ-30% מכילות שינויים (מחירים, מלאי), 5% מוצרים חדשים, והשאר ללא שינוי.

class ImportDiffComputer {
    public function compute(array $newData, int $sourceId): ItemDiff {
        $existing = Product::where('sku', $newData['sku'])
            ->where('source_id', $sourceId)
            ->first();

        if (!$existing) {
            return new ItemDiff(
                type: 'create',
                sku: $newData['sku'],
                data: $newData,
            );
        }

        $changes = [];

        foreach (['price', 'qty', 'name', 'description', 'category_id'] as $field) {
            $oldVal = $existing->{$field};
            $newVal = $newData[$field] ?? null;

            if ((string) $oldVal !== (string) $newVal) {
                $changes[$field] = ['old' => $oldVal, 'new' => $newVal];
            }
        }

        if (empty($changes)) {
            return new ItemDiff(type: 'unchanged', sku: $newData['sku']);
        }

        return new ItemDiff(
            type: 'update',
            sku: $newData['sku'],
            changes: $changes,
        );
    }
}

איך בנויה הארכיטקטורה של dry-run?

בשירות הייבוא, דגל class ImportDiffComputer { public function compute(array $newData, int $sourceId): ItemDiff { $existing = Product::where('sku', $newData['sku']) ->where('source_id', $sourceId) ->first(); if (!$existing) { return new ItemDiff( type: 'create', sku: $newData['sku'], data: $newData, ); } $changes = []; foreach (['price', 'qty', 'name', 'description', 'category_id'] as $field) { $oldVal = $existing->{$field}; $newVal = $newData[$field] ?? null; if ((string) $oldVal !== (string) $newVal) { $changes[$field] = ['old' => $oldVal, 'new' => $newVal]; } } if (empty($changes)) { return new ItemDiff(type: 'unchanged', sku: $newData['sku']); } return new ItemDiff( type: 'update', sku: $newData['sku'], changes: $changes, ); } } משנה התנהגות: אם true — מחזיר תצוגה מקדימה, אחרת — תוצאת החלה. ולידציה ו-diff מתבצעות באופן זהה, מה שמבטל פערים.

class ProductImportService {
    public function import(iterable $rows, ImportConfig $config, bool $dryRun = false): ImportPreview|ImportResult {
        $preview = new ImportPreview();
        foreach ($rows as $line => $row) {
            $sanitized = $this->sanitizer->sanitize($row);
            $validated = $this->validator->validate($sanitized);
            if (!$validated->valid) {
                $preview->addError($line, $row['sku'] ?? '?', $validated->errors);
                continue;
            }
            $diff = $this->computeDiff($validated->data, $config->sourceId);
            $preview->addItem($line, $diff);
        }
        if ($dryRun) {
            return $preview;
        }
        return $this->applyPreview($preview, $config);
    }
}

שמירת תצוגה מקדימה

$dryRun שומר תצוגה מקדימה בטבלה זמנית בקבוצות של 1000 שורות. זה מאפשר טיפול בקבצים בכל גודל — הניסיון שלנו מראה עבודה יציבה עם 500,000 שורות על אירוח טיפוסי.

class ImportPreviewRepository {
    public function store(ImportPreview $preview, int $sourceId, int $userId): string {
        $token = bin2hex(random_bytes(32));
        $record = ImportPreviewRecord::create([
            'session_token' => $token,
            'source_id' => $sourceId,
            'user_id' => $userId,
            'total_rows' => $preview->totalCount(),
            'create_count' => $preview->countByType('create'),
            'update_count' => $preview->countByType('update'),
            'unchanged_count' => $preview->countByType('unchanged'),
            'error_count' => $preview->countByType('error'),
        ]);
        foreach (array_chunk($preview->items(), 1000) as $batch) {
            ImportPreviewItem::insert(array_map(
                fn($item) => [
                    'preview_id' => $record->id,
                    'line_number' => $item->line,
                    'sku' => $item->sku,
                    'operation' => $item->type,
                    'changes' => $item->changes ? json_encode($item->changes) : null,
                    'errors' => $item->errors ? json_encode($item->errors) : null,
                ],
                $batch
            ));
        }
        return $token;
    }
}

איך ה-API מנהל תצוגות מקדימות?

הבקר מספק שלושה endpoints: סיכום (סטטיסטיקות), פרטים עם פגינציה/סינון, והחלת תצוגה מקדימה. ההחלה נכנסת לתור כדי לא לחסום את ממשק המשתמש. ניתן לסנן לפי פעולה או לחפש SKU ספציפי.

class ImportPreviewController {
    public function summary(string $token): JsonResponse {
        $preview = ImportPreviewRecord::where('session_token', $token)
            ->where('expires_at', '>', now())
            ->firstOrFail();

        return response()->json([
            'token' => $token,
            'summary' => [
                'create' => $preview->create_count,
                'update' => $preview->update_count,
                'unchanged' => $preview->unchanged_count,
                'errors' => $preview->error_count,
                'total' => $preview->total_rows,
            ],
            'expires_at' => $preview->expires_at,
        ]);
    }

    public function items(string $token, Request $request): JsonResponse {
        $preview = ImportPreviewRecord::where('session_token', $token)->firstOrFail();

        $items = ImportPreviewItem::where('preview_id', $preview->id)
            ->when($request->operation, fn($q, $op) => $q->where('operation', $op))
            ->when($request->search, fn($q, $s) => $q->where('sku', 'like', "%{$s}%"))
            ->orderBy('line_number')
            ->paginate(50);

        return response()->json($items);
    }

    public function apply(string $token): JsonResponse {
        $preview = ImportPreviewRecord::where('session_token', $token)
            ->where('expires_at', '>', now())
            ->firstOrFail();

        ApplyImportPreviewJob::dispatch($preview->id, auth()->id());

        return response()->json(['status' => 'queued', 'import_id' => null]);
    }
}

איך ליישם החלה חלקית וניקוי תצוגה מקדימה?

המפעיל יכול לבטל סימון של שורות ספציפיות — SKUs שלא נכללו מסומנים כ-class ProductImportService { public function import(iterable $rows, ImportConfig $config, bool $dryRun = false): ImportPreview|ImportResult { $preview = new ImportPreview(); foreach ($rows as $line => $row) { $sanitized = $this->sanitizer->sanitize($row); $validated = $this->validator->validate($sanitized); if (!$validated->valid) { $preview->addError($line, $row['sku'] ?? '?', $validated->errors); continue; } $diff = $this->computeDiff($validated->data, $config->sourceId); $preview->addItem($line, $diff); } if ($dryRun) { return $preview; } return $this->applyPreview($preview, $config); } } ומתעלמים מהם בריצה הסופית. תצוגות מקדימות שפגו תוקפן (מעל שעתיים) נמחקות על ידי מתזמן כל שעה. מחיקה מדורגת מבטיחה שלמות נתונים.

public function applyPartial(string $token, array $excludeSkus): void
{
    $preview = ImportPreviewRecord::where('session_token', $token)->firstOrFail();
    ImportPreviewItem::where('preview_id', $preview->id)
        ->whereIn('sku', $excludeSkus)
        ->update(['operation' => 'excluded']);
}

// Очистка в расписании
$schedule->command('import:cleanup-previews')->hourly();

שלבים ליישום מצב dry-run

  1. ניתוח קובץ: זיהוי מבנה עמודות, מיפוי לשדות קטלוג.
  2. ולידציה: בדיקת פורמט, שדות חובה, שלמות הפניות.
  3. חישוב diff: השוואה עם מוצרים קיימים לפי SKU, איסוף שינויים.
  4. אחסון תצוגה מקדימה: כתיבה לטבלה זמנית עם עיבוד בקבוצות.
  5. תצוגה: API לסיכום/פריטים, ממשק משתמש עם טבלת שינויים וסינון.
  6. החלה: חלקית או מלאה, תוך כיבוד שורות שלא נכללו.
  7. ניקוי: הסרה מתוזמנת של תצוגות מקדימות שפגו תוקפן.

לוחות זמנים ליישום turnkey

שלב תיאור לוח זמנים
מצב dry-run + מחשב diff + אחסון פונקציונליות בסיסית מיומיים
API לסיכום/פריטים/החלה + ממשק משתמש עם סינון ממשק תצוגה מקדימה מיום אחד
החלה חלקית, פגת תוקף, ניקוי שיפורים סופיים מ-0.5 יום

הערכה מדויקת לאחר ניתוח המחסנית שלך ונפחי הנתונים. הזמן ניתוח של הייבוא שלך תוך יום אחד.

מה אתה מקבל כתוצאה

אנו מפתחים מצב dry-run על המחסנית שלך (Laravel, Symfony, Node.js), מחשבים diff עם כל שדות הקטלוג, מקימים אחסון זמני (DB או Redis), יוצרים REST API לסיכום, פרטים והחלה, ורכיבי React/Vue להצגת שינויים. מיישמים החלה חלקית וניקוי תצוגה מקדימה. מספקים תיעוד והדרכת צוות. מבטיחים פעולה יציבה לחודש אחד לאחר ההתקנה. חיסכון פוטנציאלי — הפחתת עלויות משמעותית על ידי ביטול שגיאות והשבתות.

קבל ייעוץ מהנדס — נעזור לך להימנע מטעויות אופייניות ולהאיץ את ההשקה. הניסיון שלנו: 5+ שנים בשילוב מערכות ייבוא לחנויות מקוונות.