def _generate_order_line_element(self, stock_picking): ret = [] i = 1 id_table = {} for ordered_id in sorted([x.id for x in stock_picking.move_lines]): id_table[str(ordered_id)] = i i += 1 for move in stock_picking.move_lines: xml = create_element('Position') pos_no = id_table[str(move.id)] if move.yc_posno and move.yc_posno != pos_no: raise Warning( _("Move line has been used before, and there is a mismatch" ), move.name) move.write({'yc_posno': pos_no}) xml.append(create_element('PosNo', pos_no)) xml.append( create_element('ArticleNo', move.product_id.default_code)) xml.append(create_element('Quantity', move.product_qty)) xml.append(create_element('QuantityISO', move.product_uom.uom_iso)) xml.append(create_element('PosText', move.product_id.name)) ret.append(xml) xsd_error = validate_xml(self._factory_name, xml, print_error=self.print_errors) if xsd_error: logger.error('XSD validation error: {0}'.format(xsd_error)) return ret
def _generate_partner_address_element(self, partner, partner_type): ''' Creates and returns a <Partner> tag for the WAB. ''' # Having this local, makes possible to pass languages to getTextAlias context = self.context.copy() if partner.lang: context['lang'] = partner.lang MAX_NUMBER_OF_NAME_TAGS = 4 xml = create_element("Partner") xml.append(create_element('PartnerType', text=partner_type)) partner_no = self.get_param('partner_no', required=True) xml.append(create_element('PartnerNo', text=partner_no)) if partner.is_company: partner_ref = partner.ref else: partner_ref = partner.parent_id.ref xml.append(create_element('PartnerReference', text=partner_ref)) if partner.title: xml.append(create_element('Title', partner.title.name)) names = self.__generate_partner_name(partner) for idx in xrange(min(len(names), MAX_NUMBER_OF_NAME_TAGS)): # This will generate elements Name1 ... Name4 xml.append( create_element('Name{0}'.format(idx + 1), text=names[idx])) street = False if partner.street: street = ' '.join([partner.street, partner.street_no or '']).strip(' ') elif partner.po_box: street = ' '.join([_('P.O. Box'), partner.po_box]) if street: xml.append(create_element('Street', text=street)) xml.append(create_element('CountryCode', text=partner.country_id.code)) xml.append(create_element('ZIPCode', text=partner.zip)) xml.append(create_element('City', text=partner.city)) if partner.phone: xml.append(create_element('PhoneNo', text=partner.phone)) if partner.mobile: xml.append(create_element('MobileNo', text=partner.mobile)) if partner.email: xml.append(create_element('Email', text=partner.email)) if not partner.lang: raise Warning( _("Missing partner #{0} language code").format(partner.id)) # Language code is required xml.append(create_element('LanguageCode', text=partner.lang[:2])) xsd_error = validate_xml("wab", xml, print_error=self.print_errors) if xsd_error: logger.error('XSD validation error: {0}'.format(xsd_error)) return xml
def create_element(entity, text=None, attrib=None, ns='https://service.swisspost.ch/apache/yellowcube/YellowCube_ART_REQUEST_Artikelstamm.xsd'): element = old_create_element(entity, text, attrib, ns) if entity in _element_to_check: validation_errors = validate_xml('art', element, print_error=False) if validation_errors: f = _element_to_check[entity] t, a = f(text, attrib) logger.debug("Changing element {0} values {1}, {2} into: {3}, {4}".format(entity, text, attrib, t, a)) element = old_create_element(entity, t, a, ns) return element
def _generate_order_position_element(self, stock_picking): PICKINGMESSAGE_TAG_MAX_LENGTH = 132 SHORTDESCRIPTION_TAG_MAX_LENGTH = 40 ret = [] i = 1 id_table = {} for ordered_id in sorted([x.id for x in stock_picking.move_lines]): id_table[str(ordered_id)] = i i += 1 for move in stock_picking.move_lines: if self.get_param('enable_product_lifecycle') and (move.product_id.product_state not in ['in_production']): raise Warning(_('Product {0} is not ready on the lifecycle. State: {1}').format(move.product_id.default_code, move.product_id.product_state)) xml = create_element('Position') xml.append(create_element('PosNo', text=id_table[str(move.id)])) xml.append(create_element('ArticleNo', text=move.product_id.default_code)) value = getattr(move, 'restrict_lot_id' if version_info[0] > 7 else 'prodlot_id', None) if value: xml.append(create_element('Lot', text=value.name)) xml.append(create_element('Plant', text=self.get_param('plant_id', required=True))) xml.append(create_element('Quantity', text=move.product_qty)) if not move.product_uom.uom_iso: raise Warning(_("Undefined ISO code for UOM '{0}', in product {1}").format(move.product_uom.name, move.product_id.default_code)) xml.append(create_element('QuantityISO', text=move.product_uom.uom_iso)) # Conditionally fills the tag <ShortDescription>, depending on if it's used as it has to be used # (encoding the product's name), or if it's used to encode another information... short_description_usage = self.get_param('wab_shortdescription_mapping') if short_description_usage == 'name': short_description_value = move.product_id.name[:SHORTDESCRIPTION_TAG_MAX_LENGTH] else: # if short_description_usage == 'price': short_description_value = "%0.2f" % move.product_id.list_price xml.append(create_element('ShortDescription', text=short_description_value)) # Conditionally fills the tag <PickingMessage>, depending on if it's not used or if it's used # to encode the reference of the carrier of the picking... This is supposed to be a temporary # solution until the new XSD is crafted. picking_message_usage = self.get_param('wab_pickingmessage_mapping') if picking_message_usage == 'carrier_tracking_ref': carrier_tracking_ref = stock_picking.carrier_tracking_ref if carrier_tracking_ref: xml.append(create_element('PickingMessage', text=carrier_tracking_ref[:PICKINGMESSAGE_TAG_MAX_LENGTH])) if stock_picking.yellowcube_return_reason: xml.append(create_element('ReturnReason', text=stock_picking.yellowcube_return_reason)) ret.append(xml) xsd_error = validate_xml("wab", xml, print_error=self.print_errors) if xsd_error: logger.error('XSD validation error: {0}'.format(xsd_error)) return ret
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 generate_root_element(self, stock_location, domain=None): self.success = True self.processed_items = [] # xml = '<?xml version="1.0" encoding="UTF-8"?>\n' # xml = '{0}<WAB xsi:noNamespaceSchemaLocation="YellowCube_WAB_Warenausgangsbestellung.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\n'.format(xml) # xsi = 'http://www.host.org/2001/XMLSchema-instance' # art_loc = 'https://service.swisspost.ch/apache/yellowcube/YellowCube_ART_REQUEST_Artikelstamm.xsd' xml_root = create_root('{{{art}}}ART') # WAB > ControlReference now = datetime.datetime.now() xml_control_reference = create_element('ControlReference') xml_control_reference.append(create_element('Type', text='ART')) xml_control_reference.append(create_element('Sender', text=self.get_param('sender', required=True))) xml_control_reference.append(create_element('Receiver', text=self.get_param('receiver', required=True))) xml_control_reference.append(create_element( 'Timestamp', text='{0:04d}{1:02d}{2:02d}{3:02d}{4:02d}{5:02d}'.format(now.year, now.month, now.day, now.hour, now.hour, now.minute) )) xml_control_reference.append(create_element('OperatingMode', text=self.get_param('operating_mode', required=True))) xml_control_reference.append(create_element('Version', text='1.0')) xml_root.append(xml_control_reference) xml_article_list = create_element('ArticleList') xml_root.append(xml_article_list) product_pool = self.pool.get('product.product') self.cr.execute("SELECT code FROM res_lang WHERE active") languages = [x[0] for x in self.cr.fetchall()] basic_domain = [ ('type', '!=', 'service') ] if self.force_product_ids: basic_domain.append(('id', 'in', self.force_product_ids)) if self.ignore_product_ids: basic_domain.append(('id', 'not in', self.ignore_product_ids)) if self.get_param('enable_product_lifecycle'): # Field 'product_state' is defined on the product's lifecycle. basic_domain.append(('product_state', '!=', 'draft')) else: # If we don't have the product's lifecycle, we just consider if # the product is active. basic_domain.append(('active', '=', True)) if domain: # If a domain is passed by argument, we extend the basic one for x in domain: basic_domain.append(x) empty_file = True product_ids = product_pool.search(self.cr, self.uid, basic_domain, context=self.context) for product_id in product_ids: self.processed_items.append(('product.product', product_id)) empty_file = False xml_article_list.append(self._generate_article_element(product_pool.browse(self.cr, self.uid, product_id, context=self.context), languages)) if empty_file: return None xsd_error = validate_xml("art", xml_root, print_error=self.print_errors) if xsd_error: raise Warning(xsd_error) return xml_root
def _generate_article_element(self, product, languages, raise_error=False): def mtq_to_cmq(number): ''' Receives a number which is in MTQ (cubic meters) and returns its equivalent in CMQ (cubic centimeters). 1 m^3 == 1000000 cm^3. ''' return 1000000.0 * number xml = create_element('Article') xml.append(etree.Comment("Model: product.product ID: {0}".format(product.id))) errors_list = [] # The ChangeFlag tag is computed differently depending on if the # product's lifecycle is enabled or not. change_flag = '' if self.get_param('enable_product_lifecycle'): if product.product_state == 'in_production': if product.yc_YCArticleNo: change_flag = 'U' else: change_flag = 'I' elif product.product_state == 'deactivated': change_flag = 'D' else: change_flag = 'U' else: def _product_under_sale(): sol = self.env['sale.order.line'].search([('product_id', '=', product.id), ('state', 'not in', ['cancel', 'done'])], limit=1) return True if sol else False if (product.state != 'obsolete') or _product_under_sale(): if product.yc_YCArticleNo: change_flag = 'U' else: change_flag = 'I' else: change_flag = 'D' dp_pool = self.pool.get('decimal.precision') weight_format = '{{0:.{0}f}}'.format(dp_pool.precision_get(self.cr, self.uid, 'Stock Weight')) length_format = '{{0:.{0}f}}'.format(dp_pool.precision_get(self.cr, self.uid, 'Stock Length')) width_format = '{{0:.{0}f}}'.format(dp_pool.precision_get(self.cr, self.uid, 'Stock Width')) height_format = '{{0:.{0}f}}'.format(dp_pool.precision_get(self.cr, self.uid, 'Stock Height')) volume_format = '{{0:.{0}f}}'.format(dp_pool.precision_get(self.cr, self.uid, 'Stock Volume')) xml.append(create_element('ChangeFlag', change_flag)) xml.append(create_element('DepositorNo', self.get_param('depositor_no', required=True))) xml.append(create_element('PlantID', self.get_param('plant_id', required=True))) xml.append(create_element('ArticleNo', product.default_code)) if not product.uom_id.uom_iso: logger.error(_("Undefined UOM ISO code: {0}").format(product.uom_id.name)) xml.append(create_element('BaseUOM', product.uom_id.uom_iso)) xml.append(create_element('NetWeight', weight_format.format(product.weight_net), {'ISO': 'KGM'})) xml.append(create_element('BatchMngtReq', '1' if product.track_outgoing else '0')) min_rem_life = product.expiration_accept_time period_exp_date_type = product.expiration_accept_time_uom if period_exp_date_type and product.track_outgoing: # If they UOM is not set, then we don't add this field xml.append(create_element('MinRemLife', int(min_rem_life))) # Each Unit of Measure has a different code according to the mapping table X01.00 translations = {'days': '', 'weeks': '1', 'months': '2', 'years': '3'} error_message = _("Value for system's parameter 'default_expiration_accept_time_uom' is '{0}' while it must be one of {1}.").format(period_exp_date_type, ','.join(translations.keys())) partial_success = self._check(product, period_exp_date_type in translations, error_message) if partial_success: xml.append(create_element('PeriodExpDateType', translations[period_exp_date_type])) else: errors_list.append(error_message) # OPTIONAL xml.append(create_element('PeriodExpDateType', '???')) xml.append(create_element('SerialNoFlag', '1' if product.yc_track_outgoing_scan else '0')) xml_uom = create_element('UnitsOfMeasure') xml.append(xml_uom) if product.ean13: xml_uom.append(create_element('EAN', product.ean13, {'EANType': 'HE'})) xml_uom.append(create_element('AlternateUnitISO', product.uom_id.uom_iso)) xml_uom.append(create_element('GrossWeight', weight_format.format(product.weight), {'ISO': 'KGM'})) xml_uom.append(create_element('Length', length_format.format(product.length), {'ISO': 'CMT'})) xml_uom.append(create_element('Width', width_format.format(product.width), {'ISO': 'CMT'})) xml_uom.append(create_element('Height', height_format.format(product.height), {'ISO': 'CMT'})) xml_uom.append(create_element('Volume', volume_format.format(mtq_to_cmq(product.volume)), {'ISO': 'CMQ'})) xml_desc = create_element('ArticleDescriptions') xml.append(xml_desc) product_pool = self.pool.get('product.product') names = {} for lang in languages: if lang[:2] not in ['de', 'fr', 'it', 'en']: continue _name = product_pool.read(self.cr, self.uid, [product.id], ['name'], {'lang': lang})[0]['name'] if lang[:2] not in names: xml_desc.append(create_element('ArticleDescription', _name, {'ArticleDescriptionLC': lang[:2]})) names[lang[:2]] = _name xsd_error = validate_xml(self._factory_name, xml, print_error=self.print_errors) if xsd_error: if raise_error: raise osv.except_osv('XSD validation error', xsd_error) else: logger.error('XSD validation error: {0}'.format(xsd_error)) else: date_current_str = datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT) self.pool.get('product.product').write(self.cr, self.uid, [product.id], {'yc_last_changeflag_submitted': change_flag, 'yc_last_changeflag_submitted_date': date_current_str}) if self.success: return xml else: raise Warning(_('Errors appeared while generating the ART file:\n{0}'.format('\n'.join(errors_list))))
def generate_root_element(self, stock_picking): # xml = '<?xml version="1.0" encoding="UTF-8"?>\n' # xml = '{0}<WAB xsi:noNamespaceSchemaLocation="YellowCube_WAB_Warenausgangsbestellung.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\n'.format(xml) DELIVERYINSTRUCTIONS_TAG_MAX_LENGTH = 15 xml_root = create_element('WAB') self.context['yc_customer_order_no'] = stock_picking.yellowcube_customer_order_no picking_mode = stock_picking.type sale_order = stock_picking.sale_id xml_root.append(Comment(_('Sale Order #{0}: {1}').format(sale_order.id, sale_order.name))) if not sale_order: raise Warning(_("There is no sale.order related to this stock.picking (type={0})").format(picking_mode)) if not self.context.get('yc_ignore_wab_reports', False): sale_order.generate_reports() # WAB > ControlReference now = datetime.now() xml_control_reference = create_element('ControlReference') xml_control_reference.append(create_element('Type', text='WAB')) xml_control_reference.append(create_element('Sender', text=self.get_param('sender', required=True))) xml_control_reference.append(create_element('Receiver', text=self.get_param('receiver', required=True))) xml_control_reference.append(create_element( 'Timestamp', text='{0:04d}{1:02d}{2:02d}{3:02d}{4:02d}{5:02d}'.format(now.year, now.month, now.day, now.hour, now.hour, now.minute) )) xml_control_reference.append(create_element('OperatingMode', text=self.get_param('operating_mode', required=True))) xml_control_reference.append(create_element('Version', text='1.0')) xml_root.append(xml_control_reference) # WAB -> Order xml_order = create_element('Order') xml_root.append(xml_order) # WAB -> OrderHeader xml_order_header = create_element('OrderHeader') xml_order_header.append(create_element('DepositorNo', self.get_param('depositor_no', required=True))) xml_order_header.append(create_element('CustomerOrderNo', text=stock_picking.get_customer_order_no()[stock_picking.id])) dateorder = sale_order.date_order.split(' ')[0] xml_order_header.append(create_element('CustomerOrderDate', text=dateorder.replace('-', ''))) xml_order.append(xml_order_header) # WAB -> PartnerAddress xml_partner_address = create_element('PartnerAddress') xml_partner_address.append(self._generate_partner_address_element(sale_order.partner_shipping_id, self.get_param('wab_partner_type_for_shipping_address'))) if self.get_param('wab_add_invoicing_address'): xml_partner_address.append(self._generate_partner_address_element(sale_order.partner_invoice_id, self.get_param('wab_partner_type_for_invoicing_address'))) xml_order.append(xml_partner_address) # WAB -> ValueAddedServices xml_value_added_services = create_element('ValueAddedServices') xml_additional_service = create_element('AdditionalService') if picking_mode in ['out', 'outgoing']: # <BasicShippingServices> under ValueAddedServices/AdditionalService if stock_picking.carrier_id and stock_picking.carrier_id.yc_basic_shipping: xml_additional_service.append(create_element('BasicShippingServices', text=stock_picking.carrier_id.yc_basic_shipping)) else: raise Warning(_('Missing Basic shipping in delivery method'), sale_order.name) else: xml_additional_service.append(create_element('BasicShippingServices', text="RETOURE")) # <AdditionalShippingServices> under ValueAddedServices/AdditionalService if stock_picking.carrier_id and stock_picking.carrier_id.yc_additional_shipping: xml_additional_service.append(create_element('AdditionalShippingServices', text=stock_picking.carrier_id.yc_additional_shipping)) # <DeliveryInstructions> under ValueAddedServices/AdditionalService if stock_picking.carrier_id and stock_picking.carrier_id.pc_delivery_instructions: xml_additional_service.append(create_element('DeliveryInstructions', text=stock_picking.carrier_id.pc_delivery_instructions[:DELIVERYINSTRUCTIONS_TAG_MAX_LENGTH])) # <FrightShippingFlag> under ValueAddedServices/AdditionalService xml_additional_service.append(create_element('FrightShippingFlag', text=('1' if stock_picking.carrier_id.pc_freight_shipping else '0'))) # <ShippingInterface> under ValueAddedServices/AdditionalService if stock_picking.carrier_id and stock_picking.carrier_id.pc_shipping_interface: xml_additional_service.append(create_element('ShippingInterface', text=stock_picking.carrier_id.pc_shipping_interface)) xml_value_added_services.append(xml_additional_service) xml_order.append(xml_value_added_services) # WAB -> OrderPositions xml_order_positions = create_element('OrderPositions') for position in self._generate_order_position_element(stock_picking): xml_order_positions.append(position) xml_order.append(xml_order_positions) # WAB -> OrderDocuments xml_order_documents = create_element('OrderDocuments', attrib={'OrderDocumentsFlag': '1'}) xml_order_doc_filenames = create_element('OrderDocFilenames') for filename in self.get_export_files(stock_picking): xml_order_doc_filenames.append(create_element('OrderDocFilename', text=filename)) xml_order_documents.append(xml_order_doc_filenames) xml_order.append(xml_order_documents) xsd_error = validate_xml("wab", xml_root, print_error=self.print_errors) if xsd_error: raise Warning(xsd_error) if 'yc_customer_order_no' in self.context: del self.context['yc_customer_order_no'] return xml_root
def import_file(self, file_text): configuration_data = self.pool.get('configuration.data').get( self.cr, self.uid, []) logger.debug("Processing WAR file") self.success = True self.errors = [] stock_obj = self.pool.get("stock.picking") partner_obj = self.pool.get('res.partner') stock_move_obj = self.pool.get('stock.move') product_obj = self.pool.get('product.product') connection_obj = self.pool.get('stock.connect') # Gets the warehouse of the YellowCube. warehouse = connection_obj.browse( self.cr, self.uid, self.connection_id, context=self.context).warehouse_ids[0] xml = open_xml(file_text, _type='war', print_error=self.print_errors) if nspath(xml, '//warr:WAR_List'): i = 0 self.cr.execute("SAVEPOINT yellowcube_war_xml_factory__WARList;") for x in nspath(xml, '//warr:WAR_List/warr:WAR'): i += 1 # First, we try to check the records try: text = xml_to_string(x) self.import_file(text) except Warning as w: self.cr.execute( "ROLLBACK TO SAVEPOINT yellowcube_war_xml_factory__WARList;" ) print 'error>>>' * 5 print text print '<<<error' * 5 raise Warning('Error on sub WAR file number {0}'.format(i), format_exception(w)) self.cr.execute( "RELEASE SAVEPOINT yellowcube_war_xml_factory__WARList;") return True validate_xml('war', xml, print_error=False) order_header = nspath(xml, "//warr:CustomerOrderHeader")[0] customer_order_no = nspath(order_header, "warr:CustomerOrderNo")[0].text stock_ids = stock_obj.search( self.cr, self.uid, [('yellowcube_customer_order_no', '=', customer_order_no), ('state', 'in', ['confirmed', 'assigned'])], context=self.context) # Checks if the stock.picking exists. Otherwise, logs an issue an continues with the next one. self._check( warehouse, len(stock_ids) > 0, _("There is not any stock.picking with CustomerOrderNo ={0} in state confirmed or assigned." ).format(customer_order_no)) if not self.success: raise Warning( 'There where some errors in the WAR file: {0}'.format( '\n'.join(self.errors))) # Gets the stock picking out associated to this WAR. picking_out = stock_obj.browse(self.cr, self.uid, stock_ids, context=self.context)[0] # Saves BookingVoucherID and BookingVoucherYear on the stock.move goods_issue_header = nspath(xml, "//warr:GoodsIssueHeader")[0] booking_voucher_id = nspath(goods_issue_header, "warr:BookingVoucherID")[0].text booking_voucher_year = nspath(goods_issue_header, "warr:BookingVoucherYear")[0].text # TODO: Put this at the end, like in the WBA. # for move_line in picking_out.move_lines: # stock_move_obj.write(self.cr, self.uid, move_line.id, {'booking_voucher_id': booking_voucher_id, # 'booking_voucher_year': booking_voucher_year, # }, self.context) # Validates DepositorNo against the system's parameter. If does not match, then aborts and logs an issue. depositor_no = nspath(goods_issue_header, "warr:DepositorNo")[0].text expected_depositor_no = self.get_param('depositor_no', required=True) self._check( warehouse, expected_depositor_no, _("Variable YC DepositorNo is not defined in the configuration data." )) self._check( warehouse, depositor_no == expected_depositor_no, _("Configuration variable YC DepositorNo does not match with that of tag 'DepositorNo'" )) # <YCDeliveryNo> yellowcube_delivery_no = nspath(order_header, "warr:YCDeliveryNo")[0].text if yellowcube_delivery_no and picking_out.yellowcube_delivery_no and picking_out.yellowcube_delivery_no != yellowcube_delivery_no: self.post_issue( warehouse, _('YCDeliveryNo {0} does not match its current value {1} in the stock picking.' ).format(picking_out.yellowcube_delivery_no, yellowcube_delivery_no), create=True, reopen=True) if picking_out.yellowcube_delivery_no != yellowcube_delivery_no: stock_obj.write(self.cr, self.uid, [picking_out.id], {'yellowcube_delivery_no': yellowcube_delivery_no}, context=self.context) # <YCDeloveryDate> yellowcube_delivery_date = nspath(order_header, "warr:YCDeliveryDate")[0].text if yellowcube_delivery_date and picking_out.yellowcube_delivery_date and picking_out.yellowcube_delivery_date != yellowcube_delivery_date: self.post_issue( warehouse, _('YCDeliveryDate {0} does not match its current value {1} in the stock picking.' ).format(picking_out.yellowcube_delivery_date, yellowcube_delivery_date), create=True, reopen=True) if picking_out.yellowcube_delivery_date != yellowcube_delivery_date: stock_obj.write( self.cr, self.uid, [picking_out.id], {'yellowcube_delivery_date': yellowcube_delivery_date}, context=self.context) # <PartnerReference> partner_reference = nspath(order_header, "warr:PartnerReference") if partner_reference: partner_reference = partner_reference[0].text if picking_out.partner_id.ref: self._check( warehouse, picking_out.partner_id.ref == partner_reference, _('PartnerReference does not match its current value in the stock picking.' )) else: partner_obj.write(self.cr, self.uid, picking_out.partner_id.id, {'ref': partner_reference}, context=self.context) # <PostalShipmentNo> carrier_tracking_ref = nspath(order_header, "warr:PostalShipmentNo")[0].text stock_obj.write(self.cr, self.uid, [picking_out.id], {'carrier_tracking_ref': carrier_tracking_ref}, context=self.context) partials = {} id_table = {} i = 1 for line in sorted([x.id for x in picking_out.move_lines]): id_table[i] = line i += 1 for order_move in nspath(xml, "//warr:CustomerOrderDetail"): partial = {} pos_no = int(nspath(order_move, "warr:CustomerOrderPosNo")[0].text) # Gets the stock.move associated to this line. move_line = None for line in picking_out.move_lines: if line.id == id_table[pos_no]: move_line = line break # Checks that the line exists. self._check( picking_out, move_line is not None, _('CustomerOrderPosNo={0}: Mismatch with stock picking line number' ).format(pos_no)) if not self.success: raise Warning('Error parsing WAR file: {0}'.format('\n'.join( self.errors))) partials[move_line if V8 else "move{0}".format(move_line.id )] = partial # Caches the product of the stock.move. product_id = move_line.product_id.id partial['product_id'] = product_id product = product_obj.browse(self.cr, self.uid, product_id, self.context) # <YCArticleNo> yc_article_no = nspath(order_move, "warr:YCArticleNo")[0].text if product.yc_YCArticleNo: self._check( picking_out, product.yc_YCArticleNo == yc_article_no, _('Product {0} (id={1}): YCArticleNo does not match with YCArticleNo.' ).format(product.name, product_id)) else: product_obj.write(self.cr, self.uid, product_id, {'yc_YCArticleNo': yc_article_no}, self.context) # <ArticleNo> article_no = nspath(order_move, "warr:ArticleNo") if article_no: article_no = article_no[0].text self._check( picking_out, product.default_code == article_no, _('Product {0} (id={1}): ArticleNo does not match with default_code.' ).format(product.name, product_id)) # <EAN> ean = nspath(order_move, "warr:EAN") if ean: ean = ean[0].text if product.ean13: self._check( picking_out, product.ean13 == ean, _('Product {0} (id={1}): EAN does not match with ean13.' ).format(product.name, product_id)) else: product_obj.write(self.cr, self.uid, product_id, {'ean13': ean}, self.context) # <Lot> lot = nspath(order_move, "warr:Lot") if lot: lot = lot[0].text # Searches for that lot in the system. lot_ids = self.pool.get('stock.production.lot').search( self.cr, self.uid, [('name', '=', lot), ('product_id', '=', product_id)], context=self.context) if not lot_ids: self._check( warehouse, False, _('Lot {0} for product {1} (id={2}) does not exist in the system' ).format(lot, product.name, product_id)) elif getattr(move_line, 'restrict_lot_id' if V8 else 'prodlot_id'): if self._check( picking_out, getattr(move_line, 'restrict_lot_id' if V8 else 'prodlot_id').name == lot, _('Product {0} (id={1}): Lot does not match the lot indicated of the original stock.move.' ).format(product_obj.name, product_id)): partial['restrict_lot_id' if V8 else 'prodlot_id'] = lot_ids[0] if product.track_outgoing: self._check( warehouse, lot, _("The WAR file must contain a lot, otherwise the stock.move can not be updated for product {0}" .format(product.name))) # <Plant> plant = nspath(order_move, "warr:Plant")[0].text current_plant = self.get_param('plant_id', required=True) if current_plant: self._check( picking_out, current_plant == plant, _('Product {0} (id={1}): Plant does not match with the value of the configuration parameter YC PlantID.' ).format(product.name, product_id)) elif not current_plant: configuration_data.write(self.cr, self.uid, configuration_data.id, {'yc_plant_id': plant}, self.context) # <QuantityUOM> quantity_uom = float( nspath(order_move, "warr:QuantityUOM")[0].text) self._check( picking_out, move_line.product_qty >= quantity_uom, _('Product {0} (id={1}): QuantityUOM is greater than that of the stock.move.' ).format(product.name, product_id)) partial['product_qty'] = quantity_uom # <QuantityISO> quantity_iso = nspath(order_move, "warr:QuantityUOM")[0].attrib['QuantityISO'] uom_iso_list = self.pool.get('product.uom').search( self.cr, self.uid, [('uom_iso', '=', quantity_iso)], context=self.context) if len(uom_iso_list) > 0 and move_line.product_uom and ( quantity_iso != move_line.product_uom.uom_iso): self._check( picking_out, False, _('Product {0} (id={1}): Attribute QuantityISO does not match the ISO code indicated of the original stock.move.' ).format(product.name, product_id)) else: if not move_line.product_uom: product_uom = uom_iso_list[0] partial['product_uom'] = product_uom else: self._check( picking_out, move_line.product_uom.uom_iso == quantity_iso, _('Product {0} (id={1}): Attribute QuantityISO does not match that of the stock.move.' ).format(product.name, product_id)) partial['product_uom'] = move_line.product_uom.id # Checks <StorageLocation> and <StockType> # Notes: Check together with StorageLocation against location_id on stock.move - alarm if wrong. # If free type (' ', '0', 'F') use the StorageLocation, otherwise location YBLK. storage_location = nspath(order_move, "warr:StorageLocation")[0].text stock_type = nspath(order_move, "warr:StockType") if move_line.location_id or move_line.location_dest_id: location_names = [] if move_line.location_id: location_names.append(move_line.location_id.name) if move_line.location_dest_id: location_names.append(move_line.location_dest_id.name) if stock_type: # If there exists the tag <StockType>, then we follow the rules. stock_type = stock_type[0].text if stock_type not in ('X', 'S', '2', '3', '0', 'F', ' '): self._check( picking_out, False, _("Product {0} (id={1}): StockType had value '{2}', which is not allowed." ).format(product.name, product_id, stock_type)) elif stock_type in ('0', 'F', ' '): self._check( picking_out, storage_location in location_names, _('Product {0} (id={1}): StorageLocation {2} and StockType {3} does not match with the location indicated in the stock.move {4}' ).format(product.name, product_id, storage_location, stock_type, location_names)) else: self._check( picking_out, 'YBLK' in location_names, _("Product {0} (id={1}): StorageLocation must be 'YBLK' since StockType is not a free type." ).format(product.name, product_id)) else: # If <StockType> does not exist, it just checks that the values match. if storage_location not in location_names: self._check( picking_out, False, _('Product {0} (id={1}): StorageLocation {2} does not match with the location indicated in the stock.move {3}' ).format(product.name, product_id, storage_location, location_names)) else: self._check( picking_out, False, _('Product {0} (id={1}): The stock move does not have a location_id.' ).format(product.name, product_id)) # <Serial Numbers> serial_numbers = nspath(order_move, "warr:SerialNumbers") if serial_numbers: serial_numbers = serial_numbers[0].text if move_line.serial_number_scanned: self._check( picking_out, move_line.serial_number_scanned == serial_numbers, _('Product {0} (id={1}): SerialNumbers does not match the serial_number_scanned indicated of the original stock.move.' ).format(product.name, product_id)) else: stock_move_obj.write( self.cr, self.uid, move_line.id, {'serial_number_scanned': serial_numbers}, self.context) if self.success: picking_id = picking_out.id picking_out.message_post( _('Imported WAR file BookingVoucherID={0} BookingVoucherYear={1}' ).format(booking_voucher_id, booking_voucher_year)) if V8: for move in partials: vals = partials[move] new_move_id = stock_move_obj.split( self.cr, self.uid, move, vals['product_qty'], restrict_lot_id=vals.get('restrict_lot_id', False), context=self.context) stock_move_obj.action_done(self.cr, self.uid, [new_move_id], context=self.context) else: backorder_id, picking_id = picking_out.wrapper_do_partial( partials) # Pickings created by WARs as backorders are not sent to the warehouse, by default. if backorder_id: stock_obj.write(self.cr, self.uid, backorder_id, { 'do_not_send_to_warehouse': True, }, context=self.context) picking_to_deliver = stock_obj.browse(self.cr, self.uid, picking_id, context=self.context) picking_to_deliver.action_done() picking_to_deliver.set_stock_moves_done() # Stores the values associated to BookingVoucherId and BookingVoucherYear, for reference. move_ids = [move.id for move in picking_out.move_lines] stock_move_obj.write( self.cr, self.uid, move_ids, { 'yc_booking_voucher_id': booking_voucher_id, 'yc_booking_voucher_year': booking_voucher_year, }, self.context) self.mark_record(picking_out.id, 'stock.picking' if V8 else 'stock.picking.out') # The message is sent ONLY if we had success. # picking_out.message_post(body=_("""Your order has been shipped through {0} and it can be tracked in the next link:\ # <br/>\ # <a href='https://www.post.ch/swisspost-tracking?formattedParcelCodes={1}'>Track&Trace</a>\ # """).format(picking_out.carrier_id.name, urllib.quote(picking_out.carrier_tracking_ref)), # type='comment', # subtype="mail.mt_comment", # context=self.context, # partner_ids=picking_out.carrier_id and [picking_out.carrier_id.partner_id.id] or []) else: raise Warning( 'There where some errors in the WAR file: {0}'.format( '\n'.join(self.errors))) return True
def generate_root_element(self, stock_picking): # xml = '<?xml version="1.0" encoding="UTF-8"?>\n' # xml = '{0}<WAB xsi:noNamespaceSchemaLocation="YellowCube_WAB_Warenausgangsbestellung.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\n'.format(xml) xml_root = create_element('WBL') purchase_order = stock_picking.purchase_id # WAB > ControlReference now = datetime.now() xml_control_reference = create_element('ControlReference') xml_control_reference.append(create_element('Type', text='WBL')) xml_control_reference.append( create_element('Sender', text=self.get_param('sender', required=True))) xml_control_reference.append( create_element('Receiver', text=self.get_param('receiver', required=True))) xml_control_reference.append( create_element( 'Timestamp', text='{0:04d}{1:02d}{2:02d}{3:02d}{4:02d}{5:02d}'.format( now.year, now.month, now.day, now.hour, now.hour, now.minute))) xml_control_reference.append( create_element('OperatingMode', text=self.get_param('operating_mode', required=True))) xml_control_reference.append(create_element('Version', text='1.1')) xml_root.append(xml_control_reference) xml_supplier_order = create_element("SupplierOrder") xml_root.append(xml_supplier_order) xml_supplier_order_header = create_element('SupplierOrderHeader') xml_supplier_order.append(xml_supplier_order_header) xml_supplier_order_header.append( create_element('DepositorNo', self.get_param('depositor_no', required=True))) xml_supplier_order_header.append( create_element('Plant', self.get_param('plant_id', required=True))) # Regarding the 'YC SupplierNo', we first check if the supplier has a supplier number, # and if that's the case we use it. Otherwise, we use the default supplier number # set for the connector. if stock_picking.partner_id.supplier and stock_picking.partner_id.yc_supplier_no: yc_supplier_no = stock_picking.partner_id.yc_supplier_no else: yc_supplier_no = self.get_param('supplier_no', required=True) xml_supplier_order_header.append( create_element('SupplierNo', yc_supplier_no)) # xml_supplier_order_header.append(create_element('SupplierOrderNo', stock_picking.yellowcube_customer_order_no)) xml_supplier_order_header.append( etree.Comment( text='res.partner#{0}'.format(stock_picking.partner_id.id))) xml_supplier_order_header.append( create_element('SupplierName1', stock_picking.partner_id.name)) xml_supplier_order_header.append( create_element( 'SupplierStreet', '{0} {1}'.format(stock_picking.partner_id.street, stock_picking.partner_id.street_no))) xml_supplier_order_header.append( create_element('SupplierCountryCode', text=stock_picking.partner_id.country_id.code)) xml_supplier_order_header.append( create_element('SupplierZIPCode', text=stock_picking.partner_id.zip)) xml_supplier_order_header.append( create_element('SupplierCity', text=stock_picking.partner_id.city)) xml_supplier_order_header.append( create_element( 'SupplierOrderNo', stock_picking.get_customer_order_no()[stock_picking.id])) # CustomerOrderNo is only required for cross-docking # xml_supplier_order_header.append(create_element('CustomerOrderNo', stock_picking.get_customer_order_no()[stock_picking.id])) if stock_picking.date: dateorder = stock_picking.date.split(' ')[0] xml_supplier_order_header.append( create_element('SupplierOrderDate', dateorder.replace('-', ''))) if stock_picking.min_date: dateorder = stock_picking.min_date.split(' ')[0] xml_supplier_order_header.append( create_element('SupplierOrderDeliveryDate', dateorder.replace('-', ''))) xml_supplier_order_positions = create_element('SupplierOrderPositions') xml_supplier_order.append(xml_supplier_order_positions) for purchase_order_line in self._generate_order_line_element( stock_picking): xml_supplier_order_positions.append(purchase_order_line) xsd_error = validate_xml(self._factory_name, xml_root, print_error=self.print_errors) if xsd_error: raise Warning(xsd_error) return xml_root