Initial commit of connector_walmart for Odoo 11.0 (using beta version of connector_ecommerce)

This commit is contained in:
Jared Kipe
2018-07-07 12:43:35 -07:00
parent b7096b5187
commit cfd25c425b
34 changed files with 2518 additions and 2 deletions

View File

@@ -0,0 +1,6 @@
from . import walmart_backend
from . import walmart_binding
from . import sale_order
from . import stock_picking
from . import delivery
from . import account

View File

@@ -0,0 +1 @@
from . import account_fiscal_position

View File

@@ -0,0 +1,68 @@
# © 2017,2018 Hibou Corp.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
from odoo.exceptions import ValidationError
from logging import getLogger
_logger = getLogger(__name__)
class AccountFiscalPosition(models.Model):
_inherit = 'account.fiscal.position'
is_connector_walmart = fields.Boolean(string='Use Walmart Order Item Rate')
@api.multi
def map_tax(self, taxes, product=None, partner=None, order_line=None):
if not taxes or not self.is_connector_walmart:
return super(AccountFiscalPosition, self).map_tax(taxes, product=product, partner=partner)
AccountTax = self.env['account.tax'].sudo()
result = AccountTax.browse()
for tax in taxes:
if not order_line:
raise ValidationError('Walmart Connector fiscal position requires order item details.')
if not order_line.walmart_bind_ids:
if order_line.price_unit == 0.0:
continue
else:
raise ValidationError('Walmart Connector fiscal position requires Walmart Order Lines')
tax_rate = order_line.walmart_bind_ids[0].tax_rate
if tax_rate == 0.0:
continue
# step 1: Check if we already have this rate.
tax_line = self.tax_ids.filtered(lambda x: tax_rate == x.tax_dest_id.amount and x.tax_src_id.id == tax.id)
if not tax_line:
#step 2: find or create this tax and tax_line
new_tax = AccountTax.search([
('name', 'like', 'Walmart %'),
('amount', '=', tax_rate),
('amount_type', '=', 'percent'),
('type_tax_use', '=', 'sale'),
], limit=1)
if not new_tax:
new_tax = AccountTax.create({
'name': 'Walmart Tax %0.2f %%' % (tax_rate,),
'amount': tax_rate,
'amount_type': 'percent',
'type_tax_use': 'sale',
'account_id': tax.account_id.id,
'refund_account_id': tax.refund_account_id.id,
})
tax_line = self.env['account.fiscal.position.tax'].sudo().create({
'position_id': self.id,
'tax_src_id': tax.id,
'tax_dest_id': new_tax.id,
})
# step 3: map the tax
result |= tax_line.tax_dest_id
return result

View File

@@ -0,0 +1 @@
from . import common

View File

@@ -0,0 +1,52 @@
# © 2017,2018 Hibou Corp.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api
class DeliveryCarrier(models.Model):
""" Adds Walmart specific fields to ``delivery.carrier``
``walmart_code``
Code of the carrier delivery method in Walmart.
Example: ``Standard``
``walmart_carrier_code``
Walmart specific list of carriers.
"""
_inherit = "delivery.carrier"
walmart_code = fields.Selection(
selection=[
('Value', 'Value'),
('Standard', 'Standard'),
('Express', 'Express'),
('Oneday', 'Oneday'),
('Freight', 'Freight'),
],
string='Walmart Method Code',
required=False,
)
# From API:
# UPS, USPS, FedEx, Airborne, OnTrac, DHL, NG, LS, UDS, UPSMI, FDX
walmart_carrier_code = fields.Selection(
selection=[
('UPS', 'UPS'),
('USPS', 'USPS'),
('FedEx', 'FedEx'),
('Airborne', 'Airborne'),
('OnTrac', 'OnTrac'),
('DHL', 'DHL'),
('NG', 'NG'),
('LS', 'LS'),
('UDS', 'UDS'),
('UPSMI', 'UPSMI'),
('FDX', 'FDX'),
],
string='Walmart Base Carrier Code',
required=False,
)

