Esempio n. 1
0
        private IEnumerable <JobLogDB.EventLogMaterial> CreateUnloadMaterial(MessageState state, string pal, IReadOnlyList <LogEntry> oldEvents = null)
        {
            var    oldMat   = FindMaterial(pal, oldEvents)[0];
            var    ret      = new List <JobLogDB.EventLogMaterial>();
            string partName = "";
            int    count    = 1;

            if (state.PartCompletedMessagesBySetup.Count > 0)
            {
                // use highest setup value
                var msgs = state.PartCompletedMessagesBySetup.ElementAt(state.PartCompletedMessagesBySetup.Count - 1).Value;
                count    = msgs.Count;
                partName = msgs[0].PartName;
            }
            Log.Debug("During unload, found {cnt} parts with name {part} that were unloaded/completed",
                      count, partName);

            _log.SetDetailsForMaterialID(oldMat.MaterialID, null, partName, null);

            if (_settings.SerialType == BlackMaple.MachineFramework.SerialType.AssignOneSerialPerCycle ||
                _settings.SerialType == BlackMaple.MachineFramework.SerialType.AssignOneSerialPerMaterial
                )
            {
                _log.RecordSerialForMaterialID(oldMat, _settings.ConvertMaterialIDToSerial(oldMat.MaterialID).PadLeft(_settings.SerialLength, '0'), state.LastSeenMessage.TimeUTC);
            }
            ret.Add(oldMat);

            var oldMatDetails = _log.GetMaterialDetails(oldMat.MaterialID);

            //allocate new materials, one per completed part in addition to the existing one
            for (int i = 1; i < count; i++)
            {
                var newId  = _log.AllocateMaterialID(oldMatDetails.JobUnique, oldMatDetails.PartName, 1);
                var newMat = new JobLogDB.EventLogMaterial()
                {
                    MaterialID = newId,
                    Process    = 1,
                    Face       = ""
                };
                if (_settings.SerialType == BlackMaple.MachineFramework.SerialType.AssignOneSerialPerCycle)
                {
                    _log.RecordSerialForMaterialID(newMat, oldMatDetails.Serial, state.LastSeenMessage.TimeUTC);
                }
                else if (_settings.SerialType == BlackMaple.MachineFramework.SerialType.AssignOneSerialPerMaterial)
                {
                    _log.RecordSerialForMaterialID(newMat, _settings.ConvertMaterialIDToSerial(newId).PadLeft(_settings.SerialLength, '0'), state.LastSeenMessage.TimeUTC);
                }
                ret.Add(newMat);
            }

            return(ret);
        }
Esempio n. 2
0
        private static void AddUnloads(JobLogDB log, IEnumerable <LoadAction> currentActions, PalletStatus pallet, TimeSpan?elapsedLoadTime, List <BlackMaple.MachineWatchInterface.LogEntry> oldCycles, CurrentStatus status)
        {
            // For some reason, sometimes parts to unload don't show up in PalletSubStatus table.
            // So create them here if that happens

            foreach (var unload in currentActions)
            {
                if (unload.LoadEvent)
                {
                    continue;
                }
                if (unload.LoadStation != pallet.CurrentPalletLocation.Num)
                {
                    continue;
                }

                var matIDs = new Queue <long>(FindMatIDsFromOldCycles(oldCycles, unload.Unique, unload.Process));
                status.Jobs.TryGetValue(unload.Unique, out InProcessJob job);

                for (int i = 0; i < unload.Qty; i += 1)
                {
                    string face  = unload.Process.ToString();
                    long   matID = -1;
                    if (matIDs.Count > 0)
                    {
                        matID = matIDs.Dequeue();
                    }

                    var matDetails = log.GetMaterialDetails(matID);
                    var inProcMat  = new InProcessMaterial()
                    {
                        MaterialID          = matID,
                        JobUnique           = unload.Unique,
                        PartName            = unload.Part,
                        Process             = unload.Process,
                        Path                = unload.Path,
                        Serial              = matDetails?.Serial,
                        WorkorderId         = matDetails?.Workorder,
                        SignaledInspections =
                            log.LookupInspectionDecisions(matID)
                            .Where(x => x.Inspect)
                            .Select(x => x.InspType)
                            .ToList(),
                        Location = new InProcessMaterialLocation()
                        {
                            Type   = InProcessMaterialLocation.LocType.OnPallet,
                            Pallet = pallet.Pallet,
                            Face   = unload.Process
                        },
                        Action = new InProcessMaterialAction()
                        {
                            Type = InProcessMaterialAction.ActionType.UnloadToCompletedMaterial,
                            ElapsedLoadUnloadTime = elapsedLoadTime
                        }
                    };
                    if (job != null)
                    {
                        if (unload.Process == job.NumProcesses)
                        {
                            inProcMat.Action.Type = InProcessMaterialAction.ActionType.UnloadToInProcess;
                        }
                        var queue = job.GetOutputQueue(process: unload.Process, path: unload.Path);
                        if (!string.IsNullOrEmpty(queue))
                        {
                            inProcMat.Action.UnloadIntoQueue = queue;
                        }
                    }
                    status.Material.Add(inProcMat);
                }
            }
        }
