[{"data":1,"prerenderedAt":1102},["ShallowReactive",2],{"pf:\u002Fportfolio\u002Femvo-notification-system":3,"page-globals":247,"pf-related-src":302},{"id":4,"title":5,"body":6,"canonical":209,"client":210,"date":211,"description":197,"extension":212,"featured":213,"image":214,"meta":215,"navigation":216,"og":217,"path":221,"seo":222,"stack":233,"stem":235,"summary":236,"tags":237,"twitter":243,"type":245,"__hash__":246},"portfolio\u002Fportfolio\u002Femvo-notification-system.md","EMVO EVI — system powiadomień o zdarzeniach EMVS",{"type":7,"value":8,"toc":196},"minimark",[9,14,18,21,25,28,31,34,38,41,44,66,69,73,118,122,125,128,149,152,155,159,162,166,169,172,175,179],[10,11,13],"h2",{"id":12},"problem","Problem",[15,16,17],"p",{},"Europejska infrastruktura weryfikacji leków składa się z wielu współpracujących ze sobą systemów krajowych i centralnych. Wystąpienie awarii, planowanej przerwy technicznej lub znanego problemu wymagało szybkiego przekazania precyzyjnej informacji do zainteresowanych organizacji i użytkowników.",[15,19,20],{},"Dotychczasowa obsługa takich komunikatów była trudna do skalowania. Odbiorcy potrzebowali informacji dotyczących wyłącznie wybranych krajów, systemów i rodzajów zdarzeń, natomiast operatorzy musieli kontrolować cały cykl życia zgłoszenia — od jego utworzenia, przez aktualizacje, aż po zamknięcie incydentu.",[10,22,24],{"id":23},"kontekst","Kontekst",[15,26,27],{},"EVI — European Medicines Verification System Information — pełniło rolę centralnego źródła informacji o zdarzeniach operacyjnych w ramach EMVS.",[15,29,30],{},"System musiał obsługiwać zdarzenia dotyczące wielu krajów, środowisk i komponentów infrastruktury. Każda publikacja mogła zmieniać się w czasie: otrzymywać aktualizacje, nowe przewidywane terminy rozwiązania, informacje o przyczynach lub potwierdzenie przywrócenia prawidłowego działania.",[15,32,33],{},"Równocześnie użytkownicy powinni otrzymywać tylko te wiadomości, które odpowiadały ich subskrypcjom, bez konieczności ręcznego śledzenia wszystkich komunikatów.",[10,35,37],{"id":36},"podejście","Podejście",[15,39,40],{},"Zaprojektowałem i rozwijałem backend systemu jako Web API w ASP.NET Core. Aplikacja rozdzielała odpowiedzialność za zarządzanie zgłoszeniami, publikowanie zdarzeń, obsługę subskrypcji oraz wysyłkę powiadomień.",[15,42,43],{},"Zdarzenie było traktowane jako obiekt posiadający własny cykl życia. Operator mógł określić między innymi:",[45,46,47,51,54,57,60,63],"ul",{},[48,49,50],"li",{},"kraj lub grupę krajów,",[48,52,53],{},"system i środowisko, którego dotyczy zdarzenie,",[48,55,56],{},"rodzaj oraz poziom wpływu,",[48,58,59],{},"czas rozpoczęcia i zakończenia,",[48,61,62],{},"aktualny status,",[48,64,65],{},"opis problemu i kolejne aktualizacje.",[15,67,68],{},"Po opublikowaniu lub zmianie zgłoszenia system identyfikował właściwych subskrybentów i przygotowywał dla nich odpowiednie powiadomienia.",[10,70,72],{"id":71},"architektura","Architektura",[45,74,75,82,88,94,100,106,112],{},[48,76,77,81],{},[78,79,80],"strong",{},"ASP.NET Core Web API"," jako warstwa udostępniająca operacje związane ze zgłoszeniami, użytkownikami i subskrypcjami.",[48,83,84,87],{},[78,85,86],{},"Microsoft SQL Server"," jako źródło prawdy o zdarzeniach, odbiorcach, konfiguracji subskrypcji i historii wysyłki.",[48,89,90,93],{},[78,91,92],{},"SSO"," zapewniające kontrolowany dostęp do funkcji administracyjnych.",[48,95,96,99],{},[78,97,98],{},"Quartz.NET"," do realizacji zadań cyklicznych i przetwarzania operacji wykonywanych poza żądaniem HTTP.",[48,101,102,105],{},[78,103,104],{},"SendGrid"," jako usługa odpowiedzialna za dystrybucję wiadomości e-mail.",[48,107,108,111],{},[78,109,110],{},"Microsoft Azure"," jako środowisko uruchomieniowe aplikacji i jej infrastruktury.",[48,113,114,117],{},[78,115,116],{},"GitOps"," do kontrolowanego, powtarzalnego wdrażania zmian pomiędzy środowiskami.",[10,119,121],{"id":120},"implementacja","Implementacja",[15,123,124],{},"Jednym z kluczowych elementów był mechanizm dopasowywania zgłoszeń do subskrypcji. Użytkownik mógł określić zakres interesujących go informacji, a system oceniał każde nowe lub zaktualizowane zdarzenie na podstawie kilku kryteriów jednocześnie.",[15,126,127],{},"Proces obejmował:",[129,130,131,134,137,140,143,146],"ol",{},[48,132,133],{},"zapisanie lub aktualizację zgłoszenia,",[48,135,136],{},"określenie krajów, systemów i środowisk objętych zdarzeniem,",[48,138,139],{},"wyszukanie pasujących subskrypcji,",[48,141,142],{},"utworzenie powiadomień dla właściwych odbiorców,",[48,144,145],{},"przekazanie wiadomości do SendGrid,",[48,147,148],{},"zapisanie wyniku wysyłki i historii komunikacji.",[15,150,151],{},"Operacje wysyłania zostały oddzielone od podstawowej obsługi żądań API. Dzięki temu publikowanie zgłoszenia nie było uzależnione od czasu odpowiedzi zewnętrznego dostawcy poczty, a nieudane próby mogły być bezpiecznie ponawiane przez zadania Quartz.NET.",[15,153,154],{},"Istotnym elementem była również audytowalność. System przechowywał historię zmian zgłoszeń oraz informacje o wygenerowanych i wysłanych powiadomieniach, co ułatwiało analizowanie przebiegu komunikacji.",[10,156,158],{"id":157},"stack","Stack",[15,160,161],{},"ASP.NET Core Web API, Microsoft SQL Server, Microsoft Azure, Quartz.NET, SendGrid, SSO oraz proces wdrożeniowy oparty na GitOps.",[10,163,165],{"id":164},"rezultat","Rezultat",[15,167,168],{},"Powstał centralny system umożliwiający uporządkowane zarządzanie informacjami o zdarzeniach operacyjnych dotyczących wielu krajów i komponentów EMVS.",[15,170,171],{},"Operatorzy mogli prowadzić zgłoszenie przez cały jego cykl życia i publikować kolejne aktualizacje, natomiast odbiorcy automatycznie otrzymywali tylko te informacje, które odpowiadały zakresowi ich subskrypcji.",[15,173,174],{},"Rozdzielenie obsługi zgłoszeń od mechanizmu dystrybucji wiadomości zwiększyło odporność rozwiązania na błędy usług zewnętrznych i umożliwiło bezpieczne ponawianie niedostarczonych powiadomień.",[10,176,178],{"id":177},"lessons-learned","Lessons learned",[45,180,181,184,187,190,193],{},[48,182,183],{},"W systemach wielokrajowych filtrowanie odbiorców jest równie istotne jak samo publikowanie komunikatów.",[48,185,186],{},"Powiadomienie powinno być osobnym elementem domeny z własnym statusem, historią i możliwością ponowienia wysyłki.",[48,188,189],{},"Operacje zależne od zewnętrznych dostawców nie powinny blokować podstawowego procesu publikowania zgłoszenia.",[48,191,192],{},"Historia zmian i wysyłki jest niezbędna w systemach, w których liczy się możliwość odtworzenia pełnego przebiegu komunikacji.",[48,194,195],{},"GitOps ułatwia utrzymanie spójnej konfiguracji aplikacji działającej w kilku środowiskach.",{"title":197,"searchDepth":198,"depth":198,"links":199},"",3,[200,202,203,204,205,206,207,208],{"id":12,"depth":201,"text":13},2,{"id":23,"depth":201,"text":24},{"id":36,"depth":201,"text":37},{"id":71,"depth":201,"text":72},{"id":120,"depth":201,"text":121},{"id":157,"depth":201,"text":158},{"id":164,"depth":201,"text":165},{"id":177,"depth":201,"text":178},"https:\u002F\u002Fjetstreamaviation.pl\u002Fportfolio\u002Femvo-notification-system","Confidential","2022-05-01","md",false,"\u002Fimages\u002Fportfolio\u002Femvo-evi.webp",{},true,{"title":218,"description":219,"type":220},"EMVO EVI — powiadomienia o zdarzeniach EMVS","System obsługi zgłoszeń, subskrypcji i wielokrajowej dystrybucji powiadomień dla infrastruktury weryfikacji leków.","article","\u002Fportfolio\u002Femvo-notification-system",{"title":223,"description":224,"keywords":225,"robots":232},"EMVO EVI — system powiadomień EMVS | Case study","Case study systemu EMVO EVI do obsługi zdarzeń operacyjnych, subskrypcji i automatycznych powiadomień dla europejskiej infrastruktury weryfikacji leków.",[226,227,228,229,230,231,104,98],"EMVO","EVI","EMVS","ASP.NET Core","Azure","SQL Server","index,follow",[80,234,230,98,104,92,116],"MSSQL","portfolio\u002Femvo-notification-system","Wielokrajowy system obsługi zdarzeń operacyjnych i powiadomień dla europejskiej infrastruktury weryfikacji leków — od rejestracji zgłoszenia po dystrybucję informacji do subskrybentów.",[238,239,240,241,242],"backend","healthcare","notifications","cloud","devops",{"card":244},"summary_large_image","Software \u002F Healthcare","N28g6OVh1jB1P1osWC_T6GYRKJxpvVqGOMbQvwz4G-c",{"id":248,"title":249,"actions":250,"areas":250,"aviationList":250,"blogChrome":251,"body":266,"canonical":250,"cards":250,"contact":250,"cta":278,"description":285,"extension":212,"eyebrow":250,"hero":250,"heroImage":250,"list":250,"meta":286,"navigation":216,"og":250,"partners":250,"path":287,"perspective":250,"portfolioChrome":288,"process":250,"sectionHeader":250,"sections":250,"seo":298,"serviceLabels":250,"services":250,"stem":300,"twitter":250,"__hash__":301},"pages\u002Fpages\u002Fglobals.md","Globalne teksty UI",null,{"readingTimeSuffix":252,"tocTitle":253,"cta":254,"related":258,"tag":261},"min czytania","Spis treści",{"eyebrow":255,"title":256,"text":257},"Współpraca","Ten temat dotyczy Twojego systemu?","Jeśli mierzysz się z podobnym problemem w praktyce — napisz. Spojrzę na konkret i podpowiem kierunek.",{"eyebrow":259,"title":260},"Czytaj dalej","Powiązane wpisy",{"eyebrow":262,"bodyPrefix":263,"backLabel":264,"emptyText":265},"Tag","Wpisy powiązane z tematem","Wróć do wszystkich wpisów →","Brak wpisów dla tego tagu.",{"type":7,"value":267,"toc":276},[268],[15,269,270,271,275],{},"Ten dokument nie ma własnej strony (",[272,273,274],"code",{},"noindex","). Służy wyłącznie jako źródło współdzielonych tekstów interfejsu, które pojawiają się na wielu stronach naraz — dzięki temu można je edytować w jednym miejscu, bez wchodzenia w kod komponentów.",{"title":197,"searchDepth":198,"depth":198,"links":277},[],{"eyebrow":255,"title":279,"text":280,"primaryLabel":281,"primaryTo":282,"secondaryLabel":283,"secondaryTo":284},"Porozmawiajmy o projekcie","Opisz problem albo cel — odezwę się z konkretną propozycją następnego kroku: rozmowy, audytu, zakresu prac lub ofertą.","Napisz do mnie","\u002Fkontakt","Zobacz usługi IT","\u002Fuslugi-it","Współdzielone etykiety i domyślne treści renderowane na wielu stronach (CtaBlock, strony szczegółów blog\u002Fportfolio, tagi). Edytowane tutaj, bez zmian w kodzie.",{},"\u002Fpages\u002Fglobals",{"caseStudyEyebrow":289,"meta":290,"stackTitle":158,"areasTitle":294,"related":295},"Case study",{"type":291,"client":292,"date":293},"Typ","Klient","Data","Obszary",{"eyebrow":296,"title":297},"Więcej","Powiązane projekty",{"robots":299,"title":249,"description":285},"noindex,nofollow","pages\u002Fglobals","_s0SDmcqMxpaZUVz5yYm0sZItpGh6_JwnFv3nOsfADs",[303,733,843,964],{"id":304,"title":305,"body":306,"canonical":707,"client":210,"date":708,"description":197,"extension":212,"featured":216,"image":709,"meta":710,"navigation":216,"og":711,"path":713,"seo":714,"stack":723,"stem":727,"summary":728,"tags":729,"twitter":730,"type":731,"__hash__":732},"portfolio\u002Fportfolio\u002Fproxmox-provider-vps-platform.md","Proxmox Provider dla platformy VPS",{"type":7,"value":307,"toc":697},[308,310,313,315,318,320,340,360,362,388,390,393,667,670,672,675,677,680,682,693],[10,309,13],{"id":12},[15,311,312],{},"Platforma sprzedająca serwery VPS zdecydowała się wdrożyć nowe środowisko wirtualizacji - PVE (Proxmox Virtual Environment). W związku z tym należało zaprojektować usługę, która weźmnie całą odpowiedzialność za zarządzanie cyklem życia maszyn wirtualnych klientów oraz ich monitorowaniem. Wymagana była niezawodność, idempotencja oraz spójność przy jednocześnie rosnącej liczbie klientów.",[10,314,24],{"id":23},[15,316,317],{},"Warstwa wykonawcza opierała się na Proxmox VE. Panel klienta i panel administracyjny potrzebowały jednego, spójnego sposobu zlecania operacji na maszynach oraz wglądu w ich aktualny stan, a także możliwości monitorowania klastra — najlepiej w czasie rzeczywistym, bez ręcznego odświeżania. W celach administracyjnych wymagane było zarządzanie takimi funkcjami jak sieci wirtualne lub polityki migawek.",[10,319,37],{"id":36},[15,321,322,323,326,327,326,330,326,333,326,336,339],{},"Zaprojektowałem osobny moduł — „provider\" — odpowiedzialny wyłącznie za komunikację z Proxmox i tłumaczenie intencji biznesowych (",[272,324,325],{},"utwórz maszynę",", ",[272,328,329],{},"zmień plan",[272,331,332],{},"usuń",[272,334,335],{},"uruchom",[272,337,338],{},"wykonaj migawkę",") na konkretne operacje w infrastrukturze. Panel nigdy nie rozmawia z Proxmox bezpośrednio; zawsze przez jawny kontrakt providera.",[15,341,342,343,326,346,326,349,326,352,355,356,359],{},"Operacje długotrwałe potraktowałem jako zadania asynchroniczne z jawnym cyklem życia: ",[272,344,345],{},"zlecone",[272,347,348],{},"w toku",[272,350,351],{},"zakończone",[272,353,354],{},"błąd",". Za ich orkiestrację w obrębie modułu odpowiada mechanizm ",[272,357,358],{},"internal commands processor",". Dzięki temu interfejs mógł pokazywać rzeczywisty postęp, a nie tylko „proszę czekać\".",[10,361,72],{"id":71},[45,363,364,370,376,382],{},[48,365,366,369],{},[78,367,368],{},"Provider"," jako granica: cała wiedza o Proxmox zamknięta za jednym kontraktem.",[48,371,372,375],{},[78,373,374],{},"Kolejka zadań"," dla operacji długotrwałych, z idempotentnymi krokami.",[48,377,378,381],{},[78,379,380],{},"PostgreSQL"," jako źródło prawdy o stanie zamówień i maszyn.",[48,383,384,387],{},[78,385,386],{},"Redis"," do koordynacji zadań i cache'owania stanu odpytywanego często.",[10,389,121],{"id":120},[15,391,392],{},"Kluczowa była idempotentność. Ponowienie zadania po awarii nie mogło tworzyć drugiej maszyny ani pozostawiać systemu w stanie niespójnym. Każdy krok sprawdzał najpierw, czy jego efekt już nie istnieje.",[394,395,399],"pre",{"className":396,"code":397,"language":398,"meta":197,"style":197},"language-csharp shiki shiki-themes one-dark-pro one-dark-pro","public async Task\u003CResult> Handle(ProvisionVm task, CancellationToken ct)\n{\n    var existing = await proxmox.FindByTagAsync(task.CorrelationId, ct);\n    if (existing is not null)\n        return Result.AlreadyDone(existing.VmId); \u002F\u002F idempotentnie: brak duplikatu\n\n    var vm = await proxmox.CreateAsync(task.Spec, task.CorrelationId, ct);\n    await progress.PublishAsync(task.OrderId, VmState.Provisioned, ct);\n    return Result.Ok(vm.VmId);\n}\n","csharp",[272,400,401,451,456,499,520,550,556,598,637,661],{"__ignoreMap":197},[402,403,406,410,413,417,421,424,427,431,434,437,440,442,445,448],"span",{"class":404,"line":405},"line",1,[402,407,409],{"class":408},"sCyHM","public",[402,411,412],{"class":408}," async",[402,414,416],{"class":415},"sZsuq"," Task",[402,418,420],{"class":419},"shasQ","\u003C",[402,422,423],{"class":415},"Result",[402,425,426],{"class":419},"> ",[402,428,430],{"class":429},"sw6ri","Handle",[402,432,433],{"class":419},"(",[402,435,436],{"class":415},"ProvisionVm",[402,438,439],{"class":415}," task",[402,441,326],{"class":419},[402,443,444],{"class":415},"CancellationToken",[402,446,447],{"class":415}," ct",[402,449,450],{"class":419},")\n",[402,452,453],{"class":404,"line":201},[402,454,455],{"class":419},"{\n",[402,457,458,461,465,469,472,475,478,481,483,486,488,491,493,496],{"class":404,"line":198},[402,459,460],{"class":408},"    var",[402,462,464],{"class":463},"sh-OU"," existing",[402,466,468],{"class":467},"seqx8"," =",[402,470,471],{"class":419}," await ",[402,473,474],{"class":415},"proxmox",[402,476,477],{"class":419},".",[402,479,480],{"class":429},"FindByTagAsync",[402,482,433],{"class":419},[402,484,485],{"class":415},"task",[402,487,477],{"class":419},[402,489,490],{"class":415},"CorrelationId",[402,492,326],{"class":419},[402,494,495],{"class":463},"ct",[402,497,498],{"class":419},");\n",[402,500,502,505,508,511,514,518],{"class":404,"line":501},4,[402,503,504],{"class":408},"    if",[402,506,507],{"class":419}," (",[402,509,510],{"class":463},"existing",[402,512,513],{"class":419}," is not ",[402,515,517],{"class":516},"sDrLb","null",[402,519,450],{"class":419},[402,521,523,526,529,531,534,536,538,540,543,546],{"class":404,"line":522},5,[402,524,525],{"class":408},"        return",[402,527,528],{"class":415}," Result",[402,530,477],{"class":419},[402,532,533],{"class":429},"AlreadyDone",[402,535,433],{"class":419},[402,537,510],{"class":415},[402,539,477],{"class":419},[402,541,542],{"class":415},"VmId",[402,544,545],{"class":419},"); ",[402,547,549],{"class":548},"sIbxt","\u002F\u002F idempotentnie: brak duplikatu\n",[402,551,553],{"class":404,"line":552},6,[402,554,555],{"emptyLinePlaceholder":216},"\n",[402,557,559,561,564,566,568,570,572,575,577,579,581,584,586,588,590,592,594,596],{"class":404,"line":558},7,[402,560,460],{"class":408},[402,562,563],{"class":463}," vm",[402,565,468],{"class":467},[402,567,471],{"class":419},[402,569,474],{"class":415},[402,571,477],{"class":419},[402,573,574],{"class":429},"CreateAsync",[402,576,433],{"class":419},[402,578,485],{"class":415},[402,580,477],{"class":419},[402,582,583],{"class":415},"Spec",[402,585,326],{"class":419},[402,587,485],{"class":415},[402,589,477],{"class":419},[402,591,490],{"class":415},[402,593,326],{"class":419},[402,595,495],{"class":463},[402,597,498],{"class":419},[402,599,601,604,607,609,612,614,616,618,621,623,626,628,631,633,635],{"class":404,"line":600},8,[402,602,603],{"class":419},"    await ",[402,605,606],{"class":415},"progress",[402,608,477],{"class":419},[402,610,611],{"class":429},"PublishAsync",[402,613,433],{"class":419},[402,615,485],{"class":415},[402,617,477],{"class":419},[402,619,620],{"class":415},"OrderId",[402,622,326],{"class":419},[402,624,625],{"class":415},"VmState",[402,627,477],{"class":419},[402,629,630],{"class":415},"Provisioned",[402,632,326],{"class":419},[402,634,495],{"class":463},[402,636,498],{"class":419},[402,638,640,643,645,647,650,652,655,657,659],{"class":404,"line":639},9,[402,641,642],{"class":408},"    return",[402,644,528],{"class":415},[402,646,477],{"class":419},[402,648,649],{"class":429},"Ok",[402,651,433],{"class":419},[402,653,654],{"class":415},"vm",[402,656,477],{"class":419},[402,658,542],{"class":415},[402,660,498],{"class":419},[402,662,664],{"class":404,"line":663},10,[402,665,666],{"class":419},"}\n",[15,668,669],{},"Stan każdego zadania był publikowany na bieżąco, więc panel administracyjny i panel klienta widziały te same, aktualne informacje.",[10,671,158],{"id":157},[15,673,674],{},".NET, ASP.NET Core, PostgreSQL, Redis, Docker oraz API Proxmox VE.",[10,676,165],{"id":164},[15,678,679],{},"Provisioning maszyn stał się w pełni automatyczny i powtarzalny. Czas realizacji typowego zamówienia skrócił się z minut liczonych ręcznie do operacji wykonywanej bez udziału człowieka. Panele pokazują rzeczywisty stan maszyn na żywo, a operacje można bezpiecznie ponawiać po awarii.",[10,681,178],{"id":177},[45,683,684,687,690],{},[48,685,686],{},"Idempotentność nie jest dodatkiem — przy operacjach na infrastrukturze to warunek bezpieczeństwa.",[48,688,689],{},"Zamknięcie integracji za jednym kontraktem opłaciło się przy każdej późniejszej zmianie w Proxmox.",[48,691,692],{},"Komunikacja w czasie rzeczywistym najbardziej pomogła nie klientom, lecz administracji — skróciła czas reakcji na problemy.",[694,695,696],"style",{},"html pre.shiki code .sCyHM, html code.shiki .sCyHM{--shiki-default:#C678DD;--shiki-dark:#C678DD}html pre.shiki code .sZsuq, html code.shiki .sZsuq{--shiki-default:#E5C07B;--shiki-dark:#E5C07B}html pre.shiki code .shasQ, html code.shiki .shasQ{--shiki-default:#ABB2BF;--shiki-dark:#ABB2BF}html pre.shiki code .sw6ri, html code.shiki .sw6ri{--shiki-default:#61AFEF;--shiki-dark:#61AFEF}html pre.shiki code .sh-OU, html code.shiki .sh-OU{--shiki-default:#E06C75;--shiki-dark:#E06C75}html pre.shiki code .seqx8, html code.shiki .seqx8{--shiki-default:#56B6C2;--shiki-dark:#56B6C2}html pre.shiki code .sDrLb, html code.shiki .sDrLb{--shiki-default:#D19A66;--shiki-dark:#D19A66}html pre.shiki code .sIbxt, html code.shiki .sIbxt{--shiki-default:#7F848E;--shiki-default-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":197,"searchDepth":198,"depth":198,"links":698},[699,700,701,702,703,704,705,706],{"id":12,"depth":201,"text":13},{"id":23,"depth":201,"text":24},{"id":36,"depth":201,"text":37},{"id":71,"depth":201,"text":72},{"id":120,"depth":201,"text":121},{"id":157,"depth":201,"text":158},{"id":164,"depth":201,"text":165},{"id":177,"depth":201,"text":178},"https:\u002F\u002Fjetstreamaviation.pl\u002Fportfolio\u002Fproxmox-provider-vps-platform","2026-07-13","\u002Fimages\u002Fportfolio\u002Fproxmox-provider.webp",{},{"title":305,"description":712,"type":220},"Automatyzacja cyklu życia maszyn wirtualnych na Proxmox.","\u002Fportfolio\u002Fproxmox-provider-vps-platform",{"title":715,"description":716,"keywords":717,"robots":232},"Proxmox Provider — case study integracji platformy VPS","Case study systemu automatyzującego provisioning maszyn wirtualnych na Proxmox: architektura, kolejki zadań i komunikacja w czasie rzeczywistym.",[718,719,720,721,722,380],"Proxmox",".NET","VPS","infrastructure","Docker",[724,718,722,380,386,725,726],".NET Core","YARP","Grafana\u002FPrometheus\u002FLoki","portfolio\u002Fproxmox-provider-vps-platform","System integrujący panel platformy VPS z infrastrukturą Proxmox — automatyczne provisionowanie, cykl życia maszyn i komunikacja w czasie rzeczywistym.",[721,238,242],{"card":244},"Software \u002F Infrastructure","XoEMuTt59FnMAoRcEmjDmSb_DQZQwFyH1Ag8vW7t7Z0",{"id":734,"title":735,"body":736,"canonical":816,"client":210,"date":817,"description":197,"extension":212,"featured":213,"image":818,"meta":819,"navigation":216,"og":820,"path":822,"seo":823,"stack":832,"stem":834,"summary":835,"tags":836,"twitter":840,"type":841,"__hash__":842},"portfolio\u002Fportfolio\u002Fcomarch-erp-optima-ksef-integration.md","Automatyczna integracja Comarch ERP Optima z KSeF",{"type":7,"value":737,"toc":808},[738,740,743,746,748,751,754,756,782,784,787,789,792,795,797],[10,739,13],{"id":12},[15,741,742],{},"Proces wysyłania faktur z Comarch ERP Optima do KSeF wymagał automatyzacji bez pomijania mechanizmów biznesowych i ewidencyjnych systemu ERP.",[15,744,745],{},"Rozwiązanie musiało poprawnie przetwarzać dokumenty, przekazywać je do KSeF, odbierać Urzędowe Poświadczenie Odbioru oraz zapisywać rezultat w taki sposób, aby cały proces pozostawał widoczny dla użytkowników Optimy.",[10,747,37],{"id":36},[15,749,750],{},"Zaprojektowałem i wdrożyłem usługę Windows działającą w tle. Co 10 sekund wyszukuje ona w bazie Microsoft SQL Server faktury oczekujące na przetworzenie.",[15,752,753],{},"Dostęp do danych został zaimplementowany przy użyciu Dappera, natomiast właściwa wysyłka dokumentów odbywa się przez bibliotekę DLL API Comarch ERP Optima. Dzięki temu faktury są obsługiwane zgodnie z mechanizmami Optimy, a informacje o wysyłce i odebranym UPO pozostają dostępne bezpośrednio w systemie ERP.",[10,755,72],{"id":71},[45,757,758,764,770,776],{},[48,759,760,763],{},[78,761,762],{},"Windows Service"," realizujący cykliczne przetwarzanie dokumentów.",[48,765,766,769],{},[78,767,768],{},"Dapper i SQL Server"," do szybkiego wyszukiwania faktur oczekujących na wysłanie.",[48,771,772,775],{},[78,773,774],{},"Comarch ERP Optima API"," do przetwarzania dokumentów i komunikacji z KSeF.",[48,777,778,781],{},[78,779,780],{},"Obsługa UPO"," wraz z zapisaniem wyniku procesu w Optimie.",[10,783,158],{"id":157},[15,785,786],{},".NET Framework 4.7.2, Windows Service, Microsoft SQL Server, Dapper oraz DLL API Comarch ERP Optima.",[10,788,165],{"id":164},[15,790,791],{},"Proces wysyłania faktur do KSeF został całkowicie zautomatyzowany. Dokumenty są regularnie wykrywane, poprawnie przetwarzane przez API Optimy i wysyłane do KSeF, a następnie uzupełniane o odebrane UPO.",[15,793,794],{},"Użytkownicy mogą kontrolować status faktur bezpośrednio w Comarch ERP Optima, bez korzystania z dodatkowego panelu lub ręcznego uruchamiania wysyłki.",[10,796,178],{"id":177},[45,798,799,802,805],{},[48,800,801],{},"Integracja przez oficjalne API systemu ERP pozwala zachować spójność jego procesów biznesowych.",[48,803,804],{},"Usługa działająca w tle dobrze sprawdza się przy regularnym przetwarzaniu niewielkich kolejek dokumentów.",[48,806,807],{},"Rezultat integracji powinien być widoczny w systemie, z którego użytkownicy korzystają na co dzień.",{"title":197,"searchDepth":198,"depth":198,"links":809},[810,811,812,813,814,815],{"id":12,"depth":201,"text":13},{"id":36,"depth":201,"text":37},{"id":71,"depth":201,"text":72},{"id":157,"depth":201,"text":158},{"id":164,"depth":201,"text":165},{"id":177,"depth":201,"text":178},"https:\u002F\u002Fjetstreamaviation.pl\u002Fportfolio\u002Fcomarch-optima-ksef-integration","2026-04-01","\u002Fimages\u002Fportfolio\u002Foptima-ksef.webp",{},{"title":735,"description":821,"type":220},"Automatyczna wysyłka faktur do KSeF i odbieranie UPO bezpośrednio w środowisku Comarch ERP Optima.","\u002Fportfolio\u002Fcomarch-erp-optima-ksef-integration",{"title":824,"description":825,"keywords":826,"robots":232},"Integracja Comarch ERP Optima z KSeF | Case study","Usługa Windows automatyzująca wysyłkę faktur z Comarch ERP Optima do KSeF oraz pobieranie UPO.",[827,828,829,231,830,831],"Comarch ERP Optima","KSeF",".NET Framework","Dapper","integracja ERP",[833,774,86,830,762,828],".NET Framework 4.7.2","portfolio\u002Fcomarch-erp-optima-ksef-integration","Usługa Windows automatyzująca przetwarzanie faktur z Comarch ERP Optima, wysyłkę do KSeF oraz odbieranie Urzędowego Poświadczenia Odbioru.",[238,837,838,839],"integration","erp","automation",{"card":244},"Software \u002F ERP Integration","pLQ3RV8OBlrJ-g4BY6Lv1mG64n_dKjWPXIPhvt0PHy0",{"id":844,"title":845,"body":846,"canonical":934,"client":210,"date":935,"description":197,"extension":212,"featured":216,"image":936,"meta":937,"navigation":216,"og":938,"path":941,"seo":942,"stack":951,"stem":956,"summary":957,"tags":958,"twitter":961,"type":962,"__hash__":963},"portfolio\u002Fportfolio\u002Faviation-training-systems.md","Cyfryzacja procesów w organizacji szkolenia lotniczego",{"type":7,"value":847,"toc":924},[848,850,853,855,858,860,863,866,870,896,898,901,903,906,908,911,913],[10,849,13],{"id":12},[15,851,852],{},"Organizacja szkoleniowa prowadziła ewidencję szkoleń i dokumentację operacyjną w sposób rozproszony: część w arkuszach, część w plikach, część w pamięci konkretnych osób. Przy audytach i codziennej pracy oznaczało to ryzyko niespójności oraz duży nakład czasu na odtworzenie stanu faktycznego.",[10,854,24],{"id":23},[15,856,857],{},"W lotnictwie dokumentacja nie jest formalnością — to element bezpieczeństwa i zgodności. Rozwiązanie musiało więc nie tylko usprawnić pracę, ale przede wszystkim być zgodne z wymaganiami i odporne na błędy wynikające z rutyny.",[10,859,37],{"id":36},[15,861,862],{},"Zamiast od razu wdrażać narzędzie, zacząłem od procesów. Zmapowałem realny przebieg szkolenia i obiegu dokumentów: gdzie powstają dane, kto za nie odpowiada i w którym momencie stają się wiążące. Dopiero na tej podstawie projektowałem uporządkowanie.",[15,864,865],{},"Przeniosłem podejście znane z operacji lotniczych: jasne role, checklisty dla kroków krytycznych oraz jeden, wiarygodny stan zamiast wielu równoległych wersji prawdy.",[10,867,869],{"id":868},"architektura-procesu","Architektura procesu",[45,871,872,878,884,890],{},[48,873,874,877],{},[78,875,876],{},"Jedno źródło prawdy"," dla ewidencji szkoleń i statusów.",[48,879,880,883],{},[78,881,882],{},"Jawne role i odpowiedzialności"," na każdym etapie obiegu.",[48,885,886,889],{},[78,887,888],{},"Checklisty"," dla kroków, w których błąd jest kosztowny.",[48,891,892,895],{},[78,893,894],{},"Ślad zmian"," — kto, co i kiedy zatwierdził.",[10,897,121],{"id":120},[15,899,900],{},"Wdrożenie było etapowe. Najpierw uporządkowaliśmy dane i ustaliliśmy pojedyncze źródło prawdy, potem sformalizowaliśmy procedury, a na końcu — dopiero na końcu — dobraliśmy narzędzia wspierające. Taka kolejność ograniczyła ryzyko, że narzędzie wymusi zły proces.",[10,902,158],{"id":157},[15,904,905],{},"Praca koncepcyjna i procesowa: projektowanie procesów, dokumentacja, procedury oraz podejście do compliance. Warstwa narzędziowa była wtórna wobec uporządkowanego procesu.",[10,907,165],{"id":164},[15,909,910],{},"Dokumentacja i ewidencja stały się spójne i możliwe do szybkiego odtworzenia. Przygotowanie do audytu przestało być awaryjnym zrywem, a codzienna praca zyskała jasne punkty odpowiedzialności. Organizacja zyskała stan, któremu można zaufać.",[10,912,178],{"id":177},[45,914,915,918,921],{},[48,916,917],{},"Narzędzie nie naprawi złego procesu — najpierw proces, potem software.",[48,919,920],{},"Checklisty sprawdzają się w dokumentacji tak samo dobrze jak w kokpicie.",[48,922,923],{},"Jedno źródło prawdy to najtańsza inwestycja w spokój podczas audytu.",{"title":197,"searchDepth":198,"depth":198,"links":925},[926,927,928,929,930,931,932,933],{"id":12,"depth":201,"text":13},{"id":23,"depth":201,"text":24},{"id":36,"depth":201,"text":37},{"id":868,"depth":201,"text":869},{"id":120,"depth":201,"text":121},{"id":157,"depth":201,"text":158},{"id":164,"depth":201,"text":165},{"id":177,"depth":201,"text":178},"https:\u002F\u002Fjetstreamaviation.pl\u002Fportfolio\u002Faviation-training-systems","2024-03-2","\u002Fimages\u002Fportfolio\u002Faviation-training-systems.jpg",{},{"title":845,"description":939,"image":940,"type":220},"Zgodność, powtarzalność i użyteczność operacyjna.","\u002Fimages\u002Fog\u002Faviation-training-systems.jpg","\u002Fportfolio\u002Faviation-training-systems",{"title":943,"description":944,"keywords":945,"robots":232},"Cyfryzacja procesów w ATO — case study","Case study porządkowania dokumentacji i procedur w organizacji szkolenia lotniczego: zgodność, powtarzalność i użyteczność operacyjna.",[946,947,948,949,950],"organizacja szkoleniowa","ATO","procedury lotnicze","compliance","dokumentacja",[952,953,954,955],"Aviation","Compliance","Process design","Documentation","portfolio\u002Faviation-training-systems","Uporządkowanie dokumentacji, ewidencji szkoleń i procedur w organizacji szkoleniowej — z naciskiem na zgodność, powtarzalność i realną użyteczność.",[959,949,960],"aviation","process",{"card":244},"Consulting \u002F Aviation IT","ivhmSUqIp6bgyU8lFp7Vuqv7lri6gNLjstlQqG0Tpcc",{"id":4,"title":5,"body":965,"canonical":209,"client":210,"date":211,"description":197,"extension":212,"featured":213,"image":214,"meta":1095,"navigation":216,"og":1096,"path":221,"seo":1097,"stack":1099,"stem":235,"summary":236,"tags":1100,"twitter":1101,"type":245,"__hash__":246},{"type":7,"value":966,"toc":1085},[967,969,971,973,975,977,979,981,983,985,987,1001,1003,1005,1035,1037,1039,1041,1055,1057,1059,1061,1063,1065,1067,1069,1071,1073],[10,968,13],{"id":12},[15,970,17],{},[15,972,20],{},[10,974,24],{"id":23},[15,976,27],{},[15,978,30],{},[15,980,33],{},[10,982,37],{"id":36},[15,984,40],{},[15,986,43],{},[45,988,989,991,993,995,997,999],{},[48,990,50],{},[48,992,53],{},[48,994,56],{},[48,996,59],{},[48,998,62],{},[48,1000,65],{},[15,1002,68],{},[10,1004,72],{"id":71},[45,1006,1007,1011,1015,1019,1023,1027,1031],{},[48,1008,1009,81],{},[78,1010,80],{},[48,1012,1013,87],{},[78,1014,86],{},[48,1016,1017,93],{},[78,1018,92],{},[48,1020,1021,99],{},[78,1022,98],{},[48,1024,1025,105],{},[78,1026,104],{},[48,1028,1029,111],{},[78,1030,110],{},[48,1032,1033,117],{},[78,1034,116],{},[10,1036,121],{"id":120},[15,1038,124],{},[15,1040,127],{},[129,1042,1043,1045,1047,1049,1051,1053],{},[48,1044,133],{},[48,1046,136],{},[48,1048,139],{},[48,1050,142],{},[48,1052,145],{},[48,1054,148],{},[15,1056,151],{},[15,1058,154],{},[10,1060,158],{"id":157},[15,1062,161],{},[10,1064,165],{"id":164},[15,1066,168],{},[15,1068,171],{},[15,1070,174],{},[10,1072,178],{"id":177},[45,1074,1075,1077,1079,1081,1083],{},[48,1076,183],{},[48,1078,186],{},[48,1080,189],{},[48,1082,192],{},[48,1084,195],{},{"title":197,"searchDepth":198,"depth":198,"links":1086},[1087,1088,1089,1090,1091,1092,1093,1094],{"id":12,"depth":201,"text":13},{"id":23,"depth":201,"text":24},{"id":36,"depth":201,"text":37},{"id":71,"depth":201,"text":72},{"id":120,"depth":201,"text":121},{"id":157,"depth":201,"text":158},{"id":164,"depth":201,"text":165},{"id":177,"depth":201,"text":178},{},{"title":218,"description":219,"type":220},{"title":223,"description":224,"keywords":1098,"robots":232},[226,227,228,229,230,231,104,98],[80,234,230,98,104,92,116],[238,239,240,241,242],{"card":244},1784052984838]