changes from channel to web_shop

This commit is contained in:
Elvis 2023-09-01 08:29:48 -05:00
parent 9929a86c09
commit 71103f224d
13 changed files with 337 additions and 234 deletions

View File

@ -10,6 +10,8 @@ from . import rappi
from . import api_log from . import api_log
from . import ir from . import ir
from . import routes from . import routes
from . import shop
from . import web
__all__ = ['register', 'routes'] __all__ = ['register', 'routes']
@ -17,26 +19,27 @@ __all__ = ['register', 'routes']
def register(): def register():
Pool.register( Pool.register(
web.Shop,
web_channel.SaleWebChannel, web_channel.SaleWebChannel,
mercado_libre.MercadoLibre, # mercado_libre.MercadoLibre,
shopify.Shopify, # shopify.Shopify,
rappi.Rappi, # rappi.Rappi,
sale.Sale, sale.Sale,
sale.SaleForChannelStart, # sale.SaleForChannelStart,
web_channel.SynchronizeChannelOrdersStart, # web_channel.SynchronizeChannelOrdersStart,
web_channel.SynchronizeChannelOrdersDone, # web_channel.SynchronizeChannelOrdersDone,
web_channel.FinishInvoicesStart, # web_channel.FinishInvoicesStart,
party.Party, party.Party,
api_log.ApiLog, api_log.ApiLog,
ir.Cron, ir.Cron,
module='sale_web_channel', type_='model') module='sale_web_channel', type_='model')
Pool.register( Pool.register(
sale.SaleUploadInvoice, sale.SaleUploadInvoice,
sale.SaleForChannel, # sale.SaleForChannel,
web_channel.SynchronizeChannelOrders, # web_channel.SynchronizeChannelOrders,
web_channel.FinishInvoices, # web_channel.FinishInvoices,
web_channel.SynchronizeMenuWizard, # web_channel.SynchronizeMenuWizard,
module='sale_web_channel', type_='wizard') module='sale_web_channel', type_='wizard')
Pool.register( Pool.register(
sale.SaleForChannelReport, # sale.SaleForChannelReport,
module='sale_web_channel', type_='report') module='sale_web_channel', type_='report')

View File

@ -10,7 +10,8 @@ STATES = {'readonly': True}
class ApiLog(metaclass=PoolMeta): class ApiLog(metaclass=PoolMeta):
"API Log" "API Log"
__name__ = "api.log" __name__ = "api.log"
channel = fields.Many2One('sale.web_channel', 'channel', states=STATES) web_shop = fields.Many2One('web.shop', 'Web Shop', states=STATES)
# channel = fields.Many2One('sale.web_channel', 'channel', states=STATES)
# number = fields.Char('Number Doc', states=STATES) # number = fields.Char('Number Doc', states=STATES)
# order = fields.Char('Order', states=STATES) # order = fields.Char('Order', states=STATES)
# record_date = fields.Date('Record Date', states=STATES) # record_date = fields.Date('Record Date', states=STATES)
@ -25,15 +26,15 @@ class ApiLog(metaclass=PoolMeta):
super(ApiLog, cls).__setup__() super(ApiLog, cls).__setup__()
cls._order.insert(0, ('record_date', 'DESC')) cls._order.insert(0, ('record_date', 'DESC'))
@classmethod # @classmethod
def process_log_cron(cls): # def process_log_cron(cls):
logs = cls.search([ # logs = cls.search([
('status', '=', 'pending') # ('status', '=', 'pending')
], limit=10) # ], limit=10)
for log in logs: # for log in logs:
data = log.file_json.decode("utf-8") # data = log.file_json.decode("utf-8")
res = {} # res = {}
if log.channel.channel_name == 'mercadolibre': # if log.channel.channel_name == 'mercadolibre':
MercadoLibre = Pool().get('sale.web_channel.mercado_libre') # MercadoLibre = Pool().get('sale.web_channel.mercado_libre')
res = MercadoLibre.request_api(json.loads(data)) # res = MercadoLibre.request_api(json.loads(data))
cls.write([log], res) # cls.write([log], res)

View File

