from trytond.model import fields, ModelView from trytond.pool import PoolMeta, Pool from trytond.pyson import Eval, And from datetime import datetime, timedelta from trytond.wizard import (Wizard, StateTransition, StateView, Button) from .exceptions import ErrorSynchronizingSale from trytond.i18n import gettext from trytond.transaction import Transaction from . mercado_libre import MercadoLibre from . shopify import Shopify from . import 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'), ] # TODO FOR EXECUTE in database """ alter database rename table sale_web_channel to web_shop RENAME COLUMN channel_name by type RENAME COLUMN creation_time by token_create_date """ 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'), }) shop = fields.Many2One('sale.shop', 'Shop', domain=[ ('company', '=', Eval('company')) ]) 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']), }) password_api = fields.Char('Api Password', states={ 'invisible': (Eval('type') != 'shopify'), }) host_name = fields.Char('Host Name', states={ 'invisible': And( Eval('type') != 'shopify', Eval('type') != 'rappi'), }) api_key = fields.Char('Api Key', states={ 'invisible': (Eval('type') != 'shopify'), }) price_list = fields.Many2One('product.price_list', 'Price list', ondelete="RESTRICT") @classmethod def __setup__(cls): super(Shop, cls).__setup__() cls.type.selection = TYPES cls._buttons.update({ 'generate_token_access': { 'invisible': ~Eval('type').in_(['mercadolibre', 'rappi']), }, 'send_products': { 'invisible': ~Eval('type').in_(['mercadolibre', 'rappi']), }, 'get_orders': { '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('web.shop') mercadolibre_rec = MercadoLibre(record) # record.access_token, refresh_token, client_id, client_secret = mercadolibre_rec._validate_token() record.access_token = mercadolibre_rec._validate_token() record.save() if record.type == 'rappi': rappi_rec = rappi.Rappi(record) record.access_token = rappi_rec.get_token_access()['access_token'] record.save() @classmethod @ModelView.button def send_products(cls, records): for record in records: if record.type == 'rappi': rappi_rec = rappi.Rappi(record) rappi_rec.synchronize_menu_rappi(record.products, record.categories) @classmethod @ModelView.button def get_orders(cls, records): for record in records: if record.type == 'rappi': rappi_rec = rappi.Rappi(record) rappi_rec.create_sales() class SynchronizeChannelOrdersStart(ModelView): 'Synchronize Channel orders Start' __name__ = 'sale_web_channel.synchronize_orders.start' company = fields.Many2One('company.company', 'Company', required=True) shop = fields.Many2One('sale.shop', 'Shop', required=True, domain=[ ('company', '=', Eval('company')) ]) channel = fields.Many2One('web.shop', 'Channel', required=True, domain=[ ('shop', '=', Eval('shop')) ]) operation = fields.Selection([ ('', ''), ('orders_for_day', 'Orders for day'), ('especific_order', 'Especific Orders') ], 'Operation', required=True) type = fields.Selection([ ('', ''), ('shopify', 'Shopify'), ('mercadolibre', 'Mercado libre') ], 'Name Channel', required=True) order_id = fields.Char('Order ID', states={ 'invisible': (Eval('operation') != 'especific_order'), 'required': (Eval('operation') == 'especific_order'), }) date = fields.Date('Date', states={ 'invisible': (Eval('operation') != 'orders_for_day'), 'required': (Eval('operation') == 'orders_for_day'), }) date_end = fields.Date('Finish Date', states={ 'invisible': (Eval('operation') != 'orders_for_day') | (Eval('name_channel') != 'shopify'), 'required': (Eval('operation') == 'orders_for_day') & (Eval('name_channel') != 'mercadolibre'), }) @staticmethod def default_company(): return Transaction().context.get('company') @fields.depends('channel') def on_change_channel(self): self.type = self.channel.type if self.channel else None class SynchronizeChannelOrders(Wizard): 'Synchronize Channel Orders' __name__ = 'sale_web_channel.synchronize_orders' start = StateView('sale_web_channel.synchronize_orders.start', 'sale_web_channel.synchronize_orders_view_form', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Create', 'accept', 'tryton-ok', default=True), ]) accept = StateTransition() done = StateView('sale_web_channel.synchronize_orders.done', 'sale_web_channel.synchronize_orders_done', [ Button('Done', 'end', 'tryton-ok', default=True), ]) @classmethod def __setup__(cls): super(SynchronizeChannelOrders, cls).__setup__() def transition_accept(self): pool = Pool() Sale = pool.get('sale.sale') channel = self.start.channel operation = self.start.operation if channel.type == 'mercadolibre': mercado_libre = MercadoLibre(channel) if operation == 'especific_order': order_id = self.start.order_id result = mercado_libre._validate_number_id(order_id, channel.access_token) if not result: raise ErrorSynchronizingSale( gettext('sale_web_channel.msg_error_synchronizing_sale', s=result)) sale_created = result if sale_created: generic_code = self.start.channel.generic_product.code if self.start.channel.generic_product else None product_generic = [ line.product.code for line in sale_created.lines if line.product.code == generic_code and generic_code] if not sale_created.pack_id and \ sale_created.comment in ['shipped', 'delivered'] and \ len(product_generic) < 1: Sale.process([sale_created]) else: date_from = str(self.start.date) + 'T00:00:00.000-00:00' date_to = str(self.start.date + timedelta(days=1) ) + 'T00:00:00.000-00:00' URI = 'https://api.mercadolibre.com/orders/search?seller=%s&order.date_created.from=%s&order.date_created.to=%s&access_token=%s&order.status=paid' % ( channel.seller_id, date_from, date_to, channel.access_token) result = MercadoLibre.get_response(URI).json() for res in result['results']: sales = Sale.search([('reference', 'like', '%' + str(res['id']) + '%' )]) shipment_status = '' if not sales: sale_created = channel.validate_sale(res) shipment_status = sale_created.comment sales = [sale_created] else: sfm_id = res['shipping']['id'] shipment_ = channel.get_shipment_api(sfm_id) shipment_status = shipment_['status'] if res.get('pack_id') or not shipment_status: continue if shipment_status in ['shipped', 'delivered']: for sale in sales: if not sale.invoices: Sale.process([sale]) if channel.type == 'shopify': shopify = Shopify(channel) # channel, = Shopify.search([('shop', '=', self.start.shop.id), ]) if operation == 'especific_order': order_id = self.start.order_id URI = 'https://%s:%s@%s/admin/api/2020-10/orders.json?status=any&ids=%s' % ( channel.api_key, channel.password_api, channel.host_name, order_id) result = shopify.get_response(URI).json() if not result: raise ErrorSynchronizingSale( gettext('sale_web_channel.msg_error_synchronizing_sale', s=result)) sale_created = shopify._create_sale(result['orders'][0]) if sale_created and \ sale_created.description.count('fulfilled'): Sale.process([sale_created]) else: date_from = str(self.start.date) + 'T00:00:00.000-00:00' date_to = str(self.start.date_end) + 'T00:00:00.000-00:00' URI = 'https://%s:%s@%s/admin/api/2020-10/orders.json?status=any&created_at_min=%s&created_at_max=%s' % ( channel.api_key, channel.password_api, channel.host_name, date_from, date_to) result = Shopify.get_response(URI).json() print('quantity orders----> ', len(result['orders'])) for res in result['orders']: sales = Sale.search([('reference', '=', str(res['id']))]) fulfillment_status = '' if not sales: sale_created = channel._create_sale(res) fulfillment_status = sale_created.description sales = [sale_created] else: fulfillment_status = res['fulfillment_status'] if not fulfillment_status: continue if fulfillment_status.count('fulfilled'): for sale in sales: if not sale.invoices: Sale.process([sale]) return 'done' class SynchronizeChannelOrdersDone(ModelView): 'Synchronize Channel Orders Done' __name__ = 'sale_web_channel.synchronize_orders.done' result = fields.Char('Result', readonly=True) @staticmethod def default_result(): return 'successful synchronization'