def _generate_order_line_element(self, stock_picking):
     ret = []
     i = 1
     id_table = {}
     for ordered_id in sorted([x.id for x in stock_picking.move_lines]):
         id_table[str(ordered_id)] = i
         i += 1
     for move in stock_picking.move_lines:
         xml = create_element('Position')
         pos_no = id_table[str(move.id)]
         if move.yc_posno and move.yc_posno != pos_no:
             raise Warning(
                 _("Move line has been used before, and there is a mismatch"
                   ), move.name)
         move.write({'yc_posno': pos_no})
         xml.append(create_element('PosNo', pos_no))
         xml.append(
             create_element('ArticleNo', move.product_id.default_code))
         xml.append(create_element('Quantity', move.product_qty))
         xml.append(create_element('QuantityISO', move.product_uom.uom_iso))
         xml.append(create_element('PosText', move.product_id.name))
         ret.append(xml)
         xsd_error = validate_xml(self._factory_name,
                                  xml,
                                  print_error=self.print_errors)
         if xsd_error:
             logger.error('XSD validation error: {0}'.format(xsd_error))
     return ret
Ejemplo n.º 2
0
    def _generate_partner_address_element(self, partner, partner_type):
        ''' Creates and returns a <Partner> tag for the WAB.
        '''
        # Having this local, makes possible to pass languages to getTextAlias
        context = self.context.copy()
        if partner.lang:
            context['lang'] = partner.lang

        MAX_NUMBER_OF_NAME_TAGS = 4

        xml = create_element("Partner")
        xml.append(create_element('PartnerType', text=partner_type))

        partner_no = self.get_param('partner_no', required=True)
        xml.append(create_element('PartnerNo', text=partner_no))
        if partner.is_company:
            partner_ref = partner.ref
        else:
            partner_ref = partner.parent_id.ref
        xml.append(create_element('PartnerReference', text=partner_ref))

        if partner.title:
            xml.append(create_element('Title', partner.title.name))

        names = self.__generate_partner_name(partner)
        for idx in xrange(min(len(names), MAX_NUMBER_OF_NAME_TAGS)):
            # This will generate elements Name1 ... Name4
            xml.append(
                create_element('Name{0}'.format(idx + 1), text=names[idx]))

        street = False
        if partner.street:
            street = ' '.join([partner.street, partner.street_no
                               or '']).strip(' ')
        elif partner.po_box:
            street = ' '.join([_('P.O. Box'), partner.po_box])
        if street:
            xml.append(create_element('Street', text=street))
        xml.append(create_element('CountryCode', text=partner.country_id.code))
        xml.append(create_element('ZIPCode', text=partner.zip))
        xml.append(create_element('City', text=partner.city))
        if partner.phone:
            xml.append(create_element('PhoneNo', text=partner.phone))
        if partner.mobile:
            xml.append(create_element('MobileNo', text=partner.mobile))
        if partner.email:
            xml.append(create_element('Email', text=partner.email))
        if not partner.lang:
            raise Warning(
                _("Missing partner #{0} language code").format(partner.id))
            # Language code is required
        xml.append(create_element('LanguageCode', text=partner.lang[:2]))
        xsd_error = validate_xml("wab", xml, print_error=self.print_errors)
        if xsd_error:
            logger.error('XSD validation error: {0}'.format(xsd_error))
        return xml
Ejemplo n.º 3
0
def create_element(entity, text=None, attrib=None, ns='https://service.swisspost.ch/apache/yellowcube/YellowCube_ART_REQUEST_Artikelstamm.xsd'):
    element = old_create_element(entity, text, attrib, ns)
    if entity in _element_to_check:
        validation_errors = validate_xml('art', element, print_error=False)
        if validation_errors:
            f = _element_to_check[entity]
            t, a = f(text, attrib)
            logger.debug("Changing element {0} values {1}, {2} into: {3}, {4}".format(entity, text, attrib, t, a))
            element = old_create_element(entity, t, a, ns)
    return element
Ejemplo n.º 4
0
    def _generate_order_position_element(self, stock_picking):
        PICKINGMESSAGE_TAG_MAX_LENGTH = 132
        SHORTDESCRIPTION_TAG_MAX_LENGTH = 40

        ret = []
        i = 1
        id_table = {}
        for ordered_id in sorted([x.id for x in stock_picking.move_lines]):
            id_table[str(ordered_id)] = i
            i += 1
        for move in stock_picking.move_lines:
            if self.get_param('enable_product_lifecycle') and (move.product_id.product_state not in ['in_production']):
                raise Warning(_('Product {0} is not ready on the lifecycle. State: {1}').format(move.product_id.default_code, move.product_id.product_state))
            xml = create_element('Position')
            xml.append(create_element('PosNo', text=id_table[str(move.id)]))
            xml.append(create_element('ArticleNo', text=move.product_id.default_code))
            value = getattr(move, 'restrict_lot_id' if version_info[0] > 7 else 'prodlot_id', None)
            if value:
                xml.append(create_element('Lot', text=value.name))
            xml.append(create_element('Plant', text=self.get_param('plant_id', required=True)))
            xml.append(create_element('Quantity', text=move.product_qty))
            if not move.product_uom.uom_iso:
                raise Warning(_("Undefined ISO code for UOM '{0}', in product {1}").format(move.product_uom.name, move.product_id.default_code))
            xml.append(create_element('QuantityISO', text=move.product_uom.uom_iso))

            # Conditionally fills the tag <ShortDescription>, depending on if it's used as it has to be used
            # (encoding the product's name),  or if it's used to encode another information...
            short_description_usage = self.get_param('wab_shortdescription_mapping')
            if short_description_usage == 'name':
                short_description_value = move.product_id.name[:SHORTDESCRIPTION_TAG_MAX_LENGTH]
            else:  # if short_description_usage == 'price':
                short_description_value = "%0.2f" % move.product_id.list_price
            xml.append(create_element('ShortDescription', text=short_description_value))

            # Conditionally fills the tag <PickingMessage>, depending on if it's not used or if it's used
            # to encode  the reference of the carrier of the picking... This is supposed to be a temporary
            # solution until the new XSD is crafted.
            picking_message_usage = self.get_param('wab_pickingmessage_mapping')
            if picking_message_usage == 'carrier_tracking_ref':
                carrier_tracking_ref = stock_picking.carrier_tracking_ref
                if carrier_tracking_ref:
                    xml.append(create_element('PickingMessage', text=carrier_tracking_ref[:PICKINGMESSAGE_TAG_MAX_LENGTH]))

            if stock_picking.yellowcube_return_reason:
                xml.append(create_element('ReturnReason', text=stock_picking.yellowcube_return_reason))
            ret.append(xml)
            xsd_error = validate_xml("wab", xml, print_error=self.print_errors)
            if xsd_error:
                logger.error('XSD validation error: {0}'.format(xsd_error))
        return ret