View File

@@ -0,0 +1,2 @@
from . import common
from . import importer

View File

@@ -0,0 +1,209 @@
# © 2017,2018 Hibou Corp.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
import odoo.addons.decimal_precision as dp
from odoo import models, fields, api
from odoo.addons.queue_job.job import job
from odoo.addons.component.core import Component
from odoo.addons.queue_job.exception import RetryableJobError
_logger = logging.getLogger(__name__)
class WalmartSaleOrder(models.Model):
_name = 'walmart.sale.order'
_inherit = 'walmart.binding'
_description = 'Walmart Sale Order'
_inherits = {'sale.order': 'odoo_id'}
odoo_id = fields.Many2one(comodel_name='sale.order',
string='Sale Order',
required=True,
ondelete='cascade')
walmart_order_line_ids = fields.One2many(
comodel_name='walmart.sale.order.line',
inverse_name='walmart_order_id',
string='Walmart Order Lines'
)
customer_order_id = fields.Char(string='Customer Order ID')
total_amount = fields.Float(
string='Total amount',
digits=dp.get_precision('Account')
)
total_amount_tax = fields.Float(
string='Total amount w. tax',
digits=dp.get_precision('Account')
)
shipping_method_code = fields.Selection(
selection=[
('Value', 'Value'),
('Standard', 'Standard'),
('Express', 'Express'),
('Oneday', 'Oneday'),
('Freight', 'Freight'),
],
string='Shipping Method Code',
required=False,
)
@job(default_channel='root.walmart')
@api.model
def import_batch(self, backend, filters=None):
""" Prepare the import of Sales Orders from Walmart """
return super(WalmartSaleOrder, self).import_batch(backend, filters=filters)
@api.multi
def action_confirm(self):
for order in self:
if order.backend_id.acknowledge_order == 'order_confirm':
self.with_delay().acknowledge_order(order.backend_id, order.external_id)
@job(default_channel='root.walmart')
@api.model
def acknowledge_order(self, backend, external_id):
with backend.work_on(self._name) as work:
adapter = work.component(usage='backend.adapter')
return adapter.acknowledge_order(external_id)
class SaleOrder(models.Model):
_inherit = 'sale.order'
walmart_bind_ids = fields.One2many(
comodel_name='walmart.sale.order',
inverse_name='odoo_id',
string="Walmart Bindings",
)
@api.multi
def action_confirm(self):
res = super(SaleOrder, self).action_confirm()
self.walmart_bind_ids.action_confirm()
return res
class WalmartSaleOrderLine(models.Model):
_name = 'walmart.sale.order.line'
_inherit = 'walmart.binding'
_description = 'Walmart Sale Order Line'
_inherits = {'sale.order.line': 'odoo_id'}
walmart_order_id = fields.Many2one(comodel_name='walmart.sale.order',
string='Walmart Sale Order',
required=True,
ondelete='cascade',
index=True)
odoo_id = fields.Many2one(comodel_name='sale.order.line',
string='Sale Order Line',
required=True,
ondelete='cascade')
backend_id = fields.Many2one(
related='walmart_order_id.backend_id',
string='Walmart Backend',
readonly=True,
store=True,
# override 'walmart.binding', can't be INSERTed if True:
required=False,
)
tax_rate = fields.Float(string='Tax Rate',
digits=dp.get_precision('Account'))
walmart_number = fields.Char(string='Walmart lineNumber')
# notes = fields.Char()
@api.model
def create(self, vals):
walmart_order_id = vals['walmart_order_id']
binding = self.env['walmart.sale.order'].browse(walmart_order_id)
vals['order_id'] = binding.odoo_id.id
binding = super(WalmartSaleOrderLine, self).create(vals)
return binding
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
walmart_bind_ids = fields.One2many(
comodel_name='walmart.sale.order.line',
inverse_name='odoo_id',
string="Walmart Bindings",
)
@api.multi
def _compute_tax_id(self):
"""
This overrides core behavior because we need to get the order_line into the order
to be able to compute Walmart taxes.
:return:
"""
for line in self:
fpos = line.order_id.fiscal_position_id or line.order_id.partner_id.property_account_position_id
# If company_id is set, always filter taxes by the company
taxes = line.product_id.taxes_id.filtered(lambda r: not line.company_id or r.company_id == line.company_id)
line.tax_id = fpos.map_tax(taxes, line.product_id, line.order_id.partner_shipping_id, order_line=line) if fpos else taxes
class SaleOrderAdapter(Component):
_name = 'walmart.sale.order.adapter'
_inherit = 'walmart.adapter'
_apply_on = 'walmart.sale.order'
def search(self, from_date=None, next_cursor=None):
"""
:param filters: Dict of filters
:param from_date:
:param next_cursor:
:return: List
"""
if next_cursor:
arguments = {'nextCursor': next_cursor}
else:
arguments = {'createdStartDate': from_date.isoformat()}
api_instance = self.api_instance
orders_response = api_instance.orders.all(**arguments)
_logger.debug(orders_response)
if not 'list' in orders_response:
return []
next = orders_response['list']['meta']['nextCursor']
if next:
self.env[self._apply_on].with_delay().import_batch(
self.backend_record,
filters={'next_cursor': next}
)
orders = orders_response['list']['elements']['order']
return map(lambda o: o['purchaseOrderId'], orders)
def read(self, id, attributes=None):
""" Returns the information of a record
:rtype: dict
"""
api_instance = self.api_instance
record = api_instance.orders.get(id)
if 'order' in record:
order = record['order']
order['orderLines'] = order['orderLines']['orderLine']
return order
raise RetryableJobError('Order "' + str(id) + '" did not return an order response.')
def acknowledge_order(self, id):
""" Returns the order after ack
:rtype: dict
"""
_logger.warn('BEFORE ACK ' + str(id))
api_instance = self.api_instance
record = api_instance.orders.acknowledge(id)
_logger.warn('AFTER ACK RECORD: ' + str(record))
if 'order' in record:
return record['order']
raise RetryableJobError('Acknowledge Order "' + str(id) + '" did not return an order response.')

