Budowanie instrukcji warunkowych z pliku konfiguracyjnego/tekstowego

0

Wprowadzając do kontekstu zagadnienia,

Artykuł produkcyjny w ERP ma zdefiniowane m.in listę materiałową (z czego się składa) oraz marszrutę technologiczną (opisującą proces produkcyjny tego artykułu)
Marszruta technologiczna, składa się z operacji technologicznych ułożonych we właściwiej kolejności odwzorowującej proces produkcyjny danego wyrobu, różne wyroby różnie się produkuję dlatego też mają różne marszruty.
Każda operacja technologiczna ma parę parametrów m.in. kod stanowiska na jakim jest wykonywana, czasy normatywne, dział produkcyjny na jakim jest to stanowisko, zasoby itp.

Narzędzie poniżej służy do hurtowej zmiany jednego z parametrów operacji technologicznej (Tn) we wszystkich marszrutach zdefiniowanych w ERP, w zależności od tego jaka operacja technologiczna i z jakimi parametrami jest po danej operacji w tej samej marszrucie.

Generalnie narzędzie miało uporządkować coś raz i dlatego kod do niego wygląda tak jak niżej, czyli wszystko było w kodzie. Teraz obserwuje zjawisko ze coraz częściej są wprowadzane kolejne reguły globalne i coraz częściej musze to edytować w kodzie, chciałbym to zmienić.
Najpierw jak to wygląda teraz.

foreach (var marszruta in listaMarszrut)
{
    for (int i = 0; i < marszruta.ListaOperacji.Count - 1; i++)
    {
        // (1) czas Tn pomiedzy roznymi działami.
        if (marszruta.ListaOperacji[i].DzialOperacji != marszruta.ListaOperacji[i + 1].DzialOperacji)
        {
            marszruta.ListaOperacji[i].CzasNakl_h = "7.5";
            marszruta.ListaOperacji[i].CzyZmieniona = true;
        }

        // (2) czas Tn pomiedzy operacjami na tych samych działach
        if ((marszruta.ListaOperacji[i].DzialOperacji == marszruta.ListaOperacji[i + 1].DzialOperacji))
        {
            marszruta.ListaOperacji[i].CzasNakl_h = "0";
            marszruta.ListaOperacji[i].CzyZmieniona = true;
        }

        // (3) czas Tn dla operacji jezeli najpierw jest magazyn a potem inny dzial niz magazyn
        if (marszruta.ListaOperacji[i].DzialOperacji == DzialEnum.Magazyn && marszruta.ListaOperacji[i + 1].DzialOperacji != DzialEnum.Magazyn)
        {
            marszruta.ListaOperacji[i].CzasNakl_h = "16.01";
            marszruta.ListaOperacji[i].CzyZmieniona = true;
        }

        // (4) czas Tn dla operacji magazynu jezeli po niej jest kolejna operacja magazynu
        if (marszruta.ListaOperacji[i].DzialOperacji == DzialEnum.Magazyn && marszruta.ListaOperacji[i + 1].DzialOperacji == DzialEnum.Magazyn)
        {
            marszruta.ListaOperacji[i].CzasNakl_h = "0.01";
            marszruta.ListaOperacji[i].CzyZmieniona = true;
        }

        // (5) czas Tn dla operacji Montaz0101 jezeli następną operacją jest operacja na dziale montazu pierwszego
        if (marszruta.ListaOperacji[i].KodStanowiska == "Montaz0101" && marszruta.ListaOperacji[i + 1].DzialOperacji == DzialEnum.MontazPierwszy)
        {
            marszruta.ListaOperacji[i].CzasNakl_h = "2.25";
            marszruta.ListaOperacji[i].CzyZmieniona = true;
        }
        // (6) czas Tn dla operacji Spawanie jezeli po niej jest operacja na Slusarni lub Warsztacie
        if (marszruta.ListaOperacji[i].KodStanowiska == "Spawanie" && (marszruta.ListaOperacji[i + 1].DzialOperacji == DzialEnum.Warsztat || marszruta.ListaOperacji[i + 1].DzialOperacji == DzialEnum.Slusarnia))
        {
            marszruta.ListaOperacji[i].CzasNakl_h = "23.01";
            marszruta.ListaOperacji[i].CzyZmieniona = true;
        }
//(..duzo duzo wiecej regul...)
    }

}

