diff --git a/__unported__/connector_ecommerce/payment_method.py b/__unported__/connector_ecommerce/payment_method.py deleted file mode 100644 index 928fc148..00000000 --- a/__unported__/connector_ecommerce/payment_method.py +++ /dev/null @@ -1,72 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Copyright 2011-2013 Akretion -# @author Sébastien BEAU -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -from openerp.osv import orm, fields - - -class payment_method(orm.Model): - _inherit = "payment.method" - - def _get_import_rules(self, cr, uid, context=None): - return [('always', 'Always'), - ('never', 'Never'), - ('paid', 'Paid'), - ('authorized', 'Authorized'), - ] - - def __get_import_rules(self, cr, uid, context=None): - return self._get_import_rules(cr, uid, context=context) - - _columns = { - # the logic around the 2 following fields has to be implemented - # in the connectors (magentoerpconnect, prestashoperpconnect,...) - 'days_before_cancel': fields.integer( - 'Days before cancel', - help="After 'n' days, if the 'Import Rule' is not fulfilled, the " - "import of the sale order will be canceled."), - 'import_rule': fields.selection(__get_import_rules, - string="Import Rule", - required=True) - } - - _defaults = { - 'import_rule': 'always', - 'days_before_cancel': 30, - } - - def get_or_create_payment_method(self, cr, uid, payment_method, - context=None): - """ - try to get id of 'payment_method' or create if not exists - :param str payment_method: payment method like PayPal, etc. - :rtype: int - :return: id of required payment method - """ - pay_method_obj = self.pool.get('payment.method') - domain = [('name', '=ilike', payment_method)] - method_ids = pay_method_obj.search(cr, uid, domain, context=context) - if method_ids: - method_id = method_ids[0] - else: - method_id = pay_method_obj.create(cr, uid, - {'name': payment_method}, - context=context) - return method_id diff --git a/__unported__/connector_ecommerce/product.py b/__unported__/connector_ecommerce/product.py deleted file mode 100644 index dcf88ef2..00000000 --- a/__unported__/connector_ecommerce/product.py +++ /dev/null @@ -1,179 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Author: Sébastien BEAU -# Copyright 2011-2013 Akretion -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -from openerp.osv import orm, fields -from openerp.addons.connector.session import ConnectorSession -from .event import on_product_price_changed - - -class product_template(orm.Model): - _inherit = 'product.template' - - # TODO implement set function and also support multi tax - def _get_tax_group_id(self, cr, uid, ids, field_name, args, context=None): - result = {} - for product in self.browse(cr, uid, ids, context=context): - taxes = product.taxes_id - result[product.id] = taxes[0].group_id.id if taxes else False - return result - - _columns = { - 'tax_group_id': fields.function( - _get_tax_group_id, - string='Tax Group', - type='many2one', - relation='account.tax.group', - store=False, - help='Tax group are used with some external ' - 'system like Prestashop'), - } - - def _price_changed(self, cr, uid, ids, vals, context=None): - """ Fire the ``on_product_price_changed`` on all the variants of - the template if the price if the product could have changed. - - If one of the field used in a sale pricelist item has been - modified, we consider that the price could have changed. - - There is no guarantee that's the price actually changed, - because it depends on the pricelists. - """ - if context is None: - context = {} - type_obj = self.pool['product.price.type'] - price_fields = type_obj.sale_price_fields(cr, uid, context=context) - # restrict the fields to the template ones only, so if - # the write has been done on product.product, we won't - # update all the variant if a price field of the - # variant has been changed - tmpl_fields = [field for field in vals if field in self._columns] - if any(field in price_fields for field in tmpl_fields): - product_obj = self.pool['product.product'] - session = ConnectorSession(cr, uid, context=context) - product_ids = product_obj.search(cr, uid, - [('product_tmpl_id', 'in', ids)], - context=context) - # when the write is done on the product.product, avoid - # to fire the event 2 times - if context.get('from_product_ids'): - product_ids = list(set(product_ids) - - set(context['from_product_ids'])) - for prod_id in product_ids: - on_product_price_changed.fire(session, - product_obj._name, - prod_id) - - def write(self, cr, uid, ids, vals, context=None): - if isinstance(ids, (int, long)): - ids = [ids] - result = super(product_template, self).write(cr, uid, ids, - vals, context=context) - self._price_changed(cr, uid, ids, vals, context=context) - return result - - -class product_product(orm.Model): - _inherit = 'product.product' - - def _get_checkpoint(self, cr, uid, ids, name, arg, context=None): - result = {} - checkpoint_obj = self.pool.get('connector.checkpoint') - model_obj = self.pool.get('ir.model') - model_id = model_obj.search(cr, uid, - [('model', '=', 'product.product')], - context=context)[0] - for product_id in ids: - point_ids = checkpoint_obj.search(cr, uid, - [('model_id', '=', model_id), - ('record_id', '=', product_id), - ('state', '=', 'need_review')], - context=context) - result[product_id] = bool(point_ids) - return result - - _columns = { - 'has_checkpoint': fields.function(_get_checkpoint, - type='boolean', - readonly=True, - string='Has Checkpoint'), - } - - def _price_changed(self, cr, uid, ids, vals, context=None): - """ Fire the ``on_product_price_changed`` if the price - if the product could have changed. - - If one of the field used in a sale pricelist item has been - modified, we consider that the price could have changed. - - There is no guarantee that's the price actually changed, - because it depends on the pricelists. - """ - type_obj = self.pool['product.price.type'] - price_fields = type_obj.sale_price_fields(cr, uid, context=context) - if any(field in price_fields for field in vals): - session = ConnectorSession(cr, uid, context=context) - for prod_id in ids: - on_product_price_changed.fire(session, self._name, prod_id) - - def write(self, cr, uid, ids, vals, context=None): - if context is None: - context = {} - if isinstance(ids, (int, long)): - ids = [ids] - context = context.copy() - context['from_product_ids'] = ids - result = super(product_product, self).write( - cr, uid, ids, vals, context=context) - self._price_changed(cr, uid, ids, vals, context=context) - return result - - def create(self, cr, uid, vals, context=None): - product_ids = super(product_product, self).create( - cr, uid, vals, context=context) - self._price_changed(cr, uid, [product_ids], vals, context=context) - return product_ids - - -class product_price_type(orm.Model): - _inherit = 'product.price.type' - - _columns = { - 'pricelist_item_ids': fields.one2many( - 'product.pricelist.item', 'base', - string='Pricelist Items', - readonly=True) - } - - def sale_price_fields(self, cr, uid, context=None): - """ Returns a list of fields used by sale pricelists. - Used to know if the sale price could have changed - when one of these fields has changed. - """ - item_obj = self.pool['product.pricelist.item'] - item_ids = item_obj.search( - cr, uid, - [('price_version_id.pricelist_id.type', '=', 'sale')], - context=context) - type_ids = self.search(cr, uid, - [('pricelist_item_ids', 'in', item_ids)], - context=context) - types = self.read(cr, uid, type_ids, ['field'], context=context) - return [t['field'] for t in types] diff --git a/__unported__/connector_ecommerce/sale.py b/__unported__/connector_ecommerce/sale.py deleted file mode 100644 index f6526bcc..00000000 --- a/__unported__/connector_ecommerce/sale.py +++ /dev/null @@ -1,525 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Author: Guewen Baconnier -# Copyright 2011-2013 Camptocamp SA -# Author: Sébastien Beau -# Copyright 2010-2013 Akretion -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -import logging - -from openerp.osv import orm, fields, osv -from openerp.tools.translate import _ -from openerp import netsvc -from openerp.addons.connector.connector import ConnectorUnit - -_logger = logging.getLogger(__name__) - - -class sale_shop(orm.Model): - _inherit = 'sale.shop' - - def _get_payment_default(self, cr, uid, context=None): - """ Return a arbitrary account.payment.term record for the sale.shop - - ``sale.shop`` records are created dynamically from the backends - and the field ``payment_default_id`` needs a default value. - """ - data_obj = self.pool.get('ir.model.data') - __, payment_id = data_obj.get_object_reference( - cr, uid, 'account', 'account_payment_term_immediate') - return payment_id - - _defaults = { - # see method docstring for explanation - 'payment_default_id': _get_payment_default, - } - - -class sale_order(orm.Model): - """ Add a cancellation mecanism in the sales orders - - When a sale order is canceled in a backend, the connectors can flag - the 'canceled_in_backend' flag. It will: - - * try to automatically cancel the sales order - * block the confirmation of the sales orders using a 'sales exception' - - When a sales order is canceled or the user used the button to force - to 'keep it open', the flag 'cancellation_resolved' is set to True. - - The second axe which can be used by the connectors is the 'parent' - sale order. When a sales order has a parent sales order (logic to - link with the parent to be defined by each connector), it will be - blocked until the cancellation of the sale order is resolved. - - This is used by, for instance, the magento connector, when one - modifies a sales order, Magento cancels it and create a new one with - the first one as parent. - """ - _inherit = 'sale.order' - - def _get_parent_id(self, cr, uid, ids, name, arg, context=None): - return self.get_parent_id(cr, uid, ids, context=context) - - def get_parent_id(self, cr, uid, ids, context=None): - """ Need to be inherited in the connectors to implement the - parent logic. - - See an implementation example in ``magentoerpconnect``. - """ - return dict.fromkeys(ids, False) - - def _get_need_cancel(self, cr, uid, ids, name, arg, context=None): - result = {} - for order in self.browse(cr, uid, ids, context=context): - result[order.id] = self._need_cancel(cr, uid, order, - context=context) - return result - - def _get_parent_need_cancel(self, cr, uid, ids, name, arg, context=None): - result = {} - for order in self.browse(cr, uid, ids, context=context): - result[order.id] = self._parent_need_cancel(cr, uid, order, - context=context) - return result - - _columns = { - 'canceled_in_backend': fields.boolean('Canceled in backend', - readonly=True), - # set to True when the cancellation from the backend is - # resolved, either because the SO has been canceled or - # because the user manually chosed to keep it open - 'cancellation_resolved': fields.boolean('Cancellation from the ' - 'backend resolved'), - 'parent_id': fields.function(_get_parent_id, - string='Parent Order', - type='many2one', - help='A parent sales order is a sales ' - 'order replaced by this one.', - relation='sale.order'), - 'need_cancel': fields.function(_get_need_cancel, - string='Need to be canceled', - type='boolean', - help='Has been canceled on the backend' - ', need to be canceled.'), - 'parent_need_cancel': fields.function( - _get_parent_need_cancel, - string='A parent sales orders needs cancel', - type='boolean', - help='A parent sales orders has been canceled on the backend' - ' and needs to be canceled.'), - } - - def _need_cancel(self, cr, uid, order, context=None): - """ Return True if the sales order need to be canceled - (has been canceled on the Backend) """ - return order.canceled_in_backend and not order.cancellation_resolved - - def _parent_need_cancel(self, cr, uid, order, context=None): - """ Return True if at least one parent sales order need to - be canceled (has been canceled on the backend). - Follows all the parent sales orders. - """ - def need_cancel(order): - if self._need_cancel(cr, uid, order, context=context): - return True - if order.parent_id: - return need_cancel(order.parent_id) - else: - return False - if not order.parent_id: - return False - return need_cancel(order.parent_id) - - def _try_auto_cancel(self, cr, uid, ids, context=None): - """ Try to automatically cancel a sales order canceled - in a backend. - - If it can't cancel it, does nothing. - """ - wkf_states = ('draft', 'sent') - action_states = ('manual', 'progress') - wf_service = netsvc.LocalService("workflow") - resolution_msg = _("

Resolution:

    " - "
  1. Cancel the linked invoices, delivery " - "orders, automatic payments.
  2. " - "
  3. Cancel the sales order manually.
  4. " - "