View File

@@ -0,0 +1,347 @@
# © 2017,2018 Hibou Corp.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from datetime import datetime, timedelta
from copy import deepcopy, copy
from odoo import _
from odoo.addons.component.core import Component
from odoo.addons.connector.components.mapper import mapping
from odoo.addons.queue_job.exception import NothingToDoJob, FailedJobError
_logger = logging.getLogger(__name__)
def walk_charges(charges):
item_amount = 0.0
tax_amount = 0.0
for charge in charges['charge']:
#charge_details = charge['charge']
charge_details = charge
charge_amount_details = charge_details['chargeAmount']
assert charge_amount_details['currency'] == 'USD', ("Invalid currency: " + charge_amount_details['currency'])
tax_details = charge_details['tax']
tax_amount_details = tax_details['taxAmount'] if tax_details else {'amount': 0.0}
item_amount += float(charge_amount_details['amount'])
tax_amount += float(tax_amount_details['amount'])
return item_amount, tax_amount
class SaleOrderBatchImporter(Component):
_name = 'walmart.sale.order.batch.importer'
_inherit = 'walmart.delayed.batch.importer'
_apply_on = 'walmart.sale.order'
def _import_record(self, external_id, job_options=None, **kwargs):
if not job_options:
job_options = {
'max_retries': 0,
'priority': 5,
}
return super(SaleOrderBatchImporter, self)._import_record(
external_id, job_options=job_options)
def run(self, filters=None):
""" Run the synchronization """
if filters is None:
filters = {}
from_date = filters.get('from_date')
next_cursor = filters.get('next_cursor')
external_ids = self.backend_adapter.search(
from_date=from_date,
next_cursor=next_cursor,
)
for external_id in external_ids:
self._import_record(external_id)
class SaleOrderImportMapper(Component):
_name = 'walmart.sale.order.mapper'
_inherit = 'walmart.import.mapper'
_apply_on = 'walmart.sale.order'
direct = [('purchaseOrderId', 'external_id'),
('customerOrderId', 'customer_order_id'),
]
children = [('orderLines', 'walmart_order_line_ids', 'walmart.sale.order.line'),
]
# def _map_child(self, map_record, from_attr, to_attr, model_name):
# return super(SaleOrderImportMapper, self)._map_child(map_record, from_attr, to_attr, model_name)
def _add_shipping_line(self, map_record, values):
record = map_record.source
line_builder = self.component(usage='order.line.builder.shipping')
line_builder.price_unit = 0.0
if values.get('carrier_id'):
carrier = self.env['delivery.carrier'].browse(values['carrier_id'])
line_builder.product = carrier.product_id
line = (0, 0, line_builder.get_line())
values['order_line'].append(line)
return values
def finalize(self, map_record, values):
values.setdefault('order_line', [])
self._add_shipping_line(map_record, values)
values.update({
'partner_id': self.options.partner_id,
'partner_invoice_id': self.options.partner_invoice_id,
'partner_shipping_id': self.options.partner_shipping_id,
})
onchange = self.component(
usage='ecommerce.onchange.manager.sale.order'
)
return onchange.play(values, values['walmart_order_line_ids'])
@mapping
def name(self, record):
name = record['purchaseOrderId']
prefix = self.backend_record.sale_prefix
if prefix:
name = prefix + name
return {'name': name}
@mapping
def date_order(self, record):
return {'date_order': datetime.fromtimestamp(record['orderDate'] / 1e3)}
@mapping
def fiscal_position_id(self, record):
if self.backend_record.fiscal_position_id:
return {'fiscal_position_id': self.backend_record.fiscal_position_id.id}
@mapping
def team_id(self, record):
if self.backend_record.team_id:
return {'team_id': self.backend_record.team_id.id}
@mapping
def payment_mode_id(self, record):
assert self.backend_record.payment_mode_id, ("Payment mode must be specified.")
return {'payment_mode_id': self.backend_record.payment_mode_id.id}
@mapping
def project_id(self, record):
if self.backend_record.analytic_account_id:
return {'project_id': self.backend_record.analytic_account_id.id}
@mapping
def warehouse_id(self, record):
if self.backend_record.warehouse_id:
return {'warehouse_id': self.backend_record.warehouse_id.id}
@mapping
def shipping_method(self, record):
method = record['shippingInfo']['methodCode']
carrier = self.env['delivery.carrier'].search([('walmart_code', '=', method)], limit=1)
if not carrier:
raise ValueError('Delivery Carrier for methodCode "%s", cannot be found.' % (method, ))
return {'carrier_id': carrier.id, 'shipping_method_code': method}
@mapping
def backend_id(self, record):
return {'backend_id': self.backend_record.id}
@mapping
def total_amount(self, record):
lines = record['orderLines']
total_amount = 0.0
total_amount_tax = 0.0
for l in lines:
item_amount, tax_amount = walk_charges(l['charges'])
total_amount += item_amount + tax_amount
total_amount_tax += tax_amount
return {'total_amount': total_amount, 'total_amount_tax': total_amount_tax}
class SaleOrderImporter(Component):
_name = 'walmart.sale.order.importer'
_inherit = 'walmart.importer'
_apply_on = 'walmart.sale.order'
def _must_skip(self):
if self.binder.to_internal(self.external_id):
return _('Already imported')
def _before_import(self):
# @TODO check if the order is released
pass
def _create_partner(self, values):
return self.env['res.partner'].create(values)
def _partner_matches(self, partner, values):
for key, value in values.items():
if key == 'state_id':
if value != partner.state_id.id:
return False
elif key == 'country_id':
if value != partner.country_id.id:
return False
elif bool(value) and value != getattr(partner, key):
return False
return True
def _get_partner_values(self):
record = self.walmart_record
# find or make partner with these details.
if 'customerEmailId' not in record:
raise ValueError('Order does not have customerEmailId in : ' + str(record))
customer_email = record['customerEmailId']
shipping_info = record['shippingInfo']
phone = shipping_info.get('phone', '')
postal_address = shipping_info.get('postalAddress', [])
name = postal_address.get('name', 'Undefined')
street = postal_address.get('address1', '')
street2 = postal_address.get('address2', '')
city = postal_address.get('city', '')
state_code = postal_address.get('state', '')
zip_ = postal_address.get('postalCode', '')
country_code = postal_address['country']
country = self.env['res.country'].search([('code', '=', country_code)], limit=1)
state = self.env['res.country.state'].search([
('country_id', '=', country.id),
('code', '=', state_code)
], limit=1)
return {
'email': customer_email,
'name': name,
'phone': phone,
'street': street,
'street2': street2,
'zip': zip_,
'city': city,
'state_id': state.id,
'country_id': country.id,
}
def _import_addresses(self):
record = self.walmart_record
partner_values = self._get_partner_values()
partner = self.env['res.partner'].search([
('email', '=', partner_values['email']),
], limit=1)
if not partner:
# create partner.
partner = self._create_partner(copy(partner_values))
if not self._partner_matches(partner, partner_values):
partner_values['parent_id'] = partner.id
partner_values['active'] = False
shipping_partner = self._create_partner(copy(partner_values))
else:
shipping_partner = partner
self.partner = partner
self.shipping_partner = shipping_partner
def _check_special_fields(self):
assert self.partner, (
"self.partner should have been defined "
"in SaleOrderImporter._import_addresses")
assert self.shipping_partner, (
"self.shipping_partner should have been defined "
"in SaleOrderImporter._import_addresses")
def _create_data(self, map_record, **kwargs):
# non dependencies
self._check_special_fields()
return super(SaleOrderImporter, self)._create_data(
map_record,
partner_id=self.partner.id,
partner_invoice_id=self.shipping_partner.id,
partner_shipping_id=self.shipping_partner.id,
**kwargs
)
def _create(self, data):
binding = super(SaleOrderImporter, self)._create(data)
# Without this, it won't map taxes with the fiscal position.
if binding.fiscal_position_id:
binding.odoo_id._compute_tax_id()
if binding.backend_id.acknowledge_order == 'order_create':
binding.with_delay().acknowledge_order(binding.backend_id, binding.external_id)
return binding
def _import_dependencies(self):
record = self.walmart_record
self._import_addresses()
# @TODO Import lines?
# Actually, maybe not, since I'm just going to reference by sku
class SaleOrderLineImportMapper(Component):
_name = 'walmart.sale.order.line.mapper'
_inherit = 'walmart.import.mapper'
_apply_on = 'walmart.sale.order.line'
def _finalize_product_values(self, record, values):
# This would be a good place to create a vendor or add a route...
return values
def _product_values(self, record):
item = record['item']
sku = item['sku']
item_amount, _ = walk_charges(record['charges'])
values = {
'default_code': sku,
'name': item.get('productName', sku),
'type': 'product',
'list_price': item_amount,
'categ_id': self.backend_record.product_categ_id.id,
}
return self._finalize_product_values(record, values)
@mapping
def product_id(self, record):
item = record['item']
sku = item['sku']
product = self.env['product.template'].search([
('default_code', '=', sku)
], limit=1)
if not product:
# we could use a record like (0, 0, values)
product = self.env['product.template'].create(self._product_values(record))
return {'product_id': product.product_variant_id.id}
@mapping
def price_unit(self, record):
order_line_qty = record['orderLineQuantity']
product_uom_qty = int(order_line_qty['amount'])
item_amount, tax_amount = walk_charges(record['charges'])
tax_rate = (tax_amount / item_amount) * 100.0 if item_amount else 0.0
price_unit = item_amount / product_uom_qty
return {'product_uom_qty': product_uom_qty, 'price_unit': price_unit, 'tax_rate': tax_rate}
@mapping
def walmart_number(self, record):
return {'walmart_number': record['lineNumber']}
@mapping
def backend_id(self, record):
return {'backend_id': self.backend_record.id}