@ -2,10 +2,13 @@ import requests
import json import json
from datetime import datetime from datetime import datetime
from collections import namedtuple from collections import namedtuple
from rich import print
from . import endpoints_rappi
URL_AUTH_DEV = 'https://rests-integrations-dev.auth0.com/oauth/token' URL_AUTH_DEV = 'https://rests-integrations-dev.auth0.com/oauth/token'
URL_AUTH_PRODUCTION = 'https://rests-integrations.auth0.com/oauth/token' URL_AUTH_PRODUCTION = 'https://rests-integrations.auth0.com/oauth/token'
URL_ENV = 'https://microservices.dev.rappi.com/api/v2/restaurants-integrations-public-api' URL_ENV = 'https://microservices.dev.rappi.com/api/v2/restaurants-integrations-public-api'
AUDIENCE = 'https://int-public-api-v2/api'
HEADERS = { HEADERS = {
'Accept': 'application/json', 'Accept': 'application/json',
@ -29,11 +32,23 @@ channel = ChannelRappi(
class RappiAPI: class RappiAPI:
def __init__(self, channel): def __init__(self, channel):
self.api_key = channel.api_key # self.api_key = channel.api_key
# self.url_base = 'https://microservices.dev.rappi.com/api/v2/restaurants-integrations-public-api'
# self.url_auth = URL_AUTH_DEV
# self.session = requests.Session()
# self.session.headers.update({'x-authorization': self.api_key})
# self.session.headers.update({'Content-Type': 'application/json'})
# self.store_name = channel.shop.name
self.seller_id = channel.seller_id
self.app_id = channel.app_id
self.store_integration_id = channel.app_id
self.url_base = 'https://microservices.dev.rappi.com/api/v2/restaurants-integrations-public-api' self.url_base = 'https://microservices.dev.rappi.com/api/v2/restaurants-integrations-public-api'
self.url_auth = URL_AUTH_DEV self.url_auth = URL_AUTH_DEV
self.token = channel.access_token if channel.access_token else ''
self.session = requests.Session() self.session = requests.Session()
self.session.headers.update({'x-authorization': self.api_key}) self.session.headers.update({
'x-authorization': 'bearer ' + self.token
})
self.session.headers.update({'Content-Type': 'application/json'}) self.session.headers.update({'Content-Type': 'application/json'})
def _send_request(self, endpoint=None, method='GET', data: dict = None, auth: bool = False): def _send_request(self, endpoint=None, method='GET', data: dict = None, auth: bool = False):
@ -45,6 +60,7 @@ class RappiAPI:
if method == 'GET': if method == 'GET':
response = self.session.get(url, timeout=10) response = self.session.get(url, timeout=10)
elif method == 'POST': elif method == 'POST':
print(data)
response = self.session.post(url, data=json.dumps(data), timeout=10) response = self.session.post(url, data=json.dumps(data), timeout=10)
elif method == 'PUT': elif method == 'PUT':
response = self.session.put(url, data=json.dumps(data), timeout=10) response = self.session.put(url, data=json.dumps(data), timeout=10)
@ -56,8 +72,10 @@ class RappiAPI:
content_type = response.headers.get('Content-Type', '') content_type = response.headers.get('Content-Type', '')
# if response.status_code == 200: # if response.status_code == 200:
if content_type.startswith('application/json'): if content_type.startswith('application/json'):
print(response.json())
return response.json() return response.json()
else: else:
print(response.text)
return response.text return response.text
# else: # else:
# if content_type.startswith('application/json'): # if content_type.startswith('application/json'):
@ -67,11 +85,11 @@ class RappiAPI:
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
raise RuntimeError(f'Request error: {str(e)}') raise RuntimeError(f'Request error: {str(e)}')
def get_token_access(self, client_id: str, client_secret: str, audience: str): def get_token_access(self, client_id: str, client_secret: str):
data = { data = {
"client_id": client_id, "client_id": client_id,
"client_secret": client_secret, "client_secret": client_secret,
"audience": audience, "audience": AUDIENCE,
"grant_type": "client_credentials" "grant_type": "client_credentials"
} }
return self._send_request(method='POST', data=data, auth=True) return self._send_request(method='POST', data=data, auth=True)
@ -137,13 +155,13 @@ class RappiAPI:
endpoint = f'/menu/approved/{store_id}' endpoint = f'/menu/approved/{store_id}'
return self._send_request(endpoint=endpoint) return self._send_request(endpoint=endpoint)
def create_menu_store(self, store_id, items): def create_menu_store(self, items):
""" """
NOTE: If menu don't created return response message dict {message: 'reason'} NOTE: If menu don't created return response message dict {message: 'reason'}
""" """
endpoint = '/menu' endpoint = '/menu'
data = { data = {
'storeId': store_id, 'storeId': self.store_integration_id,
'items': items, 'items': items,
} }
return self._send_request(endpoint=endpoint, method='POST', data=data) return self._send_request(endpoint=endpoint, method='POST', data=data)
@ -220,12 +238,12 @@ class RappiAPI:
# Ejemplo de uso # Ejemplo de uso
print(channel.seller_id, 'this is objetc') # print(channel.seller_id, 'this is objetc')
token = { # token = {
'access_token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3Jlc3RzLWludGVncmF0aW9ucy1kZXYuYXV0aDAuY29tLyIsInN1YiI6ImlKQjlVdnhDaE5nam12WEdnUENiSXpVWWFZekNmN1BKQGNsaWVudHMiLCJhdWQiOiJodHRwczovL2ludC1wdWJsaWMtYXBpLXYyL2FwaSIsImlhdCI6MTY5MzAxNzExNCwiZXhwIjoxNjkzNjIxOTEyLCJhenAiOiJpSkI5VXZ4Q2hOZ2ptdlhHZ1BDYkl6VVlhWXpDZjdQSiIsInNjb3BlIjoidXBkYXRlOmF2YWlsYWJpbGl0eV9zdG9yZXMgdXBkYXRlOmF2YWlsYWJpbGl0eV9zdG9yZXNfaXRlbXMgdXBkYXRlOmF2YWlsYWJpbGl0eV9zdG9yZXNfaXRlbXNfcmFwcGkgcmVhZDptZW51X3RoaXJkcGFydHkgdXBkYXRlOm1lbnUgcmVhZDptZW51X3JhcHBpIHVwZGF0ZTpvcmRlcnNfcmVhZHlfZm9yX3BpY2t1cCByZWFkOm1lbnVzX3JhcHBpIHJlYWQ6Y29uZmlnIGNyZWF0ZTpjb25maWcgdXBkYXRlOmNvbmZpZyBkZWxldGU6Y29uZmlnIHJlYWQ6b3JkZXJzIHBpY2t1cDpvcmRlciByZWplY3Q6b3JkZXIgdGFrZTpvcmRlciByZWFkOnN0b3JlcyByZWFkOmFwcF9jbGllbnRzIGNyZWF0ZTphcHBfY2xpZW50cyBkZWxldGU6YXBwX2NsaWVudHMgY3JlYXRlOmFwcF9jbGllbnRzX3N0b3JlcyByZWFkOm9yZGVyX2V2ZW50cyBkZWxldGU6YXBwX2NsaWVudHNfc3RvcmVzIGNyZWF0ZTpjbGllbnRzIHJlYWQ6bWVudV9zdGF0dXNfcmFwcGkgcmVhZDphdmFpbGFiaWxpdHlfc3RvcmVzX2l0ZW1zIHJlYWQ6d2ViaG9vayBjcmVhdGU6d2ViaG9vayB1cGRhdGU6d2ViaG9vayBkZWxldGU6d2ViaG9vayB1cGRhdGU6d2ViaG9va19zZWNyZXQiLCJndHkiOiJjbGllbnQtY3JlZGVudGlhbHMifQ.Loh_g9qB3jNhOHxXPVrWgDJQAr7n9DzB6jWbPD_vUDQ', # 'access_token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3Jlc3RzLWludGVncmF0aW9ucy1kZXYuYXV0aDAuY29tLyIsInN1YiI6ImlKQjlVdnhDaE5nam12WEdnUENiSXpVWWFZekNmN1BKQGNsaWVudHMiLCJhdWQiOiJodHRwczovL2ludC1wdWJsaWMtYXBpLXYyL2FwaSIsImlhdCI6MTY5MzAxNzExNCwiZXhwIjoxNjkzNjIxOTEyLCJhenAiOiJpSkI5VXZ4Q2hOZ2ptdlhHZ1BDYkl6VVlhWXpDZjdQSiIsInNjb3BlIjoidXBkYXRlOmF2YWlsYWJpbGl0eV9zdG9yZXMgdXBkYXRlOmF2YWlsYWJpbGl0eV9zdG9yZXNfaXRlbXMgdXBkYXRlOmF2YWlsYWJpbGl0eV9zdG9yZXNfaXRlbXNfcmFwcGkgcmVhZDptZW51X3RoaXJkcGFydHkgdXBkYXRlOm1lbnUgcmVhZDptZW51X3JhcHBpIHVwZGF0ZTpvcmRlcnNfcmVhZHlfZm9yX3BpY2t1cCByZWFkOm1lbnVzX3JhcHBpIHJlYWQ6Y29uZmlnIGNyZWF0ZTpjb25maWcgdXBkYXRlOmNvbmZpZyBkZWxldGU6Y29uZmlnIHJlYWQ6b3JkZXJzIHBpY2t1cDpvcmRlciByZWplY3Q6b3JkZXIgdGFrZTpvcmRlciByZWFkOnN0b3JlcyByZWFkOmFwcF9jbGllbnRzIGNyZWF0ZTphcHBfY2xpZW50cyBkZWxldGU6YXBwX2NsaWVudHMgY3JlYXRlOmFwcF9jbGllbnRzX3N0b3JlcyByZWFkOm9yZGVyX2V2ZW50cyBkZWxldGU6YXBwX2NsaWVudHNfc3RvcmVzIGNyZWF0ZTpjbGllbnRzIHJlYWQ6bWVudV9zdGF0dXNfcmFwcGkgcmVhZDphdmFpbGFiaWxpdHlfc3RvcmVzX2l0ZW1zIHJlYWQ6d2ViaG9vayBjcmVhdGU6d2ViaG9vayB1cGRhdGU6d2ViaG9vayBkZWxldGU6d2ViaG9vayB1cGRhdGU6d2ViaG9va19zZWNyZXQiLCJndHkiOiJjbGllbnQtY3JlZGVudGlhbHMifQ.Loh_g9qB3jNhOHxXPVrWgDJQAr7n9DzB6jWbPD_vUDQ',
'scope': 'update:availability_stores update:availability_stores_items update:availability_stores_items_rappi read:menu_thirdparty update:menu read:menu_rappi update:orders_ready_for_pickup read:menus_rappi read:config create:config update:config delete:config read:orders pickup:order reject:order take:order read:stores read:app_clients create:app_clients delete:app_clients create:app_clients_stores read:order_events delete:app_clients_stores create:clients read:menu_status_rappi read:availability_stores_items read:webhook create:webhook update:webhook delete:webhook update:webhook_secret', 'expires_in': 604798, 'token_type': 'Bearer'} # 'scope': 'update:availability_stores update:availability_stores_items update:availability_stores_items_rappi read:menu_thirdparty update:menu read:menu_rappi update:orders_ready_for_pickup read:menus_rappi read:config create:config update:config delete:config read:orders pickup:order reject:order take:order read:stores read:app_clients create:app_clients delete:app_clients create:app_clients_stores read:order_events delete:app_clients_stores create:clients read:menu_status_rappi read:availability_stores_items read:webhook create:webhook update:webhook delete:webhook update:webhook_secret', 'expires_in': 604798, 'token_type': 'Bearer'}
channel = channel._replace(api_key=token['access_token'], creation_time=datetime.now()) # channel = channel._replace(api_key=token['access_token'], creation_time=datetime.now())
rappi = RappiAPI(f'Bearer {channel.api_key}', URL_ENV, URL_AUTH_DEV) # rappi = RappiAPI(f'Bearer {channel.api_key}', URL_ENV, URL_AUTH_DEV)
# get token # get token
# token = rappi.get_token_access(channel.seller_id, channel.secret_key, 'https://int-public-api-v2/api') # token = rappi.get_token_access(channel.seller_id, channel.secret_key, 'https://int-public-api-v2/api')
@ -245,9 +263,9 @@ rappi = RappiAPI(f'Bearer {channel.api_key}', URL_ENV, URL_AUTH_DEV)
# create menu # create menu
# NOTE: If menu don't created return response message json {message: 'reason'} # NOTE: If menu don't created return response message json {message: 'reason'}
items = [] # items = []
menu_created = rappi.create_menu_store(store_id=channel.app_id, items=items) # menu_created = rappi.create_menu_store(store_id=channel.app_id, items=items)
print(menu_created, 'menu created') # print(menu_created, 'menu created')
# Order options to call enpoints to manage order client # Order options to call enpoints to manage order client
# get_orders, take_order or reject_order, ready_order # get_orders, take_order or reject_order, ready_order

234
rappi.py
View File

@ -9,14 +9,10 @@ from trytond.transaction import Transaction
from urllib.parse import urlencode from urllib.parse import urlencode
from datetime import datetime, date from datetime import datetime, date
from trytond.pyson import Eval from trytond.pyson import Eval
from .web_channel import SaleWebChannel from .web import Shop
from urllib.parse import urlencode
import json import json
import requests import requests
import base64 from . import endpoints_rappi
import hmac
import hashlib
from pprint import pprint
URL_DEV = 'https://rests-integrations-dev.auth0.com/oauth/token' URL_DEV = 'https://rests-integrations-dev.auth0.com/oauth/token'
@ -28,18 +24,18 @@ HEADERS = {
} }
class Rappi(SaleWebChannel): class Rappi(Shop):
'Rappi' 'Rappi'
__name__ = 'sale.web_channel.rappi' __name__ = 'web.shop.rappi'
@classmethod @classmethod
def __setup__(cls): def __setup__(cls):
super(Rappi, cls).__setup__() super(Rappi, cls).__setup__()
cls._buttons.update({ # cls._buttons.update({
'generate_token_access': { # 'generate_token_access': {
'invisible': Eval('state') != 'active', # 'invisible': Eval('state') != 'active',
}, # },
}) # })
def _get_context(self): def _get_context(self):
print(self) print(self)
@ -52,7 +48,6 @@ class Rappi(SaleWebChannel):
} }
def generate_token_access(self): def generate_token_access(self):
print('holis')
token_request = { token_request = {
"client_id": self.app_id, "client_id": self.app_id,
"client_secret": self.secret_key, "client_secret": self.secret_key,
@ -68,172 +63,53 @@ class Rappi(SaleWebChannel):
self.save() self.save()
def synchronize_menu_rappi(self): def synchronize_menu_rappi(self):
menu = { items = []
# 'storeId': self.store_id, for category in self.admin_category.childs:
'storeId': '900152684', items.extend(self.get_products(category))
'items': [] print(items)
} api_rappi = endpoints_rappi.RappiAPI(self)
menu_append = menu['items'].append api_rappi.create_menu_store(items)
pool = Pool()
Category = pool.get('product.template-product.category')
# price_list = self.shop.price_list.lines
# for index, line in enumerate(price_list):
# if line.category and not line.product:
# self.create_menu_by_global_category(line, Category)
# elif not line.category and line.product:
# # elif line.category and line.product:
# # pass
# # else:
# # raise UserWarning('BAD CONFIGURATION IN LINE ID ', line.id)
# # menu_append({ def get_products(self, category):
# # 'category': { children_list = []
# # 'id': line.product.categories[0].id, for child in category.childs:
# # 'maxQty': 0, children_list.append({
# # 'minQty': 0,
# # 'name': line.product.categories[0].name,
# # },
# # 'children': [],
# # 'name': line.product.categories[0].name,
# # 'description': line.product.description if line.product.description else 'no esta disponible',
# # 'imageUrl': line.product.images[0].image_url if line.product.images else '',
# # 'price': float(line.price_w_tax_computed),
# # 'sku': line.product.code,
# # 'sortingPosition': 0,
# # "type": "PRODUCT",
# # # 'maxLimit': '',
# # })
# # if hasattr(line.product, 'products_mix') and line.product.products_mix:
# # for mix in line.product.products_mix:
# # menu['items'][0]['children'].append({
# # "category": {
# # "id": mix.categories[0].id,
# # "maxQty": 1,
# # "minQty": 0,
# # "name": mix.categories[0].name,
# # "sortingPosition": 0
# # },
# # "name": mix.name,
# # "description": mix.description,
# # "price": float(mix.list_price),
# # "sku": mix.id,
# # "maxLimit": 1,
# # "sortingPosition": 1,
# # "type": "PRODUCT"
# # })
menu = {
"storeId": "900152684",
"items": [
{
"name": "Grilled Chicken Burger",
"description": "Grilled chicken burger description",
"price": 14000,
"sku": "10",
"sortingPosition": 0,
"type": "PRODUCT",
"category": { "category": {
"id": "2090019638", "id": category.id,
"maxQty": 0, "maxQty": 0,
"minQty": 0, "minQty": 0,
"name": "Burgers", "name": category.name,
"sortingPosition": 0 "sortingPosition": 0,
"children": []
}, },
"children": [
{
"category": {
"id": "211",
"maxQty": 1,
"minQty": 0,
"name": "Do you want to add?",
"sortingPosition": 0
},
"name": "French Fries",
"description": "crunchy french fries",
"price": 5000,
"sku": "1",
"maxLimit": 1,
"sortingPosition": 1,
"type": "TOPPING"
},
{
"category": {
"id": "211",
"maxQty": 1,
"minQty": 0,
"name": "Do you want to add?",
"sortingPosition": 0
},
"name": "Potato Wedges",
"price": 7000,
"sku": "2",
"maxLimit": 1,
"sortingPosition": 1,
"type": "TOPPING"
}
]
},
{
"name": "Hawaiian Pizza",
"description": "hawaiian pizza description",
"price": 18000,
"sku": "11",
"sortingPosition": 1,
"type": "PRODUCT",
"category": {
"id": "2090019639",
"maxQty": 0,
"minQty": 0,
"name": "Pizzas",
"sortingPosition": 1
},
"children": []
}
]
}
menu_json = json.dumps(menu)
URL = 'https://microservices.dev.rappi.com/api/v2/restaurants-integrations-public-api/menu'
token = self.access_token
headers = {
'Content-Type': 'application/json',
'x-authorization': 'bearer ' + token,
}
pprint(menu_json)
response = requests.request("POST", URL, headers=headers, data=menu_json)
pprint(response.text.encode('utf8'))
def create_menu_by_global_category(self, line, Category):
pass
# category = Category.search_read([('categories', 'in', '')])
# dict_menu = {
# 'category': {
# 'id': line.product.category.id,
# 'maxQty': 0,
# 'minQty': 0,
# 'name': line.product.category.name,
# },
# dict_menu['children'].append(dict_child)
# 'children': [],
# 'name': line.product.categories[0].name,
# 'description': line.product.description if line.product.description else 'no esta disponible',
# 'imageUrl': line.product.images[0].image_url if line.product.images else '',
# 'price': float(line.price_w_tax_computed),
# 'sku': line.product.code,
# 'sortingPosition': 0,
# "type": "PRODUCT",
# # 'maxLimit': '',
# })
def create_menu_by_local_category(self, line):
if line.product.categories[0]:
category = {
'id': line.product.categories[0].id,
'maxQty': 0,
'minQty': 0,
'name': line.product.categories[0].name,
'children': []
}
category['children'].append({
}) })
children_list[0]['children'] = self.get_products(child)
if not category.childs:
for product in category.products:
children_list.append({
"name": product.name,
"description": product.description if product.description else 'not available',
"price": float(product.list_price),
"sku": product.code,
"sortingPosition": 0,
"type": "PRODUCT",
"category": {
"id": category.id,
"maxQty": 0,
"minQty": 0,
"name": category.name,
"sortingPosition": 0
}
})
return children_list
# rappi_api = endpoints_rappi.RappiAPI(self)
# rappi_api.create_menu_store(self.shop.store_id, items)
# menu_json = json.dumps(menu)
# URL = 'https://microservices.dev.rappi.com/api/v2/restaurants-integrations-public-api/menu'
# token = self.access_token
# headers = {
# 'Content-Type': 'application/json',
# 'x-authorization': 'bearer ' + token,
# }
# response = requests.request("POST", URL, headers=headers, data=menu_json)