Ejemplo n.º 5
0
    def import_file(self, file_text, only_check=False):
        self.success = True
        self.errors = []
        xml = open_xml(file_text, _type='bur', print_error=self.print_errors)
        if nspath(xml, '//bur:BUR_List'):
            i = 0
            for x in nspath(xml, '//bur:BUR_List/bur:BUR'):
                i += 1
                # First, we try to check the records
                try:
                    text = xml_to_string(x)
                    self.import_file(text, only_check=True)
                except Warning as w:
                    print 'error>>>' * 5
                    print text
                    print '<<<error' * 5
                    raise Warning('Error on sub BUR file number {0}'.format(i),
                                  format_exception(w))
            for x in nspath(xml, '//bur:BUR_List/bur:BUR'):
                self.import_file(xml_to_string(x), only_check=False)
            return True

        validate_xml('bur', xml, print_error=False)
        imports = []
        product_obj = self.pool.get("product.product")
        lot_obj = self.pool.get('stock.production.lot')
        connection_obj = self.pool.get('stock.connect')
        mapping_bur_transactiontypes_obj = self.pool.get(
            'mapping_bur_transactiontypes')
        location_obj = self.pool.get('stock.location')

        # Gets the warehouse of the YellowCube.
        warehouse = connection_obj.browse(
            self.cr, self.uid, self.connection_id,
            context=self.context).warehouse_ids[0]
        # Header fields (under <GoodsReceiptHeader>)
        header = nspath(xml, "//bur:GoodsMovementsHeader")[0]

        # <BookingVoucherID> and <BookingVoucherYear>.
        # TODO: Check or save the value
        booking_voucher_id = nspath(header, "bur:BookingVoucherID")[0].text
        # TODO: Check or save the value
        booking_voucher_year = nspath(header, "bur:BookingVoucherYear")[0].text
        depositor_no = nspath(header, "bur:DepositorNo")[0].text

        self._check(warehouse, depositor_no == self.get_param('depositor_no'),
                    _('Invalid DepositorNo'))

        for article in nspath(xml, "//bur:BookingList/bur:BookingDetail"):
            partial_success = True
            element = {}

            # YCArticleNo
            element['yc_YCArticleNo'] = nspath(article,
                                               "bur:YCArticleNo")[0].text
            search_domain = [("yc_YCArticleNo", "=", element['yc_YCArticleNo'])
                             ]
            # ArticleNo
            article_no = nspath(article, "bur:ArticleNo")
            if len(article_no) > 0:
                # ArticleNo: Only set on dictionary, when needed for search (this avoids overwrite)
                element['default_code'] = article_no[0].text
                search_domain = [("default_code", "=", element['default_code'])
                                 ]
            ids = product_obj.search(self.cr,
                                     self.uid,
                                     search_domain,
                                     context=self.context)
            if len(ids) > 0:
                element['id'] = ids[0]
            else:
                element['id'] = -1
            imports.append(element)
            if not self._check(
                    warehouse,
                    len(ids) == 1,
                    _('Invalid search domain {0}').format(search_domain)):
                continue

            product = product_obj.browse(self.cr,
                                         self.uid,
                                         ids,
                                         context=self.context)[0]
            # EAN
            ean13 = nspath(article, 'bur:EAN')
            if ean13:
                element['ean13'] = ean13[0].text
                if product.ean13:
                    partial_success &= self._check(
                        product, product.ean13 == element['ean13'],
                        _('Product EAN13 {0} differs from XML EAN {1}').format(
                            product.ean13, element['ean13']))
            # BVPosNo
            # TODO: Check or save the value
            bv_pos_no = nspath(article, 'bur:BVPosNo')[0].text

            # Plant
            plant = nspath(article, 'bur:Plant')[0].text
            partial_success &= self._check(product,
                                           plant == self.get_param('plant_id'),
                                           _('Mismatching PlantID'))

            # MovePlant
            # TODO: Check or save
            move_plant = nspath(article, 'bur:MovePlant')

            # StorageLocation
            source_location = nspath(article, 'bur:StorageLocation')[0].text

            # MoveStorageLocation
            destination_location = nspath(article, "bur:MoveStorageLocation")
            if destination_location:
                destination_location = destination_location[0].text
            else:
                destination_location = False

            # TransactionType
            transaction_type = nspath(
                article, "bur:TransactionType"
            )[0].text  # Mandatory field, so won't fail.

            # We now determine the origin and destination locations based on the fields
            # StorageLocation, MoveStorageLocation, and TransactionType.
            # Have a look at task with ID=3725 for the algorithm which is copied below:
            # IF StorageLocation is recognized as a valid location in Odoo
            # AND MoveSorageLocation is recognized as a valid location in Odoo
            # THEN use those
            if location_obj.search(self.cr, self.uid, [('name', '=', source_location)], context=self.context, count=True) and \
               destination_location and \
               location_obj.search(self.cr, self.uid, [('name', '=', destination_location)], context=self.context, count=True):
                element['location'] = source_location
                element['destination'] = destination_location
            else:
                # ELSE look up the TransactionType given in the BUR in the configured TransactionType list,
                # and if found and the locations are valid, use them.
                is_mapped, mapped_origin_location, mapped_destination_location = \
                    mapping_bur_transactiontypes_obj.get_mapping(self.cr, self.uid, [], transaction_type, context=self.context)
                if is_mapped and mapped_origin_location and mapped_destination_location:
                    element['location'] = mapped_origin_location.name
                    element['destination'] = mapped_destination_location.name

                else:
                    # ELSE create an issue and stop processing the BUR. after resolving the TransactionType mapping, the import can be restarted...
                    self.success = False  # We know now that we had no success.
                    error_message = _(
                        'Error when importing BUR: StorageLocation and/or MoveStorageLocation were not defined or incorrect, AND '
                        'no correct mapping was defined for TransactionType={0}'
                    ).format(transaction_type)
                    self.post_issue(warehouse, error_message)

            # YCLot
            # TODO: check
            yc_lot = nspath(article, 'bur:YCLot')
            if yc_lot:
                element['yellowcube_lot'] = yc_lot[0].text

            # Lot
            lot = nspath(article, 'bur:Lot')
            if len(lot) > 0:
                element['lot'] = lot[0].text

                lot_id = lot_obj.search(self.cr,
                                        self.uid,
                                        [('product_id', '=', product.id),
                                         ('name', '=', element['lot'])],
                                        context=self.context)
                if not self._check(
                        product,
                        len(lot_id) <= 1,
                        _('Impossible to find a unique lot {0}'.format(
                            element['lot']))):
                    continue
                if not lot_id:
                    values = {'product_id': product.id, 'name': element['lot']}
                    production_date = nspath(article, "bur:ProductionDate")
                    if production_date:
                        values['date'] = self.str_date_to_postgres(
                            production_date[0].text)
                    lot_use_date = nspath(article, "bur:BestBeforeDate")
                    if lot_use_date:
                        values['use_date'] = self.str_date_to_postgres(
                            lot_use_date[0].text)
                    if only_check:
                        lot_id = None
                    else:
                        lot_id = [
                            lot_obj.create(self.cr,
                                           self.uid,
                                           values,
                                           context=self.context)
                        ]
                if lot_id is None and only_check:
                    lot = None
                else:
                    lot = lot_obj.browse(self.cr,
                                         self.uid,
                                         lot_id,
                                         context=self.context)[0]

            # StockType
            element['stock_type'] = nspath(article, 'bur:StockType')[0].text

            # Quantity
            element['qty_available'] = nspath(article,
                                              "bur:QuantityUOM")[0].text

            # QuantityUOM
            qty_uom = nspath(article,
                             "bur:QuantityUOM")[0].attrib['QuantityISO']
            qty_uom_ids = self.pool.get('product.uom').search(
                self.cr,
                self.uid, [('uom_iso', '=', qty_uom)],
                context=self.context)

            partial_success &= self._check(
                product, qty_uom_ids,
                _('There is not any Unit of Measure with ISO code being {0}.'.
                  format(qty_uom)))
            if partial_success:
                element['qty_uom_id'] = qty_uom_ids[0]

            write_on_lot = {}
            # BestBeforDate
            lot_use_date = nspath(article, "bur:BestBeforeDate")
            element['lot_use_date'] = False
            if len(lot_use_date) > 0:
                lot_use_date = lot_use_date[0].text
                element['lot_use_date'] = self.str_date_to_postgres(
                    lot_use_date)
                if lot is None:
                    self._check(product, only_check,
                                _('The lot may not exists in a two step file'))
                else:
                    if not lot.use_date:
                        write_on_lot['use_date'] = element['lot_use_date']
                    else:
                        partial_success &= self._check(
                            product,
                            self.keep_only_date(
                                lot.use_date) == element['lot_use_date'],
                            _('Mismatch with lot best before date'))

            # ProductionDate
            production_date = nspath(article, "bur:ProductionDate")
            element['date'] = False
            if production_date:
                lot_date = lot_use_date[0].text
                element['lot_date'] = self.str_date_to_postgres(lot_date)
                if not lot.date:
                    write_on_lot['date'] = element['lot_date']
                else:
                    partial_success &= self._check(
                        product,
                        self.keep_only_date(lot.date) == element['lot_date'],
                        _('Mismatch with lot fabrication date'))

            if write_on_lot and partial_success:
                lot.write(write_on_lot)

            element['name'] = "BUR-{0}-{1}".format(
                nspath(xml, "//bur:ControlReference/bur:Timestamp")[0].text,
                nspath(article, "bur:BVPosNo")[0].text)

        # print imports
        if not self.context.get('force_import', False):
            bad_imports = [x['yc_YCArticleNo'] for x in imports if x['id'] < 0]
            if len(bad_imports) > 0:
                raise Exception(
                    "Invalid XML Elements: {0}".format(bad_imports))

        if not self.success:
            raise Warning(
                'There where errors on the import process. See import log thread.',
                self.errors)

        if only_check:
            # Everything was OK, and it could be imported in a second step.
            return True

        stock_move_pool = self.pool.get("stock.move")
        for article in imports:
            _id = article['id']
            self.mark_record(_id, 'product.product')
            _lot = False
            if 'lot' in article:
                _lot = self.pool.get('stock.production.lot').search(
                    self.cr,
                    self.uid, [('name', '=', article['lot']),
                               ('product_id', '=', _id)],
                    context=self.context)
                if len(_lot) > 0:
                    _lot = _lot[0]
                else:
                    _lot = self.pool.get('stock.production.lot').create(
                        self.cr,
                        self.uid, {
                            'name': article['lot'],
                            'product_id': _id,
                            'date': element.get('lot_date', None),
                            'use_date': element.get('lot_use_date', None)
                        },
                        context=self.context)

            def loc(is_input, element, warehouse):
                key = 'location' if is_input else 'destination'
                if (not input) and element['stock_type'] not in ['', '0', 'F']:
                    return warehouse.lot_blocked_id.id
                if element[key] == 'YROD':
                    return warehouse.lot_input_id.id
                if element[key] == 'YAFS':
                    return warehouse.lot_stock_id.id
                return self.pool['ir.model.data'].get_object_reference(
                    self.cr, self.uid, 'stock', 'location_inventory')[1]

            stock_move_id = stock_move_pool.create(
                self.cr,
                self.uid, {
                    'name':
                    article['name'],
                    'product_id':
                    _id,
                    'location_id':
                    loc(True, article, warehouse),
                    'location_dest_id':
                    loc(False, article, warehouse),
                    'product_uom_qty' if V8 else 'product_qty':
                    article['qty_available'],
                    'product_uom':
                    article['qty_uom_id'],
                    'state':
                    'done',
                    'restrict_lot_id' if V8 else 'prodlot_id':
                    _lot,
                    'origin':
                    'YellowCube',
                    'type':
                    'internal',
                    'yc_booking_voucher_id':
                    booking_voucher_id,
                    'yc_booking_voucher_year':
                    booking_voucher_year,
                },
                context=self.context)
            self.mark_record(_id, 'product.product')

        return True
