Skip to content

Commit

Permalink
[IMP] basic sale_import_amazon OK
Browse files Browse the repository at this point in the history
  • Loading branch information
clementmbr committed Mar 19, 2024
1 parent bef613e commit 6cca2b7
Show file tree
Hide file tree
Showing 24 changed files with 337 additions and 117 deletions.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# generated from manifests external_dependencies
extendable_pydantic
fastapi
python-amazon-sp-api
12 changes: 7 additions & 5 deletions sale_import_amazon/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@
"version": "16.0.1.0.0",
"license": "AGPL-3",
"author": "Akretion",
"website": "http://akretion.com",
"website": "https://github.com/akretion/sale-import",
"depends": [
"sale_stock",
"stock",
# https://github.com/akretion/sale-import/
"sale_import_base",
],
"data": [
# 'views/amazon_marketplace.xml',
# 'views/sale_channel.xml',
# "views/sale_order.xml",
"views/amazon_marketplace.xml",
"views/sale_channel.xml",
"views/sale_order.xml",
"data/amazon_marketplace.xml",
"data/amazon_cron.xml",
"security/ir.model.access.csv",
],
"demo": [],
Expand Down
21 changes: 21 additions & 0 deletions sale_import_amazon/data/amazon_cron.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>

<record id="ir_cron_amazon_create_queue_job_chunk" model="ir.cron">
<field name="name">Amazon: import orders</field>
<field name="model_id" ref="model_sale_channel" />
<field name="state">code</field>
<field name="code">model.amazon_import_orders_chunk_cron()</field>
<field name="user_id" ref="base.user_root" />
<field name="interval_number">60</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field
name="nextcall"
eval="(DateTime.now() + timedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S')"
/>
<field name="doall" eval="False" />
<field name="priority">1000</field>
</record>

</odoo>
117 changes: 117 additions & 0 deletions sale_import_amazon/data/amazon_marketplace.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo noupdate="1">

<!-- Supported Amazon marketplaces -->
<!-- List on https://developer-docs.amazon.com/sp-api/docs/marketplace-ids -->

<!-- North America -->
<record id="marketplace_BR" model="amazon.marketplace">
<field name="name">Amazon.com.br</field>
<field name="marketplace_ref">A2Q3Y263D00KWC</field>
<field name="country_code">BR</field>

</record>
<record id="marketplace_CA" model="amazon.marketplace">
<field name="name">Amazon.ca</field>
<field name="marketplace_ref">A2EUQ1WTGCTBG2</field>
<field name="country_code">CA</field>
</record>
<record id="marketplace_MX" model="amazon.marketplace">
<field name="name">Amazon.com.mx</field>
<field name="marketplace_ref">A1AM78C64UM0Y8</field>
<field name="country_code">MX</field>
</record>
<record id="marketplace_US" model="amazon.marketplace">
<field name="name">Amazon.com</field>
<field name="marketplace_ref">ATVPDKIKX0DER</field>
<field name="country_code">US</field>
</record>
<!-- Europe -->
<record id="marketplace_AE" model="amazon.marketplace">
<field name="name">Amazon.ae</field>
<field name="marketplace_ref">A2VIGQ35RCS4UG</field>
<field name="country_code">AE</field>
</record>
<record id="marketplace_BE" model="amazon.marketplace">
<field name="name">Amazon.com.be</field>
<field name="marketplace_ref">AMEN7PMS3EDWL</field>
<field name="country_code">BE</field>
</record>
<record id="marketplace_DE" model="amazon.marketplace">
<field name="name">Amazon.de</field>
<field name="marketplace_ref">A1PA6795UKMFR9</field>
<field name="country_code">DE</field>
</record>
<record id="marketplace_EG" model="amazon.marketplace">
<field name="name">Amazon.eg</field>
<field name="marketplace_ref">ARBP9OOSHTCHU</field>
<field name="country_code">EG</field>
</record>
<record id="marketplace_ES" model="amazon.marketplace">
<field name="name">Amazon.es</field>
<field name="marketplace_ref">A1RKKUPIHCS9HS</field>
<field name="country_code">ES</field>
</record>
<record id="marketplace_FR" model="amazon.marketplace">
<field name="name">Amazon.fr</field>
<field name="marketplace_ref">A13V1IB3VIYZZH</field>
<field name="country_code">FR</field>
</record>
<record id="marketplace_IN" model="amazon.marketplace">
<field name="name">Amazon.in</field>
<field name="marketplace_ref">A21TJRUUN4KGV</field>
<field name="country_code">IN</field>
</record>
<record id="marketplace_IT" model="amazon.marketplace">
<field name="name">Amazon.it</field>
<field name="marketplace_ref">APJ6JRA9NG5V4</field>
<field name="country_code">IT</field>
</record>
<record id="marketplace_NL" model="amazon.marketplace">
<field name="name">Amazon.nl</field>
<field name="marketplace_ref">A1805IZSGTT6HS</field>
<field name="country_code">NL</field>
</record>
<record id="marketplace_PL" model="amazon.marketplace">
<field name="name">Amazon.pl</field>
<field name="marketplace_ref">A1C3SOZRARQ6R3</field>
<field name="country_code">PL</field>
</record>
<record id="marketplace_SA" model="amazon.marketplace">
<field name="name">Amazon.sa</field>
<field name="marketplace_ref">A17E79C6D8DWNP</field>
<field name="country_code">SA</field>
</record>
<record id="marketplace_SE" model="amazon.marketplace">
<field name="name">Amazon.se</field>
<field name="marketplace_ref">A2NODRKZP88ZB9</field>
<field name="country_code">SE</field>
</record>
<record id="marketplace_TR" model="amazon.marketplace">
<field name="name">Amazon.com.tr</field>
<field name="marketplace_ref">A33AVAJ2PDY3EV</field>
<field name="country_code">TR</field>
</record>
<record id="marketplace_UK" model="amazon.marketplace">
<field name="name">Amazon.co.uk</field>
<field name="marketplace_ref">A1F83G8C2ARO7P</field>
<field name="country_code">UK</field>
</record>
<!-- Far East -->
<record id="marketplace_AU" model="amazon.marketplace">
<field name="name">Amazon.com.au</field>
<field name="marketplace_ref">A39IBJ37TRP1C6</field>
<field name="country_code">AU</field>
</record>
<record id="marketplace_JP" model="amazon.marketplace">
<field name="name">Amazon.co.jp</field>
<field name="marketplace_ref">A1VC38T7YXB528</field>
<field name="country_code">JP</field>
</record>
<record id="marketplace_SG" model="amazon.marketplace">
<field name="name">Amazon.sg</field>
<field name="marketplace_ref">A19VAU5U5O7RUS</field>
<field name="country_code">SG</field>
</record>