8
shop.py Normal file
View File

@ -0,0 +1,8 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.pool import Pool, PoolMeta
from trytond.model import fields
class SaleShop(metaclass=PoolMeta):
__name__ = 'sale.shop'

12
shop.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="sale_shop_view_form">
<field name="model">sale.shop</field>
<field name="inherit" ref="sale_shop.sale_shop_view_form"/>
<field name="name">shop_form</field>
</record>
</data>
</tryton>

View File

@ -1,12 +1,13 @@
[tryton] [tryton]
version=6.0.3 version=6.0.3
depends: depends:
ir
web_shop
sale_pos sale_pos
product_reference product_reference
log log
ir
xml: xml:
web_channel.xml web.xml
sale.xml sale.xml
party.xml party.xml
api_log.xml api_log.xml

View File

@ -18,6 +18,10 @@ The COPYRIGHT file at the top level of this repository contains the full copyrig
<field name="secret_key"/> <field name="secret_key"/>
<label name="seller_id"/> <label name="seller_id"/>
<field name="seller_id"/> <field name="seller_id"/>
<label name="admin_category"/>
<field name="admin_category"/>
<label name="price_list"/>
<field name="price_list"/>
<label name="redirect_uri"/> <label name="redirect_uri"/>
<field name="redirect_uri"/> <field name="redirect_uri"/>
<label name="authorization_code"/> <label name="authorization_code"/>
@ -55,20 +59,9 @@ The COPYRIGHT file at the top level of this repository contains the full copyrig
<label name="template_notification"/> <label name="template_notification"/>
<field name="template_notification"/> <field name="template_notification"/>
<group col="6" colspan="4" id="state_buttons"> <group col="6" colspan="4" id="state_buttons">
<label name="state"/>
<field name="state"/>
<group col="5" colspan="2" id="buttons"> <group col="5" colspan="2" id="buttons">
<button name="draft" string="Draft"
icon="tryton-clear"/>
<button name="active" string="Active"
icon="tryton-ok"/>
<button name="finished" string="Finished"
icon="tryton-forward"/>
<button name="refresh_token_b" string="Refresh Token"
icon="tryton-ok"/>
<button name="generate_token_access" string="generate_token_access" <button name="generate_token_access" string="generate_token_access"
icon="tryton-ok"/> icon="tryton-ok"/>
</group> </group>
</group> </group>
</form> </form>

