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)
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 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)
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 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)
#!/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'),
#!/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')
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')
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
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'})