Cambios en Post Timbres y conteo CD

This commit is contained in:
fjrodriguez
2023-09-15 09:41:33 -06:00
parent 9e6416fecb
commit 4bdb833cdb
12 changed files with 358 additions and 184 deletions

View File

@@ -15,7 +15,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.getenv("adminAS_KEY")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
DEBUG = True
ALLOWED_HOSTS = ['*']
# Application definition
@@ -133,7 +133,7 @@ if DEBUG:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'cfdi_as2',
'NAME': 'cfdi_as',
'USER': 'root',
'PASSWORD':os.getenv("BD_PASS"),
'HOST': '127.0.0.1',

216
Admin/settingsProd.py Normal file
View File

@@ -0,0 +1,216 @@
from pathlib import Path
import os
import pytz
#print time zones
# for x in pytz.all_timezones_set:
# print(x)
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv("adminAS_KEY")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
#'django.contrib.sites',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'allauth',
'allauth.account',
'allauth.socialaccount',
'widget_tweaks',
'import_export',
'Clientes',
'IMMEX',
'Sistemas',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
}
SITE_ID = 1
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'Admin.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,'Templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
# `allauth` needs this from django
'django.template.context_processors.request',
#custom context processor
'Clientes.saldo_context_proc.get_saldo',
],
},
},
]
AUTHENTICATION_BACKENDS = [
# Needed to login by username in Django admin, regardless of `allauth`
'django.contrib.auth.backends.ModelBackend',
# `allauth` specific authentication methods, such as login by e-mail
'allauth.account.auth_backends.AuthenticationBackend',
]
WSGI_APPLICATION = 'Admin.wsgi.application'
ACCOUNT_FORMS = {
'login': 'allauth.account.forms.LoginForm',
'signup': 'allauth.account.forms.SignupForm',
'add_email': 'allauth.account.forms.AddEmailForm',
'change_password': 'allauth.account.forms.ChangePasswordForm',
'set_password': 'allauth.account.forms.SetPasswordForm',
'reset_password': 'allauth.account.forms.ResetPasswordForm',
'reset_password_from_key': 'allauth.account.forms.ResetPasswordKeyForm',
'disconnect': 'allauth.socialaccount.forms.DisconnectForm',
}
LOGIN_REDIRECT_URL = '/'
ACCOUNT_LOGOUT_REDIRECT_URL = '/accounts/login/'
ACCOUNT_SIGNUP_REDIRECT_URL =LOGIN_REDIRECT_URL
ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True
#for email auth
ACCOUNT_EMAIL_REQUIRED =True
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
EMAIL_TIMEOUT = 10
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_SUBJECT_PREFIX = 'AS Timbres'
# if DEBUG :
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = 'aduanasoftpruebas@gmail.com'
EMAIL_HOST_PASSWORD = 'zsgtbxsuwyacyhqq'#os.getenv("test_pwd_email")
#EMAIL_USE_SSL=False
# else:
# EMAIL_USE_TLS = True
# EMAIL_HOST = 'secure.emailsrvr.com'
# EMAIL_PORT = 465
# EMAIL_HOST_USER = 'noreply@aduanasoft.com.mx'
# EMAIL_HOST_PASSWORD = 'N036p7y!'#os.getenv("pwd_email")
# #EMAIL_USE_SSL=True
# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
if DEBUG:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'cfdi_as2',
'USER': 'root',
'PASSWORD':os.getenv("BD_PASS"),
'HOST': '127.0.0.1',
'PORT': '',
'OPTIONS': {'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"},
},
}
else:
DATABASES={
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'Aduanasoft$cfdi_as',
'USER': 'Aduanasoft',
'PASSWORD':'Soluciones28@',
'HOST': 'Aduanasoft.mysql.pythonanywhere-services.com',
'PORT': '3306',
'OPTIONS': {'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"},
}
}
# Password validation
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/
LANGUAGE_CODE = 'es-MX'
TIME_ZONE = 'America/Chihuahua'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/
if not DEBUG:
#STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATICFILES_DIRS = [
BASE_DIR / 'static'
]
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
STATIC_URL = '/static/'
if DEBUG:
STATICFILES_DIRS = [
BASE_DIR / 'static',
]
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
STATIC_URL = '/static/'
#STATIC_ROOT = os.path.join(BASE_DIR, "static")
# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