12
view/shop_form.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- This file is part sale_shop module for Tryton.
The COPYRIGHT file at the top level of this repository contains the full copyright notices and license terms. -->
<data>
<xpath
expr="/form/notebook/page[@id='salesmans']"
position="after">
<page string="Web Channels" id="web_channels">
<field name="sale_web_channels"/>
</page>
</xpath>
</data>

31
view/web_shop_form.xml Normal file
View File

@ -0,0 +1,31 @@
<?xml version="1.0"?>
<!-- This file is part sale_shop module for Tryton.
The COPYRIGHT file at the top level of this repository contains the full copyright notices and license terms. -->
<data>
<xpath
expr="/form/notebook/page[@id='products']"
position="after">
<page string="Connection Data" id="connection_data">
<label name="secret_key"/>
<field name="secret_key"/>
<label name="app_id"/>
<field name="app_id"/>
<label name="redirect_uri"/>
<field name="redirect_uri"/>
<label name="authorization_code"/>
<field name="authorization_code"/>
<label name="access_token"/>
<field name="access_token"/>
<label name="status_token"/>
<field name="status_token"/>
<label name="refresh_token"/>
<field name="refresh_token"/>
<label name="seller_id"/>
<field name="seller_id"/>
<group col="6" colspan="4" id="state_buttons">
<button name="generate_token_access" string="generate_token_access"
icon="tryton-ok"/>
</group>
</page>
</xpath>
</data>