Ejemplo n.º 6
0
    def import_file(self, file_text):
        logger.debug("Processing WBA file")
        self.success = True
        self.errors = []

        # Caches the pools.
        product_obj = self.pool.get('product.product')
        stock_obj = self.pool.get('stock.picking')
        stock_move_obj = self.pool.get('stock.move')
        warehouse_obj = self.pool.get('stock.warehouse')
        purchase_order_obj = self.pool.get('purchase.order')
        config_param_obj = self.pool.get('ir.config_parameter')
        connection_obj = self.pool.get('stock.connect')
        stock_production_lot_obj = self.pool.get('stock.production.lot')

        warehouse_id = connection_obj.browse(self.cr, self.uid, self.connection_id, context=self.context).warehouse_ids[0].id
        warehouse = warehouse_obj.browse(self.cr, self.uid, warehouse_id, context=self.context)

        xml = open_xml(file_text, _type='wba', print_error=self.print_errors)
        if nspath(xml, '//wba:WBA_List'):
            i = 0
            self.cr.execute("SAVEPOINT yellowcube_wba_xml_factory__WBAList;")
            for x in nspath(xml, '//wba:WBA_List/wba:WBA'):
                i += 1
                # First, we try to check the records
                try:
                    text = xml_to_string(x)
                    self.import_file(text)
                except Warning as w:
                    self.cr.execute("ROLLBACK TO SAVEPOINT yellowcube_wba_xml_factory__WBAList;")
                    print 'error>>>' * 5
                    print text
                    print '<<<error' * 5
                    raise Warning('Error on sub WBA file number {0}'.format(i), format_exception(w))
            self.cr.execute("RELEASE SAVEPOINT yellowcube_wba_xml_factory__WBAList;")
            return True

        validate_xml('wba', xml, print_error=False)

        imports = []

        # Gets the timestamp.
        timestamp_postgres = self.str_date_to_postgres(nspath(xml, "//wba:Timestamp")[0].text)

        # Header fields (under <GoodsReceiptHeader>)
        header = nspath(xml, "//wba:GoodsReceiptHeader")[0]

        # <BookingVoucherID> and <BookingVoucherYear>.
        booking_voucher_id = nspath(header, "wba:BookingVoucherID")[0].text
        booking_voucher_year = nspath(header, "wba:BookingVoucherYear")[0].text
        supplier_order_no = nspath(header, "wba:SupplierOrderNo")[0].text  # This is the stock.picking's ID.
        picking_in_ids = stock_obj.search(self.cr, self.uid, [('yellowcube_customer_order_no', '=', supplier_order_no),
                                                              ('state', 'not in', ['cancel', 'done']),
                                                              ], context=self.context)

        # Checks if the stock.picking exists. Otherwise, logs an issue an continues with the next one.
        self._check(warehouse, len(picking_in_ids) > 0, _("There is not any stock.picking with SupplierOrderNo (id) ={0}").format(supplier_order_no))
        if not self.success:
            raise Warning('There where some errors in the WBA file.', self.errors)

        # Gets the stock picking in associated to this purchase order.
        picking_in = stock_obj.browse(self.cr, self.uid, picking_in_ids[0], self.context)

        # <SupplierNo>.
        # We first check if the supplier has a supplier number, and if that's the case we
        # compare against it. Otherwise, we compare against the default supplier number
        # set for the connector.
        supplier_no = nspath(header, "wba:SupplierNo")[0].text
        if picking_in.partner_id.supplier and picking_in.partner_id.yc_supplier_no:
            yc_supplier_no = picking_in.partner_id.yc_supplier_no
            self._check(warehouse, yc_supplier_no == supplier_no, _("Configuration variable YC SupplierNo does not match with that of tag 'SupplierNo' on the supplier."))
        else:
            yc_supplier_no = self.get_param('supplier_no', required=True)
            self._check(warehouse, yc_supplier_no, _("Configuration variable YC SupplierNo is not defined in the system."))
            self._check(warehouse, yc_supplier_no == supplier_no, _("Configuration variable YC SupplierNo does not match with that of tag 'SupplierNo' on the connector."))

        # <SupplierOrderNo>.
        stock_picking_in_count = stock_obj.search(self.cr, self.uid, [('yellowcube_customer_order_no', '=', supplier_order_no)], context=self.context, count=True)
        self._check(warehouse, stock_picking_in_count > 0, _("Stock picking in with ID={0} does not exist in the system, thus can not be processed in the WBA.").format(supplier_order_no))

        id_table = {}
        last_posno = 0
        # Update missing values
        for line in picking_in.move_lines:
            if line.yc_posno:
                if line.yc_posno > last_posno:
                    last_posno = line.yc_posno
            id_table[line.id] = line
        for line_id in sorted([x for x in id_table]):
            line = id_table[line_id]
            if not line.yc_posno:
                last_posno += 1
                line.yc_posno = last_posno
                line.write({'yc_posno': last_posno})
        # Refresh the record
        picking_in = stock_obj.browse(self.cr, self.uid, picking_in_ids[0], self.context)

        for article in nspath(xml, "//wba:GoodsReceiptList/wba:GoodsReceiptDetail"):
            partials = {}
            partial = {}

            # <SupplierOrderPosNo>
            pos_no = int(nspath(article, "wba:SupplierOrderPosNo")[0].text)

            # Gets the stock.move associated to this line.
            move_line = None
            for line in picking_in.move_lines:
                if line.yc_posno == pos_no:
                    move_line = line
                    break

            self._check(picking_in, move_line is not None, _('Mismatch with stock picking line number {0}/{1}').format(pos_no, [x.yc_posno for x in picking_in.move_lines]))
            if not self.success:
                raise Warning('Error parsing wba file', self.errors)

            partials[move_line if V8 else "move{0}".format(move_line.id)] = partial

            partial['delivery_date'] = timestamp_postgres

            # Caches the product of the stock.move.
            product_id = move_line.product_id.id
            partial['product_id'] = product_id
            product = product_obj.browse(self.cr, self.uid, product_id, self.context)

            # <YCArticleNo>
            yc_article_no = nspath(article, "wba:YCArticleNo")[0].text
            if not product.yc_YCArticleNo:
                product_obj.write(self.cr, self.uid, product_id, {'yc_YCArticleNo': yc_article_no}, self.context)
                product.message_post(_('Product {0} with ID={1} did not have a YCArticleNo, so it was created with value {2}').format(product.name, product_id, yc_article_no))
            else:
                # If the product already had a YCArticleNo, then we check if the values match.
                self._check(warehouse, product.yc_YCArticleNo == yc_article_no, _("The 'YCArticleNo' does not match with the field 'YCArticleNo' of the product."))

            # <ArticleNo>
            article_no = nspath(article, "wba:ArticleNo")
            if article_no:
                article_no = article_no[0].text
                if not product.default_code:
                    product_obj.write(self.cr, self.uid, product_id, {'default_code': article_no}, self.context)
                    product.message_post(_('Product {0} with ID={1} did not have a default_code, so it was created with value {2}').format(product.name, product_id, article_no))
                else:
                    # If the product already has an ArticleNo (field 'default_code' in Odoo), then we check if the values match.
                    self._check(warehouse, product.default_code == article_no,
                                '{0} [{1}!={2}]'.format(_("The 'ArticleNo' does not match with the field 'default_code' of the product."), product.default_code, article_no))

            # <EAN>
            ean = nspath(article, "wba:EAN")
            if ean:
                ean = ean[0].text
                if not product.ean13:
                    product_obj.write(self.cr, self.uid, product_id, {'ean13': ean}, self.context)
                    product.message_post(_('Product {0} with ID={1} did not have an ean13 code, so it was created with value {2}').format(product.name, product_id, ean))
                else:
                    # If the product already has an EAN (field 'ean13' in Odoo) then we check if both values match.
                    self._check(warehouse, product.ean13 == ean, _("The 'EAN' does not match with the field 'ean13' of the product."))

            # <Lot>
            lot_search_domain = [('product_id', '=', product_id)]
            lot = nspath(article, 'wba:Lot')
            if lot:
                lot = lot[0].text
                lot_search_domain.append(('name', '=', lot))

            # <YCLot>
            yc_lot = nspath(article, 'wba:YCLot')
            if yc_lot:
                yc_lot = yc_lot[0].text