") - for order_id in ids: - state = self.read(cr, uid, order_id, - ['state'], context=context)['state'] - if state == 'cancel': - continue - elif state == 'done': - message = _("The sales order cannot be automatically " - "canceled because it is already done.") - elif state in wkf_states + action_states: - try: - # respect the same cancellation methods than - # the sales order view: quotations use the workflow - # action, sales orders use the action_cancel method. - if state in wkf_states: - wf_service.trg_validate(uid, 'sale.order', - order_id, 'cancel', cr) - elif state in action_states: - self.action_cancel(cr, uid, order_id, context=context) - else: - raise ValueError('%s should not fall here.' % state) - except osv.except_osv: - # the 'cancellation_resolved' flag will stay to False - message = _("The sales order could not be automatically " - "canceled.") + resolution_msg - else: - message = _("The sales order has been automatically " - "canceled.") - else: - # shipping_except, invoice_except, ... - # can not be canceled from the view, so assume that it - # should not be canceled here neiter, exception to - # resolve - message = _("The sales order could not be automatically " - "canceled for this status.") + resolution_msg - self.message_post(cr, uid, [order_id], body=message, - context=context) - - def _log_canceled_in_backend(self, cr, uid, ids, context=None): - message = _("The sales order has been canceled on the backend.") - self.message_post(cr, uid, ids, body=message, context=context) - for order in self.browse(cr, uid, ids, context=context): - message = _("Warning: the origin sales order %s has been canceled " - "on the backend.") % order.name - if order.picking_ids: - picking_obj = self.pool.get('stock.picking') - picking_obj.message_post(cr, uid, order.picking_ids, - body=message, context=context) - if order.invoice_ids: - picking_obj = self.pool.get('account.invoice') - picking_obj.message_post(cr, uid, order.invoice_ids, - body=message, context=context) - - def create(self, cr, uid, values, context=None): - order_id = super(sale_order, self).create(cr, uid, values, - context=context) - if values.get('canceled_in_backend'): - self._log_canceled_in_backend(cr, uid, [order_id], context=context) - self._try_auto_cancel(cr, uid, [order_id], context=context) - return order_id - - def write(self, cr, uid, ids, values, context=None): - result = super(sale_order, self).write(cr, uid, ids, values, - context=context) - if values.get('canceled_in_backend'): - self._log_canceled_in_backend(cr, uid, ids, context=context) - self._try_auto_cancel(cr, uid, ids, context=context) - return result - - def action_cancel(self, cr, uid, ids, context=None): - if not hasattr(ids, '__iter__'): - ids = [ids] - super(sale_order, self).action_cancel(cr, uid, ids, context=context) - sales = self.read(cr, uid, ids, - ['canceled_in_backend', - 'cancellation_resolved'], - context=context) - for sale in sales: - # the sale order is canceled => considered as resolved - if (sale['canceled_in_backend'] and - not sale['cancellation_resolved']): - self.write(cr, uid, sale['id'], - {'cancellation_resolved': True}, - context=context) - return True - - def ignore_cancellation(self, cr, uid, ids, reason, context=None): - """ Manually set the cancellation from the backend as resolved. - - The user can choose to keep the sale order active for some reason, - so it just push a button to keep it alive. - """ - message = (_("Despite the cancellation of the sales order on the " - "backend, it should stay open.

Reason: %s") % - reason) - self.message_post(cr, uid, ids, body=message, context=context) - self.write(cr, uid, ids, - {'cancellation_resolved': True}, - context=context) - return True - - def action_view_parent(self, cr, uid, ids, context=None): - """ Return an action to display the parent sale order """ - if isinstance(ids, (list, tuple)): - assert len(ids) == 1 - ids = ids[0] - - mod_obj = self.pool.get('ir.model.data') - act_obj = self.pool.get('ir.actions.act_window') - - parent = self.browse(cr, uid, ids, context=context).parent_id - if not parent: - return - - view_xmlid = ('sale', 'view_order_form') - if parent.state in ('draft', 'sent', 'cancel'): - action_xmlid = ('sale', 'action_quotations') - else: - action_xmlid = ('sale', 'action_orders') - - ref = mod_obj.get_object_reference(cr, uid, *action_xmlid) - action_id = False - if ref: - __, action_id = ref - action = act_obj.read(cr, uid, [action_id], context=context)[0] - - ref = mod_obj.get_object_reference(cr, uid, *view_xmlid) - action['views'] = [(ref[1] if ref else False, 'form')] - action['res_id'] = parent.id - return action - - # TODO: remove in odoo 8.0 - def _convert_special_fields(self, cr, uid, vals, order_lines, - context=None): - """ Deprecated! Replaced by LineBuilder classes - - Convert the special 'fake' field into an order line. - - Special fields are: - - shipping amount and shipping_tax_rate - - cash_on_delivery and cash_on_delivery_taxe_rate - - gift_certificates - - :param vals: values of the sale order to create - :type vals: dict - :param order_lines: lines of the orders to import - :return: the value for the sale order with the special field converted - :rtype: dict - """ - _logger.warning('sale_order._convert_special_fields() has been ' - 'deprecated. Use a specialized ' - 'SpecialOrderLineBuilder class instead.') - shipping_fields = ['shipping_amount_tax_excluded', - 'shipping_amount_tax_included', - 'shipping_tax_amount'] - - def check_key(keys): - return len(set(shipping_fields) & set(keys)) >= 2 - - vals.setdefault('order_line', []) - for line in order_lines: - for field in shipping_fields: - if field in line[2]: - vals[field] = vals.get(field, 0.0) + line[2][field] - del line[2][field] - - if 'shipping_tax_rate' not in vals and check_key(vals.keys()): - if 'shipping_amount_tax_excluded' not in vals: - amount_incl = vals['shipping_amount_tax_included'] - tax_amount = vals['shipping_tax_amount'] - tax_excl = (amount_incl - tax_amount) - vals['shipping_amount_tax_excluded'] = tax_excl - - elif 'shipping_tax_amount' not in vals: - amount_incl = vals['shipping_amount_tax_included'] - amount_excl = vals['shipping_amount_tax_excluded'] - vals['shipping_tax_amount'] = (amount_incl - amount_excl) - if vals['shipping_amount_tax_excluded']: - tax_amount = vals['shipping_tax_amount'] - tax_amount_excl = vals['shipping_amount_tax_excluded'] - vals['shipping_tax_rate'] = (tax_amount / tax_amount_excl) - else: - vals['shipping_tax_rate'] = 0. - del vals['shipping_tax_amount'] - for option in self._get_special_fields(cr, uid, context=context): - vals = self._add_order_extra_line(cr, uid, vals, - option, context=context) - return vals - - # TODO: remove in odoo 8.0 - def _get_special_fields(self, cr, uid, context=None): - """ Deprecated! Replaced by LineBuilder classes """ - return [ - {'price_unit_tax_excluded': 'shipping_amount_tax_excluded', - 'price_unit_tax_included': 'shipping_amount_tax_included', - 'tax_rate_field': 'shipping_tax_rate', - 'product_ref': ('connector_ecommerce', - 'product_product_shipping'), - }, - {'tax_rate_field': 'cash_on_delivery_taxe_rate', - 'price_unit_tax_excluded': 'cash_on_delivery_amount_tax_excluded', - 'price_unit_tax_included': 'cash_on_delivery_amount_tax_included', - 'product_ref': ('connector_ecommerce', - 'product_product_cash_on_delivery'), - }, - # gift certificate doesn't have any tax - {'price_unit_tax_excluded': 'gift_certificates_amount', - 'price_unit_tax_included': 'gift_certificates_amount', - 'product_ref': ('connector_ecommerce', 'product_product_gift'), - 'code_field': 'gift_certificates_code', - 'sign': -1, - }, - ] - - # TODO: remove in odoo 8.0 - def _get_order_extra_line_vals(self, cr, uid, vals, option, product, - price_unit, context=None): - """ Deprecated! Replaced by LineBuilder classes """ - return { - 'product_id': product.id, - 'name': product.name, - 'product_uom': product.uom_id.id, - 'product_uom_qty': 1, - 'price_unit': price_unit - } - - # TODO: remove in odoo 8.0 - def _add_order_extra_line(self, cr, uid, vals, option, context=None): - """ Deprecated! Replaced by LineBuilder classes - - Add or substract amount on order as a separate line item - with single quantity for each type of amounts like: shipping, - cash on delivery, discount, gift certificates... - - :param dict vals: values of the sale order to create - :param option: dictionary of options for the special field to process - """ - if context is None: - context = {} - sign = option.get('sign', 1) - if (context.get('is_tax_included') and - vals.get(option['price_unit_tax_included'])): - price_unit = vals.pop(option['price_unit_tax_included']) * sign - elif vals.get(option['price_unit_tax_excluded']): - price_unit = vals.pop(option['price_unit_tax_excluded']) * sign - else: - return self._clean_special_fields(option, vals) - model_data_obj = self.pool.get('ir.model.data') - product_obj = self.pool.get('product.product') - __, product_id = model_data_obj.get_object_reference( - cr, uid, *option['product_ref']) - product = product_obj.browse(cr, uid, product_id, context=context) - - extra_line = self._get_order_extra_line_vals( - cr, uid, vals, option, product, price_unit, context=context) - - ext_code_field = option.get('code_field') - if ext_code_field and vals.get(ext_code_field): - extra_line['name'] = "%s [%s]" % (extra_line['name'], - vals[ext_code_field]) - vals['order_line'].append((0, 0, extra_line)) - return self._clean_special_fields(option, vals) - - # TODO: remove in odoo 8.0 - def _clean_special_fields(self, option, vals): - """ Deprecated! Replaced by LineBuilder classes """ - for key in ['price_unit_tax_excluded', - 'price_unit_tax_included', - 'tax_rate_field']: - if option.get(key) and option[key] in vals: - del vals[option[key]] - return vals # if there is no price, we have nothing to import - - -class SpecialOrderLineBuilder(ConnectorUnit): - """ Base class to build a sale order line for a sale order - - Used when extra order lines have to be added in a sale order - but we only know some parameters (product, price, ...), for instance, - a line for the shipping costs or the gift coupons. - - It can be subclassed to customize the way the lines are created. - - Usage:: - - builder = self.get_connector_for_unit(ShippingLineBuilder, - model='sale.order.line') - builder.price_unit = 100 - builder.get_line() - - """ - _model_name = None - - def __init__(self, environment): - super(SpecialOrderLineBuilder, self).__init__(environment) - self.product = None # id or browse_record - # when no product_id, fallback to a product_ref - self.product_ref = None # tuple (module, xmlid) - self.price_unit = None - self.quantity = 1 - self.sign = 1 - self.sequence = 980 - - def get_line(self): - assert self.product_ref or self.product - assert self.price_unit is not None - session = self.session - - product = product_id = self.product - if product_id is None: - model_data_obj = session.pool.get('ir.model.data') - __, product_id = model_data_obj.get_object_reference( - session.cr, session.uid, *self.product_ref) - - if not isinstance(product_id, orm.browse_record): - product = session.browse('product.product', product_id) - return {'product_id': product.id, - 'name': product.name, - 'product_uom': product.uom_id.id, - 'product_uom_qty': self.quantity, - 'price_unit': self.price_unit * self.sign, - 'sequence': self.sequence} - - -class ShippingLineBuilder(SpecialOrderLineBuilder): - """ Return values for a Shipping line """ - _model_name = None - - def __init__(self, environment): - super(ShippingLineBuilder, self).__init__(environment) - self.product_ref = ('connector_ecommerce', 'product_product_shipping') - self.sequence = 999 - - -class CashOnDeliveryLineBuilder(SpecialOrderLineBuilder): - """ Return values for a Cash on Delivery line """ - _model_name = None - _model_name = None - - def __init__(self, environment): - super(CashOnDeliveryLineBuilder, self).__init__(environment) - self.product_ref = ('connector_ecommerce', - 'product_product_cash_on_delivery') - self.sequence = 995 - - -class GiftOrderLineBuilder(SpecialOrderLineBuilder): - """ Return values for a Gift line """ - _model_name = None - - def __init__(self, environment): - super(GiftOrderLineBuilder, self).__init__(environment) - self.product_ref = ('connector_ecommerce', - 'product_product_gift') - self.sign = -1 - self.gift_code = None - self.sequence = 990 - - def get_line(self): - line = super(GiftOrderLineBuilder, self).get_line() - if self.gift_code: - line['name'] = "%s [%s]" % (line['name'], self.gift_code) - return line diff --git a/__unported__/connector_ecommerce/stock.py b/__unported__/connector_ecommerce/stock.py deleted file mode 100644 index 121633f0..00000000 --- a/__unported__/connector_ecommerce/stock.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Author: Joel Grand-Guillaume -# Copyright 2013 Camptocamp SA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -from openerp.osv import orm, fields - -from openerp.addons.connector.session import ConnectorSession -from .event import on_picking_out_done, on_tracking_number_added - - -class stock_picking(orm.Model): - _inherit = 'stock.picking' - - _columns = { - 'related_backorder_ids': fields.one2many( - 'stock.picking', 'backorder_id', - string="Related backorders"), - } - - def action_done(self, cr, uid, ids, context=None): - res = super(stock_picking, self).action_done(cr, uid, - ids, context=context) - session = ConnectorSession(cr, uid, context=context) - # Look if it exists a backorder, in that case call for partial - picking_records = self.read(cr, uid, ids, - ['id', 'related_backorder_ids', 'type'], - context=context) - for picking_vals in picking_records: - if picking_vals['type'] != 'out': - continue - if picking_vals['related_backorder_ids']: - picking_method = 'partial' - else: - picking_method = 'complete' - on_picking_out_done.fire(session, self._name, - picking_vals['id'], picking_method) - return res - - def copy(self, cr, uid, id, default=None, context=None): - if default is None: - default = {} - else: - default = default.copy() - default['related_backorder_ids'] = False - return super(stock_picking, self).copy(cr, uid, - id, default, context=context) - - -class stock_picking_out(orm.Model): - _inherit = 'stock.picking.out' - - def write(self, cr, uid, ids, vals, context=None): - if not hasattr(ids, '__iter__'): - ids = [ids] - res = super(stock_picking_out, self).write(cr, uid, ids, - vals, context=context) - if vals.get('carrier_tracking_ref'): - session = ConnectorSession(cr, uid, context=context) - for record_id in ids: - on_tracking_number_added.fire(session, self._name, record_id) - return res diff --git a/__unported__/connector_ecommerce/stock_view.xml b/__unported__/connector_ecommerce/stock_view.xml deleted file mode 100644 index cc102b30..00000000 --- a/__unported__/connector_ecommerce/stock_view.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - stock.picking.out.connector.form - stock.picking.out - - - - - - - - - - - diff --git a/__unported__/connector_ecommerce/tests/test_onchange.py b/__unported__/connector_ecommerce/tests/test_onchange.py deleted file mode 100644 index f857f050..00000000 --- a/__unported__/connector_ecommerce/tests/test_onchange.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################### -# -# connector-ecommerce for OpenERP -# Copyright (C) 2013-TODAY Akretion . -# @author Sébastien BEAU -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################### - -import mock - -from openerp.addons.connector_ecommerce.unit.sale_order_onchange import ( - SaleOrderOnChange) -from openerp.addons.connector.session import ConnectorSession -from openerp.addons.connector.connector import Environment -import openerp.tests.common as common - -DB = common.DB -ADMIN_USER_ID = common.ADMIN_USER_ID - - -class test_onchange(common.TransactionCase): - """ Test if the onchanges are applied correctly on a sale order""" - - def setUp(self): - super(test_onchange, self).setUp() - self.session = ConnectorSession(self.cr, self.uid) - - def test_play_onchange(self): - """ Play the onchange ConnectorUnit on a sale order """ - product_model = self.registry('product.product') - partner_model = self.registry('res.partner') - shop_model = self.registry('sale.shop') - tax_model = self.registry('account.tax') - cr, uid = self.cr, self.uid - - backend_record = mock.Mock() - env = Environment(backend_record, self.session, 'sale.order') - - partner_id = partner_model.create(cr, uid, - {'name': 'seb', - 'zip': '69100', - 'city': 'Villeurbanne'}) - partner_invoice_id = partner_model.create(cr, uid, - {'name': 'Guewen', - 'zip': '1015', - 'city': 'Lausanne', - 'type': 'invoice', - 'parent_id': partner_id}) - tax_id = tax_model.create(cr, uid, {'name': 'My Tax'}) - product_id = product_model.create(cr, uid, - {'default_code': 'MyCode', - 'name': 'My Product', - 'weight': 15, - 'taxes_id': [(6, 0, [tax_id])]}) - shop_id = shop_model.create(cr, uid, {'name': 'My shop'}) - - order_input = { - 'shop_id': shop_id, - 'name': 'mag_10000001', - 'partner_id': partner_id, - 'order_line': [ - (0, 0, {'product_id': product_id, - 'price_unit': 20, - 'name': 'My Real Name', - 'product_uom_qty': 1, - } - ), - ] - } - - onchange = SaleOrderOnChange(env) - order = onchange.play(order_input, - order_input['order_line']) - - self.assertEqual(order['partner_invoice_id'], partner_invoice_id) - line = order['order_line'][0][2] - self.assertEqual(line['name'], 'My Real Name') - self.assertEqual(line['th_weight'], 15) - self.assertEqual(line['tax_id'][0][2][0], tax_id) diff --git a/__unported__/connector_ecommerce/unit/__init__.py b/__unported__/connector_ecommerce/unit/__init__.py deleted file mode 100644 index b9150933..00000000 --- a/__unported__/connector_ecommerce/unit/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -import sale_order_onchange diff --git a/__unported__/connector_ecommerce/unit/sale_order_onchange.py b/__unported__/connector_ecommerce/unit/sale_order_onchange.py deleted file mode 100644 index 06702067..00000000 --- a/__unported__/connector_ecommerce/unit/sale_order_onchange.py +++ /dev/null @@ -1,265 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# connector-ecommerce for OpenERP -# Copyright (C) 2013-TODAY Akretion . -# @author Sébastien BEAU -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -from openerp.addons.connector.connector import ConnectorUnit - - -class OnChangeManager(ConnectorUnit): - def merge_values(self, record, on_change_result): - vals = on_change_result.get('value', {}) - for key in vals: - if key not in record: - record[key] = vals[key] - - -class SaleOrderOnChange(OnChangeManager): - _model_name = None - - def _get_partner_id_onchange_param(self, order): - """ Prepare the arguments for calling the partner_id change - on sale order. You can overwrite this method in your own - module if they modify the onchange signature - - :param order: a dictionary of the value of your sale order - :type: dict - - :return: a tuple of args and kwargs for the onchange - :rtype: tuple - """ - args = [ - None, # sale order ids not needed - order['partner_id'], - ] - kwargs = {'context': self.session.context} - return args, kwargs - - def _get_shop_id_onchange_param(self, order): - args = [None, - order['shop_id']] - kwargs = {'context': self.session.context} - return args, kwargs - - def _get_payment_method_id_onchange_param(self, order): - args = [None, - order['payment_method_id']] - kwargs = {'context': self.session.context} - return args, kwargs - - def _get_workflow_process_id_onchange_param(self, order): - args = [None, - order['workflow_process_id']] - kwargs = {'context': self.session.context} - return args, kwargs - - def _get_partner_address_id_onchange_param(self, order): - args = [ - None, - order['partner_invoice_id'], - order['partner_shipping_id'], - order['partner_id'], - order['shop_id'], - ] - kwargs = {'context': self.session.context} - return args, kwargs - - def _play_order_onchange(self, order): - """ Play the onchange of the sale order - - :param order: a dictionary of the value of your sale order - :type: dict - - :return: the value of the sale order updated with the onchange result - :rtype: dict - """ - sale_model = self.session.pool.get('sale.order') - - # Play partner_id onchange - args, kwargs = self._get_shop_id_onchange_param(order) - res = sale_model.onchange_shop_id(self.session.cr, - self.session.uid, - *args, - **kwargs) - self.merge_values(order, res) - - args, kwargs = self._get_partner_id_onchange_param(order) - res = sale_model.onchange_partner_id(self.session.cr, - self.session.uid, - *args, - **kwargs) - self.merge_values(order, res) - - if order.get('payment_method_id'): - # apply payment method - args, kwargs = self._get_payment_method_id_onchange_param(order) - res = sale_model.onchange_payment_method_id(self.session.cr, - self.session.uid, - *args, - **kwargs) - # If the onchange return a False fiscal position - # We do not merge it, because the onchange on the address - # will not set correctly the fiscal position as the field - # already exists in the order dict - if res.get('value') and 'fiscal_position' in res['value']: - if not res['value']['fiscal_position']: - res['value'].pop('fiscal_position') - - self.merge_values(order, res) - - if order.get('workflow_process_id'): - # apply default values from the workflow - args, kwargs = self._get_workflow_process_id_onchange_param(order) - res = sale_model.onchange_workflow_process_id(self.session.cr, - self.session.uid, - *args, - **kwargs) - self.merge_values(order, res) - - # Play onchange on address - if hasattr(sale_model, 'onchange_address_id'): - args, kwargs = self._get_partner_address_id_onchange_param(order) - res = sale_model.onchange_address_id(self.session.cr, - self.session.uid, - *args, - **kwargs) - if res.get('value') and 'fiscal_position' in res['value']: - order['fiscal_position'] = res['value']['fiscal_position'] - self.merge_values(order, res) - return order - - def _get_product_id_onchange_param(self, line, previous_lines, order): - """ Prepare the arguments for calling the product_id change - on sale order line. You can overwrite this method in your own - module if they modify the onchange signature - - :param line: the sale order line to process - :type: dict - :param previous_lines: list of dict of the previous lines processed - :type: list - :param order: data of the sale order - :type: dict - - :return: a tuple of args and kwargs for the onchange - :rtype: tuple - """ - args = [ - None, # sale order line ids not needed - order.get('pricelist_id'), - line.get('product_id') - ] - - # used in sale_markup: this is to ensure the unit price - # sent by the e-commerce connector is used for markup calculation - onchange_context = self.session.context.copy() - if line.get('unit_price', False): - onchange_context.update({'unit_price': line['unit_price'], - 'force_unit_price': True}) - - uos_qty = float(line.get('product_uos_qty', 0)) - if not uos_qty: - uos_qty = float(line.get('product_uom_qty', 0)) - - kwargs = { - 'qty': float(line.get('product_uom_qty', 0)), - 'uom': line.get('product_uom'), - 'qty_uos': uos_qty, - 'uos': line.get('product_uos'), - 'name': line.get('name'), - 'partner_id': order.get('partner_id'), - 'lang': False, - 'update_tax': True, - 'date_order': order.get('date_order'), - 'packaging': line.get('product_packaging'), - 'fiscal_position': order.get('fiscal_position'), - 'flag': False, - 'context': onchange_context, - } - return args, kwargs - - def _play_line_onchange(self, line, previous_lines, order): - """ Play the onchange of the sale order line - - :param line: the sale order line to process - :type: dict - :param previous_lines: list of dict of the previous line processed - :type: list - :param order: data of the sale order - :type: dict - - :return: the value of the sale order updated with the onchange result - :rtype: dict - """ - sale_line_model = self.session.pool.get('sale.order.line') - - # Play product_id onchange - args, kwargs = self._get_product_id_onchange_param(line, - previous_lines, - order) - res = sale_line_model.product_id_change(self.session.cr, - self.session.uid, - *args, - **kwargs) - # TODO refactor this with merge_values - vals = res.get('value', {}) - for key in vals: - if key not in line: - if sale_line_model._columns[key]._type == 'many2many': - line[key] = [(6, 0, vals[key])] - else: - line[key] = vals[key] - return line - - def play(self, order, order_lines): - """ Play the onchange of the sale order and it's lines - - :param order: data of the sale order - :type: dict - - :return: the value of the sale order updated with the onchange result - :rtype: dict - """ - # play onchange on sale order - with self.session.change_context(dict(shop_id=order.get('shop_id'))): - order = self._play_order_onchange(order) - # play onchange on sale order line - processed_order_lines = [] - line_lists = [order_lines] - if 'order_line' in order and order['order_line'] is not order_lines: - # we have both backend-dependent and oerp-native order - # lines. - # oerp-native lines can have been added to map - # shipping fees with an OpenERP Product - line_lists.append(order['order_line']) - for line_list in line_lists: - for idx, command_line in enumerate(line_list): - # line_list format:[(0, 0, {...}), (0, 0, {...})] - if command_line[0] in (0, 1): # create or update values - # keeps command number and ID (or 0) - old_line_data = command_line[2] - new_line_data = self._play_line_onchange( - old_line_data, processed_order_lines, order) - new_line = (command_line[0], - command_line[1], - new_line_data) - processed_order_lines.append(new_line) - # in place modification of the sale order line in the list - line_list[idx] = new_line - return order diff --git a/__unported__/connector_ecommerce/wizard/__init__.py b/__unported__/connector_ecommerce/wizard/__init__.py deleted file mode 100644 index f5c43f15..00000000 --- a/__unported__/connector_ecommerce/wizard/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -import sale_ignore_cancel diff --git a/connector_ecommerce/README.rst b/connector_ecommerce/README.rst new file mode 100644 index 00000000..56e5087a --- /dev/null +++ b/connector_ecommerce/README.rst @@ -0,0 +1,66 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :alt: License + +Connector for E-Commerce +======================== + +This modules aims to be a common layer for the connectors dealing with +e-commerce. + +It sits on top of the `connector`_ framework and is used by the +e-commerce connectors, like `magentoerpconnect`_ or +`prestashoperpconnect`_. + +That's a technical module, which include amongst other things: + +Events + + On which the connectors can subscribe consumers + (tracking number added, invoice paid, picking sent, ...) + +ConnectorUnit + + A piece of code which allows to play all the ``onchanges`` required + when we create a sales order. + + Another one which allows to add special lines in imported sales orders + such as Shipping fees, Cash on Delivery or Discounts. + +Data Model + + Add structures shared for e-commerce connectors + +.. _`connector`: http://odoo-connector.com +.. _`magentoerpconnect`: http://odoo-magento-connector.com +.. _`prestashoperpconnect`: https://github.com/OCA/connector-prestashop + +Installation +============ + +This module is a dependency for more advanced connectors. It does +nothing on its own and there is no reason to install it alone. + +Credits +======= + +Contributors +------------ + +See `contributors' list`_ + +.. _contributors' list: ./AUTHORS + +Maintainer +---------- + +.. image:: http://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: http://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization +whose mission is to support the collaborative development of Odoo +features and promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. diff --git a/__unported__/connector_ecommerce/__init__.py b/connector_ecommerce/__init__.py similarity index 82% rename from __unported__/connector_ecommerce/__init__.py rename to connector_ecommerce/__init__.py index bac99af0..8a51dd0b 100644 --- a/__unported__/connector_ecommerce/__init__.py +++ b/connector_ecommerce/__init__.py @@ -19,12 +19,12 @@ # ############################################################################## -import stock -import account -import product -import invoice -import payment_method -import event -import unit -import sale -import wizard +from . import stock +from . import account +from . import product +from . import invoice +from . import payment_method +from . import event +from . import unit +from . import sale +from . import wizard diff --git a/__unported__/connector_ecommerce/__openerp__.py b/connector_ecommerce/__openerp__.py similarity index 58% rename from __unported__/connector_ecommerce/__openerp__.py rename to connector_ecommerce/__openerp__.py index a0ef9380..172ee88e 100644 --- a/__unported__/connector_ecommerce/__openerp__.py +++ b/connector_ecommerce/__openerp__.py @@ -20,47 +20,14 @@ ############################################################################## {'name': 'Connector for E-Commerce', - 'version': '2.2.0', - 'category': 'Connector', - 'author': "Connector Core Editors,Odoo Community Association (OCA)", + 'version': '3.0.0', + 'category': 'Hidden', + 'author': "Camptocamp,Akretion,Odoo Community Association (OCA)", 'website': 'http://openerp-connector.com', 'license': 'AGPL-3', - 'description': """ -Connector for E-Commerce -======================== - -This modules aims to be a common layer for the connectors dealing with -e-commerce. - -It sits on top of the `connector`_ framework and is used by the -e-commerce connectors, like `magentoerpconnect`_ or -`prestashoperpconnect`_. - -That's a technical module, which include amongst other things: - -Events - - On which the connectors can subscribe consumers - (tracking number added, invoice paid, picking sent, ...) - - -ConnectorUnit - - A piece of code which allows to play all the ``onchanges`` required - when we create a sale order. - -Data Model - - Add structures shared for e-commerce connectors - - - .. _`connector`: http://openerp-connector.com -.. _`magentoerpconnect`: http://openerp-magento-connector.com -.. _`prestashoperpconnect`: https://launchpad.net/prestashoperpconnect -""", 'depends': [ 'connector', - 'sale_automatic_workflow', + 'sale_payment_method_automatic_workflow', 'sale_exceptions', 'delivery', 'connector_base_product', @@ -76,5 +43,5 @@ 'payment_method_view.xml', 'account_view.xml', ], - 'installable': False, + 'installable': True, } diff --git a/__unported__/connector_ecommerce/account.py b/connector_ecommerce/account.py similarity index 100% rename from __unported__/connector_ecommerce/account.py rename to connector_ecommerce/account.py diff --git a/__unported__/connector_ecommerce/account_view.xml b/connector_ecommerce/account_view.xml similarity index 100% rename from __unported__/connector_ecommerce/account_view.xml rename to connector_ecommerce/account_view.xml diff --git a/__unported__/connector_ecommerce/ecommerce_data.xml b/connector_ecommerce/ecommerce_data.xml similarity index 100% rename from __unported__/connector_ecommerce/ecommerce_data.xml rename to connector_ecommerce/ecommerce_data.xml diff --git a/__unported__/connector_ecommerce/event.py b/connector_ecommerce/event.py similarity index 100% rename from __unported__/connector_ecommerce/event.py rename to connector_ecommerce/event.py diff --git a/__unported__/connector_ecommerce/i18n/connector_ecommerce.pot b/connector_ecommerce/i18n/connector_ecommerce.pot similarity index 96% rename from __unported__/connector_ecommerce/i18n/connector_ecommerce.pot rename to connector_ecommerce/i18n/connector_ecommerce.pot index f2f7538d..09671056 100644 --- a/__unported__/connector_ecommerce/i18n/connector_ecommerce.pot +++ b/connector_ecommerce/i18n/connector_ecommerce.pot @@ -163,7 +163,7 @@ msgstr "" #. module: connector_ecommerce #: help:product.template,tax_group_id:0 -msgid "Tax group are used with some external system like magento or prestashop" +msgid "Tax groups are used with some external system like magento or prestashop" msgstr "" #. module: connector_ecommerce @@ -173,7 +173,7 @@ msgstr "" #. module: connector_ecommerce #: field:sale.order,parent_need_cancel:0 -msgid "A parent sales orders needs cancel" +msgid "A parent sales order needs cancel" msgstr "" #. module: connector_ecommerce @@ -215,7 +215,7 @@ msgstr "" #. module: connector_ecommerce #: help:sale.order,parent_need_cancel:0 -msgid "A parent sales orders has been canceled on the backend and needs to be canceled." +msgid "A parent sales order has been canceled on the backend and needs to be canceled." msgstr "" #. module: connector_ecommerce @@ -251,7 +251,7 @@ msgstr "" #. module: connector_ecommerce #: help:payment.method,days_before_cancel:0 -msgid "After 'n' days, if the 'Import Rule' is not fulfilled, the import of the sale order will be canceled." +msgid "After 'n' days, if the 'Import Rule' is not fulfilled, the import of the sales order will be canceled." msgstr "" #. module: connector_ecommerce @@ -281,7 +281,7 @@ msgstr "" #. module: connector_ecommerce #: help:account.invoice,sale_order_ids:0 -msgid "This is the list of sale orders related to this invoice." +msgid "This is the list of sales orders related to this invoice." msgstr "" #. module: connector_ecommerce @@ -329,7 +329,7 @@ msgstr "" #. module: connector_ecommerce #: field:account.invoice,sale_order_ids:0 -msgid "Sale Orders" +msgid "Sales Orders" msgstr "" #. module: connector_ecommerce diff --git a/__unported__/connector_ecommerce/i18n/de.po b/connector_ecommerce/i18n/de.po similarity index 97% rename from __unported__/connector_ecommerce/i18n/de.po rename to connector_ecommerce/i18n/de.po index 40d89a8c..b9e01765 100644 --- a/__unported__/connector_ecommerce/i18n/de.po +++ b/connector_ecommerce/i18n/de.po @@ -185,7 +185,7 @@ msgstr "Weitere Informationen" #. module: connector_ecommerce #: help:product.template,tax_group_id:0 msgid "" -"Tax group are used with some external system like magento or prestashop" +"Tax groups are used with some external system like magento or prestashop" msgstr "" "Steuergruppen werden für einige externe Systeme wie Magento oder Prestashop " "genutzt." @@ -197,7 +197,7 @@ msgstr "Preislistenartikel" #. module: connector_ecommerce #: field:sale.order,parent_need_cancel:0 -msgid "A parent sales orders needs cancel" +msgid "A parent sales order needs cancel" msgstr "Eine übergeordnete" #. module: connector_ecommerce @@ -246,7 +246,7 @@ msgstr "" #. module: connector_ecommerce #: help:sale.order,parent_need_cancel:0 msgid "" -"A parent sales orders has been canceled on the backend and needs to be " +"A parent sales order has been canceled on the backend and needs to be " "canceled." msgstr "" "Ein übergeordneter VK-Auftrag wurde im Backend abgebrochen und muss " @@ -287,7 +287,7 @@ msgstr "VK-Auftrag" #: help:payment.method,days_before_cancel:0 msgid "" "After 'n' days, if the 'Import Rule' is not fulfilled, the import of the " -"sale order will be canceled." +"sales order will be canceled." msgstr "" "Nach 'n' Tagen, wenn die 'Import Regel' nicht erfüllt ist, wird der Import " "abgebrochen." @@ -319,7 +319,7 @@ msgstr "Im Backend abgebrochen" #. module: connector_ecommerce #: help:account.invoice,sale_order_ids:0 -msgid "This is the list of sale orders related to this invoice." +msgid "This is the list of sales orders related to this invoice." msgstr "" "Dies ist die Liste der VK-Aufträge welche mit dieser Rechnung in beziehung " "stehen." @@ -369,7 +369,7 @@ msgstr "Name" #. module: connector_ecommerce #: field:account.invoice,sale_order_ids:0 -msgid "Sale Orders" +msgid "Sales Orders" msgstr "VK-Aufträge" #. module: connector_ecommerce diff --git a/__unported__/connector_ecommerce/i18n/es.po b/connector_ecommerce/i18n/es.po similarity index 97% rename from __unported__/connector_ecommerce/i18n/es.po rename to connector_ecommerce/i18n/es.po index 6a476ce3..3701bc7f 100644 --- a/__unported__/connector_ecommerce/i18n/es.po +++ b/connector_ecommerce/i18n/es.po @@ -186,7 +186,7 @@ msgstr "Otra información" #. module: connector_ecommerce #: help:product.template,tax_group_id:0 msgid "" -"Tax group are used with some external system like magento or prestashop" +"Tax groups are used with some external system like magento or prestashop" msgstr "" "El grupo fiscal se usa con algún sistema externo como magento do prestashop" @@ -197,7 +197,7 @@ msgstr "Listado de Precios de Artículos" #. module: connector_ecommerce #: field:sale.order,parent_need_cancel:0 -msgid "A parent sales orders needs cancel" +msgid "A parent sales order needs cancel" msgstr "Una orden de venta padre debe cancelar" #. module: connector_ecommerce @@ -248,7 +248,7 @@ msgstr "Debe ser cancelado" #. module: connector_ecommerce #: help:sale.order,parent_need_cancel:0 msgid "" -"A parent sales orders has been canceled on the backend and needs to be " +"A parent sales order has been canceled on the backend and needs to be " "canceled." msgstr "" "Una orden de venta padre ha sido cancelada en el área de administración y " @@ -289,7 +289,7 @@ msgstr "Pedidos de venta" #: help:payment.method,days_before_cancel:0 msgid "" "After 'n' days, if the 'Import Rule' is not fulfilled, the import of the " -"sale order will be canceled." +"sales order will be canceled." msgstr "" "Después de 'n' días, si la 'Regla de Importación' no está realizada, la " "importación de la orden de venta será cancelada." @@ -321,7 +321,7 @@ msgstr "Cancelado en el área de administración" #. module: connector_ecommerce #: help:account.invoice,sale_order_ids:0 -msgid "This is the list of sale orders related to this invoice." +msgid "This is the list of sales orders related to this invoice." msgstr "Esta es la lista de pedidos de venta relacionados con esta factura." #. module: connector_ecommerce @@ -370,7 +370,7 @@ msgstr "Nombre" #. module: connector_ecommerce #: field:account.invoice,sale_order_ids:0 -msgid "Sale Orders" +msgid "Sales Orders" msgstr "Pedido de Venta" #. module: connector_ecommerce diff --git a/__unported__/connector_ecommerce/i18n/fr.po b/connector_ecommerce/i18n/fr.po similarity index 97% rename from __unported__/connector_ecommerce/i18n/fr.po rename to connector_ecommerce/i18n/fr.po index 19357ee2..7c98b831 100644 --- a/__unported__/connector_ecommerce/i18n/fr.po +++ b/connector_ecommerce/i18n/fr.po @@ -184,7 +184,7 @@ msgstr "Autres informations" #. module: connector_ecommerce #: help:product.template,tax_group_id:0 msgid "" -"Tax group are used with some external system like magento or prestashop" +"Tax groups are used with some external system like magento or prestashop" msgstr "" "Les groupes de taxes sont utilisés avec des systèmes externes comme Magento " "ou Prestashop." @@ -196,7 +196,7 @@ msgstr "Lignes de liste de prix" #. module: connector_ecommerce #: field:sale.order,parent_need_cancel:0 -msgid "A parent sales orders needs cancel" +msgid "A parent sales order needs cancel" msgstr "Une commande parente doit être annulée" #. module: connector_ecommerce @@ -248,7 +248,7 @@ msgstr "Doit être annulée" #. module: connector_ecommerce #: help:sale.order,parent_need_cancel:0 msgid "" -"A parent sales orders has been canceled on the backend and needs to be " +"A parent sales order has been canceled on the backend and needs to be " "canceled." msgstr "" "Une commande parente a été annulée sur le backend et doit être annulée." @@ -288,7 +288,7 @@ msgstr "Bon de commande" #: help:payment.method,days_before_cancel:0 msgid "" "After 'n' days, if the 'Import Rule' is not fulfilled, the import of the " -"sale order will be canceled." +"sales order will be canceled." msgstr "" "Après \"n\" jours, si la règle d'import n'est pas satisfaite, l'import de la " "commande sera annulé." @@ -320,7 +320,7 @@ msgstr "Annulée dans le backend" #. module: connector_ecommerce #: help:account.invoice,sale_order_ids:0 -msgid "This is the list of sale orders related to this invoice." +msgid "This is the list of sales orders related to this invoice." msgstr "Liste des commandes liées à cette facture." #. module: connector_ecommerce @@ -368,7 +368,7 @@ msgstr "Nom" #. module: connector_ecommerce #: field:account.invoice,sale_order_ids:0 -msgid "Sale Orders" +msgid "Sales Orders" msgstr "Bons de commande" #. module: connector_ecommerce diff --git a/__unported__/connector_ecommerce/i18n/nl.po b/connector_ecommerce/i18n/nl.po similarity index 97% rename from __unported__/connector_ecommerce/i18n/nl.po rename to connector_ecommerce/i18n/nl.po index 67153249..b05be271 100644 --- a/__unported__/connector_ecommerce/i18n/nl.po +++ b/connector_ecommerce/i18n/nl.po @@ -183,7 +183,7 @@ msgstr "Overige informatie" #. module: connector_ecommerce #: help:product.template,tax_group_id:0 msgid "" -"Tax group are used with some external system like magento or prestashop" +"Tax groups are used with some external system like magento or prestashop" msgstr "" "BTW groepen worden gebruikt met sommige externe systemen, zoals Magento of " "Prestashop." @@ -195,7 +195,7 @@ msgstr "Prijslijst items" #. module: connector_ecommerce #: field:sale.order,parent_need_cancel:0 -msgid "A parent sales orders needs cancel" +msgid "A parent sales order needs cancel" msgstr "Een bovenliggende verkooporder dient te worden geannuleerd" #. module: connector_ecommerce @@ -246,7 +246,7 @@ msgstr "Dient te worden geannuleerd" #. module: connector_ecommerce #: help:sale.order,parent_need_cancel:0 msgid "" -"A parent sales orders has been canceled on the backend and needs to be " +"A parent sales order has been canceled on the backend and needs to be " "canceled." msgstr "" "Een bovenliggende verkooporder is geannuleerd in de backend en dient te " @@ -287,7 +287,7 @@ msgstr "Verkooporder" #: help:payment.method,days_before_cancel:0 msgid "" "After 'n' days, if the 'Import Rule' is not fulfilled, the import of the " -"sale order will be canceled." +"sales order will be canceled." msgstr "" "Als na 'n' dagen niet aan de 'import regel' is voldaan, wordt de import van " "de verkooporder geannuleerd." @@ -319,7 +319,7 @@ msgstr "Geannuleerd in backend" #. module: connector_ecommerce #: help:account.invoice,sale_order_ids:0 -msgid "This is the list of sale orders related to this invoice." +msgid "This is the list of sales orders related to this invoice." msgstr "" "Dit is een lijst met alle verkooporders gerelateerd aan deze factuur." @@ -370,7 +370,7 @@ msgstr "Naam" #. module: connector_ecommerce #: field:account.invoice,sale_order_ids:0 -msgid "Sale Orders" +msgid "Sales Orders" msgstr "Verkooporders" #. module: connector_ecommerce diff --git a/__unported__/connector_ecommerce/invoice.py b/connector_ecommerce/invoice.py similarity index 57% rename from __unported__/connector_ecommerce/invoice.py rename to connector_ecommerce/invoice.py index 0d72c44a..56c67572 100644 --- a/__unported__/connector_ecommerce/invoice.py +++ b/connector_ecommerce/invoice.py @@ -19,37 +19,28 @@ # ############################################################################## -from openerp.osv import fields, orm +from openerp import models, api from openerp.addons.connector.session import ConnectorSession from .event import on_invoice_paid, on_invoice_validated -class account_invoice(orm.Model): +class AccountInvoice(models.Model): _inherit = 'account.invoice' - _columns = { - 'sale_order_ids': fields.many2many( - # TODO duplicate with 'sale_ids', replace - 'sale.order', - 'sale_order_invoice_rel', - 'invoice_id', - 'order_id', - string='Sale Orders', readonly=True, - help="This is the list of sale orders related to this invoice."), - } - - def confirm_paid(self, cr, uid, ids, context=None): - res = super(account_invoice, self).confirm_paid( - cr, uid, ids, context=context) - session = ConnectorSession(cr, uid, context=context) - for record_id in ids: + @api.multi + def confirm_paid(self): + res = super(AccountInvoice, self).confirm_paid() + session = ConnectorSession(self.env.cr, self.env.uid, + context=self.env.context) + for record_id in self.ids: on_invoice_paid.fire(session, self._name, record_id) return res - def invoice_validate(self, cr, uid, ids, context=None): - res = super(account_invoice, self).invoice_validate( - cr, uid, ids, context=context) - session = ConnectorSession(cr, uid, context=context) - for record_id in ids: + @api.multi + def invoice_validate(self): + res = super(AccountInvoice, self).invoice_validate() + session = ConnectorSession(self.env.cr, self.env.uid, + context=self.env.context) + for record_id in self.ids: on_invoice_validated.fire(session, self._name, record_id) return res diff --git a/__unported__/connector_ecommerce/invoice_view.xml b/connector_ecommerce/invoice_view.xml similarity index 100% rename from __unported__/connector_ecommerce/invoice_view.xml rename to connector_ecommerce/invoice_view.xml diff --git a/connector_ecommerce/payment_method.py b/connector_ecommerce/payment_method.py new file mode 100644 index 00000000..028b19bd --- /dev/null +++ b/connector_ecommerce/payment_method.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright 2011-2013 Akretion +# @author Sébastien BEAU +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp import models, fields, api + + +class PaymentMethod(models.Model): + _inherit = "payment.method" + + @api.model + def _get_import_rules(self): + return [('always', 'Always'), + ('never', 'Never'), + ('paid', 'Paid'), + ('authorized', 'Authorized'), + ] + + # the logic around the 2 following fields has to be implemented + # in the connectors (magentoerpconnect, prestashoperpconnect,...) + days_before_cancel = fields.Integer( + string='Days before cancel', + default=30, + help="After 'n' days, if the 'Import Rule' is not fulfilled, the " + "import of the sales order will be canceled.", + ) + import_rule = fields.Selection(selection='_get_import_rules', + string="Import Rule", + default='always', + required=True) + + @api.model + def get_or_create_payment_method(self, payment_method): + """ Try to get a payment method or create if it doesn't exist + + :param payment_method: payment method like PayPal, etc. + :type payment_method: str + :return: required payment method + :rtype: recordset + """ + domain = [('name', '=ilike', payment_method)] + method = self.search(domain, limit=1) + if not method: + method = self.create({'name': payment_method}) + return method diff --git a/__unported__/connector_ecommerce/payment_method_view.xml b/connector_ecommerce/payment_method_view.xml similarity index 91% rename from __unported__/connector_ecommerce/payment_method_view.xml rename to connector_ecommerce/payment_method_view.xml index 788b3bd3..a6ef4428 100644 --- a/__unported__/connector_ecommerce/payment_method_view.xml +++ b/connector_ecommerce/payment_method_view.xml @@ -12,7 +12,7 @@ payment.method.connector_ecommerce.form payment.method - + @@ -40,7 +40,7 @@ payment.method.connector_ecommerce.tree payment.method - + diff --git a/connector_ecommerce/product.py b/connector_ecommerce/product.py new file mode 100644 index 00000000..7abb6fa8 --- /dev/null +++ b/connector_ecommerce/product.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Sébastien BEAU +# Copyright 2011-2013 Akretion +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp import models, fields, api +from openerp.addons.connector.session import ConnectorSession +from .event import on_product_price_changed + + +class ProductTemplate(models.Model): + _inherit = 'product.template' + + # TODO implement set function and also support multi tax + @api.one + @api.depends('taxes_id', 'taxes_id.group_id') + def _get_tax_group_id(self): + taxes = self.taxes_id + self.tax_group_id = taxes[0].group_id.id if taxes else False + + tax_group_id = fields.Many2one( + comodel_name='account.tax.group', + compute='_get_tax_group_id', + string='Tax Group', + help='Tax groups are used with some external ' + 'system like Prestashop', + ) + + @api.multi + def _price_changed(self, vals): + """ Fire the ``on_product_price_changed`` on all the variants of + the template if the price of the product could have changed. + + If one of the field used in a sale pricelist item has been + modified, we consider that the price could have changed. + + There is no guarantee that's the price actually changed, + because it depends on the pricelists. + """ + type_model = self.env['product.price.type'] + price_fields = type_model.sale_price_fields() + # restrict the fields to the template ones only, so if + # the write has been done on product.product, we won't + # update all the variants if a price field of the + # variant has been changed + tmpl_fields = [field for field in vals if field in self._fields] + if any(field in price_fields for field in tmpl_fields): + product_model = self.env['product.product'] + session = ConnectorSession(self.env.cr, self.env.uid, + context=self.env.context) + products = product_model.search( + [('product_tmpl_id', 'in', self.ids)] + ) + # when the write is done on the product.product, avoid + # to fire the event 2 times + if self.env.context.get('from_product_ids'): + from_product_ids = self.env.context['from_product_ids'] + remove_products = product_model.browse(from_product_ids) + products -= remove_products + for product in products: + on_product_price_changed.fire(session, + product_model._name, + product.id) + + @api.multi + def write(self, vals): + result = super(ProductTemplate, self).write(vals) + self._price_changed(vals) + return result + + +class ProductProduct(models.Model): + _inherit = 'product.product' + + @api.depends() + def _get_checkpoint(self): + checkpoint_model = self.env['connector.checkpoint'] + model_model = self.env['ir.model'] + model = model_model.search([('model', '=', 'product.product')]) + for product in self: + points = checkpoint_model.search([('model_id', '=', model.id), + ('record_id', '=', product.id), + ('state', '=', 'need_review')], + limit=1, + ) + product.has_checkpoint = bool(points) + + has_checkpoint = fields.Boolean(compute='_get_checkpoint', + string='Has Checkpoint') + + @api.multi + def _price_changed(self, vals): + """ Fire the ``on_product_price_changed`` if the price + of the product could have changed. + + If one of the field used in a sale pricelist item has been + modified, we consider that the price could have changed. + + There is no guarantee that's the price actually changed, + because it depends on the pricelists. + """ + type_model = self.env['product.price.type'] + price_fields = type_model.sale_price_fields() + if any(field in price_fields for field in vals): + session = ConnectorSession(self.env.cr, self.env.uid, + context=self.env.context) + for prod_id in self.ids: + on_product_price_changed.fire(session, self._name, prod_id) + + @api.multi + def write(self, vals): + self_context = self.with_context(from_product_ids=self.ids) + result = super(ProductProduct, self_context).write(vals) + self._price_changed(vals) + return result + + @api.model + def create(self, vals): + product = super(ProductProduct, self).create(vals) + self._price_changed(vals) + return product + + +class ProductPriceType(models.Model): + _inherit = 'product.price.type' + + pricelist_item_ids = fields.One2many( + comodel_name='product.pricelist.item', + inverse_name='base', + string='Pricelist Items', + readonly=True, + ) + + @api.model + def sale_price_fields(self): + """ Returns a list of fields used by sale pricelists. + Used to know if the sale price could have changed + when one of these fields has changed. + """ + item_model = self.env['product.pricelist.item'] + items = item_model.search( + [('price_version_id.pricelist_id.type', '=', 'sale')], + ) + types = self.search([('pricelist_item_ids', 'in', items.ids)]) + return [t.field for t in types] diff --git a/connector_ecommerce/sale.py b/connector_ecommerce/sale.py new file mode 100644 index 00000000..e8fe1b93 --- /dev/null +++ b/connector_ecommerce/sale.py @@ -0,0 +1,321 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Guewen Baconnier +# Copyright 2011-2013 Camptocamp SA +# Author: Sébastien Beau +# Copyright 2010-2013 Akretion +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import logging + +from openerp import models, fields, api, exceptions, _, osv +from openerp.addons.connector.connector import ConnectorUnit + +_logger = logging.getLogger(__name__) + + +class SaleOrder(models.Model): + """ Add a cancellation mecanism in the sales orders + + When a sales order is canceled in a backend, the connectors can flag + the 'canceled_in_backend' flag. It will: + + * try to automatically cancel the sales order + * block the confirmation of the sales orders using a 'sales exception' + + When a sales order is canceled or the user used the button to force + to 'keep it open', the flag 'cancellation_resolved' is set to True. + + The second axe which can be used by the connectors is the 'parent' + sales order. When a sales order has a parent sales order (logic to + link with the parent to be defined by each connector), it will be + blocked until the cancellation of the sales order is resolved. + + This is used by, for instance, the magento connector, when one + modifies a sales order, Magento cancels it and create a new one with + the first one as parent. + """ + _inherit = 'sale.order' + + canceled_in_backend = fields.Boolean(string='Canceled in backend', + readonly=True) + # set to True when the cancellation from the backend is + # resolved, either because the SO has been canceled or + # because the user manually chose to keep it open + cancellation_resolved = fields.Boolean(string='Cancellation from the ' + 'backend resolved') + parent_id = fields.Many2one(comodel_name='sale.order', + compute='get_parent_id', + string='Parent Order', + help='A parent sales order is a sales ' + 'order replaced by this one.') + need_cancel = fields.Boolean(compute='_need_cancel', + string='Need to be canceled', + help='Has been canceled on the backend' + ', need to be canceled.') + parent_need_cancel = fields.Boolean( + compute='_parent_need_cancel', + string='A parent sales order needs cancel', + help='A parent sales order has been canceled on the backend' + ' and needs to be canceled.', + ) + + @api.one + @api.depends() + def get_parent_id(self): + """ Need to be inherited in the connectors to implement the + parent logic. + + See an implementation example in ``magentoerpconnect``. + """ + self.parent_id = False + + @api.one + @api.depends('canceled_in_backend', 'cancellation_resolved') + def _need_cancel(self): + """ Return True if the sales order need to be canceled + (has been canceled on the Backend) + """ + self.need_cancel = (self.canceled_in_backend and + not self.cancellation_resolved) + + @api.one + @api.depends('need_cancel', 'parent_id', + 'parent_id.need_cancel', 'parent_id.parent_need_cancel') + def _parent_need_cancel(self): + """ Return True if at least one parent sales order need to + be canceled (has been canceled on the backend). + Follows all the parent sales orders. + """ + self.parent_need_cancel = False + order = self.parent_id + while order: + if order.need_cancel: + self.parent_need_cancel = True + order = order.parent_id + + @api.multi + def _try_auto_cancel(self): + """ Try to automatically cancel a sales order canceled + in a backend. + + If it can't cancel it, does nothing. + """ + wkf_states = ('draft', 'sent') + action_states = ('manual', 'progress') + resolution_msg = _("

