129 lines
3.7 KiB
Python
129 lines
3.7 KiB
Python
from decimal import Decimal, InvalidOperation
|
|
import re
|
|
|
|
from django.utils import timezone
|
|
|
|
|
|
def phone_has_valid_digits(phone):
|
|
digits = re.sub(r'\D+', '', (phone or '').strip())
|
|
return 7 <= len(digits) <= 15
|
|
|
|
|
|
def first_address_line(address):
|
|
trimmed = (address or '').strip()
|
|
if not trimmed:
|
|
return ''
|
|
return trimmed.splitlines()[0].strip()
|
|
|
|
|
|
def derive_location_label(location_label='', address=''):
|
|
label = (location_label or '').strip()
|
|
if label:
|
|
return label[:255]
|
|
return first_address_line(address)[:255]
|
|
|
|
|
|
def parse_decimal_value(raw_value, *, field_label, minimum=None, maximum=None):
|
|
value = (raw_value or '').strip()
|
|
if not value:
|
|
return None
|
|
|
|
try:
|
|
parsed = Decimal(value)
|
|
except (InvalidOperation, TypeError):
|
|
raise ValueError(f'Invalid {field_label}.')
|
|
|
|
if minimum is not None and parsed < minimum:
|
|
raise ValueError(f'{field_label.capitalize()} must be at least {minimum}.')
|
|
if maximum is not None and parsed > maximum:
|
|
raise ValueError(f'{field_label.capitalize()} must be no more than {maximum}.')
|
|
return parsed
|
|
|
|
|
|
def stringify_decimal(value):
|
|
if value in {None, ''}:
|
|
return ''
|
|
return str(value)
|
|
|
|
|
|
def build_delivery_payload(
|
|
*,
|
|
full_name='',
|
|
phone='',
|
|
address='',
|
|
location_label='',
|
|
delivery_notes='',
|
|
latitude=None,
|
|
longitude=None,
|
|
location_accuracy_m=None,
|
|
save_as_default=True,
|
|
**extra,
|
|
):
|
|
payload = {
|
|
'full_name': (full_name or '').strip(),
|
|
'phone': (phone or '').strip(),
|
|
'address': (address or '').strip(),
|
|
'location_label': derive_location_label(location_label, address),
|
|
'delivery_notes': (delivery_notes or '').strip(),
|
|
'latitude': stringify_decimal(latitude),
|
|
'longitude': stringify_decimal(longitude),
|
|
'location_accuracy_m': stringify_decimal(location_accuracy_m),
|
|
'save_as_default': bool(save_as_default),
|
|
}
|
|
payload.update(extra)
|
|
return payload
|
|
|
|
|
|
def profile_delivery_changes(*, phone, address, location_label='', latitude=None, longitude=None, location_accuracy_m=None):
|
|
return {
|
|
'phone': (phone or '').strip(),
|
|
'default_address': (address or '').strip(),
|
|
'location_label': derive_location_label(location_label, address),
|
|
'latitude': latitude,
|
|
'longitude': longitude,
|
|
'location_accuracy_m': location_accuracy_m,
|
|
'location_updated_at': timezone.now() if latitude is not None and longitude is not None else None,
|
|
}
|
|
|
|
|
|
def apply_profile_delivery_fields(profile, **kwargs):
|
|
changes = profile_delivery_changes(**kwargs)
|
|
for field, value in changes.items():
|
|
setattr(profile, field, value)
|
|
return profile
|
|
|
|
|
|
def save_profile_delivery_defaults(profile, **kwargs):
|
|
if profile is None:
|
|
return []
|
|
|
|
changes = profile_delivery_changes(**kwargs)
|
|
update_fields = []
|
|
|
|
for field, value in changes.items():
|
|
if getattr(profile, field) != value:
|
|
setattr(profile, field, value)
|
|
update_fields.append(field)
|
|
|
|
if update_fields:
|
|
profile.save(update_fields=update_fields)
|
|
|
|
return update_fields
|
|
|
|
|
|
def delivery_payload_from_profile(profile, *, full_name='', **extra):
|
|
if profile is None:
|
|
return build_delivery_payload(full_name=full_name, **extra)
|
|
|
|
return build_delivery_payload(
|
|
full_name=full_name,
|
|
phone=profile.phone,
|
|
address=profile.formatted_delivery_address,
|
|
location_label=profile.short_location,
|
|
latitude=profile.latitude,
|
|
longitude=profile.longitude,
|
|
location_accuracy_m=profile.location_accuracy_m,
|
|
save_as_default=True,
|
|
**extra,
|
|
)
|