#                 lot_search_domain.append(('yellowcube_lot', '=', yc_lot))

            # If a lot was indicated but it does not exist in the system, create it.
            lot_ids = stock_production_lot_obj.search(self.cr, self.uid, lot_search_domain, context=self.context)
            if lot and (not lot_ids):
                lot_id_ = stock_production_lot_obj.create(self.cr, self.uid, {'name': lot,
                                                                              'yellowcube_lot': yc_lot or False,
                                                                              'product_id': product_id,
                                                                              'date': timestamp_postgres},
                                                          self.context)
                lot_ids = [lot_id_]
                lot = stock_production_lot_obj.browse(self.cr, self.uid, lot_id_, self.context)
                lot.message_post(_('Stock.production.lot {0} with ID={1} did not existed, and it was created by VoucherID {2}').format(lot.name, lot.id, booking_voucher_id),)

            lot = None
            if lot_ids:
                lot = stock_production_lot_obj.browse(self.cr, self.uid, lot_ids[0], self.context)
            # If <YCLot> exists but the stock production lot does not have it, stores it. If it has it, checks.
            if yc_lot and lot:
                if not lot.yellowcube_lot:
                    stock_production_lot_obj.write(self.cr, self.uid, lot.id, {'yellowcube_lot': yc_lot}, self.context)
                    lot.message_post(_('Stock.production.lot {0} with ID={1} did not have a yellowcube_lot, so it was set with value {2}').format(lot.name, lot.id, yc_lot))
                else:
                    self._check(warehouse, lot.yellowcube_lot == yc_lot, _("YCLot in the WBA does not match with the value of the stock.production.lot"))

            if lot_ids:
                partial['restrict_lot_id' if V8 else 'prodlot_id'] = lot_ids[0]

            if product.track_incoming:
                self._check(warehouse, lot is not None, _("The WBA file must contain a lot, otherwise the stock.move can not be updated for product {0}".format(product.name)))

            # <Plant>
            plant = nspath(article, 'wba:Plant')[0].text
            current_plant = self.get_param('plant_id', required=True)
            if current_plant:
                self._check(warehouse, current_plant == plant, _('Plant does not match with the value of the configuration parameter YC PlantID.'))
            elif not current_plant:
                self.set_param('plant_id', plant)

            #  <QuantityUOM>
            quantity_uom = float(nspath(article, "wba:QuantityUOM")[0].text)
            self._check(picking_in, move_line.product_qty >= quantity_uom, _('Product {0}: QuantityUOM is greater than that of the stock.move.').format(product.name))
            partial['product_qty'] = quantity_uom

            # <QuantityISO>
            quantity_iso = nspath(article, "wba:QuantityUOM")[0].attrib['QuantityISO']
            uom_iso_list = self.pool.get('product.uom').search(self.cr, self.uid, [('uom_iso', '=', quantity_iso)], context=self.context)
            if len(uom_iso_list) > 0 and move_line.product_uom and (quantity_iso != move_line.product_uom.uom_iso):
                self._check(picking_in, False, _('Product {0}: Attribute QuantityISO does not match the ISO code indicated of the original stock.move.').format(product.name))
            else:
                if not move_line.product_uom:
                    product_uom = uom_iso_list[0]
                    partial['product_uom'] = product_uom
                else:
                    self._check(warehouse, move_line.product_uom.uom_iso == quantity_iso, _('Product {0}: Attribute QuantityISO does not match that of the stock.move.').format(product.name))
                    partial['product_uom'] = move_line.product_uom.id

            # Checks <StorageLocation> and <StockType>
            # Notes: Check together with StorageLocation against location_id on stock.move - alarm if wrong.
            #        If free type (' ', '0', 'F') use the StorageLocation, otherwise location YBLK.
            storage_location = nspath(article, "wba:StorageLocation")[0].text
            stock_type = nspath(article, "wba:StockType")
            if move_line.location_id:
                if stock_type:
                    # If there exists the tag <StockType>, then we follow the rules.
                    stock_type = stock_type[0].text

                    if stock_type not in ('X', 'S', '2', '3', '0', 'F', ' '):
                        self._check(picking_in, False, _("Product {0}: StockType had value '{1}', which is not allowed.").format(product.name, stock_type), self.context)
                    elif stock_type in ('0', 'F', ' '):
                        if move_line.location_dest_id.name != storage_location:
                            self._check(picking_in,
                                        False,
                                        _('Product {0}: StorageLocation {1} does not match with the location indicated in the stock.move {2}.').format(product.name,
                                                                                                                                                       storage_location,
                                                                                                                                                       move_line.location_dest_id.name))
                    else:
                        if move_line.location_dest_id.name != 'YBLK':
                            self._check(picking_in, False, _("Product {0}: StorageLocation must be 'YBLK' since StockType is not a free type.").format(product.name))
                else:
                    # If <StockType> does not exist, it just checks that the values match.
                    if move_line.location_dest_id.name != storage_location:
                        self._check(picking_in,
                                    False,
                                    _('Product {0}: StorageLocation {1} does not match with the location indicated in the stock.move {2}.').format(product.name,
                                                                                                                                                   storage_location,
                                                                                                                                                   move_line.location_dest_id.name))
            else:
                self._check(picking_in, False, _('Product {0}: The stock move does not have a location_id.').format(product.name))

            # <EndOfDeliveryFlag>
            if self.success:
                end_of_delivery_flag = nspath(article, "wba:EndOfDeliveryFlag")[0].text
                complete_move_ids = []
                if V8:
                    for move in partials:
                        vals = partials[move]
                        new_move_id = stock_move_obj.split(self.cr,
                                                           self.uid,
                                                           move,
                                                           vals['product_qty'],
                                                           restrict_lot_id=vals.get('restrict_lot_id', False),
                                                           context=self.context)
                        stock_move_obj.action_done(self.cr, self.uid, [new_move_id], context=self.context)
                        complete_move_ids.append(new_move_id)
                else:
                    complete_move_ids = picking_in.do_partial(partials)

                if end_of_delivery_flag == '1':  # delivery is completed.
                    number_of_pending_moves = stock_move_obj.search(self.cr, self.uid, [('picking_id', '=', picking_in.id),
                                                                                        ('state', 'in', ('draft', 'waiting', 'confirmed', 'assigned')),
                                                                                        ], context=self.context, count=True)
                    if number_of_pending_moves > 0:
                        pass  # They don't want this alarm __for the moment__.
                        #self.post_issue(picking_in, _('Tag EndOfDeliveryFlag was set, but there exists some stock move which are not in state finish nor cancelled.'))
                    else:
                        picking_in.action_done()  # Closes the picking.
                # moves may have been deleted in the process (???)
                # So that is why we need to iterate over those which are kept.
                move_ids = stock_move_obj.search(self.cr, self.uid, [('id', 'in', complete_move_ids)], context=self.context)
                stock_move_obj.write(self.cr, self.uid, move_ids, {'yc_booking_voucher_id': booking_voucher_id,
                                                                   'yc_booking_voucher_year': booking_voucher_year,
                                                                   }, self.context)

        if self.success:
            self.mark_record(picking_in.id, 'stock.picking' if V8 else 'stock.picking.in')
            # Only confirm when received the end of delivery flag
            # picking_in.action_done()
        else:
            raise Warning('There where some errors in the WBA file', self.errors)

        return True
