Repository: inventree/InvenTree

Issue:
Customer company name uniqueness issue for B2C application
Due to the reuse of company for customers, customer 'names' must be unique.  This is not a reasonable expectation for business selling direct to consumers as customers.   The unique key for a company should be relaxed or changed.  The only unique part for a 'customer' should be the email address.



Discussion:
That's a good point and I agree with you. It would take some thoughts to alter the email field without disrupting current databases (and maybe making it unique only for customers?) but I don't think it is a necessary measure, what do you think @SchrodingersGat ?
I can't see why email would not be unique for suppliers/manufacturers as well as customers.  For reference QuickBooks requires all customers/vendors to have a unique email address (you can have the same email as a vendor and a customer however).   Ideally the customers/suppliers are in separate tables and are separate objects with their own logic. 
@amishHammer The current implementation does not enforce email as a **required** field for company instances (regardless if customer or supplier) so adding uniqueness requirement could break the migration process because companies with "blank" email value will clash (it definitely happens for us, suppliers/manufacturers don't have any email address set).

Not many people buy directly from manufacturers/suppliers, using email. For customers, it would make a bit more sense to enforce email address as point of contact is necessary for selling product. But again, adding a database constraint will break existing users setups.

My proposal is to remove uniqueness requirement on the company `name` field and leave the email requirement loose in first place. If you really **need** to have the email uniqueness feature, we may be able to add it as an InvenTree setting, but not as a strict built-in requirement.
@eeintech I would strongly disagree with the assessment that purchasing is not done via email. Lots of people buy from their supply chain using purchase orders, which these days are typically emailed rather than faxed.  In most places a purchase order like this is considered a legally binding contract if it is accepted by the vendor.  At some point it would be great to see a send purchase order function in the PO section that formats the PO as a PDF and emails it to the supplier. Back to the QuickBooks reference, this functionality is fairly core to the way purchase orders work in that and most other applications that issue purchase orders.

SQL92 states that a NULL value in a unique key can exist multiple times, NULLs are not considered as part of the uniqueness check.  This may also be true in other dialects, SQLite and MySQL both act as SQL92 states.  Based on this is should be fine to add a unique key to email without breaking the DB as long as the empty emails are NULL and not empty strings.  This could be enforced with a 2 step migration, step one make empty strings NULL, step 2 add the unique key.


Sure, I don't disagree with you, I was not trying to make a case against or for one implementation or the other but rather differentiating the process for validating supplier vs customers, to refer to your original post. Anyhow, I agree and the InvenTree "company" model should stay generic for this use-case.

I have to double check for the NULL step migration you proposed, in my case it still threw migration errors. However, I'll leave it up-to @SchrodingersGat to decide if he want to enforce email as unique (InvenTree is not QuickBooks).
While I agree that this is not QB I'm just using it as an example of a product that has this feature.  At the end of the day InvenTree is the most promising solution to my problem and I'd love to be able to get this hooked up to Opencart for inventory management. 

In my limited customer base (~600) I have maybe a dozen customers with name conflicts, and this issue is the next hurdle in my implementation. I've been waiting for sales orders for a while so I can do this integration.


Resolving patch:
diff --git a/InvenTree/company/migrations/0024_unique_name_email_constraint.py b/InvenTree/company/migrations/0024_unique_name_email_constraint.py
new file mode 100644
--- /dev/null
+++ b/InvenTree/company/migrations/0024_unique_name_email_constraint.py
@@ -0,0 +1,38 @@
+from django.db import migrations, models
+
+
+def make_empty_email_field_null(apps, schema_editor):
+    Company = apps.get_model('company', 'Company')
+    for company in Company.objects.all():
+        if company.email == '':
+            company.email = None
+            company.save()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('company', '0023_auto_20200808_0715'),
+    ]
+
+    operations = [
+        # Allow email field to be NULL
+        migrations.AlterField(
+            model_name='company',
+            name='email',
+            field=models.EmailField(blank=True, help_text='Contact email address', max_length=254, null=True, unique=False, verbose_name='Email'),
+        ),
+        # Convert empty email string to NULL
+        migrations.RunPython(make_empty_email_field_null),
+        # Remove unique constraint on name field
+        migrations.AlterField(
+            model_name='company',
+            name='name',
+            field=models.CharField(help_text='Company name', max_length=100, verbose_name='Company name'),
+        ),
+        # Add unique constraint on name/email pair
+        migrations.AddConstraint(
+            model_name='company',
+            constraint=models.UniqueConstraint(fields=('name', 'email'), name='unique_name_email_pair'),
+        ),
+    ]
diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py
--- a/InvenTree/company/models.py
+++ b/InvenTree/company/models.py
@@ -12,7 +12,7 @@
 from django.utils.translation import gettext_lazy as _
 from django.core.validators import MinValueValidator
 from django.db import models
-from django.db.models import Sum, Q
+from django.db.models import Sum, Q, UniqueConstraint
 
 from django.apps import apps
 from django.urls import reverse
@@ -81,8 +81,11 @@ class Company(models.Model):
 
     class Meta:
         ordering = ['name', ]
+        constraints = [
+            UniqueConstraint(fields=['name', 'email'], name='unique_name_email_pair')
+        ]
 
-    name = models.CharField(max_length=100, blank=False, unique=True,
+    name = models.CharField(max_length=100, blank=False,
                             help_text=_('Company name'),
                             verbose_name=_('Company name'))
 
@@ -98,7 +101,8 @@ class Meta:
                              verbose_name=_('Phone number'),
                              blank=True, help_text=_('Contact phone number'))
 
-    email = models.EmailField(blank=True, verbose_name=_('Email'), help_text=_('Contact email address'))
+    email = models.EmailField(blank=True, null=True,
+                              verbose_name=_('Email'), help_text=_('Contact email address'))
 
     contact = models.CharField(max_length=100,
                                verbose_name=_('Contact'),


Test patch: