trytonpsk-sale_web_channel/shopify.py

423 lines
16 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 itertools import chain
from trytond.transaction import Transaction
import requests
from urllib.parse import urlencode
from datetime import datetime, date
from .web_channel import SaleWebChannel
import json
import base64
import hmac
import hashlib
HEADERS = {
'Accept': 'application/json',
'Content-type': 'application/json'
}
class Shopify(SaleWebChannel):
'Shopify'
__name__ = 'sale.web_channel.shopify'
@classmethod
def __setup__(cls):
super(Shopify, cls).__setup__()
def _get_context(self):
print(self)
return {
'company': self.company.id,
'user': self.user.id,
'shops': [self.shop.id],
'shop': self.shop.id,
'language': 'es'
}
# def _get_user(self):
# User = Pool().get('res.user')
# user, = User.search(
# ('login', '=', 'shopify')
# )
# return user
def _create_product(self, codes, line={}):
pool = Pool()
Product = pool.get('product.product')
Template = pool.get('product.template')
description = line['variant_title']
list_price = round(float(line['price'])/1.19, 2)
sale_price_w_tax = round(Decimal(line['price']), 2)
create_template = {
'name': line['title'],
'list_price': list_price,
'sale_price_w_tax': sale_price_w_tax,
'type': 'goods',
'salable': True,
'purchasable': True,
'purchase_uom': 1,
'sale_uom': 1,
'default_uom': 1,
'account_category': 7,
'code': code,
}
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['email']
shipment_address, phone, city = '', '', None
if customer.get('default_address'):
default_address = customer['default_address']
shipment_address = default_address['address1']
phone = default_address['phone'] or '000'
city_name = default_address['city']
if city_name:
cities = City.search([
('name', '=', city_name)
])
if cities:
city = cities[0]
customer_name = customer['first_name'] + ' ' + customer['last_name']
create_customer = {
'id_reference': str(customer['id']),
'name': customer_name.upper(),
'type_document': '13',
'id_number': default_address['company'],
'addresses': [('create', [{
'street': shipment_address,
}])],
'contact_mechanisms': [
('create', [
{'type': 'phone', 'value': phone.replace(" ","")},
{'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 _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_sale(self, sale_):
print('ingresa a crear venta')
_pool = Pool()
Sale = _pool.get('sale.sale')
sales = Sale.search([
('reference', '=', str(sale_['id']))
])
if sales:
return False
SaleLine = _pool.get('sale.line')
Party = _pool.get('party.party')
User = _pool.get('res.user')
if sale_.get('customer'):
customer = sale_['customer']
dom_party = [('id_number', '=', str(customer['default_address']['company']))]
parties = Party.search(dom_party)
if parties:
party = parties[0]
else:
party = self._create_party(customer)
create_lines = []
ctx = self._get_context()
user_ = User(ctx['user'])
date_created = sale_['created_at'].split('T')
year, month, day = date_created[0].split('-')
sale_date = date(int(year), int(month), int(day))
with Transaction().set_user(ctx['user']):
comment = ''
try:
comment = 'GUIA DE ENVIO NO. ' + sale_['fulfillments'][0].get('tracking_number', '')\
if sale_['fulfillment_status'] == 'fulfilled' and sale_['fulfillments'] else ''
except:
pass
sale, = Sale.create([{
'payment_term': 1,
'party': party.id,
'sale_date': sale_date,
'comment': comment,
'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 SHOPIFY ' + sale_['name'],
'channel': self.id,
'invoice_type': self.invoice_type,
'pack_id': ''
}])
with Transaction().set_context(ctx):
create_lines = self.get_sale_lines(sale_, sale)
SaleLine.create(create_lines)
sale.untaxed_amount_cache = sale.untaxed_amount
if sale_['cancel_reason'] is not None:
sale.state = 'cancelled'
else:
Sale.quote([sale])
sale.save()
if sale_['fulfillment_status'] == 'fulfilled':
self.order_fulfilled(sale_, [sale])
print('*******************', sale.state)
return sale
def get_sale_lines(self, sale_, sale, products_refund=[]):
pool = Pool()
Product = pool.get('product.product')
sale_items = sale_['line_items']
products_refund = {}
if sale_.get('refunds'):
for refunds in sale_['refunds']:
for line_items in refunds['refund_line_items']:
sku_code = line_items['line_item']['sku']
quantity = line_items['quantity']
if sku_code.count('+') > 0:
codes = sku_code.split('+')
for code in codes:
if code not in products_refund.keys():
products_refund[code] = quantity
else:
products_refund[code] += quantity
else:
if sku_code not in products_refund.keys():
products_refund[sku_code] = quantity
else:
products_refund[sku_code] += quantity
create_lines = []
for line in sale_items:
if line['title'] == 'Tip':
if self.tip_product:
product = self.tip_product
total_tip = line['price']
create_lines.append({
'sale': sale.id,
'type': 'line',
'unit': product.default_uom.id,
'quantity': 1,
'unit_price': Decimal(total_tip),
'unit_price_full': Decimal(total_tip),
'discount': 0,
'product': product.id,
'description': 'BONIFICACION',
})
else:
sku_code = line['sku']
if sku_code.count('+') > 0:
codes = sku_code.split('+')
line['price'] = round(Decimal(line['price']) / 2, 2)
discount = round(sum([Decimal(n['amount']) for n in line['discount_allocations']]) / 2, 2)
else:
codes = [sku_code]
discount = round(sum([Decimal(n['amount']) for n in line['discount_allocations']]))
products = Product.search([
('code', 'in', codes),
('active', '=', True)
])
description = ''
if not products:
products = self._create_product(codes, line)
for product in products:
quantity = line['quantity']
if products_refund and product.code in products_refund.keys():
if products_refund[product.code] < quantity:
quantity -= products_refund[product.code]
else:
continue
Tax = pool.get('account.tax')
un_price = Tax.reverse_compute((Decimal(line['price']) - Decimal(discount)),
product.customer_taxes_used)
create_lines.append({
'sale': sale.id,
'type': 'line',
'unit': product.default_uom.id,
'quantity': quantity,
'base_price': round(Decimal(un_price), 3),
'unit_price': round(Decimal(un_price), 3),
'unit_price_full': round(Decimal(line['price']),2),
# 'discount': 0,
'product': product.id,
'taxes': [('add', product.customer_taxes_used)],
'description': description,
})
if self.freight_product and len(sale_['shipping_lines']) > 0:
product = self.freight_product
shipping_amount = sale_['shipping_lines'][0]['price']
create_lines.append({
'sale': sale.id,
'type': 'line',
'unit': product.default_uom.id,
'discount': 0,
'quantity': 1,
'unit_price': Decimal(shipping_amount),
'unit_price_full': Decimal(shipping_amount),
'product': product.id,
'description': 'FLETE',
})
return create_lines
def order_updated(self, data, sales):
sale = sales[0]
pool = Pool()
SaleLine = pool.get('sale.line')
Sale = pool.get('sale.sale')
lines_to_remove = [line for line in sale.lines]
Sale.draft([sale])
SaleLine.delete(lines_to_remove)
create_lines = self.get_sale_lines(data, sale)
SaleLine.create(create_lines)
Sale.quote([sale])
if data['fulfillment_status'] == 'fulfilled':
self.order_fulfilled(data, [sale])
return sale
def order_cancelled(self, data, sales):
pool = Pool()
Sale = pool.get('sale.sale')
sale = sales[0]
Sale.draft([sale])
Sale.cancel([sale])
return sale
def order_fulfilled(self, data, sales):
pool = Pool()
Sale = pool.get('sale.sale')
sale = sales[0]
response = False
if sale.invoices:
return response
elif sale.state == 'quotation':
Sale.confirm([sale])
if len(sales) > 1:
# channel.upload_note(sale, 'Error, al generar factura orden duplicada')
return response
if data.get('fulfillment_status') == 'fulfilled':
track_number = ''
try:
track_number = str(data['fulfillments'][0]['tracking_number'])
except:
pass
Sale.write([sale], {
'description': sale.description + ' - ' + data['fulfillment_status'],
'comment': 'GUIA DE ENVIO NO. ' + track_number,
'tracking_number': track_number
})
return sale
@classmethod
def get_response(cls, URI, params={}):
response = requests.get(URI, headers=HEADERS, params=urlencode(params))
return response
@classmethod
def verify_webhook_shopify(cls, data, hmac_header, secret):
digest = hmac.new(secret.encode('utf-8'), data, hashlib.sha256).digest()
genHmac = base64.b64encode(digest)
return hmac.compare_digest(genHmac, hmac_header.encode('utf-8'))
@classmethod
def request_api(cls, request):
response = {'status': 'error', 'msg': 'Fail in process !!!'}
data = request.get_data()
action_topic = request.headers.get('X-Shopify-Topic')
domain_name = request.headers.get('X-Shopify-Shop-Domain')
channels = cls.search([('host_name', '=', domain_name), ])
channel = channels[0]
hmac_header = request.headers.get('X-Shopify-Hmac-SHA256')
verified = cls.verify_webhook_shopify(data, hmac_header, channel.secret_key)
if verified:
req = data.decode("utf-8")
data = json.loads(req)
if action_topic.count('create'):
res = channel._create_sale(data)
if res:
response = {'status': 'ok', 'msg': 'Successfull create sale !!!'}
else:
order_id = data.get('id')
Sale = Pool().get('sale.sale')
sales = Sale.search([
('reference', '=', str(order_id))
])
if not sales:
return response
if action_topic.count('fulfilled'):
res = channel.order_fulfilled(data, sales)
if res:
response = {'status': 'ok', 'msg': 'Successfull process sale' + res.number+' in state' + res.state + '!!!' }
elif action_topic.count('paid'):
pass
elif action_topic.count('updated'):
res = channel.order_updated(data, sales)
if res:
response = {'status': 'ok', 'msg': 'Successfull update sale ' + res.number+' in state' + res.state + '!!!' }
elif action_topic.count('cancelled'):
res = channel.order_cancelled(data, sales)
if res:
response = {'status': 'ok', 'msg': 'Successfull cancel sale ' + res.number+' in state' + res.state + '!!!' }
return response