示例#1
0
 def write(self, model, id, data):
     """ Update records on the external system """
     try:
         start = datetime.now()
         try:
             result = self.server.write(self.uid, self.password, model, id,
                                        data)
         except Exception as err:
             _logger.error("write(%s, %s, %s) failed", model, id, data)
             raise RetryableJobError(err)
         else:
             _logger.debug("write(%s, %s, %s) returned %s in %s seconds",
                           model, id, data, result,
                           (datetime.now() - start).seconds)
         return result
     except (socket.gaierror, socket.error, socket.timeout) as err:
         raise NetworkRetryableError(
             'A network error caused the failure of the job: '
             '%s' % err)
     except xmlrpc.client.ProtocolError as err:
         if err.errcode in [
                 502,  # Bad gateway
                 503,  # Service unavailable
                 504
         ]:  # Gateway timeout
             raise RetryableJobError(
                 'A protocol error caused the failure of the job:\n'
                 'URL: %s\n'
                 'HTTP/HTTPS headers: %s\n'
                 'Error code: %d\n'
                 'Error message: %s\n' %
                 (err.url, err.headers, err.errcode, err.errmsg))
         else:
             raise
示例#2
0
 def _before_import(self):
     if self.env['amazon.config.order.status'].browse(self.amazon_record.get('order_status_id')).name not in ('Canceled', 'Pending'):
         if self.amazon_record and not self.amazon_record.get('lines'):
             try:
                 self.backend_adapter.get_lines(filters=[self.amazon_record])
                 if not self.amazon_record.get('lines'):
                     status = self.backend_record.env['amazon.config.order.status'].browse(self.amazon_record.get('order_status_id')).name
                     if status and status not in ('Canceled', 'Pending'):
                         raise FailedJobError("Error recovering lines of the record (%s)", self.amazon_record['order_id'])
                     raise RetryableJobError('The record haven\'t got the lines of items', 60, True)
             except Exception as e:
                 raise RetryableJobError('The record haven\'t got the lines of items', 60, True)
         return
示例#3
0
    def advisory_lock_or_retry(self, lock, retry_seconds=1):
        """ Acquire a Postgres transactional advisory lock or retry job

        When the lock cannot be acquired, it raises a
        :exc:`odoo.addons.queue_job.exception.RetryableJobError` so the job
        is retried after n ``retry_seconds``.

        Usage example:

        .. code-block:: python

            lock_name = 'import_record({}, {}, {}, {})'.format(
                self.backend_record._name,
                self.backend_record.id,
                self.model._name,
                self.external_id,
            )
            self.advisory_lock_or_retry(lock_name, retry_seconds=2)

        See :func:`odoo.addons.connector.connector.pg_try_advisory_lock` for
        details.

        :param lock: The lock name. Can be anything convertible to a
           string.  It needs to represent what should not be synchronized
           concurrently, usually the string will contain at least: the
           action, the backend name, the backend id, the model name, the
           external id
        :param retry_seconds: number of seconds after which a job should
           be retried when the lock cannot be acquired.
        """
        if not pg_try_advisory_lock(self.env, lock):
            raise RetryableJobError('Could not acquire advisory lock',
                                    seconds=retry_seconds,
                                    ignore_retry=True)
示例#4
0
    def _retry_unique_violation(self):
        """ Context manager: catch Unique constraint error and retry the
        job later.

        When we execute several jobs workers concurrently, it happens
        that 2 jobs are creating the same record at the same time
        (especially product templates as they are shared by a lot of
        sales orders), resulting in:

            IntegrityError: duplicate key value violates unique
            constraint "jira_project_project_external_id_uniq"
            DETAIL:  Key (backend_id, external_id)=(1, 4851) already exists.

        In that case, we'll retry the import just later.

        """
        try:
            yield
        except IntegrityError as err:
            if err.pgcode == errorcodes.UNIQUE_VIOLATION:
                raise RetryableJobError(
                    'A database error caused the failure of the job:\n'
                    '%s\n\n'
                    'Likely due to 2 concurrent jobs wanting to create '
                    'the same record. The job will be retried later.' % err)
            else:
                raise
