internal ITransportplanungJob StarteTransportplanungAsync(int saNr)
        {
            // TODO check ob TP schon in Ausführung?
            Contract.Requires(saNr > 0);

            TransportplanungJob job = new TransportplanungJob(saNr);
            Task planungsTask = planungsQueue.QueueTask(() =>
            {
                job.Status = TransportplanungJobStatusTyp.Gestartet;
                try
                {
                    transactionService.ExecuteTransactional(
                        () =>
                        {
                            LöscheTransportpläne(saNr);
                        });
                    transactionService.ExecuteTransactional(
                        () =>
                        {
                            Sendungsanfrage sa = this.auftragServices.FindSendungsanfrageEntity(saNr);
                            Contract.Assume(sa != null);

                            ErzeugeTransportpläneZuSendungsanfrage(sa, job);
                            this.auftragServices.UpdateSendungsanfrageStatus(saNr, SendungsanfrageStatusTyp.Geplant);
                        });
                    job.Status = (job.Meldungen.Count == 0) ? TransportplanungJobStatusTyp.BeendetOk : TransportplanungJobStatusTyp.BeendetNok;
                }
                catch (Exception)
                {
                    job.Status = TransportplanungJobStatusTyp.BeendetNok;
                    throw;
                }
            });
            job.Task = planungsTask;

            return job;
        }
        /// <summary>
        /// Erzeugt Frachteinheiten (TEU, FEU) für Sendungspositionen.
        /// </summary>
        /// <pre>sps.Count > 0</pre>
        private List<Frachteinheit> ErzeugeFrachteinheitenFür(IList<Sendungsposition> sps, TransportplanungJob job)
        {
            Contract.Requires(sps.Count > 0);

            List<Frachteinheit> lfe = new List<Frachteinheit>();
            
            // TODO: besserer Algorithmus nötig; Volumen der Fracht wird hier nicht beachtet, sondern nur das Gewicht.
            decimal restKapazität = 0m;
            Frachteinheit fe = null;  
            foreach (Sendungsposition sp in sps)
            {
                if (sp.Bruttogewicht > FEU.MAXZULADUNG_TONS)
                {
                    // Ware zu schwer; kann nicht transportiert werden.
                    job.Meldungen.Add(new TransportplanungMeldung(
                        TransportplanungMeldungTag.FrachteinheitenBildungNichtMöglich,
                        "Das Bruttogewicht der Sendungsposition " + sp.SendungspositionsNr + " ist zu hoch."));
                    return new List<Frachteinheit>();
                }

                // Falls noch Restkapazität vorhanden und nicht die erste zu erstellende Frachteinheit
                if (restKapazität - sp.Bruttogewicht < 0 && fe != null)
                {
                    lfe.Add(fe);
                    fe = null;
                }

                if (fe == null)
                {
                    // Neue Frachteinheit anlegen, Typ (TEU, FEU) ist abhängig von Gewicht der Sendungsposition
                    if (sp.Bruttogewicht > TEU.MAXZULADUNG_TONS)
                    {
                        fe = new Frachteinheit(FrachteinheitTyp.FEU);
                        restKapazität = FEU.MAXZULADUNG_TONS;
                    }
                    else
                    {
                        fe = new Frachteinheit(FrachteinheitTyp.TEU);
                        restKapazität = TEU.MAXZULADUNG_TONS;
                    }
                }
                
                fe.Sendungspositionen.Add(sp.SendungspositionsNr);
                restKapazität = restKapazität - sp.Bruttogewicht;
            }

            // evtl. letzte erstellte Frachteinheit noch hinzunehmen
            if (fe.Sendungspositionen.Count > 0)
            {
                lfe.Add(fe);
            }

            Contract.Ensures(lfe.Count > 0);

            return lfe;
        }
        private void ErzeugeTransportpläneZuSendungsanfrage(Sendungsanfrage sa, TransportplanungJob job)
        {
            Contract.Requires(sa != null);

            List<Frachteinheit> fe = null;
            fe = ErzeugeFrachteinheitenFür(sa.Sendungspositionen, job);
            if (job.Abort)
            {
                return;
            }

            List<List<Transportbeziehung>> p = transportnetzServices.GeneriereAllePfadeVonBis(sa.StartLokation, sa.ZielLokation);
            if (p.Count == 0)
            {
                job.Meldungen.Add(new TransportplanungMeldung(
                    TransportplanungMeldungTag.KeinWegVorhanden,
                    "Kein Weg von " + sa.StartLokation + " bis " + sa.ZielLokation + " vorhanden."));
                job.Abort = true;

                return;
            }

            List<Transportplan> ltp = new List<Transportplan>();
            foreach (List<Transportbeziehung> pfad in p)
            {
                List<TransportplanSchritt> tps = ErzeugePlanFür(pfad, fe, sa.AbholzeitfensterStart, sa.AbholzeitfensterEnde);

                if (tps != null)
                {
                    Transportplan tp = new Transportplan();
                    tp.TransportplanSchritte = tps;
                    tp.Frachteinheiten = fe.Select(_fe => (Frachteinheit)_fe.Clone()).ToList();
                    tp.SaNr = sa.SaNr;
                    tp.UpdateStatus(TransportplanStatusTyp.Geplant);

                    ltp.Add(tp);
                }
            }

            Contract.Ensures(ltp != null);

            // Füge die erzeugten Transportpläne dem Repository hinzu
            ltp.ForEach((tp) => { this.tp_REPO.Save(tp); });
        }