Browse Source

feat: 添加客户缩写功能,支持SKU编码系统

name 2 months ago
parent
commit
6f63686660

+ 29 - 0
README.md

@@ -2,6 +2,20 @@
 
 up
 
+### Features
+
+#### Customer Abbreviation System
+- **Automatic Generation**: Automatically generates 3-character customer abbreviations based on customer names
+- **Smart Conflict Resolution**: Handles duplicate abbreviations using numbers (1-9) and letters (A-W)
+- **SKU Integration Ready**: Designed to work with SKU encoding systems
+- **Validation**: Ensures unique, properly formatted abbreviations
+
+**Abbreviation Generation Rules:**
+1. Takes first 2 characters from customer name
+2. If conflicts exist, appends numbers 1-9
+3. If still conflicts, appends letters A-W
+4. Falls back to numeric sequences if needed
+
 ### Installation
 
 You can install this app using the [bench](https://github.com/frappe/bench) CLI:
@@ -12,6 +26,21 @@ bench get-app $URL_OF_THIS_REPO --branch develop
 bench install-app upsystem
 ```
 
+**Note**: After installation, existing customers will automatically get abbreviations generated.
+
+### Testing
+
+To test the abbreviation generation logic:
+
+```bash
+bench --site <site> console
+```
+
+Then run:
+```python
+exec(open('apps/upsystem/upsystem/test_customer_abbr.py').read())
+```
+
 ### Contributing
 
 This app uses `pre-commit` for code formatting and linting. Please [install pre-commit](https://pre-commit.com/#installation) and enable it for this repository:

+ 58 - 0
upsystem/commands/test_abbr.py

@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+"""
+Bench command to test customer abbreviation generation
+Usage: bench --site <site> test-abbr
+"""
+
+import click
+import frappe
+from frappe import _
+
+@click.command('test-abbr')
+def test_abbr():
+    """Test customer abbreviation generation"""
+    try:
+        # Import and run the test
+        from upsystem.customer_abbr import (
+            _get_base_abbreviation,
+            _generate_unique_abbreviation,
+            _is_abbreviation_unique
+        )
+        
+        click.echo("Testing customer abbreviation generation...")
+        
+        # Test base abbreviation generation
+        test_cases = [
+            ("ACME Corporation", "AC"),
+            ("Microsoft Inc", "MI"),
+            ("Apple", "AP"),
+            ("IBM", "IB"),
+            ("A", "AC"),
+            ("", "C")
+        ]
+        
+        click.echo("\n1. Testing base abbreviation generation:")
+        for customer_name, expected in test_cases:
+            result = _get_base_abbreviation(customer_name)
+            status = "✓" if result == expected else "✗"
+            click.echo(f"  {status} '{customer_name}' -> '{result}' (expected: '{expected}')")
+        
+        # Test unique abbreviation generation
+        click.echo("\n2. Testing unique abbreviation generation:")
+        test_base = "AC"
+        result = _generate_unique_abbreviation(test_base, "test_customer")
+        click.echo(f"  Base '{test_base}' -> Unique '{result}'")
+        
+        # Test uniqueness check
+        click.echo("\n3. Testing uniqueness check:")
+        is_unique = _is_abbreviation_unique("TEST", "current_customer")
+        click.echo(f"  'TEST' is unique: {is_unique}")
+        
+        click.echo("\nTest completed successfully!")
+        
+    except Exception as e:
+        click.echo(f"Error during testing: {str(e)}")
+        frappe.log_error(f"Test abbreviation error: {str(e)}")
+
+if __name__ == '__main__':
+    test_abbr() 

+ 21 - 0
upsystem/config/custom_fields.json

@@ -0,0 +1,21 @@
+[
+  {
+    "doctype": "Custom Field",
+    "dt": "Customer",
+    "fieldname": "customer_abbr",
+    "label": "Customer Abbreviation",
+    "fieldtype": "Data",
+    "length": 3,
+    "reqd": 1,
+    "unique": 1,
+    "description": "3-character customer abbreviation for SKU encoding",
+    "in_standard_filter": 1,
+    "in_list_view": 1,
+    "in_global_search": 1,
+    "search_index": 1,
+    "depends_on": "eval:doc.customer_name",
+    "on_change": "upsystem.customer_abbr.generate_customer_abbr",
+    "insert_after": "customer_name",
+    "owner": "Administrator"
+  }
+] 

+ 113 - 0
upsystem/customer_abbr.py

@@ -0,0 +1,113 @@
+import frappe
+import re
+from frappe import _
+
+def generate_customer_abbr(doc, method):
+    """Generate customer abbreviation based on customer name"""
+    if not doc.customer_name or doc.customer_abbr:
+        return
+    
+    # Get base abbreviation from customer name
+    base_abbr = _get_base_abbreviation(doc.customer_name)
+    
+    # Generate unique abbreviation
+    unique_abbr = _generate_unique_abbreviation(base_abbr, doc.name)
+    
+    doc.customer_abbr = unique_abbr
+
+def _get_base_abbreviation(customer_name):
+    """Extract base abbreviation from customer name"""
+    if not customer_name:
+        return "C"
+    
+    # Remove special characters and split by spaces
+    clean_name = re.sub(r'[^a-zA-Z0-9\s]', '', customer_name)
+    words = clean_name.split()
+    
+    if not words:
+        return "C"
+    
+    # Get first word and extract first 2 characters
+    first_word = words[0].upper()
+    if len(first_word) >= 2:
+        return first_word[:2]
+    else:
+        return first_word + "C"
+
+def _generate_unique_abbreviation(base_abbr, current_doc_name):
+    """Generate unique 3-character abbreviation"""
+    if len(base_abbr) >= 3:
+        base_abbr = base_abbr[:2]
+    
+    # Try base abbreviation first
+    if _is_abbreviation_unique(base_abbr, current_doc_name):
+        return base_abbr.ljust(3, "0")
+    
+    # Try with numbers 1-9
+    for num in range(1, 10):
+        test_abbr = base_abbr + str(num)
+        if _is_abbreviation_unique(test_abbr, current_doc_name):
+            return test_abbr
+    
+    # Try with letters A-W (excluding X, Y, Z for future use)
+    for letter in "ABCDEFGHIJKLMNOPQRSTUVW":
+        test_abbr = base_abbr + letter
+        if _is_abbreviation_unique(test_abbr, current_doc_name):
+            return test_abbr
+    
+    # If still not unique, try with numbers 0-9 for first position
+    for num in range(0, 10):
+        test_abbr = str(num) + base_abbr[:2]
+        if _is_abbreviation_unique(test_abbr, current_doc_name):
+            return test_abbr
+    
+    # Last resort: use numbers
+    counter = 1
+    while counter <= 999:
+        test_abbr = str(counter).zfill(3)
+        if _is_abbreviation_unique(test_abbr, current_doc_name):
+            return test_abbr
+        counter += 1
+    
+    # If all else fails, use a timestamp-based approach
+    import time
+    timestamp = int(time.time()) % 1000
+    return str(timestamp).zfill(3)
+
+def _is_abbreviation_unique(abbreviation, current_doc_name):
+    """Check if abbreviation is unique in the system"""
+    existing = frappe.db.get_value(
+        "Customer",
+        {"customer_abbr": abbreviation, "name": ["!=", current_doc_name]},
+        "name"
+    )
+    return not existing
+
+def validate_customer_abbr(doc, method):
+    """Validate customer abbreviation format and uniqueness"""
+    if not doc.customer_abbr:
+        return
+    
+    # Check length
+    if len(doc.customer_abbr) != 3:
+        frappe.throw(_("Customer abbreviation must be exactly 3 characters long"))
+    
+    # Check format (alphanumeric only)
+    if not re.match(r'^[A-Z0-9]{3}$', doc.customer_abbr):
+        frappe.throw(_("Customer abbreviation must contain only uppercase letters and numbers"))
+    
+    # Check uniqueness
+    existing = frappe.db.get_value(
+        "Customer",
+        {"customer_abbr": doc.customer_abbr, "name": ["!=", doc.name]},
+        "name"
+    )
+    if existing:
+        frappe.throw(_("Customer abbreviation '{0}' already exists for customer '{1}'").format(
+            doc.customer_abbr, existing
+        ))
+
+def on_customer_name_change(doc, method):
+    """Handle customer name changes to update abbreviation if needed"""
+    if doc.has_value_changed("customer_name") and not doc.customer_abbr:
+        generate_customer_abbr(doc, method) 

+ 17 - 4
upsystem/hooks.py

@@ -82,14 +82,14 @@ app_license = "mit"
 # Installation
 # ------------
 
-# before_install = "upsystem.install.before_install"
-# after_install = "upsystem.install.after_install"
+before_install = "upsystem.install.before_install"
+after_install = "upsystem.install.after_install"
 
 # Uninstallation
 # ------------
 
-# before_uninstall = "upsystem.uninstall.before_uninstall"
-# after_uninstall = "upsystem.uninstall.after_uninstall"
+before_uninstall = "upsystem.install.before_uninstall"
+after_uninstall = "upsystem.install.after_uninstall"
 
 # Integration Setup
 # ------------------
@@ -245,8 +245,21 @@ permission_query_conditions = {
     "Sales Order": "upsystem.orderpermissions.sales_order.get_permission_query_conditions"
 }
 
+# Custom Fields
+# -------------
+fixtures = [
+    "custom_fields"
+]
+
+# Document Events
+# ---------------
 doc_events = {
 	"User": {
 		"after_insert": "upsystem.overrides.user_send_mail.custom_send_welcome_mail_to_user"
+	},
+	"Customer": {
+		"validate": "upsystem.customer_abbr.validate_customer_abbr",
+		"on_change": "upsystem.customer_abbr.generate_customer_abbr",
+		"after_insert": "upsystem.customer_abbr.generate_customer_abbr"
 	}
 }

+ 61 - 0
upsystem/install.py

@@ -0,0 +1,61 @@
+import frappe
+from frappe import _
+
+def after_install():
+    """After app installation, generate abbreviations for existing customers"""
+    frappe.msgprint(_("Generating customer abbreviations for existing customers..."))
+    
+    # Get all customers without abbreviations
+    customers = frappe.get_all(
+        "Customer",
+        filters={"customer_abbr": ["is", "null"]},
+        fields=["name", "customer_name"]
+    )
+    
+    updated_count = 0
+    for customer in customers:
+        try:
+            # Generate abbreviation
+            from .customer_abbr import generate_customer_abbr
+            
+            # Create a mock doc for abbreviation generation
+            class MockDoc:
+                def __init__(self, name, customer_name):
+                    self.name = name
+                    self.customer_name = customer_name
+                    self.customer_abbr = None
+                
+                def has_value_changed(self, field):
+                    return False
+            
+            mock_doc = MockDoc(customer.name, customer.customer_name)
+            generate_customer_abbr(mock_doc, "after_insert")
+            
+            if mock_doc.customer_abbr:
+                # Update the customer record
+                frappe.db.set_value(
+                    "Customer",
+                    customer.name,
+                    "customer_abbr",
+                    mock_doc.customer_abbr
+                )
+                updated_count += 1
+                
+        except Exception as e:
+            frappe.log_error(f"Failed to generate abbreviation for customer {customer.name}: {str(e)}")
+    
+    frappe.msgprint(_("Updated {0} customers with abbreviations").format(updated_count))
+    frappe.db.commit()
+
+def before_uninstall():
+    """Before app uninstall, remove custom fields"""
+    try:
+        # Remove custom field
+        custom_field = frappe.get_doc("Custom Field", {
+            "dt": "Customer",
+            "fieldname": "customer_abbr"
+        })
+        custom_field.delete()
+        frappe.db.commit()
+    except:
+        pass 

+ 48 - 0
upsystem/test_customer_abbr.py

@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+"""
+Test script for customer abbreviation generation
+Run this in Frappe bench console: bench --site <site> console
+Then: exec(open('apps/upsystem/upsystem/test_customer_abbr.py').read())
+"""
+
+def test_abbreviation_generation():
+    """Test the abbreviation generation logic"""
+    from .customer_abbr import (
+        _get_base_abbreviation,
+        _generate_unique_abbreviation,
+        _is_abbreviation_unique
+    )
+    
+    print("Testing customer abbreviation generation...")
+    
+    # Test base abbreviation generation
+    test_cases = [
+        ("ACME Corporation", "AC"),
+        ("Microsoft Inc", "MI"),
+        ("Apple", "AP"),
+        ("IBM", "IB"),
+        ("A", "AC"),
+        ("", "C")
+    ]
+    
+    print("\n1. Testing base abbreviation generation:")
+    for customer_name, expected in test_cases:
+        result = _get_base_abbreviation(customer_name)
+        status = "✓" if result == expected else "✗"
+        print(f"  {status} '{customer_name}' -> '{result}' (expected: '{expected}')")
+    
+    # Test unique abbreviation generation
+    print("\n2. Testing unique abbreviation generation:")
+    test_base = "AC"
+    result = _generate_unique_abbreviation(test_base, "test_customer")
+    print(f"  Base '{test_base}' -> Unique '{result}'")
+    
+    # Test uniqueness check
+    print("\n3. Testing uniqueness check:")
+    is_unique = _is_abbreviation_unique("TEST", "current_customer")
+    print(f"  'TEST' is unique: {is_unique}")
+    
+    print("\nTest completed!")
+
+if __name__ == "__main__":
+    test_abbreviation_generation()