示例#5
0
    def moves_auto_assign(self, locations):
        """Job trying to reserve moves based on product and locations

        When a product has been added to a location, it searches all*
        the moves with a source equal or above this location and try
        to reserve them.

        * all the moves that would make sense to reserve, so no chained
        moves, no MTO, ...
        """
        self.ensure_one()
        moves = self.env["stock.move"].search(self._moves_auto_assign_domain(locations))
        pickings = moves.picking_id
        if not pickings:
            return
        try:
            self.env.cr.execute(
                "SELECT id FROM stock_picking WHERE id IN %s FOR UPDATE NOWAIT",
                (tuple(pickings.ids),),
            )
        except psycopg2.OperationalError as err:
            if err.pgcode == "55P03":  # could not obtain the lock
                _logger.debug(
                    "Another job is already auto-assigning moves and acquired a"
                    " lock on one or some of stock.picking%s, retry later.",
                    tuple(pickings.ids),
                )
                raise RetryableJobError(
                    "Could not obtain lock on transfers, will retry.", ignore_retry=True
                )
            raise
        moves._action_assign()
    def build_post_init(self,
                        build,
                        post_init_action=None,
                        key_value_dict=None):
        if post_init_action is None:
            post_init_action = ""

        if key_value_dict is None:
            key_value_dict = {}

        key_value_dict.update(self._get_mandatory_args(build))
        code = self._get_mandatory_code() + post_init_action
        action = {
            'name':
            'Build Code Eval',
            'state':
            'code',
            'model_id':
            1,
            'code':
            string.Formatter().vformat(code, (), SafeDict(**key_value_dict))
        }
        try:
            action_ids = self.build_execute_kw(build, 'ir.actions.server',
                                               'create', [action])
            self.build_execute_kw(build, 'ir.actions.server', 'run',
                                  [action_ids])
        except psycopg2.OperationalError as e:
            # TODO: this is very stupid hack that can be handled only in local instance operators
            raise RetryableJobError(str(e))
示例#7
0
    def _retry_unique_violation(self):
        """ Context manager: catch Unique constraint error and retry the
        job later.

        When we execute several jobs workers concurrently, it happens
        that 2 jobs are creating the same record at the same time (binding
        record created by :meth:`_export_dependency`), resulting in:

            IntegrityError: duplicate key value violates unique
            constraint "jira_project_project_odoo_uniq"
            DETAIL:  Key (backend_id, odoo_id)=(1, 4851) already exists.

        In that case, we'll retry the import just later.

        """
        try:
            yield
        except psycopg2.IntegrityError as err:
            if err.pgcode == psycopg2.errorcodes.UNIQUE_VIOLATION:
                raise RetryableJobError(
                    'A database error caused the failure of the job:\n'
                    '%s\n\n'
                    'Likely due to 2 concurrent jobs wanting to create '
                    'the same record. The job will be retried later.' % err)
            else:
                raise
示例#8
0
 def _handle_lock_failed(self, timestamp):
     _logger.warning("Failed to acquire timestamps %s",
                     timestamp,
                     exc_info=True)
     raise RetryableJobError(
         'Concurrent job / process already syncing',
         ignore_retry=True,
     )
示例#9
0
 def import_record(self, backend, external_id):
     _super = super(AmazonProductProduct, self)
     try:
         result = _super.import_record(backend, external_id)
         if not result:
             raise RetryableJobError(
                 'The product of the backend %s hasn\'t could not be imported. \n %s',
                 backend.name, external_id, 60)
     except Exception as e:
         if e.message.find(
                 'current transaction is aborted') > -1 or e.message.find(
                     'could not serialize access due to concurrent update'
                 ) > -1:
             raise RetryableJobError(
                 'A concurrent job is already exporting the same record '
                 '(%s). The job will be retried later.' % self.model._name,
                 60, True)
         raise e