Esempio n. 3
0
        private static void AddLoads(JobLogDB log, IEnumerable <LoadAction> currentLoads, string pallet, PalletLocation palLoc, TimeSpan?elapsedLoadTime, CurrentStatus curStatus)
        {
            var queuedMats = new Dictionary <string, List <BlackMaple.MachineFramework.JobLogDB.QueuedMaterial> >();

            //process remaining loads/unloads (already processed ones have been removed from currentLoads)
            foreach (var operation in currentLoads)
            {
                if (!operation.LoadEvent || operation.LoadStation != palLoc.Num)
                {
                    continue;
                }
                for (int i = 0; i < operation.Qty; i++)
                {
                    List <BlackMaple.MachineFramework.JobLogDB.QueuedMaterial> queuedMat = null;
                    if (curStatus.Jobs.ContainsKey(operation.Unique))
                    {
                        var queue = curStatus.Jobs[operation.Unique].GetInputQueue(process: operation.Process, path: operation.Path);
                        if (!string.IsNullOrEmpty(queue))
                        {
                            //only lookup each queue once
                            if (queuedMats.ContainsKey(queue))
                            {
                                queuedMat = queuedMats[queue];
                            }
                            else
                            {
                                queuedMat = log.GetMaterialInQueue(queue).ToList();
                                queuedMats.Add(queue, queuedMat);
                            }
                        }
                    }
                    long   matId  = -1;
                    string serial = null;
                    string workId = null;
                    var    loc    = new InProcessMaterialLocation()
                    {
                        Type = InProcessMaterialLocation.LocType.Free
                    };
                    if (queuedMat != null)
                    {
                        var mat = queuedMat
                                  .Where(m => m.Unique == operation.Unique)
                                  .Select(m => (JobLogDB.QueuedMaterial?)m)
                                  .DefaultIfEmpty(null)
                                  .First();
                        if (mat.HasValue)
                        {
                            matId = mat.Value.MaterialID;
                            loc   = new InProcessMaterialLocation()
                            {
                                Type          = InProcessMaterialLocation.LocType.InQueue,
                                CurrentQueue  = mat.Value.Queue,
                                QueuePosition = mat.Value.Position,
                            };
                            queuedMat.RemoveAll(m => m.MaterialID == mat.Value.MaterialID);
                            var matDetails = log.GetMaterialDetails(matId);
                            serial = matDetails?.Serial;
                            workId = matDetails?.Workorder;
                        }
                    }
                    var inProcMat = new InProcessMaterial()
                    {
                        MaterialID  = matId,
                        JobUnique   = operation.Unique,
                        PartName    = operation.Part,
                        Process     = operation.Process,
                        Path        = operation.Path,
                        Serial      = serial,
                        WorkorderId = workId,
                        Location    = loc,
                        Action      = new InProcessMaterialAction()
                        {
                            Type                  = InProcessMaterialAction.ActionType.Loading,
                            LoadOntoPallet        = pallet,
                            LoadOntoFace          = operation.Process,
                            ProcessAfterLoad      = operation.Process,
                            PathAfterLoad         = operation.Path,
                            ElapsedLoadUnloadTime = elapsedLoadTime
                        }
                    };
                    curStatus.Material.Add(inProcMat);
                }
            }
        }
