That is going to be tough to split instance of #, ## and ### apart due to partial matching… You could try the following ensuring you do a full DB backup in case this does not work
php artisan tinker
Invoice::query()->cursor()->each(function ($invoice){
$line_items = $invoice->line_items;
foreach($line_items as $item)
{
$item->notes = str_replace("\n", " ", $item->notes);
if(preg_match("/^###$/", $item->notes)){
$item->notes = str_replace("###", "### ", $item->notes);
}
if(preg_match("/^##$/", $item->notes)){
$item->notes = str_replace("##", "## ", $item->notes);
}
if(preg_match("/^#$/", $item->notes)){
$item->notes = str_replace("#", "# ", $item->notes);
}
}
$invoice->line_items = $line_items;
$invoice->save();
});