示例#10
0
    def testing_method(self, *args, **kwargs):
        """ Method used for tests

        Return always the arguments and keyword arguments received
        """
        if kwargs.get("raise_retry"):
            raise RetryableJobError("Must be retried later")
        if kwargs.get("return_context"):
            return self.env.context
        return args, kwargs
 def call(self, method=None, resource=None, arguments=None):
     try:
         location = self._location._location
         cons_key = self._location.consumer_key
         sec_key = self._location.consumer_secret
         version = self._location.version or 'v3'
         # WooCommerce API Connection
         wcapi = API(
             url=location,  # Your store URL
             consumer_key=cons_key,  # Your consumer key
             consumer_secret=sec_key,  # Your consumer secret
             version=version,  # WooCommerce WP REST API version
             query_string_auth=True  # Force Basic Authentication as query
                                     # string true and using under HTTPS
         )
         if wcapi:
             if isinstance(arguments, list):
                 while arguments and arguments[-1] is None:
                     arguments.pop()
             start = datetime.now()
             try:
                 wooapi = getattr(wcapi, method)
                 res = wooapi(resource) if method not in ['put', 'post'] \
                 else wooapi(resource, arguments)
                 vals = res.json()
                 if not res.ok:
                     raise FailedJobError(vals)
                 result = vals
             except:
                 _logger.error("api.call(%s, %s, %s) failed", method,
                                resource, arguments)
                 raise
             else:
                 _logger.debug("api.call(%s, %s, %s) returned %s in %s\
                  seconds", method, resource, arguments, result,
                             (datetime.now() - start).seconds)
             return result
     except (socket.gaierror, socket.error, socket.timeout) as err:
         raise NetworkRetryableError(
             'A network error caused the failure of the job: '
             '%s' % err)
     except xmlrpc.client.ProtocolError as err:
         if err.errcode in [502,   # Bad gateway
                            503,   # Service unavailable
                            504]:  # Gateway timeout
             raise RetryableJobError(
                 'A protocol error caused the failure of the job:\n'
                 'URL: %s\n'
                 'HTTP/HTTPS headers: %s\n'
                 'Error code: %d\n'
                 'Error message: %s\n' %
                 (err.url, err.headers, err.errcode, err.errmsg))
         else:
             raise
示例#12
0
 def _before_import(self):
     if self.amazon_record and self.amazon_record.get('lines'):
         importer = self.component(usage='amazon.product.category', model_name='amazon.product.product')
         record = {'marketplace_id':self.amazon_record['marketplace'].id,
                   'product_product_market_ids':[
                       {'marketplace_id':self.amazon_record['marketplace'].id,
                        'sku':line['sku']}
                       for line in self.amazon_record['lines']]}
         importer.run(record)
     elif self.amazon_record and not self.amazon_record.get('lines'):
         try:
             self.backend_adapter.get_lines(filters=[self.amazon_record])
             if not self.amazon_record.get('lines'):
                 status = self.backend_record.env['amazon.config.order.status'].browse(self.amazon_record.get('order_status_id')).name
                 if status and status != 'Cancelled':
                     raise FailedJobError("Error recovering lines of the record (%s)", self.amazon_record['order_id'])
                 raise RetryableJobError('The record haven\'t got the lines of items', 60, True)
         except:
             raise RetryableJobError('The record haven\'t got the lines of items', 60, True)
     return
示例#13
0
    def throw_retry_exception_for_throttled(self, request_name, backend_id, ignore_retry=True):
        control = self.search([('request_name', '=', request_name)])
        time_now = datetime.now()
        i = 0
        while i < control.max_quota:
            self.env['amazon.control.date.request'].create({'backend_id':backend_id, 'request_id':control.id, 'request_date':time_now.isoformat()})
            i += 1

        raise RetryableJobError("MWS API has been an error produced on %s for Request Throttled" % request_name,
                                seconds=ceil(control.restore_rate) + 1,
                                ignore_retry=ignore_retry)
示例#14
0
    def use_quota_if_avaiable(self, request_name, backend_id):
        # TODO: finish the method to control the quota of the methods
        assert request_name
        control = self.search([('request_name', '=', request_name)])
        try:
            assert control
        except AssertionError:
            raise FailedJobError("The action %s doesn't exist on the quota control module (MWS)" % request_name)

        self.env['amazon.control.date.request'].update({'backend_id':str(backend_id)})

        last_requests = self.env['amazon.control.date.request'].search([('backend_id', '=', backend_id),
                                                                        ('request_id', '=', control.id)],
                                                                       order='request_date desc',
                                                                       limit=control.max_quota - ceil(control.max_quota / 3))
        time_now = datetime.now()
        if last_requests and len(last_requests) >= control.max_quota - ceil(control.max_quota / 3):
            first_date_request = datetime.strptime(last_requests[len(last_requests) - 1].request_date, '%Y-%m-%d %H:%M:%S')
            if (time_now - first_date_request).seconds < control.restore_rate:
                raise RetryableJobError("The quota of %s is empty (MWS)" % request_name, seconds=ceil(control.restore_rate * 1.2), ignore_retry=True)

        if control.max_request_quota_time > 0:
            last_requests = self.env['amazon.control.date.request'].search([('backend_id', '=', backend_id),
                                                                            ('request_id', '=', control.id)],
                                                                           order='request_date desc',
                                                                           limit=control.max_request_quota_time)

            max_request_factor = 1
            if control.units_of_mesaure_quota == 'min':
                max_request_factor = 60
            if control.units_of_mesaure_quota == 'hor':
                max_request_factor = 60 * 60
            if control.units_of_mesaure_quota == 'day':
                max_request_factor = 60 * 60 * 24

            if last_requests and len(last_requests) >= control.max_request_quota_time:
                first_date_request = datetime.strptime(last_requests[len(last_requests) - 1].request_date, '%Y-%m-%d %H:%M:%S')
                if (time_now - first_date_request).seconds < (control.max_request_quota_units * max_request_factor):
                    raise RetryableJobError("The quota of %s is empty (MWS)" % request_name, seconds=ceil(control.restore_rate * 1.2), ignore_retry=True)

        self.env['amazon.control.date.request'].create({'backend_id':backend_id, 'request_id':control.id, 'request_date':time_now.isoformat()})
