def test0005_label_request(self): label_request = LabelRequest() shipping_label_api = ShippingLabelAPI( label_request=label_request, weight_oz=10, partner_customer_id=1, partner_transaction_id=1020, requesterid=REQUESTER_ID, accountid=ACCOUNT_ID, passphrase=PASSPHRASE, test=True, ) shipping_label_api.to_xml() # # Now try to fetch label # self.assertRaises(RequestError, shipping_label_api.send_request) print "Excpected Error: %s" % shipping_label_api.error from_address = FromAddress( FromName="John Doe", ReturnAddress1="123 Main Street", FromCity="Boise", FromState="ID", FromPostalCode="83702", FromZIP4="7261", FromPhone="8005551212" ) to_address = ToAddress( ToName="Amine Khechfe", ToCompany="Endicia", ToAddress1="247 High Street", ToCity="Palo Alto", ToState="CA", ToPostalCode="84301", ToZIP4="0000", ToDeliveryPoint="00", ToPhone="8005763279" ) shipping_label_api.add_data(from_address.data) shipping_label_api.add_data(to_address.data) response = shipping_label_api.send_request() print shipping_label_api.to_xml() assert shipping_label_api.success == True res = objectify_response(response) pic_number = res.TrackingNumber filename = '/tmp/' + str(res.TrackingNumber) + '.gif' f = open(filename, 'wb') f.write(base64.decodestring(str(res.Base64LabelImage))) f.close() print "New Label at: %s" % filename
def test0005_label_request(self): label_request = LabelRequest() shipping_label_api = ShippingLabelAPI( label_request=label_request, weight_oz=10, partner_customer_id=1, partner_transaction_id=1020, requesterid=REQUESTER_ID, accountid=ACCOUNT_ID, passphrase=PASSPHRASE, test=True, ) shipping_label_api.to_xml() # # Now try to fetch label # self.assertRaises(RequestError, shipping_label_api.send_request) print "Excpected Error: %s" % shipping_label_api.error from_address = FromAddress(FromName="John Doe", ReturnAddress1="123 Main Street", FromCity="Boise", FromState="ID", FromPostalCode="83702", FromZIP4="7261", FromPhone="8005551212") to_address = ToAddress(ToName="Amine Khechfe", ToCompany="Endicia", ToAddress1="247 High Street", ToCity="Palo Alto", ToState="CA", ToPostalCode="84301", ToZIP4="0000", ToDeliveryPoint="00", ToPhone="8005763279") shipping_label_api.add_data(from_address.data) shipping_label_api.add_data(to_address.data) response = shipping_label_api.send_request() print shipping_label_api.to_xml() assert shipping_label_api.success == True res = objectify_response(response) pic_number = res.TrackingNumber filename = '/tmp/' + str(res.TrackingNumber) + '.gif' f = open(filename, 'wb') f.write(base64.decodestring(str(res.Base64LabelImage))) f.close() print "New Label at: %s" % filename
def test_pmi(self): """ Test creating a PMI shipment """ label_request = LabelRequest() shipping_label_api = ShippingLabelAPI( label_request=label_request, weight_oz=10, partner_customer_id=1, partner_transaction_id=1020, requesterid=REQUESTER_ID, accountid=ACCOUNT_ID, passphrase=PASSPHRASE, test=True, mail_class='PriorityMailInternational', EelPfc='Testing', CustomsCertify='TRUE', CustomsSigner='John', ) # A US address to an IN address from_address = self.make_from_address() to_address = self.make_to_address('IN') shipping_label_api.add_data(from_address.data) shipping_label_api.add_data(to_address.data) customs_item1 = [ Element('Description', 'My Beautiful Shoes'), Element('Quantity', 1), Element('Weight', 10), Element('Value', 50), ] customs_item2 = [ Element('Description', 'My Beautiful Dress'), Element('Quantity', 1), Element('Weight', 10), Element('Value', 50), ] shipping_label_api.add_data({ 'customsinfo': [ Element('CustomsItems', [ Element('CustomsItem', customs_item1), Element('CustomsItem', customs_item2) ]), Element('EelPfc', 'Testing'), Element('ContentsType', 'Merchandise') ], 'EelPfc': 'Testing', 'ValidateAddress': 'FALSE', 'Value': '100.00', 'Description': 'Some Fancy Stuffs', 'LabelSubtype': 'Integrated', 'IntegratedFormType': 'Form2976', 'CustomsCertify': 'TRUE', 'CustomsSigner': 'John', }) print shipping_label_api.to_xml() response = shipping_label_api.send_request() assert shipping_label_api.success == True parsed_response = parse_response(response, shipping_label_api.namespace) print parsed_response.keys() filename = '/tmp/' + parsed_response['TrackingNumber'] + '.gif' f = open(filename, 'wb') f.write(base64.decodestring(parsed_response['Base64LabelImage'])) f.close() print "New Label at: %s" % filename
def make_endicia_labels(self): """ Make labels for the given shipment :return: Tracking number as string """ Attachment = Pool().get('ir.attachment') EndiciaConfiguration = Pool().get('endicia.configuration') if self.state not in ('packed', 'done'): self.raise_user_error('invalid_state') if not ( self.carrier and self.carrier.carrier_cost_method == 'endicia' ): self.raise_user_error('wrong_carrier') if self.tracking_number: self.raise_user_error('tracking_number_already_present') endicia_credentials = EndiciaConfiguration(1).get_endicia_credentials() if not self.endicia_mailclass: self.raise_user_error('mailclass_missing') mailclass = self.endicia_mailclass.value label_request = LabelRequest( Test=endicia_credentials.is_test and 'YES' or 'NO', LabelType=( 'International' in mailclass ) and 'International' or 'Default', # TODO: Probably the following have to be configurable ImageFormat="PNG", LabelSize="6x4", ImageResolution="203", ImageRotation="Rotate270", ) # Endicia only support 1 decimal place in weight weight_oz = "%.1f" % self.weight shipping_label_request = ShippingLabelAPI( label_request=label_request, weight_oz=weight_oz, partner_customer_id=self.delivery_address.id, partner_transaction_id=self.id, mail_class=mailclass, MailpieceShape=self.endicia_mailpiece_shape, accountid=endicia_credentials.account_id, requesterid=endicia_credentials.requester_id, passphrase=endicia_credentials.passphrase, test=endicia_credentials.is_test, ) from_address = self._get_ship_from_address() shipping_label_request.add_data( from_address.address_to_endicia_from_address().data ) shipping_label_request.add_data( self.delivery_address.address_to_endicia_to_address().data ) shipping_label_request.add_data({ 'LabelSubtype': self.endicia_label_subtype, 'IncludePostage': self.endicia_include_postage and 'TRUE' or 'FALSE', }) if self.endicia_label_subtype != 'None': # Integrated form type needs to be sent for international shipments shipping_label_request.add_data({ 'IntegratedFormType': self.endicia_integrated_form_type, }) self._update_endicia_item_details(shipping_label_request) # Logging. logger.debug( 'Making Shipping Label Request for' 'Shipment ID: {0} and Carrier ID: {1}' .format(self.id, self.carrier.id) ) logger.debug('--------SHIPPING LABEL REQUEST--------') logger.debug(str(shipping_label_request.to_xml())) logger.debug('--------END REQUEST--------') try: response = shipping_label_request.send_request() except RequestError, error: self.raise_user_error('error_label', error_args=(error,))
def _make_label(self, cr, uid, omni, package, context=None): package_obj = self.pool.get('stock.out.package') omni_obj = self.pool.get('omniship') address_obj = self.pool.get('res.partner') # Getting the api credentials to be used in shipping label generation # endicia credentials are in the format : # (account_id, requester_id, passphrase, is_test) endicia_credentials = omni_obj.get_endicia_credentials( cr, uid, omni, context, ) if not endicia_credentials[0] or not endicia_credentials[1] or \ not endicia_credentials[2]: raise osv.except_osv(('Error : '), ( "Please check the account settings for Endicia account. \nSome details may be missing." )) #Determine if a Military Order apo = False if package.picking.partner_id.city.lower() in ['apo', 'fpo', 'dpo']: apo = True #Determine if International Order #Another option in the documentation is listed as "Domestic" #However an attempt returned an unknown error if package.picking.partner_id.country_id.code != 'US': mailclass = 'International' else: mailclass = 'Priority' label_request = LabelRequest( Test=endicia_credentials[3] and 'YES' or 'NO', LabelType= ('International' in mailclass) and 'International' \ or 'Priority' if apo else 'Default', LabelSubtype= 'Integrated' if apo \ else package.label_sub_type or 'None', ) #If a package weight is passed as 0, it is not sent in the API and could result #in an unknown error being returned if not package.weight or package.weight <= 0.4: package.weight = 0.5 shipping_label_api = ShippingLabelAPI( label_request=label_request, weight_oz=float(package.weight) * 16, partner_customer_id=package.picking.partner_id.id, partner_transaction_id=package.picking.id, mail_class=mailclass, accountid=endicia_credentials[0], requesterid=endicia_credentials[1], passphrase=endicia_credentials[2], test=endicia_credentials[3], ) if package.alternate_sender_address: from_addr = package.alternate_sender_address to_addr = package.picking.company_id.partner_id else: from_addr = package.picking.company_id.partner_id to_addr = package.picking.partner_id from_address = address_obj.address_to_endicia_from_address( cr, uid, from_addr, context) to_address = address_obj.address_to_endicia_to_address( cr, uid, to_addr, context) shipping_label_api.add_data(from_address.data) shipping_label_api.add_data(to_address.data) #Comment this line if not required shipping_label_api = self._add_items(cr, uid, omni, shipping_label_api, package, context) response = shipping_label_api.send_request() return (parse_response(response, shipping_label_api.namespace), parse_images(response, shipping_label_api.namespace))
def generate_shipping_labels(self, **kwargs): """ Make labels for the given shipment :return: Tracking number as string """ Attachment = Pool().get('ir.attachment') Tracking = Pool().get('shipment.tracking') Uom = Pool().get('product.uom') if self.carrier_cost_method != 'endicia': return super(ShipmentOut, self).generate_shipping_labels(**kwargs) label_request = LabelRequest( Test=self.carrier.endicia_is_test and 'YES' or 'NO', LabelType=( 'International' in self.carrier_service.code ) and 'International' or 'Default', # TODO: Probably the following have to be configurable ImageFormat="PNG", LabelSize="6x4", ImageResolution="203", ImageRotation="Rotate270", ) try: package, = self.packages except ValueError: self.raise_user_error( "There should be exactly one package to generate USPS label" "\n Multi Piece shipment is not supported yet" ) oz, = Uom.search([('symbol', '=', 'oz')]) # Endicia only support 1 decimal place in weight weight_oz = "%.1f" % Uom.compute_qty( package.weight_uom, package.weight, oz ) shipping_label_request = ShippingLabelAPI( label_request=label_request, weight_oz=weight_oz, partner_customer_id=self.delivery_address.id, partner_transaction_id=self.id, mail_class=self.carrier_service.code, accountid=self.carrier.endicia_account_id, requesterid=self.carrier.endicia_requester_id, passphrase=self.carrier.endicia_passphrase, test=self.carrier.endicia_is_test, ) shipping_label_request.mailpieceshape = package.box_type and \ package.box_type.code # Dimensions required for priority mail class and # all values must be in inches to_uom, = Uom.search([('symbol', '=', 'in')]) from_uom, = Uom.search([('symbol', '=', package.distance_unit.symbol)]) if (package.length and package.width and package.height): length, width, height = package.length, package.width, package.height if from_uom != to_uom: length = "%.1f" % Uom.compute_qty( from_uom, package.length, to_uom ) width = "%.1f" % Uom.compute_qty( from_uom, package.width, to_uom ) height = "%.1f" % Uom.compute_qty( from_uom, package.height, to_uom ) shipping_label_request.mailpiecedimensions = { 'Length': length, 'Width': width, 'Height': height } from_address = self._get_ship_from_address() shipping_label_request.add_data( from_address.address_to_endicia_from_address().data ) shipping_label_request.add_data( self.delivery_address.address_to_endicia_to_address().data ) shipping_label_request.add_data({ 'LabelSubtype': self.endicia_label_subtype, 'IncludePostage': self.endicia_include_postage and 'TRUE' or 'FALSE', }) if self.endicia_label_subtype != 'None': # Integrated form type needs to be sent for international shipments shipping_label_request.add_data({ 'IntegratedFormType': self.endicia_integrated_form_type, }) if self.delivery_address.country.code != 'US': self._update_endicia_item_details(shipping_label_request) # Logging. logger.debug( 'Making Shipping Label Request for' 'Shipment ID: {0} and Carrier ID: {1}' .format(self.id, self.carrier.id) ) logger.debug('--------SHIPPING LABEL REQUEST--------') logger.debug(str(shipping_label_request.to_xml())) logger.debug('--------END REQUEST--------') try: response = shipping_label_request.send_request() except RequestError, error: self.raise_user_error('error_label', error_args=(error.message,))
def make_endicia_labels(self): """ Make labels for the given shipment :return: Tracking number as string """ Company = Pool().get('company.company') Attachment = Pool().get('ir.attachment') if self.state not in ('packed', 'done'): self.raise_user_error('invalid_state') if not self.carrier.carrier_cost_method == 'endicia': self.raise_user_error('wrong_carrier') if self.tracking_number: self.raise_user_error('tracking_number_already_present') company = Transaction().context.get('company') endicia_credentials = Company(company).get_endicia_credentials() if not self.endicia_mailclass: self.raise_user_error('mailclass_missing') mailclass = self.endicia_mailclass.value label_request = LabelRequest( Test=endicia_credentials.usps_test and 'YES' or 'NO', LabelType=( 'International' in mailclass ) and 'International' or 'Default', # TODO: Probably the following have to be configurable ImageFormat="PNG", LabelSize="6x4", ImageResolution="203", ImageRotation="Rotate270", ) move_weights = map( lambda move: move.get_weight_for_endicia(), self.outgoing_moves ) shipping_label_request = ShippingLabelAPI( label_request=label_request, weight_oz=sum(move_weights), partner_customer_id=self.delivery_address.id, partner_transaction_id=self.id, mail_class=mailclass, accountid=endicia_credentials.account_id, requesterid=endicia_credentials.requester_id, passphrase=endicia_credentials.passphrase, test=endicia_credentials.usps_test, ) # From address is the warehouse location. So it must be filled. if not self.warehouse.address: self.raise_user_error('warehouse_address_required') shipping_label_request.add_data( self.warehouse.address.address_to_endicia_from_address().data ) shipping_label_request.add_data( self.delivery_address.address_to_endicia_to_address().data ) shipping_label_request.add_data({ 'LabelSubtype': self.endicia_label_subtype, 'IncludePostage': self.endicia_include_postage and 'TRUE' or 'FALSE', }) if self.endicia_label_subtype != 'None': # Integrated form type needs to be sent for international shipments shipping_label_request.add_data({ 'IntegratedFormType': self.endicia_integrated_form_type, }) self._update_endicia_item_details(shipping_label_request) try: response = shipping_label_request.send_request() except RequestError, error: self.raise_user_error('error_label', error_args=(error,))
def _make_label(self, carrier, shipment, options): """ Create a label for given shipment and return the response as such :param carrier: Browse Record of shipment method :param shipment: Browse Record of outgoing shipment :param options: Dictionary of values """ address_obj = self.pool.get('party.address') company_obj = self.pool.get('company.company') shipment_obj = self.pool.get('stock.shipment.out') line_weights = shipment_obj.get_move_line_weights(shipment.id, 'oz') # Getting the api credentials to be used in shipping label generation # endicia credentials are in the format : # EndiciaSettings(account_id, requester_id, passphrase, is_test) endicia_credentials = company_obj.get_endicia_credentials() mailclass = carrier.carrier_product.code or \ 'FirstClassMailInternational' label_request = LabelRequest( Test=endicia_credentials.usps_test and 'YES' or 'NO', LabelType= ('International' in mailclass) and 'International' \ or 'Default', # this size and dpi combi works only for FirstClassInternational ImageFormat="PNG", LabelSize="6x4", ImageResolution="203", ImageRotation="Rotate270", ) delivery_address = shipment.delivery_address shipping_label_api = ShippingLabelAPI( label_request=label_request, weight_oz=sum(line_weights.values()), partner_customer_id=delivery_address.id, partner_transaction_id=shipment.id, mail_class=mailclass, accountid = endicia_credentials.account_id, requesterid = endicia_credentials.requester_id, passphrase = endicia_credentials.passphrase, test = endicia_credentials.usps_test, ) #From location is the warehouse location. So it must be filled. location = shipment.warehouse.address if not location: self.raise_user_error('location_required') from_address = address_obj.address_to_endicia_from_address(location.id) to_address = address_obj.address_to_endicia_to_address( delivery_address.id) shipping_label_api.add_data(from_address.data) shipping_label_api.add_data(to_address.data) shipping_label_api.add_data({ 'LabelSubtype': options['label_sub_type'], 'IncludePostage': options['include_postage'] and 'TRUE' or 'FALSE', }) if options['label_sub_type'] != 'None': shipping_label_api.add_data({ 'IntegratedFormType': options['integrated_form_type'], }) #Comment this line if not required shipping_label_api = self._add_items_from_moves(shipping_label_api, shipment.inventory_moves, line_weights) response = shipping_label_api.send_request() return objectify_response(response)
def _make_label(self, cr, uid, omni, package, context=None): package_obj = self.pool.get('stock.out.package') omni_obj = self.pool.get('omniship') address_obj = self.pool.get('res.partner') # Getting the api credentials to be used in shipping label generation # endicia credentials are in the format : # (account_id, requester_id, passphrase, is_test) endicia_credentials = omni_obj.get_endicia_credentials( cr, uid, omni, context, ) if not endicia_credentials[0] or not endicia_credentials[1] or \ not endicia_credentials[2]: raise osv.except_osv(('Error : '), ("Please check the account settings for Endicia account. \nSome details may be missing.")) #Determine if a Military Order apo = False if package.picking.partner_id.city.lower() in ['apo', 'fpo', 'dpo']: apo = True #Determine if International Order #Another option in the documentation is listed as "Domestic" #However an attempt returned an unknown error if package.picking.partner_id.country_id.code != 'US': mailclass = 'International' else: mailclass = 'Priority' label_request = LabelRequest( Test=endicia_credentials[3] and 'YES' or 'NO', LabelType= ('International' in mailclass) and 'International' \ or 'Priority' if apo else 'Default', LabelSubtype= 'Integrated' if apo \ else package.label_sub_type or 'None', ) #If a package weight is passed as 0, it is not sent in the API and could result #in an unknown error being returned if not package.weight or package.weight <= 0.4: package.weight = 0.5 shipping_label_api = ShippingLabelAPI( label_request=label_request, weight_oz=float(package.weight)*16, partner_customer_id=package.picking.partner_id.id, partner_transaction_id=package.picking.id, mail_class=mailclass, accountid=endicia_credentials[0], requesterid=endicia_credentials[1], passphrase=endicia_credentials[2], test=endicia_credentials[3], ) if package.alternate_sender_address: from_addr = package.alternate_sender_address to_addr = package.picking.company_id.partner_id else: from_addr = package.picking.company_id.partner_id to_addr = package.picking.partner_id from_address = address_obj.address_to_endicia_from_address( cr, uid, from_addr, context) to_address = address_obj.address_to_endicia_to_address( cr, uid, to_addr, context) shipping_label_api.add_data(from_address.data) shipping_label_api.add_data(to_address.data) #Comment this line if not required shipping_label_api = self._add_items(cr, uid, omni, shipping_label_api, package, context) response = shipping_label_api.send_request() return (parse_response(response, shipping_label_api.namespace), parse_images(response, shipping_label_api.namespace))
def make_endicia_labels(self): """ Make labels for the given shipment :return: Tracking number as string """ Attachment = Pool().get('ir.attachment') if self.state not in ('packed', 'done'): self.raise_user_error('invalid_state') if not (self.carrier and self.carrier.carrier_cost_method == 'endicia'): self.raise_user_error('wrong_carrier') if self.tracking_number: self.raise_user_error('tracking_number_already_present') if not self.endicia_mailclass: self.raise_user_error('mailclass_missing') mailclass = self.endicia_mailclass.value label_request = LabelRequest( Test=self.carrier.endicia_is_test and 'YES' or 'NO', LabelType=('International' in mailclass) and 'International' or 'Default', # TODO: Probably the following have to be configurable ImageFormat="PNG", LabelSize="6x4", ImageResolution="203", ImageRotation="Rotate270", ) # Endicia only support 1 decimal place in weight weight_oz = "%.1f" % self.weight shipping_label_request = ShippingLabelAPI( label_request=label_request, weight_oz=weight_oz, partner_customer_id=self.delivery_address.id, partner_transaction_id=self.id, mail_class=mailclass, accountid=self.carrier.endicia_account_id, requesterid=self.carrier.endicia_requester_id, passphrase=self.carrier.endicia_passphrase, test=self.carrier.endicia_is_test, ) shipping_label_request.mailpieceshape = self.endicia_mailpiece_shape from_address = self._get_ship_from_address() shipping_label_request.add_data( from_address.address_to_endicia_from_address().data) shipping_label_request.add_data( self.delivery_address.address_to_endicia_to_address().data) shipping_label_request.add_data({ 'LabelSubtype': self.endicia_label_subtype, 'IncludePostage': self.endicia_include_postage and 'TRUE' or 'FALSE', }) if self.endicia_label_subtype != 'None': # Integrated form type needs to be sent for international shipments shipping_label_request.add_data({ 'IntegratedFormType': self.endicia_integrated_form_type, }) if self.delivery_address.country.code != 'US': self._update_endicia_item_details(shipping_label_request) # Logging. logger.debug('Making Shipping Label Request for' 'Shipment ID: {0} and Carrier ID: {1}'.format( self.id, self.carrier.id)) logger.debug('--------SHIPPING LABEL REQUEST--------') logger.debug(str(shipping_label_request.to_xml())) logger.debug('--------END REQUEST--------') try: response = shipping_label_request.send_request() except RequestError, error: self.raise_user_error('error_label', error_args=(error, ))
def make_endicia_labels(self): """ Make labels for the given shipment :return: Tracking number as string """ Company = Pool().get('company.company') Attachment = Pool().get('ir.attachment') if self.state not in ('packed', 'done'): self.raise_user_error('invalid_state') if not self.carrier.carrier_cost_method == 'endicia': self.raise_user_error('wrong_carrier') if self.tracking_number: self.raise_user_error('tracking_number_already_present') company = Transaction().context.get('company') endicia_credentials = Company(company).get_endicia_credentials() if not self.endicia_mailclass: self.raise_user_error('mailclass_missing') mailclass = self.endicia_mailclass.value label_request = LabelRequest( Test=endicia_credentials.usps_test and 'YES' or 'NO', LabelType=('International' in mailclass) and 'International' or 'Default', # TODO: Probably the following have to be configurable ImageFormat="PNG", LabelSize="6x4", ImageResolution="203", ImageRotation="Rotate270", ) move_weights = map(lambda move: move.get_weight_for_endicia(), self.outgoing_moves) shipping_label_request = ShippingLabelAPI( label_request=label_request, weight_oz=sum(move_weights), partner_customer_id=self.delivery_address.id, partner_transaction_id=self.id, mail_class=mailclass, accountid=endicia_credentials.account_id, requesterid=endicia_credentials.requester_id, passphrase=endicia_credentials.passphrase, test=endicia_credentials.usps_test, ) # From address is the warehouse location. So it must be filled. if not self.warehouse.address: self.raise_user_error('warehouse_address_required') shipping_label_request.add_data( self.warehouse.address.address_to_endicia_from_address().data) shipping_label_request.add_data( self.delivery_address.address_to_endicia_to_address().data) shipping_label_request.add_data({ 'LabelSubtype': self.endicia_label_subtype, 'IncludePostage': self.endicia_include_postage and 'TRUE' or 'FALSE', }) if self.endicia_label_subtype != 'None': # Integrated form type needs to be sent for international shipments shipping_label_request.add_data({ 'IntegratedFormType': self.endicia_integrated_form_type, }) self._update_endicia_item_details(shipping_label_request) try: response = shipping_label_request.send_request() except RequestError, error: self.raise_user_error('error_label', error_args=(error, ))
FromPostalCode="83702", FromZIP4="7261", FromPhone="8005551212" ) to_address = ToAddress( ToName="Amine Khechfe", ToCompany="Endicia", ToAddress1="247 High Street", ToCity="Palo Alto", ToState="CA", ToPostalCode="84301", ToZIP4="0000", ToDeliveryPoint="00", ToPhone="8005763279" ) shipping_label_api.add_data(from_address.data) shipping_label_api.add_data(to_address.data) print shipping_label_api.to_xml() response = shipping_label_api.send_request() objectify_response(response) """ A more complicated example """ customs_item1 = [ Element('Description','My Beautiful Shoes'), Element('Quantity', 1), Element('Weight', 10), Element('Value', 50), ]