View File

@@ -0,0 +1,2 @@
from . import common
from . import exporter

View File

@@ -0,0 +1,95 @@
# © 2017,2018 Hibou Corp.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo import api, models, fields
from odoo.addons.queue_job.job import job, related_action
from odoo.addons.component.core import Component
from odoo.addons.queue_job.exception import RetryableJobError
_logger = logging.getLogger(__name__)
class WalmartStockPicking(models.Model):
_name = 'walmart.stock.picking'
_inherit = 'walmart.binding'
_inherits = {'stock.picking': 'odoo_id'}
_description = 'Walmart Delivery Order'
odoo_id = fields.Many2one(comodel_name='stock.picking',
string='Stock Picking',
required=True,
ondelete='cascade')
walmart_order_id = fields.Many2one(comodel_name='walmart.sale.order',
string='Walmart Sale Order',
ondelete='set null')
@job(default_channel='root.walmart')
@related_action(action='related_action_unwrap_binding')
@api.multi
def export_picking_done(self):
""" Export a complete or partial delivery order. """
self.ensure_one()
with self.backend_id.work_on(self._name) as work:
exporter = work.component(usage='record.exporter')
return exporter.run(self)
class StockPicking(models.Model):
_inherit = 'stock.picking'
walmart_bind_ids = fields.One2many(
comodel_name='walmart.stock.picking',
inverse_name='odoo_id',
string="Walmart Bindings",
)
class StockPickingAdapter(Component):
_name = 'walmart.stock.picking.adapter'
_inherit = 'walmart.adapter'
_apply_on = 'walmart.stock.picking'
def create(self, id, lines):
api_instance = self.api_instance
_logger.warn('BEFORE SHIPPING %s list: %s' % (str(id), str(lines)))
record = api_instance.orders.ship(id, lines)
_logger.warn('AFTER SHIPPING RECORD: ' + str(record))
if 'order' in record:
return record['order']
raise RetryableJobError('Shipping Order %s did not return an order response. (lines: %s)' % (str(id), str(lines)))
class WalmartBindingStockPickingListener(Component):
_name = 'walmart.binding.stock.picking.listener'
_inherit = 'base.event.listener'
_apply_on = ['walmart.stock.picking']
def on_record_create(self, record, fields=None):
record.with_delay().export_picking_done()
class WalmartStockPickingListener(Component):
_name = 'walmart.stock.picking.listener'
_inherit = 'base.event.listener'
_apply_on = ['stock.picking']
def on_picking_dropship_done(self, record, picking_method):
return self.on_picking_out_done(record, picking_method)
def on_picking_out_done(self, record, picking_method):
"""
Create a ``walmart.stock.picking`` record. This record will then
be exported to Walmart.
:param picking_method: picking_method, can be 'complete' or 'partial'
:type picking_method: str
"""
sale = record.sale_id
if not sale:
return
for walmart_sale in sale.walmart_bind_ids:
self.env['walmart.stock.picking'].create({
'backend_id': walmart_sale.backend_id.id,
'odoo_id': record.id,
'walmart_order_id': walmart_sale.id,
})