示例#15
0
    def import_record(self, backend, external_id):
        _super = super(AmazonSaleOrder, self)
        result = _super.import_record(backend, external_id)
        if not result:
            raise RetryableJobError(
                msg=
                "The sale of the backend %s hasn\'t could not be imported.\n %s"
                % (backend.name, external_id),
                seconds=random.randint(90, 600))

        if isinstance(result, Exception):
            raise Exception(result)
示例#16
0
    def _after_import(self, binding):
        """ Hook called at the end of the import
            Recalculate the prices with taxes (all the sales on Amazon have the prices included)
        """
        try:
            backend = self.backend_record
            if binding.odoo_id.order_line:
                # Compute the prices of lines without taxes
                amount_untaxed = amount_tax = 0.0
                for line in binding.odoo_id.order_line:
                    # Get the default sale tax of the company
                    if not line.tax_id:
                        line.tax_id = backend.env['account.tax'].browse(backend.env['ir.values'].get_default('product.template',
                                                                                                             'taxes_id',
                                                                                                             company_id=backend.company_id.id))
                    if not line.tax_id.price_include and line.tax_id:
                        taxes = line.tax_id._compute_amount_taxes(line.price_unit,
                                                                  line.product_uom_qty)
                        line.update({
                            'price_tax':taxes,
                            'price_total':line.price_unit,
                            'price_subtotal':line.price_unit - taxes,
                        })

                    amount_untaxed += line.price_subtotal
                    # FORWARDPORT UP TO 10.0
                    if binding.odoo_id.company_id.tax_calculation_rounding_method == 'round_globally':
                        price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
                        taxes = line.tax_id.compute_all_with_taxes(price, line.order_id.currency_id, line.product_uom_qty,
                                                                   product=line.product_id, partner=binding.odoo_id.partner_shipping_id)
                        amount_tax += sum(t.get('amount', 0.0) for t in taxes.get('taxes', []))
                    else:
                        amount_tax += line.price_tax

                binding.odoo_id.update({
                    'amount_untaxed':binding.odoo_id.pricelist_id.currency_id.round(amount_untaxed),
                    'amount_tax':binding.odoo_id.pricelist_id.currency_id.round(amount_tax),
                    'amount_total':amount_untaxed + amount_tax,
                })

        except Exception as e:
            if e.message.find('could not serialize access due to concurrent update') > -1:
                raise RetryableJobError('', 30, True)
            raise e

        # If the order has been shipped we call to stock.picking.action_done()
        if binding.order_status_id.name == 'Shipped':
            for pick in binding.odoo_id.picking_ids:
                pick.action_done()
        if binding.order_status_id.name == 'Canceled':
            for pick in binding.odoo_id.picking_ids:
                if pick.state != 'done':
                    pick.action_cancel()
示例#17
0
 def _get_first_price(self, backend, filters):
     assert filters
     with backend.work_on(self._name) as work:
         importer = work.component(usage='amazon.product.price.import')
         detail = self.env['amazon.product.product.detail'].browse(
             filters['product_detail'])
         result = None
         if filters['method'] == 'first_price':
             result = importer.run_first_price(detail)
         elif filters['method'] == 'first_offer':
             result = importer.run_first_offer(detail)
         if not result:
             raise RetryableJobError(msg='The prices can\'t be recovered',
                                     seconds=600)
