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
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
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
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
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