W tej pętli jest lista około 30 reguł od najbardziej ogólnej do najbardziej szczegółowych, chciałbym to zoptymalizować i uwolnić się od grzebania w kodzie, chciałbym to zrobić w dwóch etapach:

1 Etap - na już aby możliwie najszybciej zleceniodawca mógł sam już to robić, chciałbym te warunki z ifów pobierać z pliku tekstowego który w odpowiednim formacie (prostym dla nie programisty) mógłby tworzyć pracownik, importować te reguły do programu i program miałby to przerobić na instrukcje warunkowe w ifach.

2 Etap - docelowo ale może trochę poczekać, chciałbym definiowanie tych reguł przenieść do interfejsu użytkownika, tak aby budował sobie reguły wybierając kolejne opcje z ComboBoxów , Check'oów, RadioButton'ów itp.

Coś na kształt budowy kolejnych filtrów w Excelu.
Przechwytywanie.PNG

To co chodzi mi po głowie:

  1. Zbudować szkielety możliwych instrukcji warunkowych z wymiennymi parametrami w formie metod, i na bazie pobieranych danych z pliku konfiguracyjnego wybierać właściwą metodę, przekazywać pobrane parametry do niej i 'odkładać' ją do delegata - a po pobraniu wszystkich uruchomić wszystko z delegata - z tym że tu mam problem bo zależy mi aby uruchamiać te metody we właściwej kolejności od reguł najbardziej ogólnych do najbardziej szczegółowych a tego delegat nie da.

  2. Utworzyć klasę 'Regula' coś na kształt tego niżej, i dane z pliku konfiguracyjnego przekształcać na takie obiekty 'Regul', odkładać je w jakimś słowniku gdzie kluczem byłoby coś (nie wiem jak odwzorować szkielet instrukcji warunkowej na klucz słownika) a wartością - wartość Tn do ustawienia dla danej reguły - ale w tym przypadku o ile reguły jak z kodu wyżej (1-5) jeszcze dałbym rade to dla przypadku (6) gdzie jest jakby zagnieżdżenie nie mam pomysłu jak to zrobić aby było i wygodne i czytelne i skalowalne

public class Regula {
    public string StanowiskoOperacji { get; set; }            
    public string DzialOperacji { get; set; }
    public string StanowiskoOperacjiNastepnej { get; set; }
    public string DzialOperacjiNastepnej { get; set; }
    public string OperatorPorownania { get; set; }
    public string CzasTn { get; set; }
}

Prośba o nakierowanie jak rozwiązać ten problem, rozwiązanie dla tego przypadku będę na pewno wykorzystywał w innych narzędziach dlatego zależy mi na tym żeby to rozwiązanie było 'takie jak to się powinno robić' ;).

0

Są takie sytuacje w życiu (inzyniera oprogramowania) że plik konfiguracyjny staje się językiem programowania. Takowym stał się wsad edytora vi czy emacs (tu troche inaczej), pliki konfiguracyjne gier / edytorów / cadów

Wiele z nich ma płynną kompatybilność, w prostych przypadkach plik wygląda jak seria postawień "nazwa.parametru" = "wartość", ale pod maską ma znacznie większe możliwosci, od warunków poczynajac (seria "podstawień" się włacza przy jakiejś opcji)

Lua, Groovy (w ekosystemie JVM), własny język zrealizowany np w Antlr

Tak w ogóle jestem zdziwiony, co to za tzw "ERP" co nie ma języka programowania dla zaawansowanego wdrożeniowca ? Czy to nie "program do faktur" przez marketingowca tak nazwany.

2

Co do kolejności - mógłbyś do reguły dodać informacje nt. kolejności np. nr. 1, 2, 3, 4, etc.

Cały koncept tego co chcesz nazywa się chyba Rule Engine lub może Query Engine

znalazłem coś takiego https://github.com/microsoft/RulesEngine ale nie analizowałem głębiej, bingnij sobie :D

[
  {
    "WorkflowName": "Discount",
    "Rules": [
      {
        "RuleName": "GiveDiscount10",
        "SuccessEvent": "10",
        "ErrorMessage": "One or more adjust rules failed.",
        "ErrorType": "Error",
        "RuleExpressionType": "LambdaExpression",
        "Expression": "input1.country == \"india\" AND input1.loyaltyFactor <= 2 AND input1.totalPurchasesToDate >= 5000"
      },
      {
        "RuleName": "GiveDiscount20",
        "SuccessEvent": "20",
        "ErrorMessage": "One or more adjust rules failed.",
        "ErrorType": "Error",
        "RuleExpressionType": "LambdaExpression",
        "Expression": "input1.country == \"india\" AND input1.loyaltyFactor >= 3 AND input1.totalPurchasesToDate >= 10000"
      }
    ]
  }
]