示例#18
0
 def _check_in_new_connector_env(self):
     # with self.do_in_new_connector_env() as new_connector_env:
     with self.do_in_new_connector_env():
         # Even when we use an advisory lock, we may have
         # concurrent issues.
         # Explanation:
         # We import Partner A and B, both of them import a
         # partner category X.
         #
         # The squares represent the duration of the advisory
         # lock, the transactions starts and ends on the
         # beginnings and endings of the 'Import Partner'
         # blocks.
         # T1 and T2 are the transactions.
         #
         # ---Time--->
         # > T1 /------------------------\
         # > T1 | Import Partner A       |
         # > T1 \------------------------/
         # > T1        /-----------------\
         # > T1        | Imp. Category X |
         # > T1        \-----------------/
         #                     > T2 /------------------------\
         #                     > T2 | Import Partner B       |
         #                     > T2 \------------------------/
         #                     > T2        /-----------------\
         #                     > T2        | Imp. Category X |
         #                     > T2        \-----------------/
         #
         # As you can see, the locks for Category X do not
         # overlap, and the transaction T2 starts before the
         # commit of T1. So no lock prevents T2 to import the
         # category X and T2 does not see that T1 already
         # imported it.
         #
         # The workaround is to open a new DB transaction at the
         # beginning of each import (e.g. at the beginning of
         # "Imp. Category X") and to check if the record has been
         # imported meanwhile. If it has been imported, we raise
         # a Retryable error so T2 is rollbacked and retried
         # later (and the new T3 will be aware of the category X
         # from the its inception).
         binder = self.binder_for(model=self.model._name)
         # binder = new_connector_env.get_connector_unit(Binder)
         if binder.to_internal(self.prestashop_id):
             raise RetryableJobError(
                 'Concurrent error. The job will be retried later',
                 seconds=RETRY_WHEN_CONCURRENT_DETECTED,
                 ignore_retry=True)
示例#19
0
 def server(self):
     if not self._login_data.is_valid():
         raise ChannelConnectorError(_("Invalid Channel Parameters!"))
     if self._server is None:
         try:
             self._server = xmlrpc.client.ServerProxy(
                 self._login_data.address)
             res, tok = self._server.acquire_token(self._login_data.user,
                                                   self._login_data.passwd,
                                                   self._login_data.pkey)
             if res == 0:
                 self._token = tok
             else:
                 self._server = None
         except Exception:
             self._server = None
             raise RetryableJobError(_("Can't connect with channel!"))
     return self._server
示例#20
0
 def odoo_execute_kw(model, method, *args, **kwargs):
     log_transmission(
         "XMLRPC DB={} URL={}".format(params.DB, params.URL),
         json.dumps([method, args, kwargs]),
     )
     try:
         common = _client.ServerProxy("{}/xmlrpc/2/common".format(
             params.URL))
         uid = common.authenticate(params.DB, secrets.USERNAME,
                                   secrets.PASSWORD, {})
         models = _client.ServerProxy("{}/xmlrpc/2/object".format(
             params.URL))
     except OSError:
         raise RetryableJobError("Error on connecting to external Odoo")
     res = models.execute_kw(params.DB, uid, secrets.PASSWORD, model,
                             method, args, kwargs)
     log("Response: %s" % res, level="debug")
     return res
示例#21
0
    def _get_report(self, report_list_id):
        report_api = Reports(backend=self._backend)

        try:
            response = report_api.get_report(report_id=report_list_id)
            if response and response.response and response.response.status_code == 200:
                return response
        except MWSError as e:
            _logger.error("report_api(%s) failed with report_ids %s",
                          '_get_report ', report_list_id)
            if len(e.args) and e.args[0].lower().index('Request is throttled'):
                raise RetryableJobError(
                    msg=
                    'The request to get_report is throttled, we must wait 5 minutes',
                    seconds=300,
                    ignore_retry=True)
            raise FailedJobError(
                'The request to get report failed: report_list_id: %s (%s)',
                report_list_id, e.args[0] if len(e.args) and e.args[0] else '')
示例#22
0
 def pick_1st_variant(prods):
     # NOTE: if the order is changed by adding `asc/desc` this can be broken
     # but it's very unlikely that the default order for product.product
     # will be changed.
     try:
         ordered = sorted(prods,
                          key=lambda var: [var[x] for x in order_by])
     except TypeError as orig_exception:
         # TypeError: '<' not supported between instances of 'bool' and 'str'
         # It means we don't have all values to determine this value.
         msg = _(
             "Cannot determine main variant for template ID: %s."
             "\nAt least one variant misses one of these values: %s."
             "\nWill try again later till 'max retries' count is reached."
         ) % (prods[0]["tmpl_record_id"], ", ".join(order_by))
         # This issue might depend on incomplete state of product info.
         # Eg: missing translation for variant matching current lang.
         # Let's retry later a bunch of times (5 by default).
         raise RetryableJobError(msg) from orig_exception
     return ordered[0].get("id") if ordered else None
