238 lines
10 KiB
Python
238 lines
10 KiB
Python
import requests
|
|
import re
|
|
import logging
|
|
|
|
from odoo import models, fields, api, _
|
|
from odoo.exceptions import UserError, ValidationError
|
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
PRICER_REQUESTS_TIMEOUT = 10
|
|
|
|
def setup_requests_session(requests_session, pricer_login, pricer_password, auth_url):
|
|
"""
|
|
Setup the jwt token for the authentification in requests_session
|
|
"""
|
|
try:
|
|
# Get a new JWT token
|
|
response = requests_session.get(auth_url, auth=(pricer_login, pricer_password), timeout=PRICER_REQUESTS_TIMEOUT)
|
|
response.raise_for_status()
|
|
jwt_token = response.json().get('token')
|
|
requests_session.headers.update({'Authorization': f'Bearer {jwt_token}'})
|
|
except requests.exceptions.RequestException as e:
|
|
_logger.warning("Failed to update the jwt token through Pricer API URL: %s: %s", auth_url, e)
|
|
|
|
|
|
class PricerStore(models.Model):
|
|
_name = 'pricer.store'
|
|
_description = 'Pricer Store regrouping pricer tags'
|
|
|
|
# The name of the Pricer store in Odoo
|
|
name = fields.Char(
|
|
string='Store Name',
|
|
help='Pricer Store name in Odoo database',
|
|
required=True
|
|
)
|
|
|
|
# Fields used for Pricer API requests
|
|
# All the 4 following fields need to be provided to clients by Pricer
|
|
pricer_store_identifier = fields.Char(
|
|
string='Pricer Store ID',
|
|
help='Identifier of the store in the Pricer system',
|
|
required=True
|
|
)
|
|
pricer_tenant_name = fields.Char(
|
|
string='Pricer Tenant Name',
|
|
help='Your company identifier at Pricer',
|
|
required=True
|
|
)
|
|
pricer_login = fields.Char(
|
|
string='Pricer Login',
|
|
help='Login of your Pricer account',
|
|
required=True
|
|
)
|
|
pricer_password = fields.Char(
|
|
string='Pricer Password',
|
|
help='Password of your Pricer account',
|
|
required=True
|
|
)
|
|
|
|
# Products and Pricer tags associated to the Pricer Store
|
|
product_ids = fields.One2many(
|
|
comodel_name='product.product',
|
|
inverse_name='pricer_store_id',
|
|
string='Products',
|
|
)
|
|
pricer_tag_ids = fields.One2many(
|
|
comodel_name='pricer.tag',
|
|
inverse_name='pricer_store_id',
|
|
string='Pricer Tags',
|
|
)
|
|
|
|
# Update status fields
|
|
last_update_datetime = fields.Datetime(
|
|
string="Last Update",
|
|
help='Date and time of the last synchronization with Pricer',
|
|
readonly=True,
|
|
)
|
|
last_update_status_message = fields.Char(
|
|
string='Last Update Status',
|
|
help='Status message of the last synchronization with Pricer',
|
|
readonly=True,
|
|
)
|
|
|
|
# Dummy fields for quick pairing
|
|
dummy_prod_barcode = fields.Char(
|
|
string='Product barcode',
|
|
help='Scan the product barcode here',
|
|
store=False,
|
|
)
|
|
|
|
dummy_tag_barcode = fields.Char(
|
|
string='Pricer tag barcode',
|
|
help='Scan the Pricer tag barcode here',
|
|
store=False,
|
|
)
|
|
|
|
# ------------------------- PRICER API URLs -------------------------
|
|
# For authentification: "https://central-manager.[PRICER_TENANT_NAME].pcm.pricer-plaza.com[PRICER_API_SUFFIX]"
|
|
# For other requests: "https://[PRICER_STORE_NAME].[PRICER_TENANT_NAME].pcm.pricer-plaza.com[PRICER_API_SUFFIX]"
|
|
# Example: "https://1234.odoo-be.pcm.pricer-plaza.com/api/public/core/v1/items"
|
|
auth_url = fields.Char(compute='_compute_auth_url')
|
|
create_or_update_products_url = fields.Char(compute='_compute_create_or_update_products_url')
|
|
link_tags_url = fields.Char(compute='_compute_link_tags_url')
|
|
|
|
# Their "compute" methods
|
|
@api.depends('pricer_tenant_name')
|
|
def _compute_auth_url(self):
|
|
for record in self:
|
|
record.auth_url = f'https://central-manager.{record.pricer_tenant_name}.pcm.pricer-plaza.com/api/public/auth/v1/login'
|
|
|
|
@api.depends('pricer_store_identifier', 'pricer_tenant_name')
|
|
def _compute_create_or_update_products_url(self):
|
|
for record in self:
|
|
record.create_or_update_products_url = f'https://{record.pricer_store_identifier}.{record.pricer_tenant_name}.pcm.pricer-plaza.com/api/public/core/v1/items'
|
|
|
|
@api.depends('pricer_store_identifier', 'pricer_tenant_name')
|
|
def _compute_link_tags_url(self):
|
|
for record in self:
|
|
record.link_tags_url = f'https://{record.pricer_store_identifier}.{record.pricer_tenant_name}.pcm.pricer-plaza.com/api/public/core/v1/labels'
|
|
|
|
|
|
# ------------------------- CONSTRAINS -------------------------
|
|
@api.constrains('pricer_store_identifier')
|
|
def _check_pricer_store_identifier(self):
|
|
"""
|
|
Pricer Store ID must:
|
|
1) Consist of: a-z, 0-9 or '-'
|
|
2) Must start with a-z or 0-9
|
|
"""
|
|
for record in self:
|
|
if not re.fullmatch(r'^[a-z0-9][a-z0-9-]*$', record.pricer_store_identifier):
|
|
raise ValidationError(_("Pricer Store ID must only contain lowercase a-z, 0-9 or '-' and not start with '-'"))
|
|
|
|
# ------------------------- API METHODS -------------------------
|
|
def unlink_label(self, pricer_tag_id):
|
|
"""
|
|
Stop displaying product infromation on a pricer tag when deleting it from Odoo database
|
|
"""
|
|
unlink_tag_url = f'https://{self.pricer_store_identifier}.{self.pricer_tenant_name}.pcm.pricer-plaza.com/api/public/core/v1/labels/{pricer_tag_id}/links'
|
|
|
|
with requests.Session() as requests_session:
|
|
try:
|
|
setup_requests_session(requests_session, self.pricer_login, self.pricer_password, self.auth_url)
|
|
response = requests_session.delete(unlink_tag_url, timeout=PRICER_REQUESTS_TIMEOUT)
|
|
response.raise_for_status()
|
|
_logger.info("Succesfully unlinked products from Pricer tag %s at Pricer API URL: %s", pricer_tag_id, unlink_tag_url)
|
|
except requests.exceptions.RequestException as e:
|
|
_logger.warning("Failed to unlink product from Pricer tag %s at Pricer API URL: %s: %s", pricer_tag_id, unlink_tag_url, e)
|
|
raise UserError(_('Failed to unlink Pricer tag %(pricer_tag)s at API url %(api_url)s', pricer_tag=pricer_tag_id, api_url=unlink_tag_url))
|
|
|
|
def action_button_update_pricer_tags(self):
|
|
"""Manually call Pricer API to update products on Pricer tags"""
|
|
updated = self._update_pricer_tags(update_all=False)
|
|
|
|
message = (
|
|
self.last_update_status_message
|
|
if updated
|
|
else _('Everything is already up to date with Pricer. No update needed.')
|
|
)
|
|
|
|
return {
|
|
'type': 'ir.actions.client',
|
|
'tag': 'display_notification',
|
|
'params': {
|
|
'message': message,
|
|
'type': 'danger' if message.startswith(_('Error')) else 'info',
|
|
},
|
|
}
|
|
|
|
def _update_pricer_tags(self, update_all):
|
|
"""This method is regularly called by a cron in pricer module
|
|
The interval is defined in data/pricer_ir_cron.xml
|
|
This is done to avoid doing excessive API requests on every action
|
|
while maintaining user's pricer tags synchronized with Odoo database
|
|
|
|
A button on list view of pricer stores allows the user to manually call this method
|
|
If called manually, instead of going through ALL the pricer stores in the database,
|
|
it will only check the pricer stores specified in its "pricer_stores_to_update" argument
|
|
|
|
:param bool update_all: only kept not to change the method signature
|
|
"""
|
|
anything_to_update = False
|
|
|
|
# Go through every Pricer store and build a JSON request body for it
|
|
with requests.Session() as requests_session:
|
|
for store in self:
|
|
products = store.product_ids.filtered("pricer_product_to_create_or_update")
|
|
tags = products.pricer_tag_ids.filtered("pricer_product_to_link")
|
|
|
|
if not products and not tags:
|
|
continue # This store is up to date
|
|
|
|
anything_to_update = True
|
|
|
|
# Call the Pricer api to update/create products and link the price tags
|
|
try:
|
|
setup_requests_session(requests_session, store.pricer_login, store.pricer_password, store.auth_url)
|
|
# Create / Update products at Pricer database
|
|
if products:
|
|
requests_session.patch(
|
|
store.create_or_update_products_url,
|
|
json=[p._get_create_or_update_body() for p in products],
|
|
timeout=PRICER_REQUESTS_TIMEOUT,
|
|
).raise_for_status()
|
|
products.write({'pricer_product_to_create_or_update': False})
|
|
_logger.info(
|
|
"Successfully created/updated products information for %s at Pricer API url: %s",
|
|
store.name, store.create_or_update_products_url,
|
|
)
|
|
# Link the new Pricer tags to the existing products on their database
|
|
if tags:
|
|
requests_session.patch(
|
|
store.link_tags_url,
|
|
json=[t._get_link_body() for t in tags],
|
|
timeout=PRICER_REQUESTS_TIMEOUT,
|
|
).raise_for_status()
|
|
tags.write({'pricer_product_to_link': False})
|
|
_logger.info(
|
|
"Successfully linked Pricer labels to products for %s at Pricer API URL: %s",
|
|
store.name, store.link_tags_url,
|
|
)
|
|
store.last_update_status_message = _("Update successfully sent to Pricer")
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
_logger.warning(e)
|
|
if e.response:
|
|
store.last_update_status_message = _(
|
|
"Error: %(code)s - %(reason)s",
|
|
code=e.response.status_code,
|
|
reason=e.response.reason,
|
|
)
|
|
else:
|
|
store.last_update_status_message = _("Error: check Pricer credentials")
|
|
finally:
|
|
store.last_update_datetime = fields.Datetime.now()
|
|
|
|
return anything_to_update
|