131
web.py Normal file
View File

@ -0,0 +1,131 @@
from trytond.model import fields, ModelView
from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval, And, Or
from datetime import datetime, timedelta
from . import endpoints_rappi
TYPES = [
('', ''),
('mercadolibre', 'Mercado Libre'),
('melhous', 'Melhous'),
('shopify', 'Shopify'),
('rappi', 'Rappi'),
]
TYPE_INVOICE = [
('', ''),
('C', 'Computador'),
('P', 'POS'),
('M', 'Manual'),
('1', 'Venta Electronica'),
('2', 'Exportacion'),
('3', 'Factura por Contingencia Facturador'),
('4', 'Factura por Contingencia DIAN'),
('91', 'Nota Crédito Eléctronica'),
('92', 'Nota Débito Eléctronica'),
]
class Shop(metaclass=PoolMeta):
__name__ = 'web.shop'
secret_key = fields.Char('Secret Key')
app_id = fields.Char('Application ID')
redirect_uri = fields.Char('Redirect URI', states={
'invisible': (Eval('type') != 'mercadolibre'),
})
authorization_code = fields.Char('Authorization Code', states={
'invisible': (Eval('type') != 'mercadolibre'),
})
access_token = fields.Char('Access Token', states={
'invisible': And(
Eval('type') != 'mercadolibre',
Eval('type') != 'rappi'),
})
token_create_date = fields.DateTime('Token Create Date', states={
'invisible': (Eval('type') != 'mercadolibre'),
})
status_token = fields.Function(fields.Selection([
('expired', 'Expired'),
('active', 'Active'),
], 'Status Token', readonly=True, states={
'invisible': (Eval('type') != 'mercadolibre'),
}), 'get_status_token')
refresh_token = fields.Char('Refresh Token', states={
'invisible': (Eval('type') != 'mercadolibre'),
})
# state = fields.Selection([
# ('draft', 'Draft'),
# ('active', 'Active'),
# ('finished', 'Finished'),
# ], 'State', select=True, readonly=True)
freight_product = fields.Many2One('product.product', 'Freight Product')
# states=STATES)
bonus_product = fields.Many2One('product.product', 'Bonus Product', states={
'invisible': (Eval('type') != 'mercadolibre'),
})
tip_product = fields.Many2One('product.product', 'Tip Product', states={
'invisible': (Eval('type') != 'shopify'),
})
generic_product = fields.Many2One('product.product', 'Generic Product')
# states={
# 'invisible': (Eval('type') != 'shopify'),
# })
report = fields.Many2One('ir.action.report', 'Report')
invoice_type = fields.Selection(TYPE_INVOICE, 'Type Invoice')
seller_id = fields.Char('Seller ID', states={
'invisible': ~Eval('type').in_(['mercadolibre', 'rappi']),
})
@classmethod
def __setup__(cls):
super(Shop, cls).__setup__()
cls.type.selection = TYPES
cls._buttons.update({
'generate_token_access': {
'invisible': ~Eval('type').in_(['mercadolibre', 'rappi']),
},
'synchronize_products': {
'invisible': ~Eval('type').in_(['mercadolibre', 'rappi']),
},
})
def get_status_token(self, name):
if self.token_create_date:
now = datetime.now()
res = (now - self.token_create_date).total_seconds()
if res >= 21600:
return 'expired'
else:
return 'active'
else:
return 'expired'
@classmethod
@ModelView.button
def generate_token_access(cls, records):
if records:
for record in records:
if record.type == 'mercadolibre':
MercadoLibre = Pool().get('sale.web_channel.mercado_libre')
channels = MercadoLibre.search([
('state', '=', 'active'),
('type', '=', 'mercadolibre'),
])
if record.type == 'rappi':
record.generate_token_access_rappi()
def generate_token_access_rappi(self):
api_rappi = endpoints_rappi.RappiAPI(self)
result = api_rappi.get_token_access(self.seller_id, self.secret_key)
self.access_token = result['access_token']
self.save()
def synchronize_products(self):
if self.type == 'rappi':
items = []
for category in self.admin_category.childs:
items.extend(self.get_products(category))
print(items)
api_rappi = endpoints_rappi.RappiAPI(self)
api_rappi.create_menu_store(items)