View File

@@ -47,9 +47,14 @@ class ClientesAdmin(admin.ModelAdmin):
list_display= ['RFC','Nombre','Activo','fecha_baja']
list_filter =['RFC','Activo']
class ErroresTimbresAdmin(admin.ModelAdmin):
list_display= ['rfcc','folio', 'created_at', 'modo']
list_filter =['rfcc','folio']
search_fields = ('rfcc','folio',)
#admin.site.register(Timbres,TimbresAdmin)
admin.site.register(saldoModel)
admin.site.register(Clientes,ClientesAdmin)
admin.site.register(ErroresTimbres)
admin.site.register(ErroresTimbres, ErroresTimbresAdmin)

View File

@@ -1,7 +1,15 @@
from rest_framework import serializers
from .models import Clientes
from .models import Clientes, Timbres
class ClienteSerializer(serializers.ModelSerializer):
class Meta:
model = Clientes
fields = ('RFC', 'Nombre', 'Activo', 'fecha_baja',)
class TimbresSerializer(serializers.ModelSerializer):
class Meta:
model = Timbres
fields = ['uuid','rfcc','fecha','folio','serie','tipo','rfcp','modo']

View File

@@ -13,15 +13,17 @@ from .views import (
#API DRF
saldo_funct2,
add_timbre2,
add_timbre,
check_RFC,
check_host,
CancelaTimbre,
SendTimbresDisponibleEmail,
)
urlpatterns = [
path('', index, name='index'),
path('add_timbre2/', add_timbre2.as_view(), name='add_timbre2'),
path('add_timbre/', add_timbre2.as_view(), name='add_timbre'),
path('add_timbreV2/', add_timbre2.as_view(), name='add_timbre'),
path('cancela_timbre/', CancelaTimbre.as_view(), name='cancela_timbre'),
path('timbres_cliente/<str:RFC>/', timbres_cliente, name='timbres_cliente'),
@@ -35,4 +37,5 @@ urlpatterns = [
path('check_host/',check_host.as_view(),name='check_host'),
path('emails_cliente/',Retrive_Cliente_Email, name='Retrive_Cliente_Email'),
path('pacs/list/',PACS_Retrive_RFCS,name='PACS_Retrive_RFCS'),
path('api/contarTimbresCD/',SendTimbresDisponibleEmail.as_view(),name='contarTimbresCD'),
]

View File

@@ -4,9 +4,13 @@ import datetime
import functools
import urllib.parse
import traceback
import json
import requests
from asgiref.sync import sync_to_async
from django.conf import settings
from django.core.files.storage import FileSystemStorage
from django.core.mail import send_mail
from django.shortcuts import render,redirect
from django.contrib import messages
from django.http import HttpResponse,JsonResponse
@@ -20,7 +24,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from .custom_decorators import is_staff_access, http_basic_auth
from .models import Clientes,Timbres,saldoModel,ErroresTimbres
from Sistemas.models import BitacoraErrores
from .serailizers import ClienteSerializer
from .serailizers import ClienteSerializer,TimbresSerializer
from .forms import ClienteForm,EmailForm
from rest_framework.views import APIView
@@ -28,8 +32,6 @@ from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from rest_framework import status
from Sistemas.permissions import ItsAdminToken
#EXCEL
@@ -462,14 +464,50 @@ class add_timbre2(APIView):
obj = Timbres.objects.create(**obj)
return Response({'data':'ok'})
except Exception as e:
data_json = json.dumps(request.data)
traceback_info = f'{data_json}\n{traceback.format_exc()}'
obj = ErroresTimbres.objects.create(
uuid=uuid,
description=e,
description= f"{e} \n {traceback_info}",
rfcc=rfcc,
folio=folio,
modo=modo
)
return Response({'Error':f'{e}'})
def post(self,request):
try:
serializer = TimbresSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'data': 'ok'}, status=status.HTTP_201_CREATED)
else:
data_json = json.dumps(request.data)
traceback_info = f'{data_json}\n{traceback.format_exc()}'
obj = ErroresTimbres.objects.create(
uuid=request.data['uuid'],
description= f"{serializer.errors} \n {traceback_info}",
rfcc=request.data['rfcc'],
folio=request.data['folio'],
modo=request.data['modo']
)
return Response({'errors': serializer.errors, 'isError': True}, status=status.HTTP_200_OK)
except Exception as e:
data_json = json.dumps(request.data)
traceback_info = f'{data_json}\n{traceback.format_exc()}'
obj = ErroresTimbres.objects.create(
uuid=request.data['uuid'],
description= f"{e} \n {traceback_info}",
rfcc=request.data['rfcc'],
folio=request.data['folio'],
modo=request.data['modo']
)
return Response(
{'Error':f'{e}','isError':True},status=status.HTTP_200_OK
)
class saldo_funct2(APIView):
"""Agrega los timbres disponibles"""
@@ -547,4 +585,23 @@ class ClientesCreateView(UserPassesTestMixin,LoginRequiredMixin,CreateView):
errors = form.errors.as_text()
return JsonResponse({'errors':f'{errors}'},status=200,content_type='application/json')
else:
return response
return response
class SendTimbresDisponibleEmail(APIView):
def get(self,request):
saldoRequest = requests.get('https://app2.comercio-digital.mx/x3/saldo?usr=SCT050708AD1&pwd=0dcu2SwCv')
send_mail(
subject='Alerta Timbres Comercio',
message=f"""
La cantidad de timbres son {saldoRequest.text} a fecha {datetime.date.today()}
este correo se envio automaticamente desde server, no es necesario contestar, solo es informativo.
Saludos
atte:
FJRR
""",
from_email='aduanasoftpruebas@gmail.com',
recipient_list=['fjrodriguez@aduanasoft.com.mx']#,'mcervantes@aduanasoft.com.mx','ddominguez@aduanasoft.com.mx'],
)
return Response({'message':'Correo enviado exitosamente'})