Ejemplo n.º 7
0
    def generate_root_element(self, stock_location, domain=None):
        self.success = True
        self.processed_items = []

        # xml = '<?xml version="1.0" encoding="UTF-8"?>\n'
        # xml = '{0}<WAB xsi:noNamespaceSchemaLocation="YellowCube_WAB_Warenausgangsbestellung.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\n'.format(xml)
        # xsi = 'http://www.host.org/2001/XMLSchema-instance'
        # art_loc = 'https://service.swisspost.ch/apache/yellowcube/YellowCube_ART_REQUEST_Artikelstamm.xsd'
        xml_root = create_root('{{{art}}}ART')

        # WAB > ControlReference
        now = datetime.datetime.now()
        xml_control_reference = create_element('ControlReference')
        xml_control_reference.append(create_element('Type', text='ART'))
        xml_control_reference.append(create_element('Sender', text=self.get_param('sender', required=True)))
        xml_control_reference.append(create_element('Receiver', text=self.get_param('receiver', required=True)))
        xml_control_reference.append(create_element(
            'Timestamp',
            text='{0:04d}{1:02d}{2:02d}{3:02d}{4:02d}{5:02d}'.format(now.year, now.month, now.day, now.hour, now.hour, now.minute)
        ))
        xml_control_reference.append(create_element('OperatingMode', text=self.get_param('operating_mode', required=True)))
        xml_control_reference.append(create_element('Version', text='1.0'))
        xml_root.append(xml_control_reference)

        xml_article_list = create_element('ArticleList')
        xml_root.append(xml_article_list)
        product_pool = self.pool.get('product.product')
        self.cr.execute("SELECT code FROM res_lang WHERE active")
        languages = [x[0] for x in self.cr.fetchall()]

        basic_domain = [
            ('type', '!=', 'service')
        ]
        if self.force_product_ids:
            basic_domain.append(('id', 'in', self.force_product_ids))
        if self.ignore_product_ids:
            basic_domain.append(('id', 'not in', self.ignore_product_ids))

        if self.get_param('enable_product_lifecycle'):
            # Field 'product_state' is defined on the product's lifecycle.
            basic_domain.append(('product_state', '!=', 'draft'))
        else:
            # If we don't have the product's lifecycle, we just consider if
            # the product is active.
            basic_domain.append(('active', '=', True))

        if domain:
            # If a domain is passed by argument, we extend the basic one
            for x in domain:
                basic_domain.append(x)
        empty_file = True
        product_ids = product_pool.search(self.cr, self.uid, basic_domain, context=self.context)
        for product_id in product_ids:
            self.processed_items.append(('product.product', product_id))
            empty_file = False
            xml_article_list.append(self._generate_article_element(product_pool.browse(self.cr, self.uid, product_id, context=self.context), languages))
        if empty_file:
            return None

        xsd_error = validate_xml("art", xml_root, print_error=self.print_errors)
        if xsd_error:
            raise Warning(xsd_error)
        return xml_root
Ejemplo n.º 8
0
    def _generate_article_element(self, product, languages, raise_error=False):

        def mtq_to_cmq(number):
            ''' Receives a number which is in MTQ (cubic meters)
                and returns its equivalent in CMQ (cubic centimeters).
                1 m^3 == 1000000 cm^3.
            '''
            return 1000000.0 * number

        xml = create_element('Article')
        xml.append(etree.Comment("Model: product.product ID: {0}".format(product.id)))
        errors_list = []

        # The ChangeFlag tag is computed differently depending on if the
        # product's lifecycle is enabled or not.
        change_flag = ''
        if self.get_param('enable_product_lifecycle'):
            if product.product_state == 'in_production':
                if product.yc_YCArticleNo:
                    change_flag = 'U'
                else:
                    change_flag = 'I'
            elif product.product_state == 'deactivated':
                change_flag = 'D'
            else:
                change_flag = 'U'
        else:
            def _product_under_sale():
                sol = self.env['sale.order.line'].search([('product_id', '=', product.id),
                                                          ('state', 'not in', ['cancel', 'done'])],
                                                         limit=1)
                return True if sol else False
            if (product.state != 'obsolete') or _product_under_sale():
                if product.yc_YCArticleNo:
                    change_flag = 'U'
                else:
                    change_flag = 'I'
            else:
                change_flag = 'D'

        dp_pool = self.pool.get('decimal.precision')
        weight_format = '{{0:.{0}f}}'.format(dp_pool.precision_get(self.cr, self.uid, 'Stock Weight'))
        length_format = '{{0:.{0}f}}'.format(dp_pool.precision_get(self.cr, self.uid, 'Stock Length'))
        width_format = '{{0:.{0}f}}'.format(dp_pool.precision_get(self.cr, self.uid, 'Stock Width'))
        height_format = '{{0:.{0}f}}'.format(dp_pool.precision_get(self.cr, self.uid, 'Stock Height'))
        volume_format = '{{0:.{0}f}}'.format(dp_pool.precision_get(self.cr, self.uid, 'Stock Volume'))

        xml.append(create_element('ChangeFlag', change_flag))
        xml.append(create_element('DepositorNo', self.get_param('depositor_no', required=True)))
        xml.append(create_element('PlantID', self.get_param('plant_id', required=True)))
        xml.append(create_element('ArticleNo', product.default_code))
        if not product.uom_id.uom_iso:
            logger.error(_("Undefined UOM ISO code: {0}").format(product.uom_id.name))
        xml.append(create_element('BaseUOM', product.uom_id.uom_iso))
        xml.append(create_element('NetWeight', weight_format.format(product.weight_net), {'ISO': 'KGM'}))
        xml.append(create_element('BatchMngtReq', '1' if product.track_outgoing else '0'))

        min_rem_life = product.expiration_accept_time
        period_exp_date_type = product.expiration_accept_time_uom
        if period_exp_date_type and product.track_outgoing:
            # If they UOM is not set, then we don't add this field
            xml.append(create_element('MinRemLife', int(min_rem_life)))
            # Each Unit of Measure has a different code according to the mapping table X01.00
            translations = {'days': '',
                            'weeks': '1',
                            'months': '2',
                            'years': '3'}
            error_message = _("Value for system's parameter 'default_expiration_accept_time_uom' is '{0}' while it must be one of {1}.").format(period_exp_date_type, ','.join(translations.keys()))
            partial_success = self._check(product, period_exp_date_type in translations, error_message)
            if partial_success:
                xml.append(create_element('PeriodExpDateType', translations[period_exp_date_type]))
            else:
                errors_list.append(error_message)

        # OPTIONAL xml.append(create_element('PeriodExpDateType', '???'))
        xml.append(create_element('SerialNoFlag', '1' if product.yc_track_outgoing_scan else '0'))

        xml_uom = create_element('UnitsOfMeasure')
        xml.append(xml_uom)
        if product.ean13:
            xml_uom.append(create_element('EAN', product.ean13, {'EANType': 'HE'}))
        xml_uom.append(create_element('AlternateUnitISO', product.uom_id.uom_iso))
        xml_uom.append(create_element('GrossWeight', weight_format.format(product.weight), {'ISO': 'KGM'}))
        xml_uom.append(create_element('Length', length_format.format(product.length), {'ISO': 'CMT'}))
        xml_uom.append(create_element('Width', width_format.format(product.width), {'ISO': 'CMT'}))
        xml_uom.append(create_element('Height', height_format.format(product.height), {'ISO': 'CMT'}))
        xml_uom.append(create_element('Volume', volume_format.format(mtq_to_cmq(product.volume)), {'ISO': 'CMQ'}))

        xml_desc = create_element('ArticleDescriptions')
        xml.append(xml_desc)
        product_pool = self.pool.get('product.product')
        names = {}
        for lang in languages:
            if lang[:2] not in ['de', 'fr', 'it', 'en']:
                continue
            _name = product_pool.read(self.cr, self.uid, [product.id], ['name'], {'lang': lang})[0]['name']
            if lang[:2] not in names:
                xml_desc.append(create_element('ArticleDescription', _name, {'ArticleDescriptionLC': lang[:2]}))
                names[lang[:2]] = _name

        xsd_error = validate_xml(self._factory_name, xml, print_error=self.print_errors)
        if xsd_error:
            if raise_error:
                raise osv.except_osv('XSD validation error', xsd_error)
            else:
                logger.error('XSD validation error: {0}'.format(xsd_error))
        else:
            date_current_str = datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
            self.pool.get('product.product').write(self.cr,
                                                   self.uid,
                                                   [product.id],
                                                   {'yc_last_changeflag_submitted': change_flag,
                                                    'yc_last_changeflag_submitted_date': date_current_str})
        if self.success:
            return xml
        else:
            raise Warning(_('Errors appeared while generating the ART file:\n{0}'.format('\n'.join(errors_list))))