12
web.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="shop_view_form">
<field name="model">web.shop</field>
<field name="inherit" ref="web_shop.shop_view_form"/>
<field name="name">web_shop_form</field>
</record>
</data>
</tryton>

View File

@ -110,9 +110,14 @@ class SaleWebChannel(Workflow, ModelSQL, ModelView):
invoice_type = fields.Selection(TYPE_INVOICE, 'Type Invoice', invoice_type = fields.Selection(TYPE_INVOICE, 'Type Invoice',
states=STATES) states=STATES)
seller_id = fields.Char('Seller ID', states={ seller_id = fields.Char('Seller ID', states={
'invisible': (Eval('channel_name') != 'mercadolibre'), 'invisible': ~Eval('channel_name').in_(['mercadolibre', 'rappi']),
'readonly': (Eval('state') != 'draft') 'readonly': (Eval('state') != 'draft')
}) })
admin_category = fields.Many2One('product.category', 'Admin Category',
domain=[
('accounting', '=', False),
])
price_list = fields.Many2One('product.price_list', 'Pricelist', ondelete="RESTRICT")
template_notification = fields.Many2One( template_notification = fields.Many2One(
'email.template', 'Template Notification') 'email.template', 'Template Notification')
api_key = fields.Char('Api Key', states={ api_key = fields.Char('Api Key', states={
@ -242,7 +247,7 @@ class SaleWebChannel(Workflow, ModelSQL, ModelView):
report = Report.execute([record.id], {'id': record.id}) report = Report.execute([record.id], {'id': record.id})
ext, data, filename, file_name = report ext, data, filename, file_name = report
return data return data
class SynchronizeChannelOrdersStart(ModelView): class SynchronizeChannelOrdersStart(ModelView):
'Synchronize Channel orders Start' 'Synchronize Channel orders Start'