I think the best way to approach this would be to have tables like so:
table Customer
ID
Name
...
table CustomerFields
CustomerID
FieldID
FieldName
... (could have things like type, required/optional, size, etc.)
table CustomerOrders
CustomerID
OrderID
OrderNumber
DeliveryDate
... (all your generics and mappable fields)
table CustomerOrderFields
CustomerID
OrderID
FieldID
FieldValue
By having the table CustomerOrders, you satisfy the ability to map common customer order data to common fields (simplifying reporting at the expense of making the import a little more painful since the fields must be mapped), and the CustomerOrderFields gives you the ability to have the custom fields per customer necessary for the un-mappable data.
The custom fields are still reportable, but not as easily as your generics as they'll come to you in multiple rows (instead of multiple columns). There are some ways around all that depending on your report creator (e.g., pivoting the results).
The only other option would be to do something like this (which, personally, I would avoid):
table CustomerFields
CustomerID
Field1Name
Field2Name
Field3Name
...
Field99Name
Table CustomerOrders
(all your generics)
Field1Value
Field2Value
...
Field99Value
This has the advantage that all your data is in one row, but also has the disadvantage that it isn't immediately apparent from the row what each value means. The first method can always be joined to the field list to give a good definition of the data in each custom field. In addition, what happens if some company requires 100 custom fields? In the above example, you'd be making changes to your data structures and code whereas in the first example, you'd never have the issue -- customers could have infinite custom fields.
I've seen it done both ways, and both ways work. Both ways have their downsides and upsides. The first is far more scalable, but harder to get in to columns (instead of rows). Everything's a trade-off.
Hope that helps some!
KEY-VALUE NO!!!
A table for phone numbers -- sure. It would have userid, phone_num, and (if you like) a phone type, such as ENUM('fax', 'home', ...). Then JOIN to the main table.
To keep unlimited, unsearchable data, have a column with a bunch of key-value stuff. I like to do it in JSON, then compress it (in the app), and store it into a BLOB or MEDIUMBLOB. That makes it easily accessible by the app, reasonable compact, and quite open-ended.
In the table, have only columns that you need to search on; put the rest into the extra JSON column.
More discussion:
http://forums.mysql.com/read.php?125,428546,428769#msg-428769
http://forums.mysql.com/read.php?125,402095,402218#msg-402218
Another approach is MariaDB's "dynamic columns". This even lets you index randomly added 'columns'.
2000-3000 customers -- Yawn.
Best Answer
I'm not familiar enough with Laravel 7 to understand how well it implements alternate keys/composite keys, but this is basically what you've described:
Notes:
Revised model based on comment: