file = $file; $this->site = $site; $this->co = Cost::where('site_id',$site->site_id) ->where('supplier_id',$so->id) ->where('billed_at',$invoice_date) ->firstOrNew(); $this->co->active = TRUE; $this->co->site_id = $site->site_id; $this->co->billed_at = $invoice_date; $this->co->supplier_id = $so->id; $this->co->save(); Cost\Broadband::where('cost_id',$this->co->id)->where('site_id',$site->site_id)->delete(); Cost\Phone::where('cost_id',$this->co->id)->where('site_id',$site->site_id)->delete(); Cost\Generic::where('cost_id',$this->co->id)->where('site_id',$site->site_id)->delete(); // @todo to be stored in supplier config $headers = [ 'INVOICEID'=>'Item ID', 'REF'=>'Reference No', 'IDTAG'=>'ID Tag', 'CATEGORY'=>'Category', 'DESC'=>'Item Description', 'QUANTITY'=>'Quantity', 'PRICEUNIT'=>'Unit Price (inc-GST)', 'PRICETOTAL'=>'Total (inc-GST)' ]; $this->columns = collect($headers)->map(fn($item)=>strtolower($item)); } /** * Execute the job. * * @return void * @throws \Exception */ public function handle() { Config::set('site',$this->site); $skip = 7; // @todo to be stored in supplier config $file = fopen('storage/app/'.$this->file,'r'); $haveHeader = FALSE; $c = 0; while (! feof($file)) { $line = stream_get_line($file,0,"\r\n"); if (str_starts_with($line,'#')) continue; // Remove any embedded CR and BOM $line = str_replace("\r",'',$line); $line = preg_replace('/^\x{feff}/u','',$line); if (($c++ < $skip) || (! $line)) continue; // The first line is a header. if (! $haveHeader) { Log::debug(sprintf('%s: Input File: %s',get_class($this),$this->file)); Log::debug(sprintf('%s: Processing columns: %s',get_class($this),join('|',$this->setColumns($line)->toArray()))); $haveHeader = TRUE; continue; } // If the line has a , between two (), then convert the comma to a space. $x = []; if (preg_match('#\(.+,.+\)#i',$line,$x)) { $replace = str_replace(',','_',$x[0]); $line = str_replace($x[0],$replace,$line); //dd($line,$x); } $fields = str_getcsv(trim($line)); if (is_null($x=$this->getColumnKey('DESC')) OR empty($fields[$x])) continue; // The first part of our item description is the service number. // This should go to a "supplier" function, since all suppliers may show different values in description. $m = []; $desc = $fields[$x]; // m[1] = Service, m[2] = Desc, m[3] = From Date, m[4] = To Date preg_match('#^([0-9]{10})\s+-\s+(.*)\(([0-9]+\s+[JFMASOND].*\s+[0-9]+)+\s+-\s+([0-9]+\s+[JFMASOND].*\s+[0-9]+)+\)$#',$fields[$x],$m); if (count($m) !== 5) { dump(sprintf('ERROR: Description didnt parse [%s] on line [%d]',$fields[$x],$c)); continue; } $cost = ($x=$this->getColumnKey('PRICETOTAL')) ? str_replace([',','$'],'',$fields[$x]) : NULL; $start_at = Carbon::createFromFormat('d M Y',$m[3]); $stop_at = Carbon::createFromFormat('d M Y',$m[4]); $so = Service::search($m[1])->active()->with(['type','product.type.supplied'])->single(); if ($so) { // r[1] = Monthly Charge or Extra Charge,r[2] = "On Plan", r[3] = Plan Info $r = []; switch ($so->product->category) { case 'broadband': $to = Cost\Broadband::where('site_id',$this->co->site_id) ->where('cost_id',$this->co->id) ->where('service_broadband_id',$so->type->id) ->where('start_at',$start_at) ->where('end_at',$stop_at) ->firstOrNew(); $to->service_broadband_id = $so->type->id; preg_match('#^(Monthly Internet Charge|Plan Change Fee|Change billing date refund for Monthly Internet Charge On Plan|First 12 Month VISP broadband plan discount|.*)\s?(On Plan)?\s?(.*)#',$m[2],$r); switch ($r[1]) { case 'Monthly Internet Charge': case 'First 12 Month VISP broadband plan discount': case 'Change billing date refund for Monthly Internet Charge On Plan': $to->base =+ $cost; break; case 'Plan Change Fee': $to->excess =+ $cost; break; default: dump(['extra charge'=>$r]); $to->excess =+ $cost; } break; case 'phone': $to = Cost\Phone::where('site_id',$this->co->site_id) ->where('cost_id',$this->co->id) ->where('service_phone_id',$so->type->id) ->where('start_at',$start_at) ->where('end_at',$stop_at) ->firstOrNew(); $to->service_phone_id = $so->type->id; preg_match('#^(Residential VOIP Plan Excess Usage|Virtual FAX Number Monthly Rental|Corporate VOIP Plan Monthly Rental|Residential VOIP Plan Monthly Rental|.*)\s?(.*)#',$m[2],$r); switch ($r[1]) { case 'Residential VOIP Plan Monthly Rental': case 'Virtual FAX Number Monthly Rental': case 'Corporate VOIP Plan Monthly Rental': $to->base =+ $cost; break; case 'Residential VOIP Plan Excess Usage': $to->excess =+ $cost; $to->notes = $r[2]; break; default: dump(['extra charge'=>$r]); $to->excess =+ $cost; } break; default: dump(['so'=>$so,'category'=>$so->product->category,'line'=>$line,'m'=>$m,'r'=>$r]); throw new \Exception(sprintf('ERROR: Service type not handled for service [%s] (%s) on line [%d]',$m[1],$so->product->category,$c)); } } else { dump(['line'=>$line,'sql'=>Service::search($m[1])->active()->with(['type','product.type.supplied'])->toSql()]); $to = Cost\Generic::where('site_id',$this->co->site_id) ->where('cost_id',$this->co->id) ->where('notes',sprintf('%s:%s',$m[1],$m[2])) ->where('start_at',$start_at) ->where('end_at',$stop_at) ->firstOrNew(); $to->excess =+ $cost; $to->notes = $line; } $to->site_id = $this->co->site_id; $to->cost_id = $this->co->id; $to->active = TRUE; $to->start_at = $start_at; $to->end_at = $stop_at; // Work out supplier product number Log::warning(sprintf('%s:Supplier product ID not matched',self::LOGKEY),['r'=>$r]); //dd($m[2],$cost,$so->product->type->supplied); // Work out if this base charge, or extra charge //dd(['M'=>__METHOD__,'fields'=>$fields,'DESC'=>$this->getColumnKey('DESC'),'desc'=>$desc,'m'=>$m,'sql'=>$so->toSql(),'bindings'=>$so->getBindings(),'so'=>$so]); $to->save(); } fclose($file); } }