View File

@@ -0,0 +1,78 @@
# © 2017,2018 Hibou Corp.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields
from odoo.addons.component.core import Component
from odoo.addons.queue_job.exception import NothingToDoJob
from logging import getLogger
_logger = getLogger(__name__)
class WalmartPickingExporter(Component):
_name = 'walmart.stock.picking.exporter'
_inherit = 'walmart.exporter'
_apply_on = ['walmart.stock.picking']
def _get_args(self, binding, lines):
sale_binder = self.binder_for('walmart.sale.order')
walmart_sale_id = sale_binder.to_external(binding.walmart_order_id)
return walmart_sale_id, lines
def _get_lines(self, binding):
"""
Normalizes picking line data into the format to export to Walmart.
:param binding: walmart.stock.picking
:return: list[ dict(number, amount, carrier, methodCode, trackingNumber, trackingUrl=None) ]
"""
ship_date = binding.date_done
# in ms
ship_date_time = int(fields.Datetime.from_string(ship_date).strftime('%s')) * 1000
lines = []
for line in binding.move_lines:
sale_line = line.procurement_id.sale_line_id
if not sale_line.walmart_bind_ids:
continue
# this is a particularly interesting way to get this,
walmart_sale_line = next(
(line for line in sale_line.walmart_bind_ids
if line.backend_id.id == binding.backend_id.id),
None
)
if not walmart_sale_line:
continue
number = walmart_sale_line.walmart_number
amount = 1 if line.product_qty > 0 else 0
carrier = binding.carrier_id.walmart_carrier_code
methodCode = binding.walmart_order_id.shipping_method_code
trackingNumber = binding.carrier_tracking_ref
trackingUrl = None
lines.append(dict(
shipDateTime=ship_date_time,
number=number,
amount=amount,
carrier=carrier,
methodCode=methodCode,
trackingNumber=trackingNumber,
trackingUrl=trackingUrl,
))
return lines
def run(self, binding):
"""
Export the picking to Walmart
:param binding: walmart.stock.picking
:return:
"""
if binding.external_id:
return 'Already exported'
lines = self._get_lines(binding)
if not lines:
raise NothingToDoJob('Cancelled: the delivery order does not contain '
'lines from the original sale order.')
args = self._get_args(binding, lines)
external_id = self.backend_adapter.create(*args)
self.binder.bind(external_id, binding)