示例#23
0
 def call(self, method, arguments, http_method=None, storeview=None):
     try:
         # When Magento is installed on PHP 5.4+, the API
         # may return garble data if the arguments contain
         # trailing None.
         if isinstance(arguments, list):
             while arguments and arguments[-1] is None:
                 arguments.pop()
         start = datetime.now()
         try:
             result = self.api_call(
                 method, arguments, http_method=http_method,
                 storeview=storeview)
         except Exception:
             _logger.error("api.call('%s', %s) failed", method, arguments)
             raise
         else:
             _logger.debug("api.call('%s', %s) returned %s in %s seconds",
                           method, arguments, result,
                           (datetime.now() - start).seconds)
         # Uncomment to record requests/responses in ``recorder``
         # record(method, arguments, result)
         return result
     except (socket.gaierror, socket.error, socket.timeout) as err:
         raise NetworkRetryableError(
             'A network error caused the failure of the job: '
             '%s' % err)
     except xmlrpc.client.ProtocolError as err:
         if err.errcode in [502,   # Bad gateway
                            503,   # Service unavailable
                            504]:  # Gateway timeout
             raise RetryableJobError(
                 'A protocol error caused the failure of the job:\n'
                 'URL: %s\n'
                 'HTTP/HTTPS headers: %s\n'
                 'Error code: %d\n'
                 'Error message: %s\n' %
                 (err.url, err.headers, err.errcode, err.errmsg))
         else:
             raise
示例#24
0
 def check(self, record):
     # cannot import this as a dependency since the data source was posted, not imported
     partner_binder = self.binder_for('magento.res.partner')
     partner_binding = partner_binder.to_internal(record['CustomerID'])
     if not partner_binding:
         raise RetryableJobError(
             "Couldn't find respective Customer ID - %s "
             "for the Request ID - %s" %
             (record['CustomerID'], record['request_id']),
             seconds=1 * 60)
     rewards_binder = self.binder_for('magento.res.partner.rewards')
     rewards_binding = rewards_binder.to_internal(record['CustomerID'])
     if rewards_binding:
         existing_date = datetime.strptime(
             rewards_binding.magento_date_modified, '%Y-%m-%d %H:%M:%S')
         modified_dt = datetime.strptime(record['ModifiedDate'],
                                         '%Y-%m-%d %H:%M:%S')
         if modified_dt < existing_date:
             raise NothingToDoJob(
                 "The most recent rewards point already imported with this Request ID - %s.\n"
                 "This is old Request ID - %s. Odoo ID - %s" %
                 (rewards_binding.request_id, record['request_id'],
                  rewards_binding.odoo_id.id))
示例#25
0
 def import_record(self, backend, external_id):
     _super = super(AmazonSaleOrder, self)
     result = _super.import_record(backend, external_id)
     if not result:
         raise RetryableJobError("The sale of the backend %s hasn\'t could not be imported.\n %s", backend.name, external_id, 60)