Resolution:

    " + "
  1. Cancel the linked invoices, delivery " + "orders, automatic payments.
  2. " + "
  3. Cancel the sales order manually.
  4. " + "

") + for order in self: + state = order.state + if state == 'cancel': + continue + elif state == 'done': + message = _("The sales order cannot be automatically " + "canceled because it is already done.") + elif state in wkf_states + action_states: + try: + # respect the same cancellation methods than + # the sales order view: quotations use the workflow + # action, sales orders use the action_cancel method. + if state in wkf_states: + order.signal_workflow('cancel') + elif state in action_states: + order.action_cancel() + else: + raise ValueError('%s should not fall here.' % state) + except (osv.except_osv, osv.orm.except_orm, + exceptions.Warning): + # the 'cancellation_resolved' flag will stay to False + message = _("The sales order could not be automatically " + "canceled.") + resolution_msg + else: + message = _("The sales order has been automatically " + "canceled.") + else: + # shipping_except, invoice_except, ... + # can not be canceled from the view, so assume that it + # should not be canceled here neiter, exception to + # resolve + message = _("The sales order could not be automatically " + "canceled for this status.") + resolution_msg + order.message_post(body=message) + + @api.multi + def _log_canceled_in_backend(self): + message = _("The sales order has been canceled on the backend.") + self.message_post(body=message) + for order in self: + message = _("Warning: the origin sales order %s has been canceled " + "on the backend.") % order.name + if order.picking_ids: + order.picking_ids.message_post(body=message) + if order.invoice_ids: + order.invoice_ids.message_post(body=message) + + @api.model + def create(self, values): + order = super(SaleOrder, self).create(values) + if values.get('canceled_in_backend'): + order._log_canceled_in_backend() + order._try_auto_cancel() + return order + + @api.multi + def write(self, values): + result = super(SaleOrder, self).write(values) + if values.get('canceled_in_backend'): + self._log_canceled_in_backend() + self._try_auto_cancel() + return result + + @api.multi + def action_cancel(self): + res = super(SaleOrder, self).action_cancel() + for sale in self: + # the sales order is canceled => considered as resolved + if (sale.canceled_in_backend and + not sale.cancellation_resolved): + sale.write({'cancellation_resolved': True}) + return res + + @api.multi + def ignore_cancellation(self, reason): + """ Manually set the cancellation from the backend as resolved. + + The user can choose to keep the sales order active for some reason, + it only requires to push a button to keep it alive. + """ + message = (_("Despite the cancellation of the sales order on the " + "backend, it should stay open.

Reason: %s") % + reason) + self.message_post(body=message) + self.write({'cancellation_resolved': True}) + return True + + @api.multi + def action_view_parent(self): + """ Return an action to display the parent sales order """ + self.ensure_one() + + parent = self.parent_id + if not parent: + return + + view_xmlid = 'sale.view_order_form' + if parent.state in ('draft', 'sent', 'cancel'): + action_xmlid = 'sale.action_quotations' + else: + action_xmlid = 'sale.action_orders' + + action = self.env.ref(action_xmlid).read()[0] + + view = self.env.ref(view_xmlid) + action['views'] = [(view.id if view else False, 'form')] + action['res_id'] = parent.id + return action + + +class SpecialOrderLineBuilder(ConnectorUnit): + """ Base class to build a sales order line for a sales order + + Used when extra order lines have to be added in a sales order + but we only know some parameters (product, price, ...), for instance, + a line for the shipping costs or the gift coupons. + + It can be subclassed to customize the way the lines are created. + + Usage:: + + builder = self.get_connector_for_unit(ShippingLineBuilder, + model='sale.order.line') + builder.price_unit = 100 + builder.get_line() + + """ + _model_name = None + + def __init__(self, connector_env): + super(SpecialOrderLineBuilder, self).__init__(connector_env) + self.product = None # id or browse_record + # when no product_id, fallback to a product_ref + self.product_ref = None # tuple (module, xmlid) + self.price_unit = None + self.quantity = 1 + self.sign = 1 + self.sequence = 980 + + def get_line(self): + assert self.product_ref or self.product + assert self.price_unit is not None + + product = self.product + if product is None: + product = self.env.ref('.'.join(self.product_ref)) + + if not isinstance(product, models.BaseModel): + product = self.env['product.product'].browse(product) + return {'product_id': product.id, + 'name': product.name, + 'product_uom': product.uom_id.id, + 'product_uom_qty': self.quantity, + 'price_unit': self.price_unit * self.sign, + 'sequence': self.sequence} + + +class ShippingLineBuilder(SpecialOrderLineBuilder): + """ Return values for a Shipping line """ + _model_name = None + + def __init__(self, connector_env): + super(ShippingLineBuilder, self).__init__(connector_env) + self.product_ref = ('connector_ecommerce', 'product_product_shipping') + self.sequence = 999 + + +class CashOnDeliveryLineBuilder(SpecialOrderLineBuilder): + """ Return values for a Cash on Delivery line """ + _model_name = None + _model_name = None + + def __init__(self, connector_env): + super(CashOnDeliveryLineBuilder, self).__init__(connector_env) + self.product_ref = ('connector_ecommerce', + 'product_product_cash_on_delivery') + self.sequence = 995 + + +class GiftOrderLineBuilder(SpecialOrderLineBuilder): + """ Return values for a Gift line """ + _model_name = None + + def __init__(self, connector_env): + super(GiftOrderLineBuilder, self).__init__(connector_env) + self.product_ref = ('connector_ecommerce', + 'product_product_gift') + self.sign = -1 + self.gift_code = None + self.sequence = 990 + + def get_line(self): + line = super(GiftOrderLineBuilder, self).get_line() + if self.gift_code: + line['name'] = "%s [%s]" % (line['name'], self.gift_code) + return line diff --git a/__unported__/connector_ecommerce/sale_view.xml b/connector_ecommerce/sale_view.xml similarity index 100% rename from __unported__/connector_ecommerce/sale_view.xml rename to connector_ecommerce/sale_view.xml diff --git a/__unported__/connector_ecommerce/security/ir.model.access.csv b/connector_ecommerce/security/ir.model.access.csv similarity index 100% rename from __unported__/connector_ecommerce/security/ir.model.access.csv rename to connector_ecommerce/security/ir.model.access.csv diff --git a/__unported__/connector_ecommerce/security/security.xml b/connector_ecommerce/security/security.xml similarity index 100% rename from __unported__/connector_ecommerce/security/security.xml rename to connector_ecommerce/security/security.xml diff --git a/connector_ecommerce/stock.py b/connector_ecommerce/stock.py new file mode 100644 index 00000000..f0132c79 --- /dev/null +++ b/connector_ecommerce/stock.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Joel Grand-Guillaume +# Copyright 2013-2015 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp import models, fields, api + +from openerp.addons.connector.session import ConnectorSession +from .event import on_picking_out_done, on_tracking_number_added + + +class StockPicking(models.Model): + _inherit = 'stock.picking' + + related_backorder_ids = fields.One2many( + comodel_name='stock.picking', + inverse_name='backorder_id', + string="Related backorders", + ) + + @api.multi + def write(self, vals): + res = super(StockPicking, self).write(vals) + if vals.get('carrier_tracking_ref'): + session = ConnectorSession(self.env.cr, self.env.uid, + context=self.env.context) + for record_id in self.ids: + on_tracking_number_added.fire(session, self._name, record_id) + return res + + @api.multi + def do_transfer(self): + # The key in the context avoid the event to be fired in + # StockMove.action_done(). Allow to handle the partial pickings + self_context = self.with_context(__no_on_event_out_done=True) + result = super(StockPicking, self_context).do_transfer() + session = ConnectorSession(self.env.cr, self.env.uid, + context=self.env.context) + for picking in self: + if picking.picking_type_id.code != 'outgoing': + continue + if picking.related_backorder_ids: + method = 'partial' + else: + method = 'complete' + on_picking_out_done.fire(session, 'stock.picking', + picking.id, method) + + return result + + +class StockMove(models.Model): + _inherit = 'stock.move' + + @api.multi + def action_done(self): + fire_event = not self.env.context.get('__no_on_event_out_done') + if fire_event: + pickings = self.mapped('picking_id') + states = {p.id: p.state for p in pickings} + + result = super(StockMove, self).action_done() + + if fire_event: + session = ConnectorSession(self.env.cr, self.env.uid, + context=self.env.context) + for picking in pickings: + if states[picking.id] != 'done' and picking.state == 'done': + if picking.picking_type_id.code != 'outgoing': + continue + # partial pickings are handled in + # StockPicking.do_transfer() + on_picking_out_done.fire(session, 'stock.picking', + picking.id, 'complete') + + return result diff --git a/connector_ecommerce/stock_view.xml b/connector_ecommerce/stock_view.xml new file mode 100644 index 00000000..954c7050 --- /dev/null +++ b/connector_ecommerce/stock_view.xml @@ -0,0 +1,18 @@ + + + + + stock.picking.form + stock.picking + + + + + + + + + + + diff --git a/__unported__/connector_ecommerce/tests/__init__.py b/connector_ecommerce/tests/__init__.py similarity index 88% rename from __unported__/connector_ecommerce/tests/__init__.py rename to connector_ecommerce/tests/__init__.py index 988e9d44..f514ea16 100644 --- a/__unported__/connector_ecommerce/tests/__init__.py +++ b/connector_ecommerce/tests/__init__.py @@ -19,13 +19,6 @@ # ############################################################################## -import test_onchange -import test_invoice_event - -fast_suite = [ -] - -checks = [ - test_onchange, - test_invoice_event, -] +from . import test_onchange +from . import test_invoice_event +from . import test_picking_event diff --git a/__unported__/connector_ecommerce/tests/test_invoice_event.py b/connector_ecommerce/tests/test_invoice_event.py similarity index 61% rename from __unported__/connector_ecommerce/tests/test_invoice_event.py rename to connector_ecommerce/tests/test_invoice_event.py index f5c7195a..9a240a55 100644 --- a/__unported__/connector_ecommerce/tests/test_invoice_event.py +++ b/connector_ecommerce/tests/test_invoice_event.py @@ -20,48 +20,39 @@ ############################################################################## import mock -from functools import partial import openerp.tests.common as common -from openerp import netsvc -class test_invoice_event(common.TransactionCase): +class TestInvoiceEvent(common.TransactionCase): """ Test if the events on the invoice are fired correctly """ def setUp(self): - super(test_invoice_event, self).setUp() - cr, uid = self.cr, self.uid - self.invoice_model = self.registry('account.invoice') - partner_model = self.registry('res.partner') - partner_id = partner_model.create(cr, uid, {'name': 'Hodor'}) - data_model = self.registry('ir.model.data') - self.get_ref = partial(data_model.get_object_reference, cr, uid) - product_id = self.get_ref('product', 'product_product_6')[1] - invoice_vals = {'partner_id': partner_id, + super(TestInvoiceEvent, self).setUp() + self.invoice_model = self.env['account.invoice'] + partner_model = self.env['res.partner'] + partner = partner_model.create({'name': 'Hodor'}) + product = self.env.ref('product.product_product_6') + invoice_vals = {'partner_id': partner.id, 'type': 'out_invoice', 'invoice_line': [(0, 0, {'name': "LCD Screen", - 'product_id': product_id, + 'product_id': product.id, 'quantity': 5, 'price_unit': 200})], } - onchange_res = self.invoice_model.onchange_partner_id( - cr, uid, [], 'out_invoice', partner_id) + onchange_res = self.invoice_model.onchange_partner_id('out_invoice', + partner.id) invoice_vals.update(onchange_res['value']) - invoice_id = self.invoice_model.create(cr, uid, invoice_vals) - self.invoice = self.invoice_model.browse(cr, uid, invoice_id) + self.invoice = self.invoice_model.create(invoice_vals) def test_event_validated(self): """ Test if the ``on_invoice_validated`` event is fired when an invoice is validated """ - cr, uid = self.cr, self.uid assert self.invoice, "The invoice has not been created" - wf_service = netsvc.LocalService('workflow') event = ('openerp.addons.connector_ecommerce.' 'invoice.on_invoice_validated') with mock.patch(event) as event_mock: - wf_service.trg_validate(uid, 'account.invoice', - self.invoice.id, 'invoice_open', cr) + self.invoice.signal_workflow('invoice_open') self.assertEqual(self.invoice.state, 'open') event_mock.fire.assert_called_with(mock.ANY, 'account.invoice', @@ -70,27 +61,23 @@ def test_event_validated(self): def test_event_paid(self): """ Test if the ``on_invoice_paid`` event is fired when an invoice is paid """ - cr, uid = self.cr, self.uid assert self.invoice, "The invoice has not been created" - wf_service = netsvc.LocalService('workflow') - wf_service.trg_validate(uid, 'account.invoice', - self.invoice.id, 'invoice_open', cr) + self.invoice.signal_workflow('invoice_open') self.assertEqual(self.invoice.state, 'open') - journal_id = self.get_ref('account', 'bank_journal')[1] - pay_account_id = self.get_ref('account', 'cash')[1] - period_id = self.get_ref('account', 'period_10')[1] + journal = self.env.ref('account.bank_journal') + pay_account = self.env.ref('account.cash') + period = self.env.ref('account.period_10') event = 'openerp.addons.connector_ecommerce.invoice.on_invoice_paid' with mock.patch(event) as event_mock: self.invoice.pay_and_reconcile( pay_amount=self.invoice.amount_total, - pay_account_id=pay_account_id, - period_id=period_id, - pay_journal_id=journal_id, - writeoff_acc_id=pay_account_id, - writeoff_period_id=period_id, - writeoff_journal_id=journal_id, + pay_account_id=pay_account.id, + period_id=period.id, + pay_journal_id=journal.id, + writeoff_acc_id=pay_account.id, + writeoff_period_id=period.id, + writeoff_journal_id=journal.id, name="Payment for test of the event on_invoice_paid") - self.invoice.refresh() self.assertEqual(self.invoice.state, 'paid') event_mock.fire.assert_called_with(mock.ANY, 'account.invoice', diff --git a/connector_ecommerce/tests/test_onchange.py b/connector_ecommerce/tests/test_onchange.py new file mode 100644 index 00000000..bced71b0 --- /dev/null +++ b/connector_ecommerce/tests/test_onchange.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +############################################################################### +# +# connector-ecommerce for OpenERP +# Copyright (C) 2013-TODAY Akretion . +# @author Sébastien BEAU +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################### + +import mock + +from openerp.addons.connector_ecommerce.unit.sale_order_onchange import ( + SaleOrderOnChange) +from openerp.addons.connector.session import ConnectorSession +from openerp.addons.connector.connector import Environment +import openerp.tests.common as common + +DB = common.DB +ADMIN_USER_ID = common.ADMIN_USER_ID + + +class TestOnchange(common.TransactionCase): + """ Test if the onchanges are applied correctly on a sales order""" + + def setUp(self): + super(TestOnchange, self).setUp() + self.session = ConnectorSession(self.cr, self.uid) + + def test_play_onchange(self): + """ Play the onchange ConnectorUnit on a sales order """ + product_model = self.env['product.product'] + partner_model = self.env['res.partner'] + tax_model = self.env['account.tax'] + payment_method_model = self.env['payment.method'] + + backend_record = mock.Mock() + env = Environment(backend_record, self.session, 'sale.order') + + partner = partner_model.create({'name': 'seb', + 'zip': '69100', + 'city': 'Villeurbanne'}) + partner_invoice = partner_model.create({'name': 'Guewen', + 'zip': '1015', + 'city': 'Lausanne', + 'type': 'invoice', + 'parent_id': partner.id}) + tax = tax_model.create({'name': 'My Tax'}) + product = product_model.create({'default_code': 'MyCode', + 'name': 'My Product', + 'weight': 15, + 'taxes_id': [(6, 0, [tax.id])]}) + payment_term = self.env.ref('account.account_payment_term_advance') + payment_method = payment_method_model.create({ + 'name': 'Cash', + 'payment_term_id': payment_term.id, + }) + + order_vals = { + 'name': 'mag_10000001', + 'partner_id': partner.id, + 'payment_method_id': payment_method.id, + 'order_line': [ + (0, 0, {'product_id': product.id, + 'price_unit': 20, + 'name': 'My Real Name', + 'product_uom_qty': 1, + 'sequence': 1, + } + ), + ], + # fake field for the lines coming from a backend + 'backend_order_line': [ + (0, 0, {'product_id': product.id, + 'price_unit': 10, + 'name': 'Line 2', + 'product_uom_qty': 2, + 'sequence': 2, + } + ), + ], + } + + extra_lines = order_vals['backend_order_line'] + + onchange = SaleOrderOnChange(env) + order = onchange.play(order_vals, extra_lines) + + self.assertEqual(order['partner_invoice_id'], partner_invoice.id) + self.assertEqual(order['payment_term'], payment_term.id) + self.assertEqual(len(order['order_line']), 1) + line = order['order_line'][0][2] + self.assertEqual(line['name'], 'My Real Name') + self.assertEqual(line['th_weight'], 15) + self.assertEqual(line['tax_id'], [(6, 0, [tax.id])]) + line = order['backend_order_line'][0][2] + self.assertEqual(line['name'], 'Line 2') + self.assertEqual(line['th_weight'], 30) + self.assertEqual(line['tax_id'], [(6, 0, [tax.id])]) diff --git a/connector_ecommerce/tests/test_picking_event.py b/connector_ecommerce/tests/test_picking_event.py new file mode 100644 index 00000000..b4d03cfc --- /dev/null +++ b/connector_ecommerce/tests/test_picking_event.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Guewen Baconnier +# Copyright 2015 Camptocamp SA +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import mock + +import openerp.tests.common as common + + +class TestPickingEvent(common.TransactionCase): + """ Test if the events on the pickings are fired correctly """ + + def setUp(self): + super(TestPickingEvent, self).setUp() + self.picking_model = self.env['stock.picking'] + self.sale_model = self.env['sale.order'] + self.sale_line_model = self.env['sale.order.line'] + + partner_model = self.env['res.partner'] + partner = partner_model.create({'name': 'Benjy'}) + self.sale = self.sale_model.create({'partner_id': partner.id}) + self.sale_line_model.create({ + 'order_id': self.sale.id, + 'product_id': self.env.ref('product.product_product_33').id, + 'name': "[HEAD-USB] Headset USB", + 'product_uom_qty': 42, + 'product_uom': self.env.ref('product.product_uom_unit').id, + 'price_unit': 65, + }) + self.sale_line_model.create({ + 'order_id': self.sale.id, + 'product_id': self.env.ref('product.product_product_28').id, + 'name': "[EXT-HDD] External Hard disk", + 'product_uom_qty': 2, + 'product_uom': self.env.ref('product.product_uom_unit').id, + 'price_unit': 405, + }) + self.sale.signal_workflow('order_confirm') + self.picking = self.sale.picking_ids + + def test_event_on_picking_out_done(self): + """ Test if the ``on_picking_out_done`` event is fired + when an outgoing picking is done """ + self.picking.force_assign() + event = ('openerp.addons.connector_ecommerce.' + 'stock.on_picking_out_done') + with mock.patch(event) as event_mock: + self.picking.action_done() + self.assertEquals(self.picking.state, 'done') + event_mock.fire.assert_called_with(mock.ANY, + 'stock.picking', + self.picking.id, + 'complete') + + def test_event_on_picking_out_done_partial(self): + """ Test if the ``on_picking_out_done`` informs of the partial + pickings """ + self.picking.force_assign() + self.picking.do_prepare_partial() + for operation in self.picking.pack_operation_ids: + operation.product_qty = 1 + event = ('openerp.addons.connector_ecommerce.' + 'stock.on_picking_out_done') + with mock.patch(event) as event_mock: + self.picking.do_transfer() + self.assertEquals(self.picking.state, 'done') + event_mock.fire.assert_called_with(mock.ANY, + 'stock.picking', + self.picking.id, + 'partial') + + def test_event_on_tracking_number_added(self): + """ Test if the ``on_tracking_number_added`` event is fired + when a tracking number is added """ + event = ('openerp.addons.connector_ecommerce.' + 'stock.on_tracking_number_added') + with mock.patch(event) as event_mock: + self.picking.carrier_tracking_ref = 'XYZ' + event_mock.fire.assert_called_with(mock.ANY, + 'stock.picking', + self.picking.id) diff --git a/connector_ecommerce/unit/__init__.py b/connector_ecommerce/unit/__init__.py new file mode 100644 index 00000000..45443ad8 --- /dev/null +++ b/connector_ecommerce/unit/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import sale_order_onchange diff --git a/connector_ecommerce/unit/sale_order_onchange.py b/connector_ecommerce/unit/sale_order_onchange.py new file mode 100644 index 00000000..015832c8 --- /dev/null +++ b/connector_ecommerce/unit/sale_order_onchange.py @@ -0,0 +1,228 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# connector-ecommerce for OpenERP +# Copyright (C) 2013-TODAY Akretion . +# @author Sébastien BEAU +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +from openerp.addons.connector.connector import ConnectorUnit + + +class OnChangeManager(ConnectorUnit): + + def merge_values(self, record, on_change_result, model=None): + record.update(self.get_new_values(record, on_change_result, + model=model)) + + def get_new_values(self, record, on_change_result, model=None): + vals = on_change_result.get('value', {}) + new_values = {} + for fieldname, value in vals.iteritems(): + if fieldname not in record: + if model: + column = self.env[model]._fields[fieldname] + if column.type == 'many2many': + value = [(6, 0, value)] + new_values[fieldname] = value + return new_values + + +class SaleOrderOnChange(OnChangeManager): + _model_name = None + + def _get_partner_id_onchange_param(self, order): + """ Prepare the arguments for calling the partner_id change + on sales order. You can overwrite this method in your own + module if they modify the onchange signature + + :param order: a dictionary of the value of your sales order + :type: dict + + :return: a tuple of args and kwargs for the onchange + :rtype: tuple + """ + args = [ + order.get('partner_id'), + ] + kwargs = {} + return args, kwargs + + def _play_order_onchange(self, order): + """ Play the onchange of the sales order + + :param order: a dictionary of the value of your sales order + :type: dict + + :return: the value of the sales order updated with the onchange result + :rtype: dict + """ + sale_model = self.env['sale.order'] + onchange_specs = sale_model._onchange_spec() + + # we need all fields in the dict even the empty ones + # otherwise 'onchange()' will not apply changes to them + all_values = order.copy() + for field in sale_model._fields: + if field not in all_values: + all_values[field] = False + + # we work on a temporary record + order_record = sale_model.new(all_values) + + new_values = {} + + # Play partner_id onchange + args, kwargs = self._get_partner_id_onchange_param(order) + values = order_record.onchange_partner_id(*args, **kwargs) + new_values.update(self.get_new_values(order, values, + model='sale.order')) + all_values.update(new_values) + + values = order_record.onchange(all_values, + 'payment_method_id', + onchange_specs) + new_values.update(self.get_new_values(order, values, + model='sale.order')) + all_values.update(new_values) + + values = order_record.onchange(all_values, + 'workflow_process_id', + onchange_specs) + new_values.update(self.get_new_values(order, values, + model='sale.order')) + all_values.update(new_values) + + res = {f: v for f, v in all_values.iteritems() + if f in order or f in new_values} + return res + + def _get_product_id_onchange_param(self, line, previous_lines, order): + """ Prepare the arguments for calling the product_id change + on sales order line. You can overwrite this method in your own + module if they modify the onchange signature + + :param line: the sales order line to process + :type: dict + :param previous_lines: list of dict of the previous lines processed + :type: list + :param order: data of the sales order + :type: dict + + :return: a tuple of args and kwargs for the onchange + :rtype: tuple + """ + args = [ + order.get('pricelist_id'), + line.get('product_id'), + ] + + # used in sale_markup: this is to ensure the unit price + # sent by the e-commerce connector is used for markup calculation + onchange_context = self.env.context.copy() + if line.get('price_unit'): + onchange_context.update({'unit_price': line.get('price_unit'), + 'force_unit_price': True}) + + uos_qty = float(line.get('product_uos_qty', 0)) + if not uos_qty: + uos_qty = float(line.get('product_uom_qty', 0)) + + kwargs = { + 'qty': float(line.get('product_uom_qty', 0)), + 'uom': line.get('product_uom'), + 'qty_uos': uos_qty, + 'uos': line.get('product_uos'), + 'name': line.get('name'), + 'partner_id': order.get('partner_id'), + 'lang': False, + 'update_tax': True, + 'date_order': order.get('date_order'), + 'packaging': line.get('product_packaging'), + 'fiscal_position': order.get('fiscal_position'), + 'flag': False, + 'context': onchange_context, + } + return args, kwargs + + def _play_line_onchange(self, line, previous_lines, order): + """ Play the onchange of the sales order line + + :param line: the sales order line to process + :type: dict + :param previous_lines: list of dict of the previous line processed + :type: list + :param order: data of the sales order + :type: dict + + :return: the value of the sales order updated with the onchange result + :rtype: dict + """ + line_model = self.env['sale.order.line'] + # Play product_id onchange + args, kwargs = self._get_product_id_onchange_param(line, + previous_lines, + order) + context = kwargs.pop('context', {}) + values = line_model.with_context(context).product_id_change(*args, + **kwargs) + self.merge_values(line, values, model='sale.order.line') + return line + + def play(self, order, order_lines): + """ Play the onchange of the sales order and it's lines + + It expects to receive a recordset containing one sales order. + It could have been generated with + ``self.env['sale.order'].new(values)`` or + ``self.env['sale.order'].create(values)``. + + :param order: data of the sales order + :type: recordset + :param order_lines: data of the sales order lines + :type: recordset + + :return: the sales order updated by the onchanges + :rtype: recordset + """ + # play onchange on sales order + order = self._play_order_onchange(order) + + # play onchange on sales order line + processed_order_lines = [] + line_lists = [order_lines] + if 'order_line' in order and order['order_line'] is not order_lines: + # we have both backend-dependent and oerp-native order + # lines. + # oerp-native lines can have been added to map + # shipping fees with an OpenERP Product + line_lists.append(order['order_line']) + for line_list in line_lists: + for idx, command_line in enumerate(line_list): + # line_list format:[(0, 0, {...}), (0, 0, {...})] + if command_line[0] in (0, 1): # create or update values + # keeps command number and ID (or 0) + old_line_data = command_line[2] + new_line_data = self._play_line_onchange( + old_line_data, processed_order_lines, order) + new_line = (command_line[0], + command_line[1], + new_line_data) + processed_order_lines.append(new_line) + # in place modification of the sales order line in the list + line_list[idx] = new_line + return order diff --git a/connector_ecommerce/wizard/__init__.py b/connector_ecommerce/wizard/__init__.py new file mode 100644 index 00000000..3935a40e --- /dev/null +++ b/connector_ecommerce/wizard/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import sale_ignore_cancel diff --git a/__unported__/connector_ecommerce/wizard/sale_ignore_cancel.py b/connector_ecommerce/wizard/sale_ignore_cancel.py similarity index 58% rename from __unported__/connector_ecommerce/wizard/sale_ignore_cancel.py rename to connector_ecommerce/wizard/sale_ignore_cancel.py index aa57c9dd..e58de7db 100644 --- a/__unported__/connector_ecommerce/wizard/sale_ignore_cancel.py +++ b/connector_ecommerce/wizard/sale_ignore_cancel.py @@ -19,28 +19,20 @@ # ############################################################################## -from openerp.osv import orm, fields +from openerp import models, fields, api -class sale_ignore_cancel(orm.TransientModel): +class SaleIgnoreCancel(models.TransientModel): _name = 'sale.ignore.cancel' _description = 'Ignore Sales Order Cancel' - _columns = { - 'reason': fields.html('Reason', required=True), - } + reason = fields.Html(required=True) - def confirm_ignore_cancel(self, cr, uid, ids, context=None): - if context is None: - context = {} - if isinstance(ids, (list, tuple)): - assert len(ids) == 1 - ids = ids[0] - order_ids = context.get('active_ids') - if order_ids is None: - return - form = self.browse(cr, uid, ids, context=context) - self.pool.get('sale.order').ignore_cancellation(cr, uid, order_ids, - form.reason, - context=context) + @api.multi + def confirm_ignore_cancel(self): + self.ensure_one() + sale_ids = self.env.context.get('active_ids') + assert sale_ids + sales = self.env['sale.order'].browse(sale_ids) + sales.ignore_cancellation(self.reason) return {'type': 'ir.actions.act_window_close'} diff --git a/__unported__/connector_ecommerce/wizard/sale_ignore_cancel_view.xml b/connector_ecommerce/wizard/sale_ignore_cancel_view.xml similarity index 100% rename from __unported__/connector_ecommerce/wizard/sale_ignore_cancel_view.xml rename to connector_ecommerce/wizard/sale_ignore_cancel_view.xml