View File

@@ -0,0 +1 @@
from . import common

View File

@@ -0,0 +1,128 @@
# © 2017,2018 Hibou Corp.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import datetime, timedelta
from logging import getLogger
from contextlib import contextmanager
from odoo import api, fields, models, _
from ...components.api.walmart import Walmart
_logger = getLogger(__name__)
IMPORT_DELTA_BUFFER = 60 # seconds
class WalmartBackend(models.Model):
_name = 'walmart.backend'
_description = 'Walmart Backend'
_inherit = 'connector.backend'
name = fields.Char(string='Name')
consumer_id = fields.Char(
string='Consumer ID',
required=True,
help='Walmart Consumer ID',
)
channel_type = fields.Char(
string='Channel Type',
required=True,
help='Walmart Channel Type',
)
private_key = fields.Char(
string='Private Key',
required=True,
help='Walmart Private Key'
)
warehouse_id = fields.Many2one(
comodel_name='stock.warehouse',
string='Warehouse',
required=True,
help='Warehouse to use for stock.',
)
company_id = fields.Many2one(
comodel_name='res.company',
related='warehouse_id.company_id',
string='Company',
readonly=True,
)
fiscal_position_id = fields.Many2one(
comodel_name='account.fiscal.position',
string='Fiscal Position',
help='Fiscal position to use on orders.',
)
analytic_account_id = fields.Many2one(
comodel_name='account.analytic.account',
string='Analytic account',
help='If specified, this analytic account will be used to fill the '
'field on the sale order created by the connector.'
)
team_id = fields.Many2one(comodel_name='crm.team', string='Sales Team')
sale_prefix = fields.Char(
string='Sale Prefix',
help="A prefix put before the name of imported sales orders.\n"
"For instance, if the prefix is 'WMT-', the sales "
"order 5571768504079 in Walmart, will be named 'WMT-5571768504079' "
"in Odoo.",
)
payment_mode_id = fields.Many2one(comodel_name='account.payment.mode', string="Payment Mode")
# New Product fields.
product_categ_id = fields.Many2one(comodel_name='product.category', string='Product Category',
help='Default product category for newly created products.')
acknowledge_order = fields.Selection([
('never', 'Never'),
('order_create', 'On Order Import'),
('order_confirm', 'On Order Confirmation'),
], string='Acknowledge Order')
import_orders_from_date = fields.Datetime(
string='Import sale orders from date',
)
@contextmanager
@api.multi
def work_on(self, model_name, **kwargs):
self.ensure_one()
walmart_api = Walmart(self.consumer_id, self.channel_type, self.private_key)
_super = super(WalmartBackend, self)
with _super.work_on(model_name, walmart_api=walmart_api, **kwargs) as work:
yield work
@api.model
def _scheduler_import_sale_orders(self):
# potential hook for customization (e.g. pad from date or provide its own)
backends = self.search([
('consumer_id', '!=', False),
('channel_type', '!=', False),
('private_key', '!=', False),
('import_orders_from_date', '!=', False),
])
return backends.import_sale_orders()
@api.multi
def import_sale_orders(self):
self._import_from_date('walmart.sale.order', 'import_orders_from_date')
return True
@api.multi
def _import_from_date(self, model_name, from_date_field):
import_start_time = datetime.now()
for backend in self:
from_date = backend[from_date_field]
if from_date:
from_date = fields.Datetime.from_string(from_date)
else:
from_date = None
self.env[model_name].with_delay().import_batch(
backend,
filters={'from_date': from_date, 'to_date': import_start_time}
)
# We add a buffer, but won't import them twice.
next_time = import_start_time - timedelta(seconds=IMPORT_DELTA_BUFFER)
next_time = fields.Datetime.to_string(next_time)
self.write({from_date_field: next_time})

