/*****************************************************************************
*
* Filename         - $Workfile$
*
*                     Name              Date      Version     CR Number
* Author           - See revision history below.
* Last Update      - See revision history below.
*
* Project Name     - TAGTRAK(Oracle Database)
*
* Source Code
* Identifier       -
*
* Design Spec
* Section          - FALCP\SS\01941
*
* Contents         - Live Schema Stored Procedure
*
* Language         - Oracle SQL*Plus script
*
* Classification   - Commercial In Confidence
*
*          (c) Ultra Electronics Limited, 2006
*
*****************************************************************************/

create or replace package Bag_Not_Seen
as

    -- Returns TRUE if the passed flight should generate a BNS message.
    --
    -- Arguments:
    -- 	id	- The Flight ID to create a BMM for.
    --
    function Send_For_Flight
    (
    	p_flight_id in flight.flight_id%type
    ) return boolean;

    -- Encodes an outgoing BNS message
    --
    -- Arguments:
    -- 	id	- The Flight ID to create a BNS message for.
    --
    procedure Build_Message
    (
    	p_flight_id in flight.flight_id%type
    );

end Bag_Not_Seen;

/

create or replace package body Bag_Not_Seen
as

    --
    -- ******************** PRIVATE ********************
    --


    --
    -- ********** Constants
    --
    ENCODED_MAXLEN		constant number := 4080;
    ENCODED_PART1_MAXLEN	constant number := 4000;
    EOL				constant char := chr(10);


    --
    -- ********** Data Types
    --

    -- This is a local exception used for abort conditions.
    --
    BNS_Exception exception;

    -- Represents a bag in the database
    --
    subtype Bag_Record is bag%rowtype;

    -- These types are used to store per-item data for the encode routines.
    -- This is as long as possible as it used to store multiple lines in some
    -- instances, ie. each Encoded_Data either starts with .V, .F, .O, or .N.
    --
    subtype Encoded_Data is varchar2(4000);

    type Encoded_List is table of Encoded_Data index by binary_integer;


    -- Used as general strings to store flight info.  The flight details stored
    -- will never exceed the length of this.
    --
    subtype String is varchar2(80);
    type String_List is table of String index by binary_integer;

    -- These types are used to store unique flights and the associated bags
    -- and passengers.
    --
    type Bag_List is table of bag.bag_tag%type index by binary_integer;
    type Pax_List is table of pax.pax_id%type index by binary_integer;

    -- Due to restrictions in some verions of Oracle this is not as obvious
    -- as it should be.
    --
    -- The original idea was that Route_Info be a record that stored the
    -- flights making up a unique route and the associated passengers and bags.
    --
    -- However we must still support Oracle 8 which does not support nested
    -- tables.
    --
    -- Now it is encoded as a comma-separated list of flights that are used to
    -- store a unique route.  Originally the intention was to store passenger
    -- and bag info as well to prevent the need to do the same query twice.
    --
    -- Unless the maximum number of onward flights supported by Gateway changes
    -- to a drastically larger number, then this format will more than suffice.
    --
    subtype Long_String is varchar2(4000);

    type Route_Info is record
    (
    	num_flights number default 0,
	route_data Long_String
    );

    type Route_List is table of Route_Info index by binary_integer;

    --
    -- ********** Cursors
    --
    cursor Flight_Classes_Cursor(a_flight flight.flight_id%type) is
	select distinct pax.pax_class from pax where pax.flight_id = a_flight;

    cursor Bags_Not_Seen_Cursor(a_flight flight.flight_id%type,
				a_class pax.pax_class%type) is
	select bag.bag_tag,bag.pax_id from bag
	    where bag.loaded_status = 'N' and
		  bag.pax_id in (select pax.pax_id from pax
				    where pax.flight_id = a_flight and
				          pax.pax_status in ('B', 'C') and
					  pax.pax_class = a_class);

    cursor Pax_Onward_Cursor(a_pax pax.pax_id%type) is
	select onward_flight.no,
	       onward_flight.odate,
	       onward_flight.dest,
	       onward_flight.class from onward_flight
		    where onward_flight.pax_id = a_pax
			    order by onward_flight.dest_order asc;

    type Weak_Cursor is ref cursor;


    --
    -- ********** Private Routines
    --

    -- Debug routine to dump a set of encoded lines out.
    --
    -- Arguments:
    -- 	data	- The data.
    --
    procedure Dump_Data(data in Encoded_List)
    is
	pragma AUTONOMOUS_TRANSACTION;
    begin
	for i in 1 .. data.count loop
	    dbms_output.put_line(data(i));
	end loop;
    end Dump_Data;


    -- Logs an error.
    --
    -- Arguments:
    -- 	e	- The error string.
    --
    procedure Log_Error(e in varchar2, code in number default null)
    is
	pragma AUTONOMOUS_TRANSACTION;
    begin
	insert into oracle_error 
	(
	    process_name,
	    errror_msg,
	    error_code,
	    error_time
	)
	values
	(
	    'Bag_Not_Seen.Build_Message',
	    e,
	    code, 
	    sysdate
	);

	commit;
    end Log_Error;


    -- Tokenise a string
    --
    -- Arguments:
    -- 	data	- The string to tokenise
    -- 	dest	- The next token, if any.  Null if no more tokens.
    -- 	sep	- The token seperator.
    -- 	pos	- Used to store state.  Set to 1 before the initial call.
    -- 
    procedure Tokenise(data in     varchar2,
		       dest out    varchar2,
		       sep  in     char,
		       pos  in out number)
    is
    	nextpos	number;
    begin
	-- Check for termination
	--
	if pos = -1 then
	    dest := null;
	end if;

	-- Find the next seperator
	--
    	nextpos := instr(data,sep,pos);

	-- If no more seperators, return the tail and note the termination for
	-- next iteration
	--
	if nextpos = 0 then
	    dest := substr(data,pos);
	    pos := -1;
	else
	    dest := substr(data,pos,nextpos-pos);
	    pos := nextpos + 1;
	end if;
    end Tokenise;


    -- Adds the passed string onto part1, until part1 is 4000 characters long,
    -- in which case it adds it to part2.
    --
    -- If adding this string would make the total length greater than 4080
    -- characters long then the string is not added and result is set to false.
    -- If the string is added, then result is set to true.
    --
    -- Arguments:
    -- 	data	- The data to add.
    -- 	part1	- Part 1 of the message part.
    -- 	part2	- Part 1 of the message part.
    -- 	result	- Whether the string was added or not.
    --
    procedure Add_To_Message_Parts(data   in		Encoded_Data,
				   part1  in out nocopy	varchar2,
				   part2  in out nocopy	varchar2,
				   result out		boolean)
    is
    begin
	-- Check if too long
	--
    	if length(part1) + length(part2) + length(data) > ENCODED_MAXLEN then
	    result := false;
	else
	    result := true;

	    -- Check to see if there's enough room left in part1.  If not, add
	    -- it to part2.
	    --
	    if length(part1) = ENCODED_PART1_MAXLEN then
	    	part2 := part2 || data;
	    elsif length(part1) + length(data) > ENCODED_PART1_MAXLEN then
		declare
		    remaining number;
		begin
		    remaining := ENCODED_PART1_MAXLEN - length(part1);
		    part1 := part1 || substr(data,1,remaining);
		    part2 := part2 || substr(data,remaining + 1);
		end;
	    else
	    	part1 := part1 || data;
	    end if;
    	end if;
    end Add_To_Message_Parts;


    -- Acts like Add_To_Message_Parts, but doesn't do the length check.  This
    -- version is used to terminate the message part, where the check is
    -- not needed.
    --
    -- Arguments:
    -- 	data	- The data to add.
    -- 	part1	- Part 1 of the message part.
    -- 	part2	- Part 1 of the message part.
    --
    procedure Terminate_Message_Parts(data   in			Encoded_Data,
				      part1  in out nocopy	varchar2,
				      part2  in out nocopy	varchar2)
    is
    begin
	-- Check to see if there's enough room left in part1.  If not, add
	-- it to part2.
	--
	if length(part1) = ENCODED_PART1_MAXLEN then
	    part2 := part2 || data;
	elsif length(part1) + length(data) > ENCODED_PART1_MAXLEN then
	    declare
		remaining number;
	    begin
		remaining := ENCODED_PART1_MAXLEN - length(part1);
		part1 := part1 || substr(data,1,remaining);
		part2 := part2 || substr(data,remaining + 1);
	    end;
	else
	    part1 := part1 || data;
	end if;
    end Terminate_Message_Parts;


    -- Stores the data for an encoded BNS, partioning as required
    --
    -- Arguments:
    -- 	id	- The message ID to relate this data to.
    -- 	data	- The data for the message.
    --
    procedure Store_Encoded_Message(id   in messages.message_id%type,
				    data in Encoded_List)
    is
    	current_v Encoded_Data := null;
	part_num number := 1;
	part1 message_parts.message_text1%type := '';
	part2 message_parts.message_text2%type := '';
	command char(1);
	buffer Encoded_Data := '';
	added boolean;
    begin
	-- The data must start with BNS.
	--
	part1 := 'BNS' || EOL;

	-- Go through all the BNS parts
	--
    	for i in 1 .. data.count loop
	    
	    -- Get the command for this block
	    --
	    if length(data(i)) > 1 then
	    	command := substr(data(i),2,1);
	    else
	    	command := null;
	    end if;

	    -- Check for message lines that need to be saved for partitioning
	    -- purposes.  Also we buffer data until we get a .N entry.
	    --
	    if command = 'V' then

		current_v := data(i);
		buffer := buffer || data(i) || '/PART' || part_num || EOL;

	    elsif command = 'F' then

		buffer := buffer || data(i);

	    elsif command = 'O' then

		buffer := buffer || data(i);

	    elsif command ='N' then

		-- Sanity check
		--
		if current_v is null then
		    Log_Error('Bad BMM (missing a .V)');
		    raise BNS_Exception;
		end if;

	    	-- Add the message part, partioning if appropriate.  Feasibly
		-- this routine could go hidedously wrong if a .N entry is too
		-- big for a part of the BNS, but that shouldn't be possible.
		--
		buffer := buffer || data(i);

		if length(buffer) > ENCODED_MAXLEN then
		    Log_Error('A single .N is too big (' ||
				    length(data(i)) || ' chars)');
		    raise BNS_Exception;
		end if;

		-- Add the buffered data on
		--
		Add_To_Message_Parts(buffer,part1,part2,added);

		-- If this can't be added, create a new message part
		--
		if not added then
		    Terminate_Message_Parts
			    ('ENDPART' || part_num || EOL,part1,part2);

		    insert into message_parts
			(
			    message_parts.message_text1,
			    message_parts.message_text2,
			    message_parts.part_no,
			    message_parts.timestamp,
			    message_parts.message_id
			)
		    values
			(
			    part1,
			    part2,
			    part_num,
			    sysdate,
			    id
			);

		    -- Now create the start of the next partition with this
		    -- bag.  The V item must always be set.
		    --
		    part_num := part_num + 1;

		    buffer := 'BNS' || EOL;
		    buffer := buffer || current_v || '/PART' || part_num || EOL;

		    buffer := buffer || data(i);

		    part1 := '';
		    part2 := '';

		    Add_To_Message_Parts(buffer,part1,part2,added);

		    if not added then
			Log_Error('Failed to initialise new BNS part');
			raise BNS_Exception;
		    end if;
		end if;

		-- Reset the buffer
		--
		buffer := '';
	    else
	    	Log_Error('Segment is not V,F,O or N -- starts with "'
					|| substr(data(i),1,10) || '"');
		raise BNS_Exception;
	    end if;
	end loop;

	-- Save the last bit of message
	--
	if length(part1) > 0 then
	    Terminate_Message_Parts('ENDBNS' || EOL,part1,part2);

	    insert into message_parts
		(
		    message_parts.message_text1,
		    message_parts.message_text2,
		    message_parts.part_no,
		    message_parts.timestamp,
		    message_parts.message_id
		)
	    values
		(
		    part1,
		    part2,
		    part_num,
		    sysdate,
		    id
		);
	end if;

	-- Save the eventual number of parts
	--
	update messages set messages.parts = part_num,
			    messages.parts_received = part_num
					    where messages.message_id = id;
    end Store_Encoded_Message;


    -- Encodes a bag tag.
    --
    -- Arguments:
    -- 	bag		- The bag to encode.
    -- Returns:
    -- 	The encoded tag (.N line).
    --
    function Encode_Tag(bag in Bag_Record) return Encoded_Data
    is
    begin
	return '.N/' || replace(bag.bag_tag,'?','0') || '001' || EOL;
    end Encode_Tag;


    -- Encodes a passenger's details.
    --
    -- Arguments:
    -- 	passenger	- The pax record to encode.
    -- 	weight_count	- The number of weighed bags for this passenger.
    -- 	total_weight	- The total weight of weighed bags for this passenger.
    --  data		- The encoded passenger is added to this.
    --
    procedure Encode_Passenger(passenger    in pax%rowtype,
			       weight_count in number,
			       total_weight in number,
			       data         in out nocopy Encoded_Data)
    is
    begin

	-- Add the passenger name
	--
	if passenger.pax_surname is not null then
	    data := data || '.P/' ||
			  nvl(passenger.no_same_surname,'') ||
			  trim(passenger.pax_surname);

	    if passenger.pax_initials is not null then
		data := data || '/' || trim(passenger.pax_initials);
	    end if;

	    data := data || EOL;

	end if;

	-- Add PNR 
	--
	if passenger.pax_name_record is not null then
	    data := data || '.L/' || trim(passenger.pax_name_record) || EOL;
	end if;

	-- Add the weight if defined
	--
	if total_weight > 0 then
	    data := data || '.W/K/' || weight_count || '/' ||
	    		total_weight || EOL;
	end if;

	-- Add Frequent Flyer info
	--
	if passenger.freq_flyer is not null then
	    data := data || trim(passenger.freq_flyer) || EOL;
	end if;

    end Encode_Passenger;


    -- Adds flight route to a Route_Info record.
    --
    -- Arguments:
    -- 	flight_no	- The flight number.
    -- 	flight_date	- The flight date.
    -- 	flight_dest	- The flight destination.
    -- 	flight_class	- The flight class.
    -- 	route		- The route to add this to.
    --
    procedure Add_Route(flight_no    in     String,
		        flight_date  in     String,
		        flight_dest  in     String,
		        flight_class in     String,
		        route        in out nocopy Route_Info)
    is
    begin
	route.num_flights := route.num_flights + 1;

	if route.num_flights > 1 then
	    route.route_data := route.route_data || ',';
    	end if;

	route.route_data := route.route_data ||
			    flight_no        || ',' ||
			    flight_date      || ',' ||
			    flight_dest      || ',' ||
			    flight_class;
    end Add_Route;


    -- Resolves a bag/passenger pair to a route, creating the route if needed.
    --
    -- Arguments:
    -- 	id		- The flight ID being processed.
    --	flight_no	- The flight number for the above.
    --	flight_date	- The flight date for the above.
    -- 	class		- The class for this route.
    -- 	tag		- The baggage tag.
    -- 	pid		- The passenger associated with the bag.
    --	routes		- A list of routes and attached bag/passengers.
    --			  This is updated on exit.
    --
    procedure Resolve_Route(id          in            flight.flight_id%type,
			    flight_no   in            String,
			    flight_date in            String,
			    class       in            pax.pax_class%type,
			    tag         in            bag.bag_tag%type,
			    pid         in            pax.pax_id%type,
			    routes      in out nocopy Route_List)
    is
    	route Route_Info;
	flight_dest String;
    begin
	--
    	-- Construct the route
	--

	-- Add the outgoing flight
	--
	select pax.pax_destination
		into flight_dest
		from pax
		where pax.pax_id = pid;

	Add_Route(flight_no,
		  flight_date,
		  flight_dest,
		  class,
		  route);

	-- Add any onward routes for this passenger
	--
	for onward in Pax_Onward_Cursor(pid) loop
	    Add_Route(replace(onward.no,' '),
	    	      onward.odate,
		      onward.dest,
		      onward.class,
		      route);
	end loop;

	-- If this route doesn't match an already existing route, add it.
	--
	for i in 1 .. routes.count loop
	    if route.num_flights = routes(i).num_flights and
	    		route.route_data = routes(i).route_data then
		return;
	    end if;
	end loop;

	routes(routes.count+1) := route;

    end Resolve_Route;


    -- Builds a query that fill fetch the unloaded bags for the supplied route.
    --
    -- Arguments:
    -- 	id		- The flight ID being processed.
    --	route		- The route data (as encoded by Resolve_Route).
    --
    -- Returns:
    --	The query.
    --
    function Get_Bag_Query(id    in flight.flight_id%type,
			   route in Long_String) return Long_String
    is
	query Long_String;
	fno String;
	fdate String;
	fdest String;
	fclass String;
	token_pos number := 1;
	dest_order number := 0;
	include_brs_bags db_config.db_value%type;
    begin

    	-- The first flight is always the outgoing flight
	--
	Tokenise(route,fno,',',token_pos);
	Tokenise(route,fdate,',',token_pos);
	Tokenise(route,fdest,',',token_pos);
	Tokenise(route,fclass,',',token_pos);

	-- Start off the query, using the outgoing flight to fetch the bags
	-- for this flight
	--
	query :=
	    'select * from bag ' ||
	    'where bag.loaded_status = ''N'' ' ||
	    'and bag.delete_marker = 0 ' ||
	    'and bag.process_name = ''DCS'' ';

	-- Filter out BRS generated bags if configured to do so
	--
	include_brs_bags := config.get_value('BNS_INC_NON_BSM');

	if (include_brs_bags is null or include_brs_bags <> 'Y') then
	    query := query || 'and bag.subunit_name <> ''SYM'' ';
	end if;

	-- Add in the start of the PAX collection
	--
	query := query || 
	    'and bag.pax_id in ' ||
	    '(select pax.pax_id from pax where pax.flight_id = ' || id ||
	    ' and pax.pax_status in (''B'', ''C'')' ||
	    ' and pax.pax_class = ''' || fclass || ''' ';

	-- Now loop through the onward flights, intersecting the passengers
	-- matching this journey leg
	--
	while token_pos <> -1 loop
	    dest_order := dest_order + 1;

	    Tokenise(route,fno,',',token_pos);
	    Tokenise(route,fdate,',',token_pos);
	    Tokenise(route,fdest,',',token_pos);
	    Tokenise(route,fclass,',',token_pos);

	    query := query ||
	    	'intersect select onward_flight.pax_id from onward_flight ' ||
		'where onward_flight.odate = ''' || fdate || ''' and ' ||
		'onward_flight.no = ''' || fno || ''' and ' ||
		'onward_flight.class = ''' || fclass || ''' and ' ||
		'onward_flight.dest_order = ' || dest_order || ' ';
	end loop;

	-- We want to then minus the passengers who have onward flights above
	-- the ones we've checked for.  Imagaine this scenario:
	--
	--	PAX	FLIGHT	ORDER
	--	1	BA001	1
	--	2	BA001	1
	--	2	BA002	2
	--
	-- When were are looking for passengers *only* going onward on BA001 we
	-- don't want to pick up PAX 2 as well.
	--
	query := query ||
	    'minus select onward_flight.pax_id from onward_flight where ' ||
	    'onward_flight.dest_order > ' || dest_order || ')';

	return query;

    end Get_Bag_Query;

    --
    -- ******************** PUBLIC ********************
    --


    -- Says whether a BNS should be generated
    --
    function Send_For_Flight
    (
    	p_flight_id  in  flight.flight_id%type
    ) return boolean
    is

	site	flight.site_id%type;
	carrier	flight.carrier_alpha_code%type;
	process	flight.process_name%type;
	unit	flight.unit_name%type;
	dcsid	flight.dcsid%type;
	subunit	flight.subunit_name%type;

	allow1	carrier_site_config.config_value%type;
	allow2	system_cfg.bns_status%type;

    begin

	select f.site_id, f.carrier_alpha_code, f.subunit_name, 
	       f.process_name, f.unit_name, f.dcsid
	    into site, carrier, subunit, process, unit, dcsid
	    from flight f
	    where f.flight_id = p_flight_id;

	select c.config_value
	    into allow1
	    from carrier_site_config c
	    where c.config_item = 'GENERATE_BNS' and c.alpha_code = carrier and
		    c.site_id = site;

	select s.bns_status
	    into allow2
	    from system_cfg s
	    where s.subunit_name = subunit and
	    	  s.process_name = process and
	    	  s.unit_name = unit and
	    	  s.dcsid = dcsid;

	return (allow1='Y' and allow2='Y');

    exception

	when NO_DATA_FOUND then
	    return false;

    end Send_For_Flight;


    -- Encodes an outgoing BNS message
    --
    procedure Build_Message
    (
    	p_flight_id  in  flight.flight_id%type
    ) 
    is
	loc		site_info.airport_code%type;
	msg_id		messages.message_id%type;
	data		Encoded_List;
	routes		Route_List;
	orig_flight_no	flight.flight_no%type;
	flight_no	String;
	flight_date	String;
    begin

	-- Get common parts to save re-fetching them.
	--
	select f.flight_no, trim(f.dcs_flight_no), f.flight_date
	    into orig_flight_no, flight_no, flight_date
	    from flight f
	    where f.flight_id = p_flight_id;

	select s.airport_code
	    into loc
	    from site_info s
	    where s.site_id =
	    	(select f.site_id from flight f
			where f.flight_id = p_flight_id);

	if flight_no is null then
	    -- If no DCS flight number, hope for the best...
	    --
	    flight_no := replace(orig_flight_no,' ');
	end if;

	-- Get the classes we're going to have to check for this message
	--
	for class in Flight_Classes_Cursor(p_flight_id) loop

	    -- Find the routes that have unseen bags for this class and flight.
	    --
	    for bns in Bags_Not_Seen_Cursor(p_flight_id,class.pax_class) loop
	    	Resolve_Route(p_flight_id,
			      flight_no,
			      flight_date,
			      class.pax_class,
			      bns.bag_tag,
			      bns.pax_id,
			      routes);
	    end loop;

	end loop;

	-- We now have a list of routes -- each one will need a separate BNS
	-- message.
	--
	for i in 1 .. routes.count loop
	    declare
	    	data      Encoded_List;
		tail	  Encoded_List;
		token_pos number := 1;
		msg_id    messages.message_id%type;
		bag_curs  Weak_Cursor;
		num_bags  number := 0;
	    begin

		-- Construct the base .V line.  We DO NOT terminate this with
		-- EOL as the storage routines put part numbers on this.
		--
		data(data.count + 1) := '.V/1T' || loc;

	    	-- Fetch the flights from the route and construct those lines
		--
		for j in 1 .. routes(i).num_flights loop
		    declare
		    	no String;
		    	date String;
		    	dest String;
		    	class String;
		    begin
		    	Tokenise(routes(i).route_data,no,',',token_pos);
		    	Tokenise(routes(i).route_data,date,',',token_pos);
		    	Tokenise(routes(i).route_data,dest,',',token_pos);
		    	Tokenise(routes(i).route_data,class,',',token_pos);

			-- The first line is a .F -- the rest are .O
			--
			if j = 1 then

			    if class = '*' then
			    	class := '';
			    else
			    	class := '/' || class;
			    end if;

			    data(data.count + 1) :=
				'.F/' || no || '/' || date ||
				    '/' || dest || class || EOL;

			else
			    data(data.count + 1) :=
				'.O/' || no || '/' || date ||
				    '/' || dest || '/' || class || EOL;
			end if;
		    end;
		end loop;

		-- Open a cursor to fetch the bags for this journey
		--
		declare
		    passenger    pax%rowtype;
		    last_pax     pax%rowtype;
		    last_pax_set boolean := false;
		    bag_data     Encoded_Data;
		    weight_count number := 0;
		    total_weight number := 0;
		begin
		    open bag_curs for Get_Bag_Query(p_flight_id,
						    routes(i).route_data);

		    loop
			declare
			    bag           Bag_Record;
			    just_tag      boolean;
			    has_passenger boolean;
			begin

			    fetch bag_curs INTO bag;
			    exit when bag_curs%NOTFOUND;

			    num_bags := num_bags + 1;

			    -- Get the passenger for this bag
			    --
			    declare
			    begin

				select * into passenger from pax
					where pax.pax_id = bag.pax_id;

				if not last_pax_set then
				    last_pax := passenger;
				    last_pax_set := true;
				end if;

				has_passenger := true;

			    exception
				when NO_DATA_FOUND then
				    has_passenger := false;
			    end;

			    -- If this is a new passenger then terminate the
			    -- bag data with the passenger details.
			    --
			    if has_passenger and
				    passenger.pax_id <> last_pax.pax_id then

				-- Add the encoded passenger details
				--
				Encode_Passenger(last_pax,
						 weight_count,
						 total_weight,
						 bag_data);

				-- Save this passenger's encoded data
				--
				data(data.count+1) := bag_data;

				-- Start the new passenger
				--
				weight_count := 0;
				total_weight := 0;
				bag_data := '';
				last_pax := passenger;

			    end if;

			    -- See if there are passenger details to go
			    -- along with this tag.  If so, record weights.
			    --
			    just_tag := true;

			    if has_passenger then

				-- Having a name, PNR or freq flyer means
				-- not just a tag...
				--
				if passenger.pax_surname is not null or
				    passenger.pax_name_record is not null or
				    passenger.freq_flyer is not null then

				    just_tag := false;

				end if;

				-- As does having some weighed baggage.
				--
				if bag.kg_weight is not null and
					bag.kg_weight > 0 then

				    just_tag := false;

				    weight_count := weight_count + 1;

				    total_weight :=
					    total_weight + bag.kg_weight;

				end if;
			    end if;

			    -- If we just have a tag, add this to the tail
			    -- list.  Otherwise add it to the string of
			    -- encoded bag tags.
			    --
			    if just_tag then
				tail(tail.count+1) := Encode_Tag(bag);
				weight_count := 0;
				total_weight := 0;
				bag_data := '';
			    else
				bag_data := bag_data || Encode_Tag(bag);
			    end if;

			end;
		    end loop;

		    -- If we come out of the loop with remaining bag data, then
		    -- add the remaining passenger data.  Note we don't need to
		    -- check passenger existance, as the simple fact of having
		    -- data is enough to assume.
		    --
		    if length(bag_data) > 0 then

			    -- Add the encoded passenger details
			    --
			    Encode_Passenger(passenger,
					     weight_count,
					     total_weight,
					     bag_data);

			    -- Save this passenger's encoded data
			    --
			    data(data.count+1) := bag_data;
		    end if;

		    close bag_curs;
		end;

		-- Add the 'just_tag' bags to the end of the message
		--
		for j in 1 .. tail.count loop
		    data(data.count+1) := tail(j);
		end loop;

		-- Only bother if there are actually bags...
		--
		if num_bags > 0 then

		    -- Create a message for this.
		    --
		    insert into messages
			(
			    messages.message_type,
			    messages.message_direction,
			    messages.flight_id,
			    messages.flight_number,
			    messages.flight_date,
			    messages.processed_state,
			    messages.version
			)
		    values
			(
			    'BNS',
			    'O',
			    p_flight_id,
			    orig_flight_no,
			    flight_date,
			    'U',
			    '1T' || loc
			) return messages.message_id into msg_id;

		    -- Store the message in the database
		    --
		    Store_Encoded_Message(msg_id,data);

		    -- Create the Gateway mail entry
		    --
		    Store_BNS_Mail(msg_id);

		end if;
	    end;
	end loop;

    exception

	when BNS_Exception then
	    null;

    end Build_Message;

end Bag_Not_Seen;

/
