trytonpsk-sale_web_channel/mercado_libre.py

577 lines
23 KiB
Python

# -*- coding: UTF-8 -*-
# This file is part electronic_mail_template module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
from decimal import Decimal
from trytond.pool import Pool
from trytond.transaction import Transaction
import requests
from urllib.parse import urlencode
from datetime import datetime, date
from .web_channel import SaleWebChannel
import json
from .exceptions import NotProductFoundError, NotProductFound
from trytond.i18n import gettext
from trytond.exceptions import UserError
HEADERS = {
'Accept': 'application/json',
'Content-type': 'application/json'
}
class MercadoLibre(SaleWebChannel):
'MercadoLibre'
__name__ = 'sale.web_channel.mercado_libre'
@classmethod
def __setup__(cls):
super(MercadoLibre, cls).__setup__()
def _get_context(self):
user_ = self._get_user()
return {
'company': user_.company.id,
'user': user_.id,
'shops': [user_.shop.id],
'shop': user_.shop.id,
'language': 'es'
}
def _get_user(self):
User = Pool().get('res.user')
user, = User.search([
('login', '=', 'mercado.libre')
])
return user
def _create_product(self, codes, line={}):
item = line['item']
pool = Pool()
Product = pool.get('product.product')
Template = pool.get('product.template')
description = ''
if item.get('variation_attributes'):
var = item['variation_attributes'][0]
if var['id'] and var['name']:
description = var['id'] + ' ' + var['name']
create_template = {
'name': item['title'],
'list_price': line['unit_price'],
'sale_price_w_tax': line['full_unit_price'],
'type': 'goods',
'salable': True,
'purchasable': True,
'purchase_uom': 1,
'sale_uom': 1,
'default_uom': 1,
'account_category': 7,
}
template, = Template.create([create_template])
create_product = []
for code in codes:
create_product.append({
'code': code,
'description': description,
'template': template.id
})
return Product.create(create_product)
def _create_party(self, customer):
_pool = Pool()
City = _pool.get('party.city_code')
Party = _pool.get('party.party')
PartyObligationTax = _pool.get('party.obligation_tax')
email = customer.get('email', '@')
shipment_address, phone, city = '', '', None
if customer.get('receiver_address'):
receiver_address = customer['receiver_address']
shipment_address = receiver_address['address_line']
phone = receiver_address['receiver_phone'] or '000'
city_name = receiver_address['city']['name']
if city_name:
cities = City.search([
('name', '=', city_name)
])
if cities:
city = cities[0]
if customer['billing_info']['doc_type'] == 'NIT':
type_document = '31'
customer_name = customer['billing_info']['business_name']
type_person = 'persona_juridica'
else:
customer_name = customer['billing_info']['first_name'] + ' ' + customer['billing_info']['last_name']
type_document = '13'
type_person = 'persona_natural'
create_customer = {
'id_reference': str(customer['id']),
'name': customer_name.upper(),
'type_document': type_document,
'type_person': type_person,
'id_number': customer['billing_info']['doc_number'],
'addresses': [('create', [{
'street': shipment_address,
}])],
'contact_mechanisms': [
('create', [
{'type': 'phone', 'value': phone},
{'type': 'email', 'value': email},
])
]
}
if not city:
city = City(149)
create_customer['addresses'][0][1][0]['department_code'] = city.department.id
create_customer['addresses'][0][1][0]['city_code'] = city.id
party, = Party.create([create_customer])
PartyObligationTax.create([{
'party': party.id,
'obligation_fiscal': 6,
}])
return party
def get_shipment_api(self, shipment_id):
if not shipment_id:
return None
URI = 'https://api.mercadolibre.com/shipments/%s?access_token=%s' % (
shipment_id, self.access_token)
res = self.get_response(URI)
return res.json()
def get_billing_info_api(self, order_id):
if not order_id:
return None
URI = 'https://api.mercadolibre.com/orders/%s/billing_info?access_token=%s' % (
order_id, self.access_token)
res = self.get_response(URI)
return res.json()
def _return_sale(self, sale):
pool = Pool()
Sale = pool.get('sale.sale')
Date = pool.get('ir.date')
ctx = self._get_context()
dev_sales = []
sales = [sale]
if sale.pack_id:
sales = Sale.search([
('pack_id', '=', sale.pack_id)
])
with Transaction().set_context(ctx):
return_sales = Sale.copy(sales)
for return_sale, sale in zip(return_sales, sales):
return_sale.origin = sale
return_sale.reference = sale.reference
return_sale.state = 'draft'
return_sale.sale_date = Date.today()
if sale.invoice_type == '1':
return_sale.invoice_type = '91'
for line in return_sale.lines:
if line.type == 'line':
line.quantity *= -1
line.save()
if return_sale.untaxed_amount_cache:
return_sale.untaxed_amount_cache *= -1
if return_sale.tax_amount_cache:
return_sale.tax_amount_cache *= -1
if return_sale.total_amount_cache:
return_sale.total_amount_cache *= -1
return_sale.save()
Sale.quote([return_sale])
Sale.write([return_sale], {'state': 'confirmed'})
dev_sales.append(return_sale)
return dev_sales
def create_lines_sale(self, sale, sale_, freight=False):
_pool = Pool()
SaleLine = _pool.get('sale.line')
Product = _pool.get('product.product')
create_lines = []
sale_items = sale_['order_items']
ctx = self._get_context()
sfm_id = sale_['shipping']['id']
# reference = sale.reference.split(',')
# reference_order = str(sale_['id'])
# if reference_order not in reference:
# sale.referece = sale.reference + ',' + reference_order
# sale.save()
with Transaction().set_context(ctx):
for line in sale_items:
item = line['item']
sku_code = item['seller_sku']
if sku_code:
generic = False
if sku_code.count('+') > 0:
codes = sku_code.split('+')
line['unit_price'] = Decimal(
round((line['unit_price'] / 2), 2))
else:
codes = [sku_code]
products = Product.search([
('code', 'in', codes),
('active', '=', True)
])
description = ''
if not products:
products = self._create_product(codes, line)
else:
if not self.generic_product:
raise NotProductFoundError(
gettext('sale_web_channel.msg_product_generic_not_found'))
products = [self.generic_product]
generic = True
description = ''
raise NotProductFound(
gettext('sale_web_channel.msg_product_not_found', s=self.generic_product.rec_name))
for product in products:
Tax = _pool.get('account.tax')
un_price = Tax.reverse_compute(line['unit_price'],
product.customer_taxes_used)
create_lines.append({
'sale': sale.id,
'type': 'line',
'unit': product.default_uom.id,
'quantity': line['quantity'],
'unit_price': round(Decimal(un_price), 3),
'unit_price_full': Decimal(line['unit_price']),
'product': product.id,
'taxes': [('add', product.customer_taxes_used)],
'description': description,
})
if freight and self.freight_product:
product = self.freight_product
URI = 'https://api.mercadolibre.com/shipments/%s/costs?access_token=%s' % (
sfm_id, self.access_token)
result = self.get_response(URI).json()
shipping_amount = result['receiver']['cost']
create_lines.append({
'sale': sale.id,
'type': 'line',
'unit': product.default_uom.id,
'quantity': 1,
'unit_price': Decimal(shipping_amount),
'unit_price_full': Decimal(shipping_amount),
'product': product.id,
'description': 'FLETE',
})
SaleLine.create(create_lines)
return generic
def validate_sale(self, sale_):
if sale_.get('pack_id'):
# if not sale_.get('shipping'):
# return
# shipment_id = sale_['shipping']['id']
# URI = 'https://api.mercadolibre.com/shipments/%s/items?access_token=%s' % (
# shipment_id, self.access_token)
# res = self.get_response(URI)
# shipment_items = res.json()
# if len(shipment_items) > 1:
URI = 'https://api.mercadolibre.com/packs/%s?access_token=%s' % (
sale_.get('pack_id'), self.access_token)
response = self.get_response(URI)
response = response.json()
if response.get('orders'):
ids = ''
for item in response['orders']:
order_id = str(item['id'])
if order_id != str(sale_['id']):
URI2 = 'https://api.mercadolibre.com/orders/%s?access_token=%s' % (
order_id, self.access_token)
order = self.get_response(URI2)
sale_order = order.json()
ids += ',' + order_id
sale_['order_items'].extend(sale_order['order_items'])
sale_['id'] = str(sale_['id']) + ids
return self._create_sale(sale_)
else:
return self._create_sale(sale_)
else:
return self._create_sale(sale_)
def _create_sale(self, sale_):
_pool = Pool()
Sale = _pool.get('sale.sale')
reference = '%' + str(sale_['id']).split(',')[0] + '%'
dom = [
('reference', 'like', reference)
]
sales = Sale.search(dom)
if sales:
if len(sales) == 1 and sale_['status'] == 'cancelled':
sale = sales[0]
if not sale.invoices:
self.cancel_sales([sale], sale.pack_id)
else:
dev_sales = self._return_sale(sale)
self._finish_sale(dev_sales[0], type='return')
return True
return False
Party = _pool.get('party.party')
User = _pool.get('res.user')
sfm_id = sale_['shipping']['id']
shipment_ = self.get_shipment_api(sfm_id)
if sale_.get('buyer'):
sale_id = str(sale_['id']).split(',')[0]
billing_info = self.get_billing_info_api(sale_id)
customer = sale_['buyer']
dom_party = [('id_reference', '=', str(customer['id']))]
if billing_info and billing_info['billing_info'].get('doc_number'):
dom_party = [('id_number', '=', str(
billing_info['billing_info']['doc_number']))]
parties = Party.search(dom_party)
if parties:
party = parties[0]
else:
customer['receiver_address'] = shipment_['receiver_address']
customer['billing_info'] = billing_info['billing_info']
for dic in customer['billing_info']['additional_info']:
customer['billing_info'][list(dic.values())[0].lower()
] = list(dic.values())[1]
try:
customer['billing_info']['additional_info'].pop()
except Exception as e:
msg = 'sin informacion de tercero' + e
raise UserError(
gettext('sale_web_channel.msg_billing_info', msg=msg))
party = self._create_party(customer)
ctx = self._get_context()
user_ = User(ctx['user'])
date_created = sale_['date_created'].split('T')
year, month, day = date_created[0].split('-')
sale_date = date(int(year), int(month), int(day))
with Transaction().set_user(ctx['user']):
sale, = Sale.create([{
'payment_term': 1,
'party': party.id,
'sale_date': sale_date,
'comment': shipment_['status'],
'state': 'draft',
'company': ctx['company'],
'currency': user_.company.currency.id,
'shop': user_.shop.id,
'reference': str(sale_['id']),
'invoice_address': Party.address_get(party, type='invoice'),
'shipment_address': Party.address_get(party, type='delivery'),
'description': 'VENTA WEB ',
'channel': self.id,
'invoice_type': self.invoice_type,
'pack_id': str(sale_['pack_id']) if sale_['pack_id'] else '',
}])
generic = self.create_lines_sale(
sale, sale_, freight=True)
sale.untaxed_amount_cache = sale.untaxed_amount
if sale_['status'] == 'cancelled':
sale.state = 'cancelled'
else:
Sale.quote([sale])
if not generic:
sale.state = 'confirmed'
sale.save()
# if shipment_['status'] in ['shipped', 'delivered']:
# self._finish_sale(sale)
return sale
def cancel_sales(self, sales, pack_id=None):
Sale = Pool().get('sale.sale')
if pack_id:
sales = Sale.search([('pack_id', '=', pack_id)])
for sale in sales:
sale.state = 'cancelled'
sale.save()
def _get_shipment_amount(self, order_id, shipment={}):
URI = 'https://api.mercadolibre.com/orders/%s?access_token=%s' % (
order_id, self.access_token)
res = self.get_response(URI)
sale_ = res.json()
shipping_amount = 0
if sale_.get('payments'):
shipping_amount = sale_['payments'][0]['shipping_cost']
return shipping_amount
def get_access_token(self, refresh_token, client_id, client_secret):
params = {'grant_type': 'refresh_token',
'client_id': client_id,
'client_secret': client_secret,
'refresh_token': refresh_token
}
uri = 'https://api.mercadolibre.com/oauth/token'
response = requests.post(uri, params=urlencode(params),
headers=HEADERS, data=params)
res = response.json()
return res['access_token'], res['refresh_token']
@classmethod
def get_response(cls, URI, params={}):
response = requests.get(URI, headers=HEADERS, params=urlencode(params))
return response
def _validate_token(self):
access_token = self.access_token
refresh_token = self.refresh_token
client_id = self.app_id
client_secret = self.secret_key
if self.status_token != 'active':
access_token, refresh_token = self.get_access_token(
refresh_token, client_id, client_secret
)
self.write([self], {'access_token': access_token,
'refresh_token': refresh_token,
'creation_time': datetime.now()
})
return access_token, refresh_token, client_id, client_secret
@classmethod
def _get_channel(cls):
channels = cls.search([
('state', '=', 'active'),
('channel_name', '=', 'mercadolibre'),
])
if channels:
return channels[0]
else:
return None
@classmethod
def request_api(cls, data):
channel = cls._get_channel()
response = {'status': 'error', 'msg_response': 'Fail in process !!!'}
if not channel:
return response
access_token, refresh_token, client_id, client_secret = channel._validate_token()
if data.get('resource') and data['resource'].count('orders'):
order_id = str(data['resource'].replace('/orders/', ''))
URI = 'https://api.mercadolibre.com/orders/%s?access_token=%s' % (
order_id, access_token)
result = cls.get_response(URI).json()
res = channel.validate_sale(result)
if res:
response = {
'status': 'ok',
'msg_response': 'Successfull process !!!',
'order': order_id
}
else:
response = {
'status': 'ok',
'msg_response': 'Sale processed before !!!',
'order': order_id
}
elif data.get('resource') and data['resource'].count('shipments'):
shipment_id = str(data['resource'].replace('/shipments/', ''))
shipment_ = channel.get_shipment_api(shipment_id)
order_id = shipment_['order_id']
Sale = Pool().get('sale.sale')
sales = Sale.search([
('reference', 'ilike', '%' + str(order_id) + '%')
])
response.update({'order': order_id})
if not sales:
return response
sale = sales[0]
if sale.invoices:
return response
if len(sales) > 1:
channel.upload_note(
sale, 'Error, al generar factura orden duplicada')
return response
if shipment_.get('tracking_number'):
Sale.write([sale], {
'description': 'GUIA DE ENVIO NO. ' + shipment_['tracking_number'],
'tracking_number': shipment_['tracking_number']
})
if shipment_['status'] in ['shipped', 'delivered']:
try:
channel._finish_sale(sale)
except:
channel.upload_note(sale, 'Error al finalizar factura')
response = {'status': 'ok',
'msg_response': 'Successfull process !!!',
'order': order_id}
else:
return {'status': 'none', 'msg_response': 'Don`t process this topic'}
return response
def _finish_sale(self, sale, type='invoice'):
ctx = self._get_context()
pool = Pool()
Sale = pool.get('sale.sale')
Invoice = pool.get('account.invoice')
Date = pool.get('ir.date')
with Transaction().set_context(ctx):
Sale.process([sale])
if not sale.invoices:
return
invoice = sale.invoices[0]
invoice.invoice_date = Date.today()
if type == 'return':
sale_origin = sale.origin
if sale_origin.invoices:
inv_origin = sale_origin.invoices[0]
invoice.credit_note_concept = '2'
invoice.original_invoice = inv_origin.id
invoice.save()
Invoice.validate_invoice([invoice])
if invoice.invoice_type not in ('C', 'P', 'M'):
try:
invoice.submit([invoice])
if not invoice.cufe:
return
except:
self.upload_note(sale, 'Error de envio DIAN')
try:
Invoice.post([invoice])
self.upload_note(sale, 'Factura generada')
self.upload_invoice(sale)
except:
pass
def upload_invoice(self, sale):
if not sale.reference or not sale.invoices:
return
invoice = sale.invoices[0]
pack_id = sale.reference
if sale.pack_id:
pack_id = sale.pack_id
URI = 'https://api.mercadolibre.com/packs/%s/fiscal_documents?access_token=%s' % (
pack_id, self.access_token)
if sale.uploaded_invoice:
response = requests.delete(URI)
report = self.render_report(invoice)
file = {"fiscal_document": report}
response = requests.post(URI, files=file)
print(response.status_code)
message = 'Error al subir factura'
if response.status_code in [200, 201, 202]:
res = response.json()
upload_ids = 'Upload ids: ' + ', '.join(list(res['ids']))
sale.write([sale], {
'uploaded_invoice': True,
'document_invoice': upload_ids,
})
message = 'Factura Cargada exitosamente'
self.upload_note(sale, message)
def upload_note(self, sale, message):
URI = 'https://api.mercadolibre.com/orders/%s/notes?access_token=%s' % (
sale.reference, self.access_token)
params = {"note": message}
request = json.dumps(params)
response = requests.post(URI, headers=HEADERS, data=request)
if not response.status_code in [200, 201, 202]:
self.send_mail_notification(
'error al crear nota en orden ' + sale.reference)