'id_teller', 'account_1' => 'account_1', 'currency_1' => 'currency_1', 'amount_local_1' => 'amount_local_1', 'value_date_1' => 'value_date_1', 'account_2' => 'account_2', 'new_cust_bal' => 'new_cust_bal', 'term_type' => 'term_type', 'term_id' => 'term_id', 'trans_reff' => 'trans_reff', 'card_no' => 'card_no', 'recipt_no' => 'recipt_no', 'transaction_code' => 'transaction_code', 'date_time' => 'date_time', 'record_status' => 'record_status', 'amount_local_2' => 'amount_local_2', 'co_code' => 'co_code', 'narrative_1' => 'narrative_1', 'wic_flag' => 'wic_flag', 'wic_cust_type' => 'wic_cust_type', 'wic_full_name' => 'wic_full_name', 'wic_alias_name' => 'wic_alias_name', 'wic_acct_no' => 'wic_acct_no', 'wic_id_type' => 'wic_id_type', 'wic_id_no' => 'wic_id_no', 'wic_npwp' => 'wic_npwp', 'wic_nationality' => 'wic_nationality', 'wic_ind_birthpl' => 'wic_ind_birthpl', 'wic_ind_birthdt' => 'wic_ind_birthdt', 'wic_address_id' => 'wic_address_id', 'wic_address_cur' => 'wic_address_cur', 'wic_city' => 'wic_city', 'wic_province' => 'wic_province', 'wic_post_code' => 'wic_post_code', 'wic_phone' => 'wic_phone', 'wic_gender' => 'wic_gender', 'wic_marital_sts' => 'wic_marital_sts', 'wic_occptn' => 'wic_occptn', 'wic_occptn_dur' => 'wic_occptn_dur', 'wic_income_avg' => 'wic_income_avg', 'wic_cor_name' => 'wic_cor_name', 'wic_cor_address' => 'wic_cor_address', 'wic_cor_phone' => 'wic_cor_phone', 'wic_cor_lgl_typ' => 'wic_cor_lgl_typ', 'wic_cor_lic_no' => 'wic_cor_lic_no', 'wic_cor_birthpl' => 'wic_cor_birthpl', 'wic_cor_birthdt' => 'wic_cor_birthdt', 'wic_cor_rel' => 'wic_cor_rel', 'wic_party_rel' => 'wic_party_rel', 'wic_amount' => 'wic_amount', 'wic_amount_type' => 'wic_amount_type', 'wic_amtbnk_name' => 'wic_amtbnk_name', 'wic_amtbnk_cunm' => 'wic_amtbnk_cunm', 'wic_fund_source' => 'wic_fund_source', 'wic_fund_use' => 'wic_fund_use', 'dr_cr_marker' => 'dr_cr_marker', 'charge_code' => 'charge_code', 'chrg_amt_local' => 'chrg_amt_local', 'charge_category' => 'charge_category', 'charge_account' => 'charge_account', 'amount_fcy_1' => 'amount_fcy_1', 'rate_1' => 'rate_1', 'deal_rate' => 'deal_rate', 'l_wic_id' => 'l_wic_id', 'account_1_co_code' => 'account_1_co_code', 'account_2_co_code' => 'account_2_co_code', 'l_charge_amt' => 'l_charge_amt', 'bl_cust_no' => 'bl_cust_no', 'stmt_no' => 'stmt_no', 'bil_customer' => 'bil_customer', 'value_date_2' => 'value_date_2', 'cheq_type' => 'cheq_type', 'cheque_number' => 'cheque_number', 'inputter' => 'inputter', 'authoriser' => 'authoriser', 'bil_product' => 'bil_product', 'fx_document' => 'fx_document', 'fx_purpose' => 'fx_purpose', 'narrative_2' => 'narrative_2', 'customer_2' => 'customer_2', 'l_sms_1' => 'l_sms_1', 'l_phone_1' => 'l_phone_1', 'kyc_incom_rng' => 'kyc_incom_rng', 'wic_rt' => 'wic_rt', 'wic_rw' => 'wic_rw', 'ktp_kelurahan' => 'ktp_kelurahan', 'ktp_kecamatan' => 'ktp_kecamatan', 'ktp_provinsi' => 'ktp_provinsi', 'wic_jenis_kelam' => 'wic_jenis_kelam', 'kyc_sumber_dana' => 'kyc_sumber_dana', 'l_manual_risk' => 'l_manual_risk', 'l_slip_no' => 'l_slip_no', 'dest_bank_id' => 'dest_bank_id', 'dr_narrative' => 'dr_narrative', 'inter_type' => 'inter_type', 'l_va_number' => 'l_va_number', 'inter_bank_id' => 'inter_bank_id', 'term_narr' => 'term_narr', 'currency_2' => 'currency_2', 'amount_fcy_2' => 'amount_fcy_2', 'rate_2' => 'rate_2', 'customer_1' => 'customer_1', 'last_version' => 'last_version' ]; // Pemetaan bidang header ke kolom model protected string $period; /** * Create a new job instance. */ public function __construct(string $period = '') { $this->period = $period; } /** * Execute the job. */ public function handle() : void { try { set_time_limit(24 * 60 * 60); if ($this->period === '') { Log::warning('No period provided for teller data processing'); return; } $stats = $this->processPeriodFile(); Log::info("ProcessTellerDataJob completed. Total processed: {$stats['processed']}, Total errors: {$stats['errors']}"); } catch (Exception $e) { Log::error("Error in ProcessTellerDataJob: " . $e->getMessage()); throw $e; } } /** * Process a single period file */ private function processPeriodFile() : array { $disk = Storage::disk(self::DISK_NAME); $filename = $this->period . self::FILE_EXTENSION; $filePath = "{$this->period}/$filename"; $processedCount = 0; $errorCount = 0; Log::info("Processing teller file: $filePath"); if (!$disk->exists($filePath)) { Log::warning("File not found: $filePath"); return ['processed' => 0, 'errors' => 0]; } $tempFilePath = $this->createTempFile($disk, $filePath, $filename); $result = $this->processCSVFile($tempFilePath, $filePath); $processedCount += $result['processed']; $errorCount += $result['errors']; // Clean up the temporary file if (file_exists($tempFilePath)) { unlink($tempFilePath); } Log::info("Completed processing $filePath. Processed {$result['processed']} records with {$result['errors']} errors."); return [ 'processed' => $processedCount, 'errors' => $errorCount ]; } /** * Create a temporary file for processing */ private function createTempFile($disk, string $filePath, string $filename) : string { $tempFilePath = storage_path("app/temp_$filename"); file_put_contents($tempFilePath, $disk->get($filePath)); return $tempFilePath; } /** * Process a CSV file and import data */ private function processCSVFile(string $tempFilePath, string $originalFilePath) : array { $processedCount = 0; $errorCount = 0; $handle = fopen($tempFilePath, "r"); if ($handle === false) { Log::error("Unable to open file: $originalFilePath"); return ['processed' => 0, 'errors' => 0]; } // Get the headers from the first row $headerRow = fgetcsv($handle, 0, self::CSV_DELIMITER); if (!$headerRow) { fclose($handle); return ['processed' => 0, 'errors' => 0]; } $rowCount = 0; while (($row = fgetcsv($handle, 0, self::CSV_DELIMITER)) !== false) { $rowCount++; if (count($headerRow) !== count($row)) { Log::warning("Row $rowCount in $originalFilePath has incorrect column count. Expected: " . count($headerRow) . ", Got: " . count($row)); continue; } $result = $this->processRow($headerRow, $row, $rowCount, $originalFilePath); $processedCount += $result['processed']; $errorCount += $result['errors']; } fclose($handle); return [ 'processed' => $processedCount, 'errors' => $errorCount ]; } /** * Process a single row from the CSV file */ private function processRow(array $headerRow, array $row, int $rowCount, string $filePath) : array { // Combine the header row with the data row $rawData = array_combine($headerRow, $row); // Map the raw data to our model fields $data = []; foreach (self::HEADER_MAP as $csvField => $modelField) { $data[$modelField] = $rawData[$csvField] ?? null; } // Skip header row if it was included in the data if ($data['id_teller'] === 'id') { return ['processed' => 0, 'errors' => 0]; } try { $teller = Teller::firstOrNew(['id_teller' => $data['id_teller']]); $teller->fill($data); $teller->save(); return ['processed' => 1, 'errors' => 0]; } catch (Exception $e) { Log::error("Error processing Teller at row $rowCount in $filePath: " . $e->getMessage()); return ['processed' => 0, 'errors' => 1]; } } }