A w takiej bardzo, bardzo prostej wersji to myślałem o czymś takim (bez wsparcia dla innych operatorów niż ==, ale to sobie możesz dopisać)

public class User
{
    public string Name { get; set; }

    public int Age { get; set; }

    public List<string> JakiesWartosci { get; set; } = new List<string>();
}

public class Rule
{
    public string? Name { get; set; }

    public int? Age { get; set; }

    public string Wartosc { get; set; }
}
public static void RuleEngine(List<User> users, List<Rule> rules)
{
    foreach (var rule in rules)
    {
        IEnumerable<User> tmp = users;

        if (rule.Name != null)
        {
            tmp = tmp.Where(x => x.Name == rule.Name);
        }

        if (rule.Age != null)
        {
            tmp = tmp.Where(x => x.Age == rule.Age);
        }

        var selectedUsers = tmp.ToList();

        foreach (var user in selectedUsers)
        {
            user.JakiesWartosci.Add(rule.Wartosc);
        }
    }
}
var users = new List<User>
{
    new User { Name = "Tom", Age = 123 },
    new User { Name = "John", Age = 55 },
    new User { Name = "John", Age = 77 },
};

var rules = new List<Rule>
{
    new Rule
    {
        Name = "John",
        Wartosc = "Wartosc#1"
    },
    new Rule
    {
        Name = "John",
        Age = 55,
        Wartosc = "Wartosc#2"
    },
    new Rule
    {
        Name = "Asdf",
        Wartosc = "Wartosc#3"
    },
    new Rule
    {
        Age = 123,
        Wartosc = "Wartosc#4"
    }
};

RuleEngine(users, rules);

foreach (var user in users)
{
    Console.WriteLine($"Name: {user.Name} Age: {user.Age}");
    Console.WriteLine(string.Join(",", user.JakiesWartosci));
    Console.WriteLine();
}
1

W jednym z projektów miałem podobny problem. Projekt głównie polegał na wyliczaniu danych z formuł, dodatkowo użytkownik chciał mieć możliwość edytowania tych formuł podczas pracy aplikacji (bez ponownego uruchomienia). Początkowo było to robione za pomocą biblioteki która potrafiła wyliczać takie formuły (trochę jak w excelu), ale niestety co chwilę okazywało się że takie formuły nie są w stanie czegoś wyliczyć i trzeba się nieźle nagimnastykować żeby to zrobić i w dodatku są powolne.

Finalnie skończyło się na tym że formuły były trzymane w postaci kodu c# który był kompilowany przez aplikację a assembly były dynamicznie ładowane, przeszukiwane pod kątem klas które implementują dany interface i wywoływane podczas obliczeń. Gdy użytkownik zmienił formułę to była ona usuwana i ładowana ponownie. Dla użytkowników było to o wiele łatwiejsze niż pisanie formuł jak w excelu, jak ktoś potrafi napisać regułę w excelu to potrafi też napisać if-a :). Dodatkowo byliśmy w stanie dostarczyć użytkownikom niektóre metody/serwisy z aplikacji.

Przyjmując takie rozwiązanie w pliku mogło by być coś takiego:

class RozneDzialyInstrukcja : Instrukcja
{
    public override void Handle(Operacja operacja, Operacja nastepnaOperacja)
    {
      if(operacja.DzialOperacji != nastepnaOperacja.DzialOperacji)
      {
        operacja.CzasNakl_h = "7.5";
        operacja.CzyZmieniona = true;
      }
    }
}

W naszym przypadku coś takiego sprawdziło się naprawdę nieźle - zwłaszcza że dostęp do edycji formuł miały osoby które były w miarę ogarnięte jeżeli chodzi IT, excela itd. Takie rozwiązanie jest czasochłonne ale daje duże możliwości. Ewentualnie mógłbyś jeszcze popatrzeć na wywoływanie IronPython z c#.

0
Wilktar napisał(a):

Ewentualnie mógłbyś jeszcze popatrzeć na wywoływanie IronPython z c#.

Python jest ogromnie tłustym językiem, i w zasadzie utracił rolę języka wbudowanego
Jak bym zamiast tego proponował Lua

1 użytkowników online, w tym zalogowanych: 0, gości: 1