Bug in yml dotnet tool PackageReference arrangement RDS Blank Change int dotnet test nuget ^ [spcepiworld].[dbo].[evnt_inf] Assembly Version WS Result bug fix and Nuget bump, PSN, Reactor and Extra RDS rule OpenInsightApi and testRunTitle editorconfig bugs Fix Type serializerValue RDS oversight PropertyNameCaseInsensitive SP1 was different Save check for file already present NoWaitDirectory MoveArchive allow empty directory and continueOnError for clean files CreatePointerFile and more on NoWaitDirectory
486 lines
20 KiB
C#
486 lines
20 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Data.SqlClient;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace Adaptation.FileHandlers.TIBCO.Transport;
|
|
|
|
public class Job
|
|
{
|
|
|
|
#nullable restore
|
|
|
|
public string AutomationMode { get; }
|
|
public string BasicType { get; }
|
|
public string CreationUser { get; }
|
|
public string Equipment { get; }
|
|
public string JobName { get; }
|
|
public string LastUpdateUser { get; }
|
|
public string LotName { get; }
|
|
public string LotState { get; }
|
|
public string PackageName { get; }
|
|
public string ProcessSpecName { get; }
|
|
public string ProcessType { get; }
|
|
public string ProductName { get; }
|
|
public string Qty { get; }
|
|
public string Qty2 { get; }
|
|
public string RecipeName { get; }
|
|
public string SpecName { get; }
|
|
public string StateModel { get; }
|
|
public string Status { get; }
|
|
//
|
|
public bool IsAreaSi { get; }
|
|
public DateTime DateTime { get; }
|
|
public List<Item> Items { get; }
|
|
|
|
public Job(string lsl2SQLConnectionString, string metrologyFileShare, string mid)
|
|
{
|
|
const int zero = 0;
|
|
Items = new List<Item>();
|
|
if (string.IsNullOrEmpty(mid) || mid[zero] != '{' || mid[mid.Length - 1] != '}' || !mid.Contains("\"Si\""))
|
|
IsAreaSi = false;
|
|
else
|
|
{
|
|
string psn;
|
|
string zone;
|
|
string layer;
|
|
int? rdsNumber;
|
|
string comment;
|
|
const string hyphen = "-";
|
|
Input input = JsonSerializer.Deserialize<Input>(mid);
|
|
if (!long.TryParse(input.Sequence, out long sequence))
|
|
DateTime = DateTime.Now;
|
|
else
|
|
DateTime = new DateTime(sequence);
|
|
int? reactorNumber = GetReactorNumber(input);
|
|
(int? workOrderNumber, int? _, int? workOrderCassette, int? slotNumber, bool isWorkOrder) = GetWorkOrder(input);
|
|
if (isWorkOrder || reactorNumber.HasValue)
|
|
(layer, psn, rdsNumber, zone) = (string.Empty, string.Empty, null, string.Empty);
|
|
else if (!string.IsNullOrEmpty(input.MID) && input.MID.Length is not 2 and not 3)
|
|
(layer, psn, rdsNumber, reactorNumber, zone) = Get(input);
|
|
else
|
|
(layer, psn, rdsNumber, reactorNumber, zone) = Get(metrologyFileShare, input);
|
|
if (IsValid(rdsNumber))
|
|
(comment, layer, rdsNumber, psn, reactorNumber, zone) = GetWithValidRDS(lsl2SQLConnectionString, layer, psn, rdsNumber, reactorNumber, zone);
|
|
else if (isWorkOrder || reactorNumber.HasValue)
|
|
(comment, layer, rdsNumber, psn, reactorNumber, zone) = Get(lsl2SQLConnectionString, layer, psn, reactorNumber, slotNumber, workOrderNumber, workOrderCassette, zone);
|
|
else
|
|
(comment, layer, zone) = (hyphen, hyphen, hyphen);
|
|
Qty = "1";
|
|
Status = hyphen; // INFO
|
|
CreationUser = hyphen; // ?
|
|
LotState = hyphen; // LAYER2
|
|
LastUpdateUser = hyphen; // ?
|
|
Equipment = input.MesEntity; // ?
|
|
PackageName = hyphen; // WAFER_ID
|
|
Qty2 = input.Sequence; // SEQUENCE
|
|
RecipeName = input.Recipe; // PPID
|
|
IsAreaSi = input.Area == "Si"; // N/A
|
|
StateModel = input.EquipmentType; // ?
|
|
JobName = DateTime.Ticks.ToString(); // ?
|
|
SpecName = !string.IsNullOrEmpty(layer) ? layer : hyphen; // LAYER
|
|
ProductName = !string.IsNullOrEmpty(psn) ? psn : hyphen; // PRODUCT
|
|
AutomationMode = string.Concat(DateTime.Ticks, ".", input.MesEntity); // ?
|
|
ProcessSpecName = !string.IsNullOrEmpty(zone) ? zone : hyphen; // WAFER_POS
|
|
BasicType = !string.IsNullOrEmpty(comment) ? comment : hyphen; // BASIC_TYPE
|
|
LotName = rdsNumber is not null ? rdsNumber.Value.ToString() : hyphen; // MID
|
|
ProcessType = reactorNumber is not null ? reactorNumber.Value.ToString() : hyphen; // PROCESS_JOBID
|
|
Items.Add(new Item { Name = "0", Type = "NA", Number = (0 + 1).ToString(), Qty = "1", CarrierName = hyphen });
|
|
}
|
|
}
|
|
|
|
private static int? GetReactorNumber(Input input)
|
|
{
|
|
int? result;
|
|
bool isReactor = !string.IsNullOrEmpty(input.MID) && input.MID.Length == 2 && Regex.IsMatch(input.MID, "^[0-9]{2}$");
|
|
if (!isReactor)
|
|
result = null;
|
|
else if (!int.TryParse(input.MID, out int reactor))
|
|
result = null;
|
|
else
|
|
result = reactor;
|
|
return result;
|
|
}
|
|
|
|
private static (int?, int?, int?, int?, bool) GetWorkOrder(Input input)
|
|
{
|
|
int? slotNumber;
|
|
int? workOrderStep = null;
|
|
int? workOrderNumber = null;
|
|
MatchCollection[] collection;
|
|
int? workOrderCassette = null;
|
|
if (string.IsNullOrEmpty(input.MID))
|
|
collection = Array.Empty<MatchCollection>();
|
|
else
|
|
{
|
|
string pattern = @"^([oiOI])?([0-9]{6,7})\.([0-5]{1})\.([0-9]{1,2})$"; // o171308.1.51
|
|
collection = (from l in input.MID.Split('-') select Regex.Matches(l, pattern)).ToArray();
|
|
}
|
|
foreach (MatchCollection matchCollection in collection)
|
|
{
|
|
if (matchCollection.Count == 0)
|
|
continue;
|
|
if (!matchCollection[0].Success || matchCollection[0].Groups.Count != 5)
|
|
continue;
|
|
if (!int.TryParse(matchCollection[0].Groups[3].Value, out int workOrderStepValue))
|
|
continue;
|
|
if (!int.TryParse(matchCollection[0].Groups[2].Value, out int workOrderNumberValue))
|
|
continue;
|
|
if (!int.TryParse(matchCollection[0].Groups[4].Value, out int workOrderCassetteValue))
|
|
continue;
|
|
workOrderStep = workOrderStepValue;
|
|
workOrderNumber = workOrderNumberValue;
|
|
workOrderCassette = workOrderCassetteValue;
|
|
break;
|
|
}
|
|
if (string.IsNullOrEmpty(input.Slot) || !int.TryParse(input.Slot, out int slot))
|
|
slotNumber = null;
|
|
else
|
|
slotNumber = slot;
|
|
return new(workOrderNumber, workOrderStep, workOrderCassette, slotNumber, workOrderStep is not null || workOrderNumber is not null || workOrderCassette is not null);
|
|
}
|
|
|
|
private static bool IsValid(int? rdsNumber) => !IsInvalid(rdsNumber);
|
|
|
|
private static bool IsInvalid(int? rdsNumber) => rdsNumber is null or < 100000 or > 100000000;
|
|
|
|
private static (string, string) GetReactorAndRDS(string defaultReactor, string defaultRDS, string text, string formattedText, string[] segments, bool hasRDS)
|
|
{
|
|
string rds;
|
|
string reactor;
|
|
if (string.IsNullOrEmpty(text) || segments.Length == 0 || string.IsNullOrEmpty(formattedText) || (segments.Length > 1 && !hasRDS))
|
|
reactor = defaultReactor;
|
|
else
|
|
reactor = segments[0];
|
|
if (segments.Length <= 1 || !int.TryParse(segments[1], out int rdsValue) || rdsValue < 99)
|
|
rds = defaultRDS;
|
|
else
|
|
rds = segments[1];
|
|
if (reactor.Length > 3)
|
|
{
|
|
rds = reactor;
|
|
reactor = defaultReactor;
|
|
}
|
|
return new(reactor, rds);
|
|
}
|
|
|
|
private static (string, string) GetLayerAndPSN(string defaultLayer, string defaultPSN, string[] segments, bool hasRDS)
|
|
{
|
|
string psn;
|
|
string layer;
|
|
if (segments.Length <= 2 || (segments.Length > 1 && !hasRDS))
|
|
{
|
|
psn = defaultPSN;
|
|
layer = defaultLayer;
|
|
}
|
|
else
|
|
{
|
|
string[] segmentsB = segments[2].Split('.');
|
|
psn = segmentsB[0];
|
|
if (segmentsB.Length <= 1)
|
|
layer = defaultLayer;
|
|
else
|
|
{
|
|
layer = segmentsB[1];
|
|
if (layer.Length > 1 && layer[0] == '0')
|
|
layer = layer.Substring(1);
|
|
}
|
|
}
|
|
return (layer, psn);
|
|
}
|
|
|
|
private static string GetZone(string[] segments)
|
|
{
|
|
string result;
|
|
if (segments.Length <= 3)
|
|
result = string.Empty;
|
|
else
|
|
{
|
|
result = segments[3];
|
|
if (result.Length > 1 && result[0] == '0')
|
|
result = result.Substring(1);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private static (string, string, int, int?, string) Get(Input input)
|
|
{
|
|
string psn;
|
|
string rds;
|
|
string zone;
|
|
string layer;
|
|
int rdsNumber;
|
|
string reactor;
|
|
int? reactorNumber;
|
|
string defaultPSN = string.Empty;
|
|
string defaultRDS = string.Empty;
|
|
string defaultLayer = string.Empty;
|
|
string defaultReactor = string.Empty;
|
|
string[] segments = input.MID.Split(new char[] { '-' });
|
|
bool hasRDS = Regex.IsMatch(input.MID, "[-]?[0-9]{5,}[-]?");
|
|
string formattedText = Regex.Replace(input.MID, @"[\\,\/,\:,\*,\?,\"",\<,\>,\|]", "_").Split('\r')[0].Split('\n')[0];
|
|
(reactor, rds) = GetReactorAndRDS(defaultReactor, defaultRDS, input.MID, formattedText, segments, hasRDS);
|
|
if (string.IsNullOrEmpty(rds))
|
|
rdsNumber = 0;
|
|
else
|
|
_ = int.TryParse(segments[1], out rdsNumber);
|
|
if (IsInvalid(rdsNumber) || !int.TryParse(reactor, out int reactorCheck))
|
|
{
|
|
psn = string.Empty;
|
|
zone = string.Empty;
|
|
layer = string.Empty;
|
|
reactorNumber = null;
|
|
}
|
|
else
|
|
{
|
|
reactorNumber = reactorCheck;
|
|
(layer, psn) = GetLayerAndPSN(defaultLayer, defaultPSN, segments, hasRDS);
|
|
zone = GetZone(segments);
|
|
}
|
|
return new(layer, psn, rdsNumber, reactorNumber, zone);
|
|
}
|
|
|
|
private static (string, string, int?, int?, string) Get(string metrologyFileShare, Input input)
|
|
{
|
|
string[] files;
|
|
string[] lines;
|
|
string moveFile;
|
|
int? reactor = null;
|
|
int? rdsNumber = null;
|
|
string psn = string.Empty;
|
|
string zone = string.Empty;
|
|
string layer = string.Empty;
|
|
string usedDirectory = Path.Combine(metrologyFileShare, "Used");
|
|
if (!Directory.Exists(metrologyFileShare))
|
|
throw new Exception($"Unable to access file-share <{metrologyFileShare}>");
|
|
if (!Directory.Exists(usedDirectory))
|
|
_ = Directory.CreateDirectory(usedDirectory);
|
|
if (string.IsNullOrEmpty(input.MID))
|
|
files = Array.Empty<string>();
|
|
else
|
|
files = Directory.GetFiles(metrologyFileShare, $"*-{input.MID}.rsv", SearchOption.TopDirectoryOnly);
|
|
foreach (string file in files.OrderByDescending(l => new FileInfo(l).LastWriteTime))
|
|
{
|
|
lines = File.ReadAllLines(file);
|
|
foreach (string line in lines)
|
|
{
|
|
if (string.IsNullOrEmpty(line))
|
|
continue;
|
|
if (!int.TryParse(line, out int rds) || IsInvalid(rds))
|
|
continue;
|
|
rdsNumber = rds;
|
|
break;
|
|
}
|
|
if (rdsNumber is not null)
|
|
{
|
|
moveFile = Path.Combine(usedDirectory, Path.GetFileName(file));
|
|
if (File.Exists(moveFile))
|
|
File.Delete(file);
|
|
else
|
|
File.Move(file, moveFile);
|
|
break;
|
|
}
|
|
}
|
|
return new(layer, psn, rdsNumber, reactor, zone);
|
|
}
|
|
|
|
private static string GetRunJson(string lsl2SQLConnectionString, int? rds, int? workOrderNumber, int? workOrderCassette, int? slot, int? reactor)
|
|
{
|
|
StringBuilder result = new();
|
|
string commandText = GetCommandText(rds, workOrderNumber, workOrderCassette, slot, reactor);
|
|
try
|
|
{
|
|
using SqlConnection sqlConnection = new(lsl2SQLConnectionString);
|
|
sqlConnection.Open();
|
|
using SqlCommand sqlCommand = new(commandText, sqlConnection);
|
|
SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(CommandBehavior.SequentialAccess);
|
|
while (sqlDataReader.Read())
|
|
_ = result.Append(sqlDataReader.GetString(0));
|
|
}
|
|
catch (Exception)
|
|
{
|
|
_ = result.Clear();
|
|
}
|
|
return result.ToString();
|
|
}
|
|
|
|
private static string GetCommandText(int? rds, int? workOrderNumber, int? workOrderCassette, int? slot, int? reactor)
|
|
{
|
|
StringBuilder result = new();
|
|
_ = result.Append(" select ").
|
|
Append(" rr.rds_no ").
|
|
Append(" , rr.reactor ").
|
|
Append(" , rr.ps_no ").
|
|
Append(" , rr.load_lock_side ").
|
|
Append(" , rr.reactor_type ").
|
|
Append(" , rr.recipe_name ").
|
|
Append(" , rr.recipe_no ").
|
|
Append(" , rr.spec_type ").
|
|
Append(" , ( ").
|
|
Append(" select max(wm.zone) ").
|
|
Append(" from lsl2sql.dbo.wm_in_slot_no wm ").
|
|
Append(" where wm.wo_no = rr.wo_no ").
|
|
Append(" and wm.rds_no = rr.rds_no ").
|
|
Append(" and wm.in_cass_no = ").Append(workOrderCassette is null ? -1 : workOrderCassette.Value).Append(' ').
|
|
Append(" and wm.slot_no = ").Append(slot is null ? -1 : slot.Value).Append(' ').
|
|
Append(" ) zone ").
|
|
Append(" from lsl2sql.dbo.react_run rr ").
|
|
Append(" where rr.rds_no = ").Append(rds is null ? -1 : rds.Value).Append(' ').
|
|
Append(" union all ").
|
|
Append(" select ").
|
|
Append(" rr.rds_no ").
|
|
Append(" , rr.reactor ").
|
|
Append(" , rr.ps_no ").
|
|
Append(" , rr.load_lock_side ").
|
|
Append(" , rr.reactor_type ").
|
|
Append(" , rr.recipe_name ").
|
|
Append(" , rr.recipe_no ").
|
|
Append(" , rr.spec_type ").
|
|
Append(" , ( ").
|
|
Append(" select max(wm.zone) ").
|
|
Append(" from lsl2sql.dbo.wm_in_slot_no wm ").
|
|
Append(" where wm.wo_no = rr.wo_no ").
|
|
Append(" and wm.rds_no = rr.rds_no ").
|
|
Append(" and wm.in_cass_no = ").Append(workOrderCassette is null ? -1 : workOrderCassette.Value).Append(' ').
|
|
Append(" and wm.slot_no = ").Append(slot is null ? -1 : slot.Value).Append(' ').
|
|
Append(" ) zone ").
|
|
Append(" from lsl2sql.dbo.react_run rr ").
|
|
Append(" where rr.rds_no = ( ").
|
|
Append(" select max(wm.rds_no) ").
|
|
Append(" from lsl2sql.dbo.wm_in_slot_no wm ").
|
|
Append(" where wm.wo_no = ").Append(workOrderNumber is null ? -1 : workOrderNumber.Value).Append(' ').
|
|
Append(" and wm.in_cass_no = ").Append(workOrderCassette is null ? -1 : workOrderCassette.Value).Append(' ').
|
|
Append(" and wm.slot_no = ").Append(slot is null ? -1 : slot.Value).Append(' ').
|
|
Append(" ) ").
|
|
Append(" union all ").
|
|
Append(" select ").
|
|
Append(" rr.rds_no ").
|
|
Append(" , rr.reactor ").
|
|
Append(" , rr.ps_no ").
|
|
Append(" , rr.load_lock_side ").
|
|
Append(" , rr.reactor_type ").
|
|
Append(" , rr.recipe_name ").
|
|
Append(" , rr.recipe_no ").
|
|
Append(" , rr.spec_type ").
|
|
Append(" , ( ").
|
|
Append(" select max(wm.zone) ").
|
|
Append(" from lsl2sql.dbo.wm_in_slot_no wm ").
|
|
Append(" where wm.wo_no = rr.wo_no ").
|
|
Append(" and wm.rds_no = rr.rds_no ").
|
|
Append(" and wm.in_cass_no = ").Append(workOrderCassette is null ? -1 : workOrderCassette.Value).Append(' ').
|
|
Append(" and wm.slot_no = ").Append(slot is null ? -1 : slot.Value).Append(' ').
|
|
Append(" ) zone ").
|
|
Append(" from lsl2sql.dbo.react_run rr ").
|
|
Append(" where rr.rds_no = ( ").
|
|
Append(" select max(qa.rds_no) ").
|
|
Append(" from lsl2sql.dbo.react_run qa ").
|
|
Append(" where qa.load_sig != '' ").
|
|
Append(" and qa.load_sig_dtm > '2022-07-01 00:00:00.000' ").
|
|
Append(" and qa.reactor = ").Append(reactor is null ? -1 : reactor.Value).Append(' ').
|
|
Append(" ) ").
|
|
Append(" for json path ");
|
|
return result.ToString();
|
|
}
|
|
|
|
private static (string, string, int?, string, int?, string) Get(string lsl2SQLConnectionString, string layer, string psn, int? reactorNumber, int? slotNumber, int? workOrderNumber, int? workOrderCassette, string zone)
|
|
{
|
|
int? rdsNumber;
|
|
string comment;
|
|
const int zero = 0;
|
|
const string hyphen = "-";
|
|
string json = GetRunJson(lsl2SQLConnectionString, rds: null, workOrderNumber, workOrderCassette, slotNumber, reactorNumber);
|
|
if (string.IsNullOrEmpty(json))
|
|
{
|
|
rdsNumber = null;
|
|
comment = hyphen;
|
|
psn = string.Empty;
|
|
zone = string.Empty;
|
|
}
|
|
else
|
|
{
|
|
Run[] runs;
|
|
try
|
|
{ runs = JsonSerializer.Deserialize<Run[]>(json); }
|
|
catch (Exception)
|
|
{ runs = Array.Empty<Run>(); }
|
|
if (!runs.Any())
|
|
{
|
|
rdsNumber = null;
|
|
comment = hyphen;
|
|
psn = string.Empty;
|
|
zone = string.Empty;
|
|
}
|
|
else
|
|
{
|
|
rdsNumber = runs[zero].RdsNo;
|
|
if (string.IsNullOrEmpty(psn))
|
|
psn = runs[zero].PSN;
|
|
if (string.IsNullOrEmpty(zone))
|
|
zone = runs[zero].Zone;
|
|
if (string.IsNullOrEmpty(layer))
|
|
layer = runs[zero].EpiLayer;
|
|
reactorNumber = runs[zero].Reactor;
|
|
string loadLockSide = runs[zero].LoadLockSide;
|
|
string loadLockSideFull = loadLockSide switch
|
|
{
|
|
"L" => "Left",
|
|
"R" => "Right",
|
|
_ => loadLockSide,
|
|
};
|
|
comment = $"{loadLockSideFull} - {runs[zero].ReactorType}";
|
|
}
|
|
}
|
|
return new(comment, layer, rdsNumber, psn, reactorNumber, zone);
|
|
}
|
|
|
|
private static (string, string, int?, string, int?, string) GetWithValidRDS(string lsl2SQLConnectionString, string layer, string psn, int? rdsNumber, int? reactorNumber, string zone)
|
|
{
|
|
string comment;
|
|
const int zero = 0;
|
|
const string hyphen = "-";
|
|
string json = GetRunJson(lsl2SQLConnectionString, rdsNumber, workOrderNumber: null, workOrderCassette: null, slot: null, reactor: null);
|
|
if (string.IsNullOrEmpty(json))
|
|
{
|
|
comment = hyphen;
|
|
zone = string.Empty;
|
|
}
|
|
else
|
|
{
|
|
Run[] runs;
|
|
try
|
|
{ runs = JsonSerializer.Deserialize<Run[]>(json); }
|
|
catch (Exception)
|
|
{ runs = Array.Empty<Run>(); }
|
|
if (!runs.Any())
|
|
{
|
|
comment = hyphen;
|
|
zone = string.Empty;
|
|
}
|
|
else
|
|
{
|
|
if (string.IsNullOrEmpty(psn))
|
|
psn = runs[zero].PSN;
|
|
if (string.IsNullOrEmpty(zone))
|
|
zone = runs[zero].Zone;
|
|
if (string.IsNullOrEmpty(layer))
|
|
layer = runs[zero].EpiLayer;
|
|
reactorNumber = runs[zero].Reactor;
|
|
string loadLockSide = runs[zero].LoadLockSide;
|
|
string loadLockSideFull = loadLockSide switch
|
|
{
|
|
"L" => "Left",
|
|
"R" => "Right",
|
|
_ => loadLockSide,
|
|
};
|
|
comment = $"{loadLockSideFull} - {runs[zero].ReactorType}";
|
|
}
|
|
}
|
|
return new(comment, layer, rdsNumber, psn, reactorNumber, zone);
|
|
}
|
|
|
|
} |