- Plate lookup: new plate_vehicles table (v1.7 migration), plate_lookup service with Mexican plate validation, GET/POST endpoints on catalog_bp, plate search UI in catalog vehicle selector - Translations: extend PART_TRANSLATIONS from ~80 to 326 entries covering brake, engine, fuel, cooling, electrical, drivetrain, suspension, steering, exhaust, A/C, lighting, body, interior, fluids, and category translations - Bulk images: image_scraper service with download+resize+placeholder generation, bulk-images and auto-image endpoints on inventory_bp Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
108
pos/services/plate_lookup.py
Normal file
108
pos/services/plate_lookup.py
Normal file
@@ -0,0 +1,108 @@
|
||||
# /home/Autopartes/pos/services/plate_lookup.py
|
||||
"""Mexican license plate lookup service.
|
||||
|
||||
Validates Mexican plate formats and searches the local plate_vehicles table.
|
||||
Since there is no free REPUVE API, this uses a tenant-local lookup table
|
||||
populated when customers register their vehicles.
|
||||
|
||||
Mexican plate formats:
|
||||
- Standard: ABC-1234 (3 letters + hyphen + 3-4 digits)
|
||||
- Alternate: AB-123-C (2 letters + 3 digits + 1 letter)
|
||||
- Also accepted without hyphens: ABC1234, AB123C
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
# Patterns for Mexican plates (with or without hyphens)
|
||||
_PATTERNS = [
|
||||
re.compile(r'^[A-Z]{3}-?\d{3,4}$'), # ABC-1234 or ABC1234
|
||||
re.compile(r'^[A-Z]{2}-?\d{3}-?[A-Z]$'), # AB-123-C or AB123C
|
||||
]
|
||||
|
||||
|
||||
def normalize_plate(plate):
|
||||
"""Normalize a plate string: uppercase, strip spaces/hyphens."""
|
||||
if not plate:
|
||||
return ''
|
||||
return re.sub(r'[\s\-]+', '', plate.strip().upper())
|
||||
|
||||
|
||||
def is_valid_mexican_plate(plate):
|
||||
"""Check if a string looks like a valid Mexican license plate."""
|
||||
norm = normalize_plate(plate)
|
||||
return any(p.match(norm) for p in _PATTERNS)
|
||||
|
||||
|
||||
def search_plate(tenant_conn, plate):
|
||||
"""Search plate_vehicles table for a matching plate.
|
||||
|
||||
Returns dict with vehicle info or None if not found.
|
||||
"""
|
||||
norm = normalize_plate(plate)
|
||||
if not norm:
|
||||
return None
|
||||
|
||||
cur = tenant_conn.cursor()
|
||||
try:
|
||||
cur.execute("""
|
||||
SELECT id, plate, make, model, year, vin, customer_id, created_at
|
||||
FROM plate_vehicles
|
||||
WHERE REPLACE(REPLACE(UPPER(plate), '-', ''), ' ', '') = %s
|
||||
LIMIT 1
|
||||
""", (norm,))
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
return None
|
||||
return {
|
||||
'id': row[0],
|
||||
'plate': row[1],
|
||||
'make': row[2],
|
||||
'model': row[3],
|
||||
'year': row[4],
|
||||
'vin': row[5],
|
||||
'customer_id': row[6],
|
||||
'created_at': row[7].isoformat() if row[7] else None,
|
||||
}
|
||||
finally:
|
||||
cur.close()
|
||||
|
||||
|
||||
def register_plate(tenant_conn, plate, make=None, model=None, year=None,
|
||||
vin=None, customer_id=None):
|
||||
"""Register or update a plate-to-vehicle mapping.
|
||||
|
||||
Returns the plate_vehicles record id.
|
||||
"""
|
||||
norm_display = normalize_plate(plate)
|
||||
if not norm_display:
|
||||
raise ValueError('Plate is required')
|
||||
|
||||
# Format nicely: ABC-1234 or AB-123-C
|
||||
if re.match(r'^[A-Z]{3}\d{3,4}$', norm_display):
|
||||
display = norm_display[:3] + '-' + norm_display[3:]
|
||||
elif re.match(r'^[A-Z]{2}\d{3}[A-Z]$', norm_display):
|
||||
display = norm_display[:2] + '-' + norm_display[2:5] + '-' + norm_display[5:]
|
||||
else:
|
||||
display = norm_display
|
||||
|
||||
cur = tenant_conn.cursor()
|
||||
try:
|
||||
cur.execute("""
|
||||
INSERT INTO plate_vehicles (plate, make, model, year, vin, customer_id)
|
||||
VALUES (%s, %s, %s, %s, %s, %s)
|
||||
ON CONFLICT (plate) DO UPDATE SET
|
||||
make = COALESCE(EXCLUDED.make, plate_vehicles.make),
|
||||
model = COALESCE(EXCLUDED.model, plate_vehicles.model),
|
||||
year = COALESCE(EXCLUDED.year, plate_vehicles.year),
|
||||
vin = COALESCE(EXCLUDED.vin, plate_vehicles.vin),
|
||||
customer_id = COALESCE(EXCLUDED.customer_id, plate_vehicles.customer_id)
|
||||
RETURNING id
|
||||
""", (display, make, model, year, vin, customer_id))
|
||||
rec_id = cur.fetchone()[0]
|
||||
tenant_conn.commit()
|
||||
return rec_id
|
||||
except Exception:
|
||||
tenant_conn.rollback()
|
||||
raise
|
||||
finally:
|
||||
cur.close()
|
||||
Reference in New Issue
Block a user