</odoo>
2 changes: 2 additions & 0 deletions sale_import_amazon/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
from . import sale_channel
from . import sale_channel_importer_amazon
from . import sale_order

from . import schemas
6 changes: 2 additions & 4 deletions sale_import_amazon/models/amazon_marketplace.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
# Copyright 2024 Akretion
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import _, api, fields, models
from odoo import fields, models


class AmazonMarketplace(models.Model):
_name = "amazon.marketplace"
_description = "Amazon MarketPlace"
# List on https://developer-docs.amazon.com/sp-api/docs/marketplace-ids

# TODO: create xml data with all the Amazon Marketplaces
name = fields.Char()
country_code = fields.Char(required=True)
marketplace_ref = fields.Char()
marketplace_ref = fields.Char(help="API Marketplace's identifier")
2 changes: 1 addition & 1 deletion sale_import_amazon/models/queue_job_chunk.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright 2024 Akretion
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import _, api, fields, models
from odoo import fields, models


class QueueJobChunk(models.Model):
Expand Down
40 changes: 27 additions & 13 deletions sale_import_amazon/models/sale_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,28 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

import json
from pprint import pprint
from datetime import timedelta


from odoo import _, api, Command, fields, models
from odoo import _, fields, models
from odoo.exceptions import ValidationError
from odoo.addons.sale_import_amazon.utils import load_order_pages, load_order_items

from odoo.addons.sale_import_amazon.utils import load_order_items, load_order_pages


class SaleChannel(models.Model):
_inherit = "sale.channel"
# Connect to API, get raw data and create chunk with raw data and processor == sale_channel_importer_amazon

type_channel = fields.Selection(selection_add=[("amazon", "Amazon")])
channel_type = fields.Selection(selection_add=[("amazon", "Amazon")])

lwa_appid = fields.Char(string="LWA App ID")
# TODO: use data_encryption to store these fields here
sp_api_refresh_token = fields.Char()
lwa_client_secret = fields.Char()
sp_api_refresh_token = fields.Char(string="SP-API Refresh Token")
lwa_client_secret = fields.Char(string="LWA Client Secret")

date_last_sale_update = fields.Datetime(
help="Date used to limit the API call to the last Amazon Orders updated after "
"this choosen date"
"this choosen date",
default=lambda self: fields.Datetime.now() - timedelta(days=30),
)

marketplace_ids = fields.Many2many(
Expand All @@ -44,11 +44,14 @@ def amazon_get_credentials(self):
)

def amazon_import_orders(self):
if self.type_channel != "amazon":
self.ensure_one()
if self.channel_type != "amazon":
raise ValidationError(_("The sale channel must be type 'Amazon'"))

orders = []
creds = self.amazon_get_credentials()
if not self.date_last_sale_update:
raise ValidationError(_("Missing Date Last Sale Update"))
date_last_sale_update = self.date_last_sale_update.isoformat(sep="T")