View File

@@ -1,6 +1,6 @@
from django.contrib import admin
from . models import Sistemas_por_cliente_A24, ClientesA24, DeviceA24
# from django.contrib import admin
# from . models import Sistemas_por_cliente_A24, ClientesA24, DeviceA24
admin.site.register(Sistemas_por_cliente_A24)
admin.site.register(ClientesA24)
admin.site.register(DeviceA24)
# admin.site.register(Sistemas_por_cliente_A24)
# admin.site.register(ClientesA24)
# admin.site.register(DeviceA24)

View File

@@ -1,128 +0,0 @@
# Generated by Django 4.1.3 on 2023-05-25 13:58
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
("Sistemas", "0019_alter_device_options"),
("authtoken", "0003_tokenproxy"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name="ClientesA24",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("RFC", models.CharField(max_length=13, unique=True)),
("Nombre", models.CharField(max_length=100)),
("Activo", models.BooleanField(default=False)),
("fecha_baja", models.DateField(blank=True, null=True)),
],
options={
"ordering": ("-Activo", "RFC"),
},
),
migrations.CreateModel(
name="DeviceA24",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("deviceName", models.CharField(max_length=255)),
("deviceOS", models.CharField(max_length=255)),
("deviceIP", models.GenericIPAddressField()),
("MAC", models.CharField(blank=True, max_length=30, null=True)),
("dataBase", models.CharField(blank=True, max_length=40, null=True)),
("timestamp", models.DateTimeField(auto_now_add=True)),
(
"clienteA24",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="IMMEX.clientesa24",
),
),
(
"sistema",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="Sistemas.sistema",
),
),
(
"token",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="authtoken.token",
),
),
(
"username",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"ordering": ("username",),
},
),
migrations.CreateModel(
name="Sistemas_por_cliente_A24",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("num_licencias", models.IntegerField(default=1)),
(
"cliente",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="cliente_spc_IMMEX",
to="IMMEX.clientesa24",
),
),
(
"id_sistema",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="sistema_spc_IMMEX",
to="Sistemas.sistema",
),
),
],
options={
"ordering": ("-cliente", "id_sistema"),
"unique_together": {("id_sistema", "cliente")},
},
),
]