Esempio n. 4
0
        public static CurrentStatus Build(JobDB jobDB, JobLogDB log, FMSSettings fmsSettings, IMachineGroupName machineGroupName, IQueueSyncFault queueSyncFault, MazakDbType dbType, MazakAllData mazakData, DateTime utcNow)
        {
            //Load process and path numbers
            Dictionary <string, int> uniqueToMaxPath;
            Dictionary <string, int> uniqueToMaxProcess;

            CalculateMaxProcAndPath(mazakData, out uniqueToMaxPath, out uniqueToMaxProcess);

            var currentLoads = new List <LoadAction>(mazakData.LoadActions);

            var curStatus = new CurrentStatus();

            foreach (var k in fmsSettings.Queues)
            {
                curStatus.QueueSizes[k.Key] = k.Value;
            }
            if (mazakData.Alarms != null)
            {
                foreach (var alarm in mazakData.Alarms)
                {
                    if (!string.IsNullOrEmpty(alarm.AlarmMessage))
                    {
                        curStatus.Alarms.Add(alarm.AlarmMessage);
                    }
                }
            }
            if (queueSyncFault.CurrentQueueMismatch)
            {
                curStatus.Alarms.Add("Queue contents and Mazak schedule quantity mismatch.");
            }

            var jobsBySchID = new Dictionary <long, InProcessJob>();
            var pathBySchID = new Dictionary <long, MazakPart.IProcToPath>();

            foreach (var schRow in mazakData.Schedules)
            {
                if (!MazakPart.IsSailPart(schRow.PartName))
                {
                    continue;
                }

                MazakPartRow partRow = null;
                foreach (var p in mazakData.Parts)
                {
                    if (p.PartName == schRow.PartName)
                    {
                        partRow = p;
                        break;
                    }
                }
                if (partRow == null)
                {
                    continue;
                }

                //Parse data from the database
                var partName = partRow.PartName;
                int loc      = partName.IndexOf(':');
                if (loc >= 0)
                {
                    partName = partName.Substring(0, loc);
                }
                string jobUnique = "";
                MazakPart.IProcToPath procToPath = null;
                bool manual = false;
                if (!string.IsNullOrEmpty(partRow.Comment))
                {
                    MazakPart.ParseComment(partRow.Comment, out jobUnique, out procToPath, out manual);
                }

                if (!uniqueToMaxProcess.ContainsKey(jobUnique))
                {
                    continue;
                }

                int numProc      = uniqueToMaxProcess[jobUnique];
                int maxProc1Path = uniqueToMaxPath[jobUnique];

                InProcessJob job;

                //Create or lookup the job
                if (curStatus.Jobs.ContainsKey(jobUnique))
                {
                    job = curStatus.Jobs[jobUnique];
                }
                else
                {
                    var jobPaths = new int[numProc];
                    for (int i = 0; i < numProc; i++)
                    {
                        jobPaths[i] = maxProc1Path;
                    }
                    job                   = new InProcessJob(jobUnique, numProc, jobPaths);
                    job.PartName          = partName;
                    job.JobCopiedToSystem = true;
                    curStatus.Jobs.Add(jobUnique, job);
                }
                jobsBySchID.Add(schRow.Id, job);
                pathBySchID.Add(schRow.Id, procToPath);

                //Job Basics
                job.SetPlannedCyclesOnFirstProcess(procToPath.PathForProc(proc: 1), schRow.PlanQuantity);
                AddCompletedToJob(schRow, job, procToPath);
                job.Priority = schRow.Priority;
                if (((HoldPattern.HoldMode)schRow.HoldMode) == HoldPattern.HoldMode.FullHold)
                {
                    job.HoldEntireJob.UserHold = true;
                }
                else
                {
                    job.HoldEntireJob.UserHold = false;
                }

                AddRoutingToJob(mazakData, partRow, job, machineGroupName, procToPath, dbType);
            }

            var loadedJobs = new HashSet <string>();

            foreach (var j in jobsBySchID.Values)
            {
                if (loadedJobs.Contains(j.UniqueStr))
                {
                    continue;
                }
                loadedJobs.Add(j.UniqueStr);
                AddDataFromJobDB(jobDB, j);
            }

            //Now add pallets

            foreach (var palRow in mazakData.Pallets)
            {
                if (palRow.PalletNumber > 0 && !curStatus.Pallets.ContainsKey(palRow.PalletNumber.ToString()))
                {
                    var palName = palRow.PalletNumber.ToString();
                    var palLoc  = FindPalletLocation(machineGroupName, mazakData, dbType, palRow.PalletNumber);

                    //Create the pallet
                    PalletStatus status = new PalletStatus()
                    {
                        Pallet = palName,
                        CurrentPalletLocation = palLoc,
                        FixtureOnPallet       = palRow.Fixture,
                        NumFaces = 1,
                        OnHold   = false
                    };
                    curStatus.Pallets.Add(status.Pallet, status);

                    var oldCycles = log.CurrentPalletLog(palName);

                    //Add the material currently on the pallet
                    foreach (var palSub in mazakData.PalletSubStatuses)
                    {
                        if (palSub.PalletNumber != palRow.PalletNumber)
                        {
                            continue;
                        }
                        if (palSub.FixQuantity <= 0)
                        {
                            continue;
                        }
                        if (!jobsBySchID.ContainsKey(palSub.ScheduleID))
                        {
                            continue;
                        }

                        status.NumFaces = Math.Max(status.NumFaces, palSub.PartProcessNumber);

                        var job        = jobsBySchID[palSub.ScheduleID];
                        var procToPath = pathBySchID[palSub.ScheduleID];

                        var matIDs = new Queue <long>(FindMatIDsFromOldCycles(oldCycles, job.UniqueStr, palSub.PartProcessNumber));

                        for (int i = 1; i <= palSub.FixQuantity; i++)
                        {
                            int  face  = palSub.PartProcessNumber;
                            long matID = -1;
                            if (matIDs.Count > 0)
                            {
                                matID = matIDs.Dequeue();
                            }

                            var matDetails = log.GetMaterialDetails(matID);
                            var inProcMat  = new InProcessMaterial()
                            {
                                MaterialID          = matID,
                                JobUnique           = job.UniqueStr,
                                PartName            = job.PartName,
                                Process             = palSub.PartProcessNumber,
                                Path                = procToPath.PathForProc(palSub.PartProcessNumber),
                                Serial              = matDetails?.Serial,
                                WorkorderId         = matDetails?.Workorder,
                                SignaledInspections =
                                    log.LookupInspectionDecisions(matID)
                                    .Where(x => x.Inspect)
                                    .Select(x => x.InspType)
                                    .Distinct()
                                    .ToList(),
                                LastCompletedMachiningRouteStopIndex =
                                    oldCycles.Any(
                                        c => c.LogType == LogType.MachineCycle &&
                                        !c.StartOfCycle &&
                                        c.Material.Any(m => m.MaterialID == matID && m.Process == palSub.PartProcessNumber)
                                        )
                    ? (int?)0
                    : null,
                                Location = new InProcessMaterialLocation()
                                {
                                    Type   = InProcessMaterialLocation.LocType.OnPallet,
                                    Pallet = status.Pallet,
                                    Face   = face
                                },
                                Action = new InProcessMaterialAction()
                                {
                                    Type = InProcessMaterialAction.ActionType.Waiting
                                }
                            };
                            curStatus.Material.Add(inProcMat);

                            //check for unloading or transfer
                            var loadNext = CheckLoadOfNextProcess(currentLoads, job.UniqueStr, palSub.PartProcessNumber, palLoc);
                            var unload   = CheckUnload(currentLoads, job.UniqueStr, palSub.PartProcessNumber, palLoc);

                            if (loadNext != null)
                            {
                                var start = FindLoadStartFromOldCycles(oldCycles, matID);
                                inProcMat.Action = new InProcessMaterialAction()
                                {
                                    Type                  = InProcessMaterialAction.ActionType.Loading,
                                    LoadOntoFace          = palSub.PartProcessNumber + 1,
                                    LoadOntoPallet        = status.Pallet,
                                    ProcessAfterLoad      = palSub.PartProcessNumber + 1,
                                    PathAfterLoad         = procToPath.PathForProc(palSub.PartProcessNumber + 1),
                                    ElapsedLoadUnloadTime = start != null ? (TimeSpan?)utcNow.Subtract(start.EndTimeUTC) : null
                                };
                            }
                            else if (unload != null)
                            {
                                var start = FindLoadStartFromOldCycles(oldCycles, matID);
                                inProcMat.Action = new InProcessMaterialAction()
                                {
                                    Type =
                                        palSub.PartProcessNumber == job.NumProcesses
                            ? InProcessMaterialAction.ActionType.UnloadToCompletedMaterial
                            : InProcessMaterialAction.ActionType.UnloadToInProcess,
                                    UnloadIntoQueue = job.GetOutputQueue(
                                        process: palSub.PartProcessNumber,
                                        path: procToPath.PathForProc(palSub.PartProcessNumber)),
                                    ElapsedLoadUnloadTime = start != null ? (TimeSpan?)utcNow.Subtract(start.EndTimeUTC) : null
                                };
                            }
                            else
                            {
                                // detect if machining
                                var start = FindMachineStartFromOldCycles(oldCycles, matID);
                                if (start != null)
                                {
                                    var machStop    = job.GetMachiningStop(inProcMat.Process, inProcMat.Path).FirstOrDefault();
                                    var elapsedTime = utcNow.Subtract(start.EndTimeUTC);
                                    inProcMat.Action = new InProcessMaterialAction()
                                    {
                                        Type = InProcessMaterialAction.ActionType.Machining,
                                        ElapsedMachiningTime           = elapsedTime,
                                        ExpectedRemainingMachiningTime =
                                            machStop != null?machStop.ExpectedCycleTime.Subtract(elapsedTime) : TimeSpan.Zero
                                    };
                                }
                            }
                        }
                    }

                    if (palLoc.Location == PalletLocationEnum.LoadUnload)
                    {
                        var start       = FindLoadStartFromOldCycles(oldCycles);
                        var elapsedLoad = start != null ? (TimeSpan?)utcNow.Subtract(start.EndTimeUTC) : null;
                        AddLoads(log, currentLoads, status.Pallet, palLoc, elapsedLoad, curStatus);
                        AddUnloads(log, currentLoads, status, elapsedLoad, oldCycles, curStatus);
                    }
                }
            }

            //now queued
            var seenMatIds = new HashSet <long>(curStatus.Material.Select(m => m.MaterialID));

            foreach (var mat in log.GetMaterialInAllQueues())
            {
                // material could be in the process of being loaded
                if (seenMatIds.Contains(mat.MaterialID))
                {
                    continue;
                }
                var matLogs  = log.GetLogForMaterial(mat.MaterialID);
                int lastProc = 0;
                foreach (var entry in log.GetLogForMaterial(mat.MaterialID))
                {
                    foreach (var entryMat in entry.Material)
                    {
                        if (entryMat.MaterialID == mat.MaterialID)
                        {
                            lastProc = Math.Max(lastProc, entryMat.Process);
                        }
                    }
                }
                var matDetails = log.GetMaterialDetails(mat.MaterialID);
                curStatus.Material.Add(new InProcessMaterial()
                {
                    MaterialID          = mat.MaterialID,
                    JobUnique           = mat.Unique,
                    PartName            = mat.PartName,
                    Process             = lastProc,
                    Path                = 1,
                    Serial              = matDetails?.Serial,
                    WorkorderId         = matDetails?.Workorder,
                    SignaledInspections =
                        log.LookupInspectionDecisions(mat.MaterialID)
                        .Where(x => x.Inspect)
                        .Select(x => x.InspType)
                        .Distinct()
                        .ToList(),
                    Location = new InProcessMaterialLocation()
                    {
                        Type          = InProcessMaterialLocation.LocType.InQueue,
                        CurrentQueue  = mat.Queue,
                        QueuePosition = mat.Position,
                    },
                    Action = new InProcessMaterialAction()
                    {
                        Type = InProcessMaterialAction.ActionType.Waiting
                    }
                });
            }


            var notCopied = jobDB.LoadJobsNotCopiedToSystem(DateTime.UtcNow.AddHours(-WriteJobs.JobLookbackHours), DateTime.UtcNow);

            foreach (var j in notCopied.Jobs)
            {
                if (curStatus.Jobs.ContainsKey(j.UniqueStr))
                {
                    //The copy to the cell succeeded but the DB has not yet been updated.
                    //The thread which copies jobs will soon notice and update the database
                    //so we can ignore it for now.
                }
                else
                {
                    curStatus.Jobs.Add(j.UniqueStr, new InProcessJob(j));
                }
            }

            foreach (var j in curStatus.Jobs)
            {
                foreach (var d in jobDB.LoadDecrementsForJob(j.Value.UniqueStr))
                {
                    j.Value.Decrements.Add(d);
                }
            }

            return(curStatus);
        }
Esempio n. 5
0
        public void ConvertsMaterialFromV17()
        {
            // existing v17 file has the following data in it
            var now = new DateTime(2018, 7, 12, 5, 6, 7, DateTimeKind.Utc);

            var mat1_1 = new LogMaterial(1, "uuu1", 1, "part1", 2, "serial1", "work1", face: "A");
            var mat1_2 = new LogMaterial(1, "uuu1", 2, "part1", 2, "serial1", "work1", face: "B");
            var mat2_1 = new LogMaterial(2, "uuu1", 1, "part1", 2, "serial2", "", face: "C");
            var mat2_2 = new LogMaterial(2, "uuu1", 1, "part1", 2, "serial2", "", face: "D");
            var mat3   = new LogMaterial(3, "uuu2", 1, "part2", 1, "", "work3", face: "E");

            _log.GetLogEntries(now, now.AddDays(1)).Should().BeEquivalentTo(new[] {
                new LogEntry(
                    cntr: -1,
                    mat: new [] { mat1_1, mat2_1 },
                    pal: "3",
                    ty: LogType.MachineCycle,
                    locName: "MC",
                    locNum: 1,
                    prog: "proggg",
                    start: false,
                    endTime: now,
                    result: "result",
                    endOfRoute: false
                    ),
                new LogEntry(
                    cntr: -1,
                    mat: new [] { mat1_2, mat2_2 },
                    pal: "5",
                    ty: LogType.MachineCycle,
                    locName: "MC",
                    locNum: 1,
                    prog: "proggg2",
                    start: false,
                    endTime: now.AddMinutes(10),
                    result: "result2",
                    endOfRoute: false
                    ),
                new LogEntry(
                    cntr: -1,
                    mat: new [] { mat1_1 },
                    pal: "",
                    ty: LogType.PartMark,
                    locName: "Mark",
                    locNum: 1,
                    prog: "MARK",
                    start: false,
                    endTime: now.AddMinutes(20),
                    result: "serial1",
                    endOfRoute: false
                    ),
                new LogEntry(
                    cntr: -1,
                    mat: new [] { mat1_1 },
                    pal: "",
                    ty: LogType.OrderAssignment,
                    locName: "Order",
                    locNum: 1,
                    prog: "",
                    start: false,
                    endTime: now.AddMinutes(30),
                    result: "work1",
                    endOfRoute: false
                    ),
                new LogEntry(
                    cntr: -1,
                    mat: new [] { mat2_2 },
                    pal: "",
                    ty: LogType.PartMark,
                    locName: "Mark",
                    locNum: 1,
                    prog: "MARK",
                    start: false,
                    endTime: now.AddMinutes(40),
                    result: "serial2",
                    endOfRoute: false
                    ),
                new LogEntry(
                    cntr: -1,
                    mat: new [] { mat3 },
                    pal: "1",
                    ty: LogType.LoadUnloadCycle,
                    locName: "L/U",
                    locNum: 5,
                    prog: "LOAD",
                    start: false,
                    endTime: now.AddMinutes(50),
                    result: "LOAD",
                    endOfRoute: false
                    ),
                new LogEntry(
                    cntr: -1,
                    mat: new [] { mat3 },
                    pal: "",
                    ty: LogType.OrderAssignment,
                    locName: "Order",
                    locNum: 1,
                    prog: "",
                    start: false,
                    endTime: now.AddMinutes(60),
                    result: "work3",
                    endOfRoute: false
                    ),
            }, options =>
                                                                            options.Excluding(x => x.Counter)
                                                                            );

            _log.GetMaterialDetails(1).Should().BeEquivalentTo(new MaterialDetails()
            {
                MaterialID   = 1,
                JobUnique    = "uuu1",
                PartName     = "part1",
                NumProcesses = 2,
                Workorder    = "work1",
                Serial       = "serial1",
                Paths        = new System.Collections.Generic.Dictionary <int, int>()
            });
            _log.GetMaterialDetails(2).Should().BeEquivalentTo(new MaterialDetails()
            {
                MaterialID   = 2,
                JobUnique    = "uuu1",
                PartName     = "part1",
                NumProcesses = 2,
                Workorder    = null,
                Serial       = "serial2",
                Paths        = new System.Collections.Generic.Dictionary <int, int>()
            });
            _log.GetMaterialDetails(3).Should().BeEquivalentTo(new MaterialDetails()
            {
                MaterialID   = 3,
                JobUnique    = "uuu2",
                PartName     = "part2",
                NumProcesses = 1,
                Workorder    = "work3",
                Serial       = null,
                Paths        = new System.Collections.Generic.Dictionary <int, int>()
            });
        }