for marketplace_id in self.marketplace_ids:
Expand All @@ -63,7 +66,8 @@ def amazon_import_orders(self):

return orders

def amazon_create_queue_job_chunk(self):
def amazon_import_orders_chunk(self):
self.ensure_one()
orders = self.amazon_import_orders()

chunk_vals = [
Expand All @@ -75,5 +79,15 @@ def amazon_create_queue_job_chunk(self):
}
for order in orders
]

return self.env["queue.job.chunk"].create(chunk_vals)
chunk_ids = self.env["queue.job.chunk"].create(chunk_vals)
self.write({"date_last_sale_update": fields.Datetime.now()})
return chunk_ids

def amazon_import_orders_chunk_cron(self):
amazon_channel_ids = self.search([("channel_type", "=", "amazon")])
chunk_ids = self.env["queue.job.chunk"]
for channel_id in amazon_channel_ids:
new_chunk_ids = channel_id.amazon_import_orders_chunk()
chunk_ids |= new_chunk_ids

return chunk_ids
49 changes: 35 additions & 14 deletions sale_import_amazon/models/sale_channel_importer_amazon.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Copyright 2024 Akretion
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import _, api, fields, models
from odoo import _, models
from odoo.exceptions import ValidationError

from odoo.addons.sale_import_amazon.utils import get_amz_date


Expand All @@ -10,8 +12,6 @@ class SaleChannelImporterAmazon(models.TransientModel):
_name = "sale.channel.importer.amazon"
_description = "Sale Channel Importer Amazon"

# TODO: manage existing SO shipped or canceled

def _get_line_vals(self, item):
qty = item["QuantityOrdered"]
discount_amount = float(item.get("PromotionDiscount", {}).get("Amount", 0))
Expand All @@ -21,7 +21,7 @@ def _get_line_vals(self, item):
if discount_amount and total_incl_tax:
discount = discount_amount / total_incl_tax

return {
line_vals = {
"product_code": item["SellerSKU"],
"description": item["Title"],
"qty": qty,
Expand All @@ -30,10 +30,12 @@ def _get_line_vals(self, item):
"discount": discount,
}

return line_vals

def _get_formatted_data(self):
raw = super()._get_formatted_data()

# We suppose we do not have access to Personally Identifiable Information (PII)
# We assume we do not have access to Personally Identifiable Information (PII)
# about Amazon Buyers :
# no customer name, only an encoded email (used as Amazon's identifier),
# shipping city, zip and country code.
Expand All @@ -48,6 +50,14 @@ def _get_formatted_data(self):
"country_code": shipping.get("CountryCode", ""),
}

marketplace_id = self.env["amazon.marketplace"].search(
[("marketplace_ref", "=", raw["MarketplaceId"])], limit=1
)
if not marketplace_id:
raise ValidationError(
_("Missing Amazon MarketPlace {}").format(raw["MarketplaceId"])
)

formatted_data = {
"name": raw["AmazonOrderId"],
"date_order": get_amz_date(raw["PurchaseDate"]).date(),
Expand All @@ -60,28 +70,39 @@ def _get_formatted_data(self):
"lines": [self._get_line_vals(item) for item in raw["OrderItems"]],
"state": raw["OrderStatus"].lower(),
"is_fulfilled_by_amazon": raw["FulfillmentChannel"] == "AFN",
"amazon_marketplace_id": marketplace_id.id,
}

if raw.get("OrderTotal"):
formatted_data.update(
{
"currency_code": raw["OrderTotal"]["CurrencyCode"],
"amount": {"amount_total": raw["OrderTotal"]["Amount"]},
}
)
currency_code = raw["OrderTotal"]["CurrencyCode"]
currency_id = self.chunk_id.reference.pricelist_id.currency_id
if currency_code != currency_id.name:
raise ValidationError(
_(
" The Curency code {} is different from Sale Channel "
"pricelist's currency {}"
).format(currency_code, currency_id.name)
)
formatted_data["amount"] = {"amount_total": raw["OrderTotal"]["Amount"]}

return formatted_data

def _manage_existing_so(self, existing_so, data):
if data["state"] == "canceled" and existing_so.state != "cancel":
existing_so._action_cancel()
if data["state"] == "shipped" and existing_so.delivery_status != "full":
elif data["state"] == "shipped" and existing_so.delivery_status != "full":
existing_so._deliver_order_by_amazon()
else:
super()._manage_existing_so(existing_so, data)

def _prepare_sale_vals(self, data):
so_vals = super()._prepare_sale_vals(data)
so_vals["is_fulfilled_by_amazon"] = data["is_fulfilled_by_amazon"]
# TODO: add markerplace_id
so_vals.update(
{
"is_fulfilled_by_amazon": data["is_fulfilled_by_amazon"],
"amazon_marketplace_id": data["amazon_marketplace_id"],
}
)

return so_vals

Expand Down
Loading

0 comments on commit 6cca2b7

Please sign in to comment.