423 lines
16 KiB
Python
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
|