Ejemplo n.º 9
0
    def generate_root_element(self, stock_picking):
        # xml = '<?xml version="1.0" encoding="UTF-8"?>\n'
        # xml = '{0}<WAB xsi:noNamespaceSchemaLocation="YellowCube_WAB_Warenausgangsbestellung.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\n'.format(xml)
        DELIVERYINSTRUCTIONS_TAG_MAX_LENGTH = 15

        xml_root = create_element('WAB')

        self.context['yc_customer_order_no'] = stock_picking.yellowcube_customer_order_no

        picking_mode = stock_picking.type
        sale_order = stock_picking.sale_id
        xml_root.append(Comment(_('Sale Order #{0}: {1}').format(sale_order.id, sale_order.name)))

        if not sale_order:
            raise Warning(_("There is no sale.order related to this stock.picking (type={0})").format(picking_mode))
        if not self.context.get('yc_ignore_wab_reports', False):
            sale_order.generate_reports()

        # WAB > ControlReference
        now = datetime.now()
        xml_control_reference = create_element('ControlReference')
        xml_control_reference.append(create_element('Type', text='WAB'))
        xml_control_reference.append(create_element('Sender', text=self.get_param('sender', required=True)))
        xml_control_reference.append(create_element('Receiver', text=self.get_param('receiver', required=True)))
        xml_control_reference.append(create_element(
            'Timestamp',
            text='{0:04d}{1:02d}{2:02d}{3:02d}{4:02d}{5:02d}'.format(now.year, now.month, now.day, now.hour, now.hour, now.minute)
        ))
        xml_control_reference.append(create_element('OperatingMode', text=self.get_param('operating_mode', required=True)))
        xml_control_reference.append(create_element('Version', text='1.0'))
        xml_root.append(xml_control_reference)

        # WAB -> Order
        xml_order = create_element('Order')
        xml_root.append(xml_order)
        # WAB -> OrderHeader
        xml_order_header = create_element('OrderHeader')
        xml_order_header.append(create_element('DepositorNo', self.get_param('depositor_no', required=True)))
        xml_order_header.append(create_element('CustomerOrderNo', text=stock_picking.get_customer_order_no()[stock_picking.id]))
        dateorder = sale_order.date_order.split(' ')[0]
        xml_order_header.append(create_element('CustomerOrderDate', text=dateorder.replace('-', '')))
        xml_order.append(xml_order_header)

        # WAB -> PartnerAddress
        xml_partner_address = create_element('PartnerAddress')
        xml_partner_address.append(self._generate_partner_address_element(sale_order.partner_shipping_id, self.get_param('wab_partner_type_for_shipping_address')))
        if self.get_param('wab_add_invoicing_address'):
            xml_partner_address.append(self._generate_partner_address_element(sale_order.partner_invoice_id, self.get_param('wab_partner_type_for_invoicing_address')))
        xml_order.append(xml_partner_address)

        # WAB -> ValueAddedServices
        xml_value_added_services = create_element('ValueAddedServices')
        xml_additional_service = create_element('AdditionalService')

        if picking_mode in ['out', 'outgoing']:
            # <BasicShippingServices> under ValueAddedServices/AdditionalService
            if stock_picking.carrier_id and stock_picking.carrier_id.yc_basic_shipping:
                xml_additional_service.append(create_element('BasicShippingServices', text=stock_picking.carrier_id.yc_basic_shipping))
            else:
                raise Warning(_('Missing Basic shipping in delivery method'), sale_order.name)
        else:
            xml_additional_service.append(create_element('BasicShippingServices', text="RETOURE"))

        # <AdditionalShippingServices> under ValueAddedServices/AdditionalService
        if stock_picking.carrier_id and stock_picking.carrier_id.yc_additional_shipping:
            xml_additional_service.append(create_element('AdditionalShippingServices', text=stock_picking.carrier_id.yc_additional_shipping))

        # <DeliveryInstructions> under ValueAddedServices/AdditionalService
        if stock_picking.carrier_id and stock_picking.carrier_id.pc_delivery_instructions:
            xml_additional_service.append(create_element('DeliveryInstructions', text=stock_picking.carrier_id.pc_delivery_instructions[:DELIVERYINSTRUCTIONS_TAG_MAX_LENGTH]))

        # <FrightShippingFlag> under ValueAddedServices/AdditionalService
        xml_additional_service.append(create_element('FrightShippingFlag', text=('1' if stock_picking.carrier_id.pc_freight_shipping else '0')))

        # <ShippingInterface> under ValueAddedServices/AdditionalService
        if stock_picking.carrier_id and stock_picking.carrier_id.pc_shipping_interface:
            xml_additional_service.append(create_element('ShippingInterface', text=stock_picking.carrier_id.pc_shipping_interface))

        xml_value_added_services.append(xml_additional_service)
        xml_order.append(xml_value_added_services)

        # WAB -> OrderPositions
        xml_order_positions = create_element('OrderPositions')
        for position in self._generate_order_position_element(stock_picking):
            xml_order_positions.append(position)
        xml_order.append(xml_order_positions)

        # WAB -> OrderDocuments
        xml_order_documents = create_element('OrderDocuments', attrib={'OrderDocumentsFlag': '1'})
        xml_order_doc_filenames = create_element('OrderDocFilenames')
        for filename in self.get_export_files(stock_picking):
            xml_order_doc_filenames.append(create_element('OrderDocFilename', text=filename))
        xml_order_documents.append(xml_order_doc_filenames)
        xml_order.append(xml_order_documents)

        xsd_error = validate_xml("wab", xml_root, print_error=self.print_errors)
        if xsd_error:
            raise Warning(xsd_error)
        if 'yc_customer_order_no' in self.context:
            del self.context['yc_customer_order_no']
        return xml_root