示例#26
0
    def run(self, external_id, force=False, record=None, **kwargs):
        """ Run the synchronization

        A record can be given, reducing number of calls when
        a call already returns data (example: user returns addresses)

        :param external_id: identifier of the record on Jira
        """
        self.external_id = external_id
        lock_name = 'import({}, {}, {}, {})'.format(
            self.backend_record._name,
            self.backend_record.id,
            self.model._name,
            self.external_id,
        )
        # Keep a lock on this import until the transaction is committed
        self.advisory_lock_or_retry(lock_name,
                                    retry_seconds=RETRY_ON_ADVISORY_LOCK)
        if record is not None:
            self.external_record = record
        else:
            try:
                self.external_record = self._get_external_data()
            except IDMissingInBackend:
                return self._handle_record_missing_on_jira()
        binding = self._get_binding()
        if not binding:
            with self.do_in_new_work_context() as new_work:
                # Even when we use an advisory lock, we may have
                # concurrent issues.
                # Explanation:
                # We import Partner A and B, both of them import a
                # partner category X.
                #
                # The squares represent the duration of the advisory
                # lock, the transactions starts and ends on the
                # beginnings and endings of the 'Import Partner'
                # blocks.
                # T1 and T2 are the transactions.
                #
                # ---Time--->
                # > T1 /------------------------\
                # > T1 | Import Partner A       |
                # > T1 \------------------------/
                # > T1        /-----------------\
                # > T1        | Imp. Category X |
                # > T1        \-----------------/
                #                     > T2 /------------------------\
                #                     > T2 | Import Partner B       |
                #                     > T2 \------------------------/
                #                     > T2        /-----------------\
                #                     > T2        | Imp. Category X |
                #                     > T2        \-----------------/
                #
                # As you can see, the locks for Category X do not
                # overlap, and the transaction T2 starts before the
                # commit of T1. So no lock prevents T2 to import the
                # category X and T2 does not see that T1 already
                # imported it.
                #
                # The workaround is to open a new DB transaction at the
                # beginning of each import (e.g. at the beginning of
                # "Imp. Category X") and to check if the record has been
                # imported meanwhile. If it has been imported, we raise
                # a Retryable error so T2 is rollbacked and retried
                # later (and the new T3 will be aware of the category X
                # from the its inception).
                binder = new_work.component(usage='binder')
                if binder.to_internal(self.external_id):
                    raise RetryableJobError(
                        'Concurrent error. The job will be retried later',
                        seconds=RETRY_WHEN_CONCURRENT_DETECTED,
                        ignore_retry=True)

        reason = self.must_skip(force=force)
        if reason:
            return reason

        if not force and self._is_uptodate(binding):
            return _('Already up-to-date.')

        self._before_import()

        # import the missing linked resources
        self._import_dependencies()

        self._import(binding, **kwargs)
示例#27
0
    def calc_price_to_export(self, id_detail, force_change=False):
        """
        Method to change the prices of the detail product
        :return:
        """
        # If on product detail change_prices is 'yes'
        # If product detail change_prices is not 'no' and product change_prices is 'yes'
        # If product detail and product change_prices is not 'no' and backend change_prices is 'yes'
        detail = self.env['amazon.product.product.detail'].browse(id_detail)
        we_have_buybox = detail.is_buybox_mine()

        # We check if we can change prices
        if force_change or ((detail.change_prices == '1' or detail.product_id.change_prices == '1' or detail.product_id.backend_id.change_prices == '1') and \
                            (detail.change_prices != '0' and detail.product_id.change_prices != '0' and detail.product_id.backend_id.change_prices != '0')):
            # We are trying to get the fee of product
            try:
                if not detail.last_update_price_date or datetime.strptime(
                        detail.last_update_price_date, '%Y-%m-%d %H:%M:%S'
                ) < datetime.today() - timedelta(hours=24):
                    importer = self.work.component(
                        usage='amazon.product.price.import')
                    importer.run_update_price(detail)
            except Exception as e:
                raise RetryableJobError(
                    msg='An error has been produced on MWS API',
                    seconds=60,
                    ignore_retry=True)

            change_price = None
            # If we have the buybox now
            if we_have_buybox:
                change_price = self.up_price_with_buybox(detail)
            # If we have the buybox and we are not get this now, we to try to up the price
            else:
                change_price = self.change_price_to_get_buybox(detail)

            # If we are in this point, we are going to check if the price has been changed, if not we need to check if the price is between the margins, else we are going to update
            if not change_price:
                margin_price = detail._get_margin_price(
                    price=detail.price, price_ship=detail.price_ship)
                margin_min = detail.min_margin or detail.product_id.min_margin or detail.product_id.backend_id.min_margin
                margin_max = detail.max_margin or detail.product_id.max_margin or detail.product_id.backend_id.max_margin
                try_price = None
                if margin_price and margin_price[1] > margin_max:
                    try_price = detail.product_id.odoo_id._calc_amazon_price(
                        backend=detail.product_id.backend_id,
                        margin=margin_max,
                        marketplace=detail.marketplace_id,
                        percentage_fee=detail.percentage_fee
                        or AMAZON_DEFAULT_PERCENTAGE_FEE,
                        ship_price=detail.price_ship)
                elif margin_price and margin_price[1] < margin_min:
                    try_price = detail.product_id.odoo_id._calc_amazon_price(
                        backend=detail.product_id.backend_id,
                        margin=margin_min,
                        marketplace=detail.marketplace_id,
                        percentage_fee=detail.percentage_fee
                        or AMAZON_DEFAULT_PERCENTAGE_FEE,
                        ship_price=detail.price_ship)
                if try_price:
                    detail.price = try_price
                    return True