View File

@@ -0,0 +1 @@
from . import common

View File

@@ -0,0 +1,66 @@
# © 2017,2018 Hibou Corp.
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models, fields
from odoo.addons.queue_job.job import job, related_action
class WalmartBinding(models.AbstractModel):
""" Abstract Model for the Bindings.
All of the models used as bindings between Walmart and Odoo
(``walmart.sale.order``) should ``_inherit`` from it.
"""
_name = 'walmart.binding'
_inherit = 'external.binding'
_description = 'Walmart Binding (abstract)'
backend_id = fields.Many2one(
comodel_name='walmart.backend',
string='Walmart Backend',
required=True,
ondelete='restrict',
)
external_id = fields.Char(string='ID in Walmart')
_sql_constraints = [
('walmart_uniq', 'unique(backend_id, external_id)', 'A binding already exists for this Walmart ID.'),
]
@job(default_channel='root.walmart')
@related_action(action='related_action_walmart_link')
@api.model
def import_batch(self, backend, filters=None):
""" Prepare the import of records modified on Walmart """
if filters is None:
filters = {}
with backend.work_on(self._name) as work:
importer = work.component(usage='batch.importer')
return importer.run(filters=filters)
@job(default_channel='root.walmart')
@related_action(action='related_action_walmart_link')
@api.model
def import_record(self, backend, external_id, force=False):
""" Import a Walmart record """
with backend.work_on(self._name) as work:
importer = work.component(usage='record.importer')
return importer.run(external_id, force=force)
# @job(default_channel='root.walmart')
# @related_action(action='related_action_unwrap_binding')
# @api.multi
# def export_record(self, fields=None):
# """ Export a record on Walmart """
# self.ensure_one()
# with self.backend_id.work_on(self._name) as work:
# exporter = work.component(usage='record.exporter')
# return exporter.run(self, fields)
#
# @job(default_channel='root.walmart')
# @related_action(action='related_action_walmart_link')
# def export_delete_record(self, backend, external_id):
# """ Delete a record on Walmart """
# with backend.work_on(self._name) as work:
# deleter = work.component(usage='record.exporter.deleter')
# return deleter.run(external_id)