Ejemplo n.º 10
0
    def import_file(self, file_text):

        configuration_data = self.pool.get('configuration.data').get(
            self.cr, self.uid, [])

        logger.debug("Processing WAR file")
        self.success = True
        self.errors = []

        stock_obj = self.pool.get("stock.picking")
        partner_obj = self.pool.get('res.partner')
        stock_move_obj = self.pool.get('stock.move')
        product_obj = self.pool.get('product.product')
        connection_obj = self.pool.get('stock.connect')

        # Gets the warehouse of the YellowCube.
        warehouse = connection_obj.browse(
            self.cr, self.uid, self.connection_id,
            context=self.context).warehouse_ids[0]

        xml = open_xml(file_text, _type='war', print_error=self.print_errors)
        if nspath(xml, '//warr:WAR_List'):
            i = 0
            self.cr.execute("SAVEPOINT yellowcube_war_xml_factory__WARList;")
            for x in nspath(xml, '//warr:WAR_List/warr:WAR'):
                i += 1
                # First, we try to check the records
                try:
                    text = xml_to_string(x)
                    self.import_file(text)
                except Warning as w:
                    self.cr.execute(
                        "ROLLBACK TO SAVEPOINT yellowcube_war_xml_factory__WARList;"
                    )
                    print 'error>>>' * 5
                    print text
                    print '<<<error' * 5
                    raise Warning('Error on sub WAR file number {0}'.format(i),
                                  format_exception(w))
            self.cr.execute(
                "RELEASE SAVEPOINT yellowcube_war_xml_factory__WARList;")
            return True

        validate_xml('war', xml, print_error=False)

        order_header = nspath(xml, "//warr:CustomerOrderHeader")[0]

        customer_order_no = nspath(order_header,
                                   "warr:CustomerOrderNo")[0].text
        stock_ids = stock_obj.search(
            self.cr,
            self.uid,
            [('yellowcube_customer_order_no', '=', customer_order_no),
             ('state', 'in', ['confirmed', 'assigned'])],
            context=self.context)

        # Checks if the stock.picking exists. Otherwise, logs an issue an continues with the next one.
        self._check(
            warehouse,
            len(stock_ids) > 0,
            _("There is not any stock.picking with CustomerOrderNo ={0} in state confirmed or assigned."
              ).format(customer_order_no))
        if not self.success:
            raise Warning(
                'There where some errors in the WAR file: {0}'.format(
                    '\n'.join(self.errors)))

        # Gets the stock picking out associated to this WAR.
        picking_out = stock_obj.browse(self.cr,
                                       self.uid,
                                       stock_ids,
                                       context=self.context)[0]

        # Saves BookingVoucherID and BookingVoucherYear on the stock.move
        goods_issue_header = nspath(xml, "//warr:GoodsIssueHeader")[0]
        booking_voucher_id = nspath(goods_issue_header,
                                    "warr:BookingVoucherID")[0].text
        booking_voucher_year = nspath(goods_issue_header,
                                      "warr:BookingVoucherYear")[0].text
        # TODO: Put this at the end, like in the WBA.
        #         for move_line in picking_out.move_lines:
        #             stock_move_obj.write(self.cr, self.uid, move_line.id, {'booking_voucher_id': booking_voucher_id,
        #                                                                     'booking_voucher_year': booking_voucher_year,
        #                                                                     }, self.context)

        # Validates DepositorNo against the system's parameter. If does not match, then aborts and logs an issue.
        depositor_no = nspath(goods_issue_header, "warr:DepositorNo")[0].text
        expected_depositor_no = self.get_param('depositor_no', required=True)
        self._check(
            warehouse, expected_depositor_no,
            _("Variable YC DepositorNo is not defined in the configuration data."
              ))
        self._check(
            warehouse, depositor_no == expected_depositor_no,
            _("Configuration variable YC DepositorNo does not match with that of tag 'DepositorNo'"
              ))

        # <YCDeliveryNo>
        yellowcube_delivery_no = nspath(order_header,
                                        "warr:YCDeliveryNo")[0].text
        if yellowcube_delivery_no and picking_out.yellowcube_delivery_no and picking_out.yellowcube_delivery_no != yellowcube_delivery_no:
            self.post_issue(
                warehouse,
                _('YCDeliveryNo {0} does not match its current value {1} in the stock picking.'
                  ).format(picking_out.yellowcube_delivery_no,
                           yellowcube_delivery_no),
                create=True,
                reopen=True)

        if picking_out.yellowcube_delivery_no != yellowcube_delivery_no:
            stock_obj.write(self.cr,
                            self.uid, [picking_out.id],
                            {'yellowcube_delivery_no': yellowcube_delivery_no},
                            context=self.context)

        # <YCDeloveryDate>
        yellowcube_delivery_date = nspath(order_header,
                                          "warr:YCDeliveryDate")[0].text
        if yellowcube_delivery_date and picking_out.yellowcube_delivery_date and picking_out.yellowcube_delivery_date != yellowcube_delivery_date:
            self.post_issue(
                warehouse,
                _('YCDeliveryDate {0} does not match its current value {1} in the stock picking.'
                  ).format(picking_out.yellowcube_delivery_date,
                           yellowcube_delivery_date),
                create=True,
                reopen=True)

        if picking_out.yellowcube_delivery_date != yellowcube_delivery_date:
            stock_obj.write(
                self.cr,
                self.uid, [picking_out.id],
                {'yellowcube_delivery_date': yellowcube_delivery_date},
                context=self.context)

        # <PartnerReference>
        partner_reference = nspath(order_header, "warr:PartnerReference")
        if partner_reference:
            partner_reference = partner_reference[0].text
            if picking_out.partner_id.ref:
                self._check(
                    warehouse, picking_out.partner_id.ref == partner_reference,
                    _('PartnerReference does not match its current value in the stock picking.'
                      ))
            else:
                partner_obj.write(self.cr,
                                  self.uid,
                                  picking_out.partner_id.id,
                                  {'ref': partner_reference},
                                  context=self.context)

        # <PostalShipmentNo>
        carrier_tracking_ref = nspath(order_header,
                                      "warr:PostalShipmentNo")[0].text
        stock_obj.write(self.cr,
                        self.uid, [picking_out.id],
                        {'carrier_tracking_ref': carrier_tracking_ref},
                        context=self.context)

        partials = {}
        id_table = {}
        i = 1
        for line in sorted([x.id for x in picking_out.move_lines]):
            id_table[i] = line
            i += 1

        for order_move in nspath(xml, "//warr:CustomerOrderDetail"):
            partial = {}

            pos_no = int(nspath(order_move, "warr:CustomerOrderPosNo")[0].text)

            # Gets the stock.move associated to this line.
            move_line = None
            for line in picking_out.move_lines:
                if line.id == id_table[pos_no]:
                    move_line = line
                    break

            # Checks that the line exists.
            self._check(
                picking_out, move_line is not None,
                _('CustomerOrderPosNo={0}: Mismatch with stock picking line number'
                  ).format(pos_no))
            if not self.success:
                raise Warning('Error parsing WAR file: {0}'.format('\n'.join(
                    self.errors)))

            partials[move_line if V8 else "move{0}".format(move_line.id
                                                           )] = partial

            # Caches the product of the stock.move.
            product_id = move_line.product_id.id
            partial['product_id'] = product_id
            product = product_obj.browse(self.cr, self.uid, product_id,
                                         self.context)

            # <YCArticleNo>
            yc_article_no = nspath(order_move, "warr:YCArticleNo")[0].text
            if product.yc_YCArticleNo:
                self._check(
                    picking_out, product.yc_YCArticleNo == yc_article_no,
                    _('Product {0} (id={1}): YCArticleNo does not match with YCArticleNo.'
                      ).format(product.name, product_id))
            else:
                product_obj.write(self.cr, self.uid, product_id,
                                  {'yc_YCArticleNo': yc_article_no},
                                  self.context)

            # <ArticleNo>
            article_no = nspath(order_move, "warr:ArticleNo")
            if article_no:
                article_no = article_no[0].text
                self._check(
                    picking_out, product.default_code == article_no,
                    _('Product {0} (id={1}): ArticleNo does not match with default_code.'
                      ).format(product.name, product_id))

            # <EAN>
            ean = nspath(order_move, "warr:EAN")
            if ean:
                ean = ean[0].text
                if product.ean13:
                    self._check(
                        picking_out, product.ean13 == ean,
                        _('Product {0} (id={1}): EAN does not match with ean13.'
                          ).format(product.name, product_id))
                else:
                    product_obj.write(self.cr, self.uid, product_id,
                                      {'ean13': ean}, self.context)

            # <Lot>
            lot = nspath(order_move, "warr:Lot")
            if lot:
                lot = lot[0].text

                # Searches for that lot in the system.
                lot_ids = self.pool.get('stock.production.lot').search(
                    self.cr,
                    self.uid, [('name', '=', lot),
                               ('product_id', '=', product_id)],
                    context=self.context)
                if not lot_ids:
                    self._check(
                        warehouse, False,
                        _('Lot {0} for product {1} (id={2}) does not exist in the system'
                          ).format(lot, product.name, product_id))
                elif getattr(move_line,
                             'restrict_lot_id' if V8 else 'prodlot_id'):
                    if self._check(
                            picking_out,
                            getattr(move_line, 'restrict_lot_id'
                                    if V8 else 'prodlot_id').name == lot,
                            _('Product {0} (id={1}): Lot does not match the lot indicated of the original stock.move.'
                              ).format(product_obj.name, product_id)):
                        partial['restrict_lot_id'
                                if V8 else 'prodlot_id'] = lot_ids[0]

            if product.track_outgoing:
                self._check(
                    warehouse, lot,
                    _("The WAR file must contain a lot, otherwise the stock.move can not be updated for product {0}"
                      .format(product.name)))

            # <Plant>
            plant = nspath(order_move, "warr:Plant")[0].text
            current_plant = self.get_param('plant_id', required=True)
            if current_plant:
                self._check(
                    picking_out, current_plant == plant,
                    _('Product {0} (id={1}): Plant does not match with the value of the configuration parameter YC PlantID.'
                      ).format(product.name, product_id))
            elif not current_plant:
                configuration_data.write(self.cr, self.uid,
                                         configuration_data.id,
                                         {'yc_plant_id': plant}, self.context)

            #  <QuantityUOM>
            quantity_uom = float(
                nspath(order_move, "warr:QuantityUOM")[0].text)
            self._check(
                picking_out, move_line.product_qty >= quantity_uom,
                _('Product {0} (id={1}): QuantityUOM is greater than that of the stock.move.'
                  ).format(product.name, product_id))
            partial['product_qty'] = quantity_uom

            # <QuantityISO>
            quantity_iso = nspath(order_move,
                                  "warr:QuantityUOM")[0].attrib['QuantityISO']
            uom_iso_list = self.pool.get('product.uom').search(
                self.cr,
                self.uid, [('uom_iso', '=', quantity_iso)],
                context=self.context)
            if len(uom_iso_list) > 0 and move_line.product_uom and (
                    quantity_iso != move_line.product_uom.uom_iso):
                self._check(
                    picking_out, False,
                    _('Product {0} (id={1}): Attribute QuantityISO does not match the ISO code indicated of the original stock.move.'
                      ).format(product.name, product_id))
            else:
                if not move_line.product_uom:
                    product_uom = uom_iso_list[0]
                    partial['product_uom'] = product_uom
                else:
                    self._check(
                        picking_out,
                        move_line.product_uom.uom_iso == quantity_iso,
                        _('Product {0} (id={1}): Attribute QuantityISO does not match that of the stock.move.'
                          ).format(product.name, product_id))
                    partial['product_uom'] = move_line.product_uom.id

            # Checks <StorageLocation> and <StockType>
            # Notes: Check together with StorageLocation against location_id on stock.move - alarm if wrong.
            #        If free type (' ', '0', 'F') use the StorageLocation, otherwise location YBLK.
            storage_location = nspath(order_move,
                                      "warr:StorageLocation")[0].text
            stock_type = nspath(order_move, "warr:StockType")
            if move_line.location_id or move_line.location_dest_id:
                location_names = []
                if move_line.location_id:
                    location_names.append(move_line.location_id.name)
                if move_line.location_dest_id:
                    location_names.append(move_line.location_dest_id.name)

                if stock_type:
                    # If there exists the tag <StockType>, then we follow the rules.
                    stock_type = stock_type[0].text
                    if stock_type not in ('X', 'S', '2', '3', '0', 'F', ' '):
                        self._check(
                            picking_out, False,
                            _("Product {0} (id={1}): StockType had value '{2}', which is not allowed."
                              ).format(product.name, product_id, stock_type))
                    elif stock_type in ('0', 'F', ' '):
                        self._check(
                            picking_out, storage_location in location_names,
                            _('Product {0} (id={1}): StorageLocation {2} and StockType {3} does not match with the location indicated in the stock.move {4}'
                              ).format(product.name, product_id,
                                       storage_location, stock_type,
                                       location_names))
                    else:
                        self._check(
                            picking_out, 'YBLK' in location_names,
                            _("Product {0} (id={1}): StorageLocation must be 'YBLK' since StockType is not a free type."
                              ).format(product.name, product_id))
                else:
                    # If <StockType> does not exist, it just checks that the values match.
                    if storage_location not in location_names:
                        self._check(
                            picking_out, False,
                            _('Product {0} (id={1}): StorageLocation {2} does not match with the location indicated in the stock.move {3}'
                              ).format(product.name, product_id,
                                       storage_location, location_names))
            else:
                self._check(
                    picking_out, False,
                    _('Product {0} (id={1}): The stock move does not have a location_id.'
                      ).format(product.name, product_id))

            # <Serial Numbers>
            serial_numbers = nspath(order_move, "warr:SerialNumbers")
            if serial_numbers:
                serial_numbers = serial_numbers[0].text
                if move_line.serial_number_scanned:
                    self._check(
                        picking_out,
                        move_line.serial_number_scanned == serial_numbers,
                        _('Product {0} (id={1}): SerialNumbers does not match the serial_number_scanned indicated of the original stock.move.'
                          ).format(product.name, product_id))
                else:
                    stock_move_obj.write(
                        self.cr, self.uid, move_line.id,
                        {'serial_number_scanned': serial_numbers},
                        self.context)

        if self.success:
            picking_id = picking_out.id

            picking_out.message_post(
                _('Imported WAR file BookingVoucherID={0} BookingVoucherYear={1}'
                  ).format(booking_voucher_id, booking_voucher_year))
            if V8:
                for move in partials:
                    vals = partials[move]
                    new_move_id = stock_move_obj.split(
                        self.cr,
                        self.uid,
                        move,
                        vals['product_qty'],
                        restrict_lot_id=vals.get('restrict_lot_id', False),
                        context=self.context)
                    stock_move_obj.action_done(self.cr,
                                               self.uid, [new_move_id],
                                               context=self.context)
            else:
                backorder_id, picking_id = picking_out.wrapper_do_partial(
                    partials)

                # Pickings created by WARs as backorders are not sent to the warehouse, by default.
                if backorder_id:
                    stock_obj.write(self.cr,
                                    self.uid,
                                    backorder_id, {
                                        'do_not_send_to_warehouse': True,
                                    },
                                    context=self.context)

            picking_to_deliver = stock_obj.browse(self.cr,
                                                  self.uid,
                                                  picking_id,
                                                  context=self.context)
            picking_to_deliver.action_done()
            picking_to_deliver.set_stock_moves_done()

            # Stores the values associated to BookingVoucherId and BookingVoucherYear, for reference.
            move_ids = [move.id for move in picking_out.move_lines]
            stock_move_obj.write(
                self.cr, self.uid, move_ids, {
                    'yc_booking_voucher_id': booking_voucher_id,
                    'yc_booking_voucher_year': booking_voucher_year,
                }, self.context)
            self.mark_record(picking_out.id,
                             'stock.picking' if V8 else 'stock.picking.out')

            # The message is sent ONLY if we had success.