View File

@@ -1,20 +0,0 @@
# Generated by Django 4.1.3 on 2023-05-25 14:11
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('authtoken', '0003_tokenproxy'),
('IMMEX', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='devicea24',
name='token',
field=models.ForeignKey(blank=True, max_length=40, null=True, on_delete=django.db.models.deletion.CASCADE, to='authtoken.token'),
),
]

View File

@@ -12,6 +12,7 @@ class ClientesA24(models.Model):
fecha_baja = models.DateField(blank=True,null=True)
class Meta:
ordering = ('-Activo','RFC',)
abstract=True
def __str__(self):
return self.Nombre
@@ -24,6 +25,7 @@ class Sistemas_por_cliente_A24(models.Model):
class Meta:
ordering= ('-cliente','id_sistema')
unique_together = ('id_sistema', 'cliente')
abstract=True
class DeviceA24(models.Model):
clienteA24 = models.ForeignKey(ClientesA24,on_delete=models.CASCADE)
@@ -39,6 +41,7 @@ class DeviceA24(models.Model):
class Meta:
ordering = ('username',)
abstract=True
def generate_username(self):

View File

@@ -38,7 +38,7 @@ from django.contrib.sessions.models import Session
from django.http.response import HttpResponse
import os
import mimetypes
import traceback
from django.core.files.base import ContentFile
from .customStorage import CustomStorage
@@ -47,6 +47,9 @@ from django.contrib.auth.decorators import login_required
from django.db.models import Q
import traceback
import json
@login_required
def uploadZipViewHTML(request):
if request.method=="GET":
@@ -228,36 +231,51 @@ class GetDeviceToken(APIView):
"""Recobra el Token DRF del Device"""
authentication_classes= [TokenAuthentication]
permissions_classes=[IsAuthenticated, HasAuthorizationHeader]
def get_unique_username_token(self,client, device_name,ip_address, macAddress):
username = f"Device_{client}_{device_name}_{macAddress}"
username_ = re.sub(r'\W+', '', username)
print(username_ ,User.objects.filter(username=username_).exists())
if User.objects.filter(username=username_).exists():
user = User.objects.get(username=username_)
return user, user.device_set.filter(username=user.id).values('token').first()
#raise ValidationError(f"El Usuario ya existe {username_}")
else:
return None, None
def post(self,request):
try:
data = request.data
#sis = Sistema.objects.get(nombre_sistema=data.get('sistema'))
#ip_address = data.get('ip_address')
cli = Clientes.objects.filter(RFC=data.get('client')).first()
print('CLI: ',cli)
cli = Clientes.objects.get(RFC=data.get('client'))
device_name= data.get('device_name')
macAddress = data.get('macAddress')
database = data.get('database')
#username_ = f"Device_{cli.RFC}_{device_name}_{ip_address}_{macAddress}"
username_ = f"Device_{cli.RFC}_{device_name}_{macAddress}"
username_ = re.sub(r'\W+', '', username_)
device = Device.objects.filter(
username__username__icontains=username_,
database=database
).first()
print('device',device)
if device is not None:
token = {"token":str(device.token)}
user_device, token = self.get_unique_username_token(cli.RFC, device_name,"", macAddress)
if user_device is not None:
print("get_unique_username", token)
else:
token = cli.device_set.filter(macAddress=macAddress, device_name=device_name, database=database).values('token').first()
print('device',token)
if token is None:
token = {"token":""}
return Response(token)
except Exception as ex:
data_json = json.dumps(request.data)
traceback_info = f'{data_json}\n{traceback.format_exc()}'
BitacoraErrores.objects.create(
level=2,
message=str(ex),
traceback=traceback.format_exc(),
traceback=traceback_info,
view='Sistemas.GetDeviceToken'
)
return Response({'Error':f'{ex}','isError':True}, status=status.HTTP_200_OK)
@@ -275,7 +293,9 @@ class RegisterDeviceView(APIView):
else:
return Response({'Error':f'{serializer.errors}','isError':True}, status=status.HTTP_200_OK)
except Exception as ex:
BitacoraErrores.objects.create(level=2, message=str(ex), traceback=traceback.format_exc(),
data_json = json.dumps(request.data)
traceback_info = f'{data_json}\n{traceback.format_exc()}'
BitacoraErrores.objects.create(level=2, message=str(ex), traceback=traceback_info,
view='Sistemas.RegisterDeviceView')
return Response({'Error':f'{ex}','isError':True}, status=status.HTTP_200_OK)
@@ -296,12 +316,16 @@ class AuthenticateDeviceView(APIView):
if device_data.serializer.is_valid:
return Response(device_data.serializer.data, status=status.HTTP_200_OK)
else:
BitacoraErrores.objects.create(level=3, message=str(device_data.serializer.errors), traceback=traceback.format_exc(), view='Sistemas.AuthenticateDeviceView')
data_json = json.dumps(request.data)
traceback_info = f'{data_json}\n{traceback.format_exc()}'
BitacoraErrores.objects.create(level=3, message=str(device_data.serializer.errors), traceback=traceback_info, view='Sistemas.AuthenticateDeviceView')
return Response(
{'Error':f'{device_data.serializer.errors}','isError':True}
, status=status.HTTP_200_OK)
except Exception as ex:
BitacoraErrores.objects.create(level=2, message=str(ex), traceback=traceback.format_exc(), view='Sistemas.AuthenticateDeviceView')
data_json = json.dumps(request.data)
traceback_info = f'{data_json}\n{traceback.format_exc()}'
BitacoraErrores.objects.create(level=2, message=str(ex), traceback=traceback_info, view='Sistemas.AuthenticateDeviceView')
return Response(
{'Error':f'{ex}','isError':True}
, status=status.HTTP_200_OK
@@ -329,7 +353,9 @@ class CheckVersionView(APIView):
ver = Sistema.objects.get(nombre_sistema="CFDI")
server_version = [int(x) for x in ver.version.split(".")]
except Exception as ex:
BitacoraErrores.objects.create(level=2, message=str(ex), traceback=traceback.format_exc(), view='Sistemas.CheckVersionView')
data_json = json.dumps(request.data)
traceback_info = f'{data_json}\n{traceback.format_exc()}'
BitacoraErrores.objects.create(level=2, message=str(ex), traceback=traceback_info, view='Sistemas.CheckVersionView')
return Response({'Error':f'{ex}','isError':True})
result=False
@@ -345,7 +371,9 @@ class CheckVersionView(APIView):
return Response({'success':True, 'actualizar':result})
except Exception as ex:
BitacoraErrores.objects.create(level=2, message=str(ex), traceback=traceback.format_exc(), view='Sistemas.CheckVersionView')
data_json = json.dumps(request.data)
traceback_info = f'{data_json}\n{traceback.format_exc()}'
BitacoraErrores.objects.create(level=2, message=str(ex), traceback=traceback_info, view='Sistemas.CheckVersionView')
return Response({'Error':f'{ex}','isError':True})
@@ -365,5 +393,7 @@ class Custom_Login(APIView):
else:
return Response({'access':False})
except Exception as ex:
BitacoraErrores.objects.create(level=2, message=str(ex), traceback=traceback.format_exc(), view='Sistemas.Custom_Login')
data_json = json.dumps(request.data)
traceback_info = f'{data_json}\n{traceback.format_exc()}'
BitacoraErrores.objects.create(level=2, message=str(ex), traceback=traceback_info, view='Sistemas.Custom_Login')
return Response({'access':False})