def sync_records(config, state, stream): write_schema( stream.tap_stream_id, stream.schema.to_dict(), stream.key_properties, ) client = Client(config['subdomain'], config['api_key']) model_name = STREAM_MODEL_MAP[stream.tap_stream_id] model = client.model(model_name) domain = get_sync_domain(state, stream, model_name) sort_order = [ ('write_date', 'asc'), ('create_date', 'asc'), ('id', 'asc'), ] # Get all fields defined in schema now fields = list(stream.schema.properties.keys()) # Add create and write date to keep track of state fields.extend(['id', 'create_date', 'write_date']) for record in model.search_read_all(domain, sort_order, fields): transform(record) write_record(stream.tap_stream_id, record, time_extracted=utils.now()) state = write_bookmark(state, stream.tap_stream_id, 'last_updated_at', record['write_date'] or record['create_date']) state = write_bookmark(state, stream.tap_stream_id, 'last_record_id', record['id']) write_state(state)
def init_app(self, app): '''Initalizes the application with the extension. :param app: The Flask application object. ''' self.client = Client( app.config['FULFIL_SUBDOMAIN'], app.config['FULFIL_API_KEY'], ) app.jinja_env.filters['client_url'] = client_url
def fulfil_client(self): key = 'fulfil:user:%s:context' % self.token context = redis_store.get(key) if context: client = Client(self.subdomain, auth=BearerAuth(self.token), context=json.loads(context)) else: client = Client(self.subdomain, auth=BearerAuth(self.token)) redis_store.set(key, json.dumps(client.context), 300) # TTL 5min return client
def init_app(self, app): '''Initalizes the application with the extension. :param app: The Flask application object. ''' offline_access_token = app.config.get('FULFIL_OFFLINE_ACCESS_TOKEN') if offline_access_token: self.client = Client(app.config['FULFIL_SUBDOMAIN'], auth=BearerAuth(offline_access_token)) else: self.client = Client( app.config['FULFIL_SUBDOMAIN'], app.config['FULFIL_API_KEY'], ) app.jinja_env.filters['client_url'] = client_url
def init_app(self, app): '''Initalizes the application with the extension. :param app: The Flask application object. ''' offline_access_token = app.config.get('FULFIL_OFFLINE_ACCESS_TOKEN') if offline_access_token: self.client = Client( app.config['FULFIL_SUBDOMAIN'], auth=BearerAuth(offline_access_token) ) else: self.client = Client( app.config['FULFIL_SUBDOMAIN'], app.config['FULFIL_API_KEY'], ) app.jinja_env.filters['client_url'] = client_url
class Fulfil(object): """ To get started you will wrap your application's app object something like this:: app = Flask(__name__) fulfil = Fulfil(app) Configuration:: FULFIL_SUBDOMAIN: the subdomain of your Fulfil account. FULFIL_API_KEY: The API_KEY to access Fulfil services. :param app: The Flask application object. Defaults to None. """ client = None def __init__(self, app=None): if app is not None: self.init_app(app) def init_app(self, app): '''Initalizes the application with the extension. :param app: The Flask application object. ''' offline_access_token = app.config.get('FULFIL_OFFLINE_ACCESS_TOKEN') if offline_access_token: self.client = Client( app.config['FULFIL_SUBDOMAIN'], auth=BearerAuth(offline_access_token), retry_on_rate_limit=True, ) else: self.client = Client( app.config['FULFIL_SUBDOMAIN'], app.config['FULFIL_API_KEY'], retry_on_rate_limit=True, ) app.jinja_env.filters['client_url'] = client_url def model(self, name): return self.client.model(name) def record(self, model_name, record_id): return self.client.record(model_name, record_id)
class TestFulfilClient(unittest.TestCase): def setUp(self): try: self.client = Client('fulfil_demo', os.environ['FULFIL_API_KEY']) except KeyError: self.fail('No FULFIL_API_KEY in environment') def tearDown(self): pass def test_000_connection(self): Model = self.client.model('ir.model') self.assertTrue(len(Model.search([])) > 0) def test_010_connection(self): Model = self.client.model('ir.model') with self.assertRaises(ServerError): Model.search([], context=1)
class Fulfil(object): """ To get started you will wrap your application's app object something like this:: app = Flask(__name__) fulfil = Fulfil(app) Configuration:: FULFIL_SUBDOMAIN: the subdomain of your Fulfil account. FULFIL_API_KEY: The API_KEY to access Fulfil services. :param app: The Flask application object. Defaults to None. """ client = None def __init__(self, app=None): if app is not None: self.init_app(app) def init_app(self, app): '''Initalizes the application with the extension. :param app: The Flask application object. ''' offline_access_token = app.config.get('FULFIL_OFFLINE_ACCESS_TOKEN') if offline_access_token: self.client = Client( app.config['FULFIL_SUBDOMAIN'], auth=BearerAuth(offline_access_token) ) else: self.client = Client( app.config['FULFIL_SUBDOMAIN'], app.config['FULFIL_API_KEY'], ) app.jinja_env.filters['client_url'] = client_url def model(self, name): return self.client.model(name) def record(self, model_name, record_id): return self.client.record(model_name, record_id)
def get_fulfil(): subdomain = Config.FULFIL_SUBDOMAIN offline_access_token = Config.FULFIL_OFFLINE_ACCESS_TOKEN try: return Client( subdomain, auth=BearerAuth(offline_access_token) ) except ClientError, e: if e.code == 401: # unauthorized flask.abort(flask.redirect(flask.url_for('user.logout'))) raise
class TestFulfilClient(unittest.TestCase): def setUp(self): try: self.client = Client('fulfil_demo', os.environ['FULFIL_API_KEY']) except KeyError: self.fail('No FULFIL_API_KEY in environment') def tearDown(self): pass def test_000_connection(self): Model = self.client.model('ir.model') self.assertTrue(len(Model.search([])) > 0)
def get_fulfil(): if 'fulfil' not in flask.session: return session_data = flask.session['fulfil'] try: return Client( flask.session['subdomain'], auth=BearerAuth( session_data['oauth_token']['access_token'] ) ) except ClientError as e: if e.code == 401: # unauthorized # TODO: Use refresh token if possible flask.abort(flask.redirect(flask.url_for('user.logout'))) raise
def get_fulfil(): subdomain = Config.FULFIL_SUBDOMAIN access_token = Config.FULFIL_OFFLINE_ACCESS_TOKEN if 'FULFIL_ACCESS_TOKEN' in flask.session: # Use current login token access_token = flask.session['FULFIL_ACCESS_TOKEN'] if access_token is None: flask.abort(403) try: return Client( subdomain, auth=BearerAuth(access_token) ) except ClientError, e: if e.code == 401: # unauthorized flask.abort(flask.redirect(flask.url_for('user.logout'))) raise
def client(): return Client('fulfil_demo', os.environ['FULFIL_API_KEY'])
#!/usr/bin/env python # -*- coding: utf-8 -*- from fulfil_client import Client client = Client('<subdomain>', '<api_key>') # ============= # Creating Sale # ============= # Sale requires customer(contact) and address id. Contact = client.model('party.party') Sale = client.model('sale.sale') # Get the contact first contacts = Contact.find([('name', 'ilike', '%Jon%')]) contact, = Contact.get(contacts[0]['id']) sale, = Sale.create([{ 'party': contact['id'], 'shipment_address': contact['addresses'][0], 'invoice_address': contact['addresses'][0], }]) # =========================== # Adding items(line) to Sale # =========================== Product = client.model('product.product') Line = client.model('sale.line')
import json import os from datetime import date, timedelta, datetime import requests from fulfil_client import Client from jinja2 import Template from chalicelib import FULFIL_API_URL from chalicelib.easypsot_tracking import get_n_days_old_orders from .common import dates_with_passed_some_work_days, listDictsToHTMLTable from chalicelib.email import send_email client = Client(os.environ.get('FULFIL_API_DOMAIN', 'aurate-sandbox'), os.environ.get('FULFIL_API_KEY', '')) LATE_ORDER_TEXT = { 'late': """ <p>First off, thank you so much for getting your gold with us. Your support means the world.</p> <p>Your order has unfortunately missed its estimated "ship by" date. Our deepest apologies here -- we know you’ve got places to be, people to see, and new looks to rock (even if it’s just virtually). Your gold is currently in good hands and should be ready shortly, but for an exact ETA our team at [email protected] is here for you.</p> <p>Since we always strive for 100% perfection and don’t like being late, ever, please use the code IOU10 for 10% off your next order.</p> <p>Thanks so much for your patience. We promise we’re doing everything we can to make sure your gold is worth the wait.</p> <p>Warmest</p> <p>X</p> <p>The A-Team</p> """, 'mto': """ <p>First off, thank you so much for getting your gold with us. Your support means the world.</p> <p>Your order has unfortunately missed its estimated "ship by" date. Our deepest apologies here -- we know you’ve got places to be, people to see, and new looks to rock (even if it’s just virtually). Your gold just needs a little extra attention; we expect to ship it out within the next 2-10 business days. If this causes any questions or concerns, please reach out to us at [email protected] and we will help in any way we can.</p>
""" Translate fulfil requests to curl. Need to have the following installed pip install curlify blinker """ import os import curlify from fulfil_client import Client from fulfil_client.signals import response_received, signals_available fulfil = Client(os.environ['FULFIL_SUBDOMAIN'], os.environ['FULFIL_API_KEY']) print("Signal Available?:", signals_available) Product = fulfil.model('product.product') products = Product.find([]) @response_received.connect def curlify_response(response): print('=' * 80) print(curlify.to_curl(response.request)) print('=' * 80) print(response.content) print('=' * 80) print Product.get_next_available_date(products[0]['id'], 1, 4, True)
#!/usr/bin/env python # -*- coding: utf-8 -*- from fulfil_client import Client client = Client('<subdomain>', '<api_key>') # ================= # Creating Contacts # ================= Contact = client.model('party.party') contact, = Contact.create([{'name': 'Jon Doe'}]) # You can create multiple contacts too ;-) contacts = Contact.create([ { 'name': 'Jon Doe' }, { 'name': 'Matt Bower' }, { 'name': 'Joe Blow' } ]) # ================ # Creating Address # ================ # You need a contact id to create a address Address = client.model('party.address')
from fulfil_client import Client # Initializing Api client = Client('indiraactive', '4b7b1a442e8a44e69e3c5d8ad8a64386') # Initializing Fulfil's Product model Product = client.model('product.product') # Getting all the Product ids products = Product.search([()]) # Making a write call on list of products to update "hs_code" # Processing purchase orders in batches of 100 for i in range(0, len(products), 100): Product.write(products[i:i + 100], {'hs_code': '3926.20'})
def test_403(): "Connect with invalid creds and get ClientError" with pytest.raises(ClientError): Client('demo', 'xxxx')
def setUp(self): try: self.client = Client('fulfil_demo', os.environ['FULFIL_API_KEY']) except KeyError: self.fail('No FULFIL_API_KEY in environment')
def oauth_client(): return Client('demo', auth=BearerAuth(os.environ['FULFIL_OAUTH_TOKEN']))
#!/usr/bin/env python # -*- coding: utf-8 -*- """ This is a complete example where you have to push an order to Fulfil.IO. The steps are: 1. Fetch inventory for the products that have been sold 2. Create new customer, address 3. Process the order. """ from datetime import date from decimal import Decimal from fulfil_client import Client client = Client('<subdomain>', '<api_key>') def get_warehouses(): """ Return the warehouses in the system """ StockLocation = client.model('stock.location') return StockLocation.find( [('type', '=', 'warehouse')], # filter just warehouses fields=['code', 'name'] # Get the code and name fields ) def get_product_inventory(product_id, warehouse_ids): """ Return the product inventory in each location. The returned response
#!/usr/bin/env python # -*- coding: utf-8 -*- from decimal import Decimal from fulfil_client import Client client = Client('<subdomain>', '<api_key>') # ========================== # Creating Product Template # ========================== Template = client.model('product.template') iphone, = Template.create([{ 'name': 'iPhone', 'account_category': True, }]) # ================= # Creating Products # ================= Product = client.model('product.product') iphone6, = Product.create([{ 'template': iphone['id'], 'variant_name': 'iPhone 6', 'code': 'IPHONE-6', 'list_price': Decimal('699'), 'cost_price': Decimal('599'),
from chalicelib.decorators import try_except subdomain = os.environ.get('FULFIL_API_DOMAIN', 'aurate-sandbox') def get_fulfil_model_url(param): FULFIL_API_URL = f'https://{subdomain}.fulfil.io/api/v2' return f'{FULFIL_API_URL}/model/{param}' headers = { 'X-API-KEY': os.environ.get('FULFIL_API_KEY'), 'Content-Type': 'application/json' } client = Client(subdomain, os.environ.get('FULFIL_API_KEY', '')) a = { "topic": "return", "trigger": "return.created", "id": "6042537", "state": "open", "created_at": "2020-10-29T16:07:31+00:00", "total": "0.13", "order_id":
def test_raises_client_error(): with pytest.raises(ClientError): Client('demo', 'wrong-api-key')
def create_fullfill_order(item): item['service'] = 'ring reshaping' subdomain = 'aurate' # subdomain = 'aurate-sandbox' token = 'ee41ebf87f4a4fd29696f8b5db6b8cfc' # token = '43cf9ddb7acc4ac69586b8f1081d65ab' client = Client(subdomain, token) headers = {'X-API-KEY': token, 'Content-Type': 'application/json'} def get_fulfil_model_url(param): FULFIL_API_URL = f'https://{subdomain}.fulfil.io/api/v2' return f'{FULFIL_API_URL}/model/{param}' errors = [] Model = client.model('sale.sale') sale = Model.search_read_all( domain=[["AND", ["reference", "=", item['order_name']]]], order=None, fields=['id', 'lines'], # batch_size=5, ) DT = str(int(item['DT'])) sale = sale.__next__() order_id = sale['id'] Model = client.model('sale.line') f_lines = Model.search_read_all( domain=[["AND", ["id", "in", sale['lines']]]], order=None, fields=['product', 'product.code', 'quantity'], ) f_lines = list(f_lines) # from .loopreturns import get_fulfil_model_url url = f'{get_fulfil_model_url("sale.sale")}/{order_id}/return_order' lines = [] if True: ll = filter(lambda x: x['product.code'] == item['sku'], f_lines) line = ll.__next__() line_id = line['id'] lines.append({ "order_line_id": line_id, # Optional fields on line # ================== # "return_quantity": body_l[''], # defaults to the order line returnable quantity # "unit_price": "320.45", # defaults to the original order line unit price. Change this amount if the refund is not the full amount of the original order line. # If the return was created on an external returns platform, # the ID of the line "channel_identifier": DT, "note": "tracking_number " + item['tracking_number'], "return_reason": item['service'], # Created if not exists 'exchange_quantity': 1, }) # if True: # Model = client.model('product.product') # # exchanges created through shopify # product = Model.search_read_all( # domain=[["AND", ["code", "=", item['sku']]]], # order=None, # fields=['id'], # ) # product_id = product.__next__()['id'] # lines[i]['exchange_quantity'] = 1 # lines[i]['exchange_product'] = product_id # lines[i]['exchange_unit_price'] = item['total'] # # # Exchange fields # # # ================== # # # +ve quantity of replacement item to ship to customer # # "exchange_quantity": 1, # # # ID of the product being sent. # # # If replacement item is not specified, the same outbound item will be shipped. # # "exchange_product": 1234, # # # If the unit price is not specified, the unit price of the exchanged item is used. # # "exchange_unit_price": "320.45", # Unit price for outbound item if True: service_line = { "order_line_id": line_id, "return_quantity": 0, "note": item['service'], "exchange_quantity": 1, "exchange_product": SERVICE_SKU[item['service']], } lines.append(service_line) payload = [{ "channel_identifier": DT, # Unique identifier for the return in the channel. This will be used as idempotency key to avoid duplication. "reference": f'repair-{item["order_name"]}', # Return order reference, RMA "lines": lines, "warehouse": 57, }] payload = dumps(payload) response = requests.put(url, data=payload, headers=headers) if response.status_code != 500: print("success") # if response.status_code != 200: # content = f''' # error response from fullfill: {response.status_code}<br/> # text: {response.text}<br/> # url {url}<br/> # payload: <br/> # {json.dumps(payload)} # ''' # send_email("Loop webhook error!!!!", content, dev_recipients=True) return response.text, errors # # # def create_fullfill_order_(item): # subdomain = 'aurate-sandbox' # token = '43cf9ddb7acc4ac69586b8f1081d65ab' # client = Client(subdomain, token) # # CHANNEL_ID = 37 # # order_details = get_order_details(item) # SaleChannel = client.model('sale.channel') # record = SaleChannel.create_order(CHANNEL_ID, order_details) # print() # # # def get_order_details(item): # a = item['address'] # channel_identifier = str(int(item['DT'])) # order_details = { # 'channel_identifier': channel_identifier, # 'reference': channel_identifier, # "confirmed_at": datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.000+00:00'), # # "confirmed_at": "2021-05-11T08:20:23.251-05:00", # 'customer': { # 'name': a['name'], # 'contacts': [ # ['email', item['email']] # ], # }, # # 'billing_address': { # 'name': a['name'], # 'address1': a['address1'], # 'address2': a['address2'], # 'city': a['city'], # 'zip': '50001', # 'subdivision_code': a['province_code'], # 'country_code': a['country_code'], # 'email': item['email'], # 'phone': a['phone'].replace('(', '').replace(')', '').replace('-', '').replace(' ', ''), # }, # 'shipping_address': { # 'name': a['name'], # 'address1': a['address1'], # 'address2': a['address2'], # 'city': a['city'], # 'zip': '', # 'subdivision_code': a['province_code'], # 'country_code': a['country_code'], # 'email': item['email'], # 'phone': a['phone'].replace('(', '').replace(')', '').replace('-', '').replace(' ', ''), # }, # 'sale_lines': [ # { # 'sku': item['sku'], # 'quantity': 1, # 'unit_price': Decimal('1.00'), # 'amount': Decimal('1.00'), # 'comment': 'Repearment' # }, # ], # 'shipping_lines': [ # ], # 'amount': Decimal('1.00'), # 'currency_code': 'USD', # 'payment_term': 'NET 30', # 'priority': 2, # 'status': 'pending', # 'financial_status': 'paid', # 'fulfillment_status': 'unshipped', # } # return order_details # # # # # # # def create_fullfill_return_(item): # subdomain = 'aurate-sandbox' # token = '43cf9ddb7acc4ac69586b8f1081d65ab' # client = Client(subdomain, token) # # CHANNEL_ID = 37 # item() # order_details = get_order(item) # SaleChannel = client.model('sale.channel') # return_created(body) # print() # def return_created(body): # errors = [] # Model = client.model('sale.sale') # sale = Model.search_read_all( # domain=[["AND", ["reference", "=", body['order_name']]]], # order=None, # fields=['id', 'lines'], # # batch_size=5, # ) # sale = list(sale) # if not sale: # errors.append(f"Can't create return, didn't find any sale with reference {body['order_name']}") # return errors # sale = sale[0] # # Model = client.model('sale.line') # f_lines = Model.search_read_all( # domain=[["AND", ["id", "in", sale['lines']]]], # order=None, # fields=['product', 'product.code', 'quantity'], # ) # f_lines = list(f_lines) # order_id = sale['id'] # url = f'{get_fulfil_model_url("sale.sale")}/{order_id}/return_order' # # lines = [] # for body_l in body['line_items']: # ll = filter(lambda x: x['product.code'] == body_l['sku'], f_lines) # if not ll: # errors.append(f"Line not found {body_l}\n") # continue # line = ll.__next__() # line_id = line['id'] # # lines.append({ # "order_line_id": line_id, # # Optional fields on line # # ================== # # "return_quantity": body_l[''], # # defaults to the order line returnable quantity # # "unit_price": "320.45", # # defaults to the original order line unit price. Change this amount if the refund is not the full amount of the original order line. # # # If the return was created on an external returns platform, # # the ID of the line # "channel_identifier": body_l['line_item_id'], # # # # "note": "tracking_number " + body['tracking_number'], # "return_reason": body_l["return_reason"], # Created if not exists # }) # if not lines: # errors.append("Can't create return, didn't find any line") # return errors # if body['exchanges']: # Model = client.model('product.product') # # # exchanges created through shopify # for i, item in enumerate(body['exchanges']): # if len(lines) > i: # product = Model.search_read_all( # domain=[["AND", ["code", "=", item['sku']]]], # order=None, # fields=['id'], # ) # product_id = product.__next__()['id'] # lines[i]['exchange_quantity'] = 1 # lines[i]['exchange_product'] = product_id # lines[i]['exchange_unit_price'] = item['total'] # # # Exchange fields # # # ================== # # # +ve quantity of replacement item to ship to customer # # "exchange_quantity": 1, # # # ID of the product being sent. # # # If replacement item is not specified, the same outbound item will be shipped. # # "exchange_product": 1234, # # # If the unit price is not specified, the unit price of the exchanged item is used. # # "exchange_unit_price": "320.45", # Unit price for outbound item # else: # errors.append(f"failed to add exchange for {item}\n " # f"there is more exchanges than returns") # break # payload = [{ # "channel_identifier": body['id'], # Unique identifier for the return in the channel. This will be used as idempotency key to avoid duplication. # "reference": body["order_name"], # Return order reference, RMA # "lines": lines, # "warehouse": 140, # }] # # response = requests.put(url, json=payload, headers=headers) # # if response.status_code != 200: # content = f''' # error response from fullfill: {response.status_code}<br/> # text: {response.text}<br/> # url {url}<br/> # payload: <br/> # {json.dumps(payload)} # ''' # send_email("Loop webhook error!!!!", content, dev_recipients=True) # # return response.text, errors