示例#28
0
    def _add_listing_to_amazon(self, record):
        if isinstance(record['product_id'], (int, float)):
            product = self.env['product.product'].browse(record['product_id'])
        else:
            product = record['product_id']

        marketplaces = record['marketplaces'] if record.get(
            'marketplaces') else self.backend_record.marketplace_ids
        margin = record['margin'] if record.get(
            'margin'
        ) else self.backend_record.max_margin or AMAZON_DEFAULT_PERCENTAGE_MARGIN

        # Get asin if we have this
        asin = None
        if not record.get('asin') and product and product.amazon_bind_ids:
            asin = product.amazon_bind_ids.asin if len(
                product.amazon_bind_ids
            ) < 2 else product.amazon_bind_ids[0].asin
        else:
            asin = record['asin'] if record.get('asin') else None

        product_doesnt_exist = True
        product_dont_match = False

        # We get the user language for match with the marketplace language
        user = self.env['res.users'].browse(self.env.uid)
        market_lang_match = marketplaces.filtered(
            lambda marketplace: marketplace.lang_id.code == user.lang)

        if market_lang_match and not asin:
            try:
                amazon_prod = self._get_asin_product(product,
                                                     market_lang_match)
            except RetryableJobError as e:
                raise RetryableJobError('The quota is Throttled', 1800, True)
            asin = amazon_prod['asin'] if amazon_prod and amazon_prod.get(
                'asin') else None
            product_dont_match = True if amazon_prod and amazon_prod.get(
                'Match name') == 'No' else False

        for marketplace in marketplaces:
            # If we haven't asin and we haven't searched yet, we search this
            if not asin and market_lang_match and market_lang_match.id != marketplace.id:
                try:
                    amazon_prod = self._get_asin_product(
                        product, market_lang_match)
                except RetryableJobError as e:
                    raise RetryableJobError('The quota is Throttled', 1800,
                                            True)
                asin = amazon_prod['asin'] if amazon_prod and amazon_prod.get(
                    'asin') else None
                product_dont_match = True if amazon_prod and amazon_prod.get(
                    'Match name') == 'No' else False

            add_product = False if not asin else True

            if not add_product:
                continue

            product_doesnt_exist = False

            price = product._calc_amazon_price(
                backend=self.backend_record,
                margin=margin,
                marketplace=marketplace,
                percentage_fee=AMAZON_DEFAULT_PERCENTAGE_FEE)

            if price:
                row = {}
                row['sku'] = product.default_code or product.product_variant_id.default_code
                row['product-id'] = asin
                row['product-id-type'] = 'ASIN'
                price = product._calc_amazon_price(
                    backend=self.backend_record,
                    margin=margin,
                    marketplace=marketplace,
                    percentage_fee=AMAZON_DEFAULT_PERCENTAGE_FEE)
                row['price'] = ("%.2f" % price).replace(
                    '.',
                    marketplace.decimal_currency_separator) if price else ''
                row['minimum-seller-allowed-price'] = ''
                row['maximum-seller-allowed-price'] = ''
                row['item-condition'] = '11'  # We assume the products are new
                row['quantity'] = '0'  # The products stocks allways is 0 when we export these
                row['add-delete'] = 'a'
                row['will-ship-internationally'] = ''
                row['expedited-shipping'] = ''
                row['merchant-shipping-group-name'] = ''
                handling_time = product._compute_amazon_handling_time() or ''
                row['handling-time'] = str(handling_time) if price else ''
                row['item_weight'] = ''
                row['item_weight_unit_of_measure'] = ''
                row['item_volume'] = ''
                row['item_volume_unit_of_measure'] = ''
                row['id_mws'] = marketplace.id_mws

                vals = {
                    'backend_id': self.backend_record.id,
                    'type': '_POST_FLAT_FILE_INVLOADER_DATA_',
                    'model': product._name,
                    'identificator': product.id,
                    'data': row,
                }
                self.env['amazon.feed.tothrow'].create(vals)

        if product_doesnt_exist and not product_dont_match:
            # TODO Create a list of products to create
            vals = {'product_id': product.product_tmpl_id.id}
            self.env['amazon.report.product.to.create'].create(vals)