Example #1
0
    def generate_files(self, domain=None, ignore_product_ids=None, force_product_ids=None, multifile=False):
        self.base_priority = -1
        if not multifile:
            # If call standard way, we delegate on typical creation
            self.ignore_product_ids = ignore_product_ids
            self.force_product_ids = force_product_ids
            return xml_abstract_factory.generate_files(self, domain=domain)
        else:
            self.ignore_product_ids = None
            self.force_product_ids = None

        product_domain = ['&',
                          ('id', 'not in', ignore_product_ids or []),
                          ('id', 'in', force_product_ids),
                          ]

        if not force_product_ids:
            product_domain = [product_domain[1]]
        else:
            self.base_priority = 1

        products_to_export = self.pool.get('product.product').search(self.cr, self.uid, product_domain, context=self.context)
        logger.debug("Exporting {0} files for {1} products".format(self._factory_name, len(products_to_export)))
        self.main_file_id = None
        sender = self.get_param('sender', required=True)
        table_model = self.pool[self._table]
        # search_domain = []#[('xml_export_state', '=', 'draft')]
        # For each object that matches the domain, we create its xml file
        object_ids = table_model.search(self.cr, self.uid, domain, context=self.context)
        for _object in table_model.browse(self.cr, self.uid, object_ids, context=self.context):
            main_file_name = self.get_main_file_name(_object)
            if not main_file_name:
                raise Warning(_('Missing filename for main object {0} {1}#{2}').format(_object.name, self._table, _object.id))
            for product_id in products_to_export:
                try:
                    object_id = _object.id
                    # We generated the final filename, according to task with ID=2922
                    object_filename = "{sender}_{factory_name}_{name}_sub{sub}.xml".format(sender=sender,
                                                                                           factory_name=self._factory_name,
                                                                                           name=export_filename(main_file_name, self.context),
                                                                                           sub=product_id)

                    logger.debug("Exporting xml for {2} {0} into file {1}".format(object_id, object_filename, self._table))
                    # The name of the main xml, is appened to each related file
                    self.context['filename_prefix'] = "{0}_".format(object_filename[:-4])
                    # The XML root is generated
                    xml_node = self.generate_root_element(_object, domain=[('id', '=', product_id)])
                    if xml_node is None:
                        continue
                    xml_output = xml_to_string(xml_node, remove_ns=True)
                    # The associated files are copied
                    self.main_file_id = None
                    self.save_file(xml_output, object_filename, main=True, binary=False, record_id=product_id, model='product.product')
                    self.mark_as_exported(_object.id)
                except Warning as e:
                    logger.error("Exception exporting into xml {0}: {1}".format(object_id, format_exception(e)))
                finally:
                    if 'filename_prefix' in self.context:
                        del self.context['filename_prefix']
        return True
Example #2
0
    def generate_files(self, domain=None):
        logger.debug("Exporting {0} files".format(self._factory_name))
        self.main_file_id = None
        sender = self.get_param('sender', required=True)
        table_model = self.pool[self._table]
        # search_domain = []#[('xml_export_state', '=', 'draft')]
        # For each object that matches the domain, we create its xml file
        for object_id in table_model.search(
                self.cr, self.uid, domain, context=self.context
        ):  # TODO: Check that self.context contains the yc_language set.
            try:
                _object = table_model.browse(self.cr,
                                             self.uid,
                                             object_id,
                                             context=self.context)

                # We generated the final filename, according to task with ID=2922
                main_file_name = self.get_main_file_name(_object)
                if not main_file_name:
                    raise Warning(
                        _('Missing filename for main object {0} {1}#{2}').
                        format(_object.name, self._table, _object.id))
                object_filename = "{sender}_{factory_name}_{name}.xml".format(
                    sender=sender,
                    factory_name=self._factory_name,
                    name=export_filename(main_file_name, self.context))

                logger.debug("Exporting xml for {2} {0} into file {1}".format(
                    object_id, object_filename, self._table))
                # The name of the main xml, is appened to each related file
                self.context['filename_prefix'] = "{0}_".format(
                    object_filename[:-4])
                # The XML root is generated
                self.processed_items = []
                xml_node = self.generate_root_element(_object)
                if xml_node is not None:
                    xml_node.append(
                        etree.Comment("Model: {0} ID: {1} Name: {2}".format(
                            self._table, _object.id, _object.name)))
                    xml_output = xml_to_string(xml_node, remove_ns=True)
                    # The associated files are copied
                    self.save_file(xml_output,
                                   object_filename,
                                   main=True,
                                   binary=False,
                                   record_id=_object.id)
                    export_files = self.get_export_files(_object)
                    logger.debug("Exporting files {0}".format(export_files))
                    for name in export_files:
                        src = export_files[name]

                        if self._file_is_pcl(src):
                            # If the file must be submitted as PCL, then it generates the PCL
                            logger.debug(
                                "PCL conversion: PCL creation for {0} STARTED."
                                .format(src))
                            pcl_output_path = self._print_pdf_to_pcl(
                                self.cr, self.uid, src, self.context)
                            logger.debug(
                                "PCL conversion: PCL creation for {0} FINISHED"
                                .format(src))
                            data = None
                            with open(pcl_output_path, 'rb') as f:
                                data = f.read()
                            self.save_file(data, name)
                        else:
                            data = None
                            with open(src, 'rb') as f:
                                data = f.read()
                            self.save_file(data, name)

                    self.mark_as_exported(_object.id)
            except Exception as e:
                logger.error("Exception exporting into xml {0}: {1}".format(
                    object_id, format_exception(e)))
                raise
            finally:
                if 'filename_prefix' in self.context:
                    del self.context['filename_prefix']
        return True
Example #3
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
Example #4
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
Example #5
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