1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/sale_ebay/models/sale_order.py
2024-12-10 09:04:09 +07:00

299 lines
14 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from datetime import datetime
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from . import product
_logger = logging.getLogger(__name__)
class SaleOrder(models.Model):
_inherit = "sale.order"
@api.model
def _process_order(self, order):
for transaction in order['TransactionArray']['Transaction']:
so = self.env['sale.order'].search(
[('client_order_ref', '=', transaction['OrderLineItemID'])], limit=1)
try:
if not so:
so = self._process_order_new(order, transaction)
so._process_order_update(order)
except Exception as e:
message = _("Ebay could not synchronize order:\n%s", e)
path = str(order)
product._log_logging(self.env, message, "_process_order", path)
_logger.exception(message)
@api.model
def _process_order_new(self, order, transaction):
(partner, shipping_partner) = self._process_order_new_find_partners(order)
fp = self.env['account.fiscal.position']._get_fiscal_position(partner, delivery=shipping_partner)
if fp:
partner.property_account_position_id = fp
create_values = {
'partner_id': partner.id,
'partner_shipping_id': shipping_partner.id,
'state': 'draft',
'client_order_ref': transaction['OrderLineItemID'],
'origin': 'eBay' + transaction['OrderLineItemID'],
'fiscal_position_id': fp.id,
'date_order': product._ebay_parse_date(order['PaidTime']),
}
if self.env['ir.config_parameter'].sudo().get_param('ebay_sales_team'):
create_values['team_id'] = int(
self.env['ir.config_parameter'].sudo().get_param('ebay_sales_team'))
sale_order = self.env['sale.order'].create(create_values)
sale_order._process_order_new_transaction(transaction)
sale_order._process_order_shipping(order)
return sale_order
def _process_order_new_find_partners(self, order):
def _find_state():
state = self.env['res.country.state'].search([
('code', '=', infos.get('StateOrProvince')),
('country_id', '=', shipping_data['country_id'])
], limit=1)
if not state:
state = self.env['res.country.state'].search([
('name', '=', infos.get('StateOrProvince')),
('country_id', '=', shipping_data['country_id'])
], limit=1)
return state
buyer_ebay_id = order['BuyerUserID']
infos = order['ShippingAddress']
partner = self.env['res.partner'].search([('ebay_id', '=', buyer_ebay_id)], limit=1)
if not partner:
partner = self.env['res.partner'].create({'name': buyer_ebay_id, 'ebay_id': buyer_ebay_id})
partner_data = {
'name': infos.get('Name'),
'ebay_id': buyer_ebay_id,
'ref': 'eBay',
}
email = order['TransactionArray']['Transaction'][0]['Buyer']['Email']
# After 15 days eBay doesn't send the email anymore but 'Invalid Request'.
if email != 'Invalid Request':
partner_data['email'] = email
# if we reuse an existing partner, addresses might already been set on it
# so we hold the address data in a temporary dictionary to see if we need to create it or not
shipping_data = {}
info_to_extract = [('name', 'Name'), ('street', 'Street1'),
('street2', 'Street2'), ('city', 'CityName'),
('zip', 'PostalCode'), ('phone', 'Phone')]
for (odoo_name, ebay_name) in info_to_extract:
shipping_data[odoo_name] = infos.get(ebay_name, '')
shipping_data['country_id'] = self.env['res.country'].search(
[('code', '=', infos['Country'])], limit=1).id
shipping_data['state_id'] = _find_state().id
shipping_partner = partner._find_existing_address(shipping_data)
if not shipping_partner:
# if the partner already has an address we create a new child contact to hold it
# otherwise we can directly set the new address on the partner
if partner.street:
contact_data = {'parent_id': partner.id, 'type': 'delivery'}
shipping_partner = self.env['res.partner'].create({**shipping_data, **contact_data})
else:
partner.write(shipping_data)
shipping_partner = partner
partner.write(partner_data)
return (partner, shipping_partner)
@api.model
def _process_all_taxes(self, tax_dict, price_unit):
"""If there is more than one product sold, price_unit should actually be the sum of all products;
price_unit is given per product, whereas the tax amount is computed over the sum of all products.
"""
tax_commands = []
tax_list_or_dict = tax_dict.get('TaxDetails', []) # returns a list if it contains more than one tax, directly returns the tax dict
for tax in [tax_list_or_dict] if isinstance(tax_list_or_dict, dict) else tax_list_or_dict:
tax_amount = float(tax['TaxAmount']['value'])
tax_rate = 100 * tax_amount / (price_unit - tax_amount) if price_unit > tax_amount > 0 else 0
tax_description = tax.get('TaxDescription', '')
tax_id = self._handle_taxes(tax_amount, tax_rate, description=tax_description)
if tax_id:
tax_commands.append((4, tax_id.id))
return tax_commands or False
@api.model
def _handle_taxes(self, amount, rate, description=''):
"""eBay use price-included taxes.
We ignore 0% taxes to avoid useless clutter,
but that could be changed if their presence is required.
"""
company = self.env.company
tax = False
if amount > 0 and rate > 0:
tax = self.env['account.tax'].with_context(active_test=False).sudo().search([
*self.env['account.tax']._check_company_domain(company),
('amount', '=', rate),
('amount_type', '=', 'percent'),
('price_include', '=', True),
('type_tax_use', '=', 'sale')], limit=1)
if not tax:
tax = self.env['account.tax'].sudo().create({
'name': 'Tax %.4f %%' % rate,
'amount': rate,
'amount_type': 'percent',
'type_tax_use': 'sale',
'description': '%s (eBay)' % description,
'company_id': company.id,
'price_include': True,
'active': False,
})
return tax
def _process_order_shipping(self, order):
self.ensure_one()
if 'ShippingServiceSelected' in order:
shipping_cost_dict = order['ShippingServiceSelected']['ShippingServiceCost']
shipping_amount = float(shipping_cost_dict['value'])
shipping_currency = self.env['res.currency'].with_context(active_test=False).search(
[('name', '=', shipping_cost_dict['_currencyID'])], limit=1)
shipping_name = order['ShippingServiceSelected']['ShippingService']
shipping_product = self.env['product.template'].search(
[('name', '=', shipping_name)], limit=1)
if not shipping_product:
shipping_product = self.env['product.template'].create({
'name': shipping_name,
'type': 'service',
'categ_id': self.env.ref('sale_ebay.product_category_ebay').id,
})
tax_dict = order['ShippingDetails']['SalesTax']
tax_amount = float(tax_dict.get('SalesTaxAmount', {}).get('value', 0))
# the rate on the tax amount is actually on the product unit price, not on the shipping
# and it's a tax not included in the price, contrarily to the product tax
tax_rate = tax_dict.get('SalesTaxPercent', '0')
tax_id = False
if tax_amount:
tax_id = self.env['account.tax'].sudo().create({
'name': tax_rate + '% Sales tax (eBay)',
'amount': tax_amount,
'amount_type': 'fixed',
'type_tax_use': 'sale',
'company_id': self.env.company.id,
'active': False,
})
price_unit = shipping_currency._convert(shipping_amount,
self.currency_id, self.company_id, self.date_order or datetime.now())
so_line = self.env['sale.order.line'].create({
'order_id': self.id,
'name': shipping_name,
'product_id': shipping_product.product_variant_ids[0].id,
'product_uom_qty': 1,
'price_unit': price_unit,
'tax_id': [(4, tax_id.id)] if tax_id else False,
'is_delivery': True,
})
def _process_transaction_product(self, transaction):
Template = self.env['product.template']
ebay_id = transaction['Item']['ItemID']
product = Template.search([('ebay_id', '=', ebay_id)], order='ebay_use desc', limit=1)
if not product:
product = Template.create({
'name': transaction['Item']['Title'],
'ebay_id': ebay_id,
'ebay_use': True,
'ebay_sync_stock': False,
})
product.message_post(body=
_('Product created from eBay transaction %s', transaction['TransactionID']))
if product.product_variant_count > 1:
if 'Variation' in transaction:
splitted_url = transaction['Variation']['VariationViewItemURL'].split("vti", 1)[1]
variant = product.product_variant_ids.filtered(
lambda l:
l.ebay_use and
l.ebay_variant_url.split("vti", 1)[1] == splitted_url
or l.name == transaction['Variation']['VariationTitle']
)
# If multiple variants but only one listed on eBay as Item Specific
else:
call_data = {'ItemID': product.ebay_id, 'IncludeItemSpecifics': True}
resp = product._ebay_execute('GetItem', call_data)
name_value_list = resp.dict()['Item']['ItemSpecifics']['NameValueList']
if not isinstance(name_value_list, list):
name_value_list = [name_value_list]
# get only the item specific in the value list
variant = product._get_variant_from_ebay_specs([n for n in name_value_list if n['Source'] == 'ItemSpecific'])
else:
variant = product.product_variant_ids[0]
variant.ebay_quantity_sold = variant.ebay_quantity_sold + int(transaction['QuantityPurchased'])
if not product.ebay_sync_stock:
variant.ebay_quantity = variant.ebay_quantity - int(transaction['QuantityPurchased'])
variant_qty = 0
if len(product.product_variant_ids.filtered('ebay_use')) > 1:
variant_qty = sum(product.product_variant_ids.mapped('ebay_quantity'))
else:
variant_qty = variant.ebay_quantity
if variant_qty <= 0:
if self.env['ir.config_parameter'].sudo().get_param('ebay_out_of_stock'):
product.ebay_listing_status = 'Out Of Stock'
else:
product.ebay_listing_status = 'Ended'
return product, variant
def _process_order_new_transaction(self, transaction):
self.ensure_one()
product, variant = self._process_transaction_product(transaction)
transaction_currency = self.env['res.currency'].with_context(active_test=False).search(
[('name', '=', transaction['TransactionPrice']['_currencyID'])], limit=1)
price_unit = float(transaction['TransactionPrice']['value'])
price_unit = transaction_currency._convert(price_unit,
self.currency_id, self.company_id, self.date_order or datetime.now())
qty = float(transaction['QuantityPurchased'])
tax_commands = self._process_all_taxes(transaction['Taxes'], price_unit * qty)
sol = self.env['sale.order.line'].create({
'product_id': variant.id,
'order_id': self.id,
'product_uom_qty': qty,
'price_unit': price_unit,
'tax_id': tax_commands,
})
if 'BuyerCheckoutMessage' in transaction:
self.message_post(body=_('The Buyer Posted :\n') + transaction['BuyerCheckoutMessage'])
self.env['product.template']._put_in_queue(product.id)
def _process_order_update(self, order):
self.ensure_one()
product_lines = self.order_line.filtered(lambda l: not l._is_delivery())
are_all_products_listed = all(product_lines.mapped('product_id.ebay_url'))
can_be_invoiced = ('order' in product_lines.mapped('product_id.invoice_policy') and
'to invoice' in product_lines.mapped('invoice_status'))
no_confirm = (self.env.context.get('ebay_no_confirm', False) or
not are_all_products_listed)
try:
if (not no_confirm and self.state in ['draft', 'sent']):
self.action_confirm()
if not no_confirm and can_be_invoiced:
self._create_invoices()
shipping_name = order['ShippingServiceSelected']['ShippingService']
if self.picking_ids and shipping_name:
self.picking_ids[-1].message_post(
body=_('The Buyer Chose The Following Delivery Method :\n') + shipping_name)
except UserError as e:
self.message_post(body=
_('Ebay Synchronisation could not confirm because of the following error:\n%s', str)(e))