#             picking_out.message_post(body=_("""Your order has been shipped through {0} and it can be tracked in the next link:\
#                                             <br/>\
#                                             <a href='https://www.post.ch/swisspost-tracking?formattedParcelCodes={1}'>Track&Trace</a>\
#                                             """).format(picking_out.carrier_id.name, urllib.quote(picking_out.carrier_tracking_ref)),
#                                      type='comment',
#                                      subtype="mail.mt_comment",
#                                      context=self.context,
#                                      partner_ids=picking_out.carrier_id and [picking_out.carrier_id.partner_id.id] or [])
        else:
            raise Warning(
                'There where some errors in the WAR file: {0}'.format(
                    '\n'.join(self.errors)))

        return True
    def generate_root_element(self, stock_picking):

        # xml = '<?xml version="1.0" encoding="UTF-8"?>\n'
        # xml = '{0}<WAB xsi:noNamespaceSchemaLocation="YellowCube_WAB_Warenausgangsbestellung.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\n'.format(xml)
        xml_root = create_element('WBL')
        purchase_order = stock_picking.purchase_id

        # WAB > ControlReference
        now = datetime.now()
        xml_control_reference = create_element('ControlReference')
        xml_control_reference.append(create_element('Type', text='WBL'))
        xml_control_reference.append(
            create_element('Sender',
                           text=self.get_param('sender', required=True)))
        xml_control_reference.append(
            create_element('Receiver',
                           text=self.get_param('receiver', required=True)))
        xml_control_reference.append(
            create_element(
                'Timestamp',
                text='{0:04d}{1:02d}{2:02d}{3:02d}{4:02d}{5:02d}'.format(
                    now.year, now.month, now.day, now.hour, now.hour,
                    now.minute)))
        xml_control_reference.append(
            create_element('OperatingMode',
                           text=self.get_param('operating_mode',
                                               required=True)))
        xml_control_reference.append(create_element('Version', text='1.1'))
        xml_root.append(xml_control_reference)

        xml_supplier_order = create_element("SupplierOrder")
        xml_root.append(xml_supplier_order)

        xml_supplier_order_header = create_element('SupplierOrderHeader')
        xml_supplier_order.append(xml_supplier_order_header)
        xml_supplier_order_header.append(
            create_element('DepositorNo',
                           self.get_param('depositor_no', required=True)))
        xml_supplier_order_header.append(
            create_element('Plant', self.get_param('plant_id', required=True)))

        # Regarding the 'YC SupplierNo', we first check if the supplier has a supplier number,
        # and if that's the case we use it. Otherwise, we use the default supplier number
        # set for the connector.
        if stock_picking.partner_id.supplier and stock_picking.partner_id.yc_supplier_no:
            yc_supplier_no = stock_picking.partner_id.yc_supplier_no
        else:
            yc_supplier_no = self.get_param('supplier_no', required=True)
        xml_supplier_order_header.append(
            create_element('SupplierNo', yc_supplier_no))

        #         xml_supplier_order_header.append(create_element('SupplierOrderNo', stock_picking.yellowcube_customer_order_no))
        xml_supplier_order_header.append(
            etree.Comment(
                text='res.partner#{0}'.format(stock_picking.partner_id.id)))
        xml_supplier_order_header.append(
            create_element('SupplierName1', stock_picking.partner_id.name))
        xml_supplier_order_header.append(
            create_element(
                'SupplierStreet',
                '{0} {1}'.format(stock_picking.partner_id.street,
                                 stock_picking.partner_id.street_no)))
        xml_supplier_order_header.append(
            create_element('SupplierCountryCode',
                           text=stock_picking.partner_id.country_id.code))
        xml_supplier_order_header.append(
            create_element('SupplierZIPCode',
                           text=stock_picking.partner_id.zip))
        xml_supplier_order_header.append(
            create_element('SupplierCity', text=stock_picking.partner_id.city))
        xml_supplier_order_header.append(
            create_element(
                'SupplierOrderNo',
                stock_picking.get_customer_order_no()[stock_picking.id]))
        # CustomerOrderNo is only required for cross-docking
        # xml_supplier_order_header.append(create_element('CustomerOrderNo', stock_picking.get_customer_order_no()[stock_picking.id]))
        if stock_picking.date:
            dateorder = stock_picking.date.split(' ')[0]
            xml_supplier_order_header.append(
                create_element('SupplierOrderDate', dateorder.replace('-',
                                                                      '')))
        if stock_picking.min_date:
            dateorder = stock_picking.min_date.split(' ')[0]
            xml_supplier_order_header.append(
                create_element('SupplierOrderDeliveryDate',
                               dateorder.replace('-', '')))

        xml_supplier_order_positions = create_element('SupplierOrderPositions')
        xml_supplier_order.append(xml_supplier_order_positions)
        for purchase_order_line in self._generate_order_line_element(
                stock_picking):
            xml_supplier_order_positions.append(purchase_order_line)

        xsd_error = validate_xml(self._factory_name,
                                 xml_root,
                                 print_error=self.print_errors)
        if xsd_error:
            raise Warning(xsd_error)
        return xml_root