[{"data":1,"prerenderedAt":1266},["ShallowReactive",2],{"pf:\u002Fportfolio\u002Fproxmox-provider-vps-platform":3,"page-globals":467,"pf-related-src":521},{"id":4,"title":5,"body":6,"canonical":434,"client":435,"date":436,"description":116,"extension":437,"featured":275,"image":438,"meta":439,"navigation":275,"og":440,"path":443,"seo":444,"stack":454,"stem":458,"summary":459,"tags":460,"twitter":463,"type":465,"__hash__":466},"portfolio\u002Fportfolio\u002Fproxmox-provider-vps-platform.md","Proxmox Provider dla platformy VPS",{"type":7,"value":8,"toc":424},"minimark",[9,14,18,22,25,29,50,70,74,103,107,110,388,391,395,398,402,405,409,420],[10,11,13],"h2",{"id":12},"problem","Problem",[15,16,17],"p",{},"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,19,21],{"id":20},"kontekst","Kontekst",[15,23,24],{},"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,26,28],{"id":27},"podejście","Podejście",[15,30,31,32,36,37,36,40,36,43,36,46,49],{},"Zaprojektowałem osobny moduł — „provider\" — odpowiedzialny wyłącznie za komunikację z Proxmox i tłumaczenie intencji biznesowych (",[33,34,35],"code",{},"utwórz maszynę",", ",[33,38,39],{},"zmień plan",[33,41,42],{},"usuń",[33,44,45],{},"uruchom",[33,47,48],{},"wykonaj migawkę",") na konkretne operacje w infrastrukturze. Panel nigdy nie rozmawia z Proxmox bezpośrednio; zawsze przez jawny kontrakt providera.",[15,51,52,53,36,56,36,59,36,62,65,66,69],{},"Operacje długotrwałe potraktowałem jako zadania asynchroniczne z jawnym cyklem życia: ",[33,54,55],{},"zlecone",[33,57,58],{},"w toku",[33,60,61],{},"zakończone",[33,63,64],{},"błąd",". Za ich orkiestrację w obrębie modułu odpowiada mechanizm ",[33,67,68],{},"internal commands processor",". Dzięki temu interfejs mógł pokazywać rzeczywisty postęp, a nie tylko „proszę czekać\".",[10,71,73],{"id":72},"architektura","Architektura",[75,76,77,85,91,97],"ul",{},[78,79,80,84],"li",{},[81,82,83],"strong",{},"Provider"," jako granica: cała wiedza o Proxmox zamknięta za jednym kontraktem.",[78,86,87,90],{},[81,88,89],{},"Kolejka zadań"," dla operacji długotrwałych, z idempotentnymi krokami.",[78,92,93,96],{},[81,94,95],{},"PostgreSQL"," jako źródło prawdy o stanie zamówień i maszyn.",[78,98,99,102],{},[81,100,101],{},"Redis"," do koordynacji zadań i cache'owania stanu odpytywanego często.",[10,104,106],{"id":105},"implementacja","Implementacja",[15,108,109],{},"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.",[111,112,117],"pre",{"className":113,"code":114,"language":115,"meta":116,"style":116},"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","",[33,118,119,169,175,219,240,270,277,319,358,382],{"__ignoreMap":116},[120,121,124,128,131,135,139,142,145,149,152,155,158,160,163,166],"span",{"class":122,"line":123},"line",1,[120,125,127],{"class":126},"sCyHM","public",[120,129,130],{"class":126}," async",[120,132,134],{"class":133},"sZsuq"," Task",[120,136,138],{"class":137},"shasQ","\u003C",[120,140,141],{"class":133},"Result",[120,143,144],{"class":137},"> ",[120,146,148],{"class":147},"sw6ri","Handle",[120,150,151],{"class":137},"(",[120,153,154],{"class":133},"ProvisionVm",[120,156,157],{"class":133}," task",[120,159,36],{"class":137},[120,161,162],{"class":133},"CancellationToken",[120,164,165],{"class":133}," ct",[120,167,168],{"class":137},")\n",[120,170,172],{"class":122,"line":171},2,[120,173,174],{"class":137},"{\n",[120,176,178,181,185,189,192,195,198,201,203,206,208,211,213,216],{"class":122,"line":177},3,[120,179,180],{"class":126},"    var",[120,182,184],{"class":183},"sh-OU"," existing",[120,186,188],{"class":187},"seqx8"," =",[120,190,191],{"class":137}," await ",[120,193,194],{"class":133},"proxmox",[120,196,197],{"class":137},".",[120,199,200],{"class":147},"FindByTagAsync",[120,202,151],{"class":137},[120,204,205],{"class":133},"task",[120,207,197],{"class":137},[120,209,210],{"class":133},"CorrelationId",[120,212,36],{"class":137},[120,214,215],{"class":183},"ct",[120,217,218],{"class":137},");\n",[120,220,222,225,228,231,234,238],{"class":122,"line":221},4,[120,223,224],{"class":126},"    if",[120,226,227],{"class":137}," (",[120,229,230],{"class":183},"existing",[120,232,233],{"class":137}," is not ",[120,235,237],{"class":236},"sDrLb","null",[120,239,168],{"class":137},[120,241,243,246,249,251,254,256,258,260,263,266],{"class":122,"line":242},5,[120,244,245],{"class":126},"        return",[120,247,248],{"class":133}," Result",[120,250,197],{"class":137},[120,252,253],{"class":147},"AlreadyDone",[120,255,151],{"class":137},[120,257,230],{"class":133},[120,259,197],{"class":137},[120,261,262],{"class":133},"VmId",[120,264,265],{"class":137},"); ",[120,267,269],{"class":268},"sIbxt","\u002F\u002F idempotentnie: brak duplikatu\n",[120,271,273],{"class":122,"line":272},6,[120,274,276],{"emptyLinePlaceholder":275},true,"\n",[120,278,280,282,285,287,289,291,293,296,298,300,302,305,307,309,311,313,315,317],{"class":122,"line":279},7,[120,281,180],{"class":126},[120,283,284],{"class":183}," vm",[120,286,188],{"class":187},[120,288,191],{"class":137},[120,290,194],{"class":133},[120,292,197],{"class":137},[120,294,295],{"class":147},"CreateAsync",[120,297,151],{"class":137},[120,299,205],{"class":133},[120,301,197],{"class":137},[120,303,304],{"class":133},"Spec",[120,306,36],{"class":137},[120,308,205],{"class":133},[120,310,197],{"class":137},[120,312,210],{"class":133},[120,314,36],{"class":137},[120,316,215],{"class":183},[120,318,218],{"class":137},[120,320,322,325,328,330,333,335,337,339,342,344,347,349,352,354,356],{"class":122,"line":321},8,[120,323,324],{"class":137},"    await ",[120,326,327],{"class":133},"progress",[120,329,197],{"class":137},[120,331,332],{"class":147},"PublishAsync",[120,334,151],{"class":137},[120,336,205],{"class":133},[120,338,197],{"class":137},[120,340,341],{"class":133},"OrderId",[120,343,36],{"class":137},[120,345,346],{"class":133},"VmState",[120,348,197],{"class":137},[120,350,351],{"class":133},"Provisioned",[120,353,36],{"class":137},[120,355,215],{"class":183},[120,357,218],{"class":137},[120,359,361,364,366,368,371,373,376,378,380],{"class":122,"line":360},9,[120,362,363],{"class":126},"    return",[120,365,248],{"class":133},[120,367,197],{"class":137},[120,369,370],{"class":147},"Ok",[120,372,151],{"class":137},[120,374,375],{"class":133},"vm",[120,377,197],{"class":137},[120,379,262],{"class":133},[120,381,218],{"class":137},[120,383,385],{"class":122,"line":384},10,[120,386,387],{"class":137},"}\n",[15,389,390],{},"Stan każdego zadania był publikowany na bieżąco, więc panel administracyjny i panel klienta widziały te same, aktualne informacje.",[10,392,394],{"id":393},"stack","Stack",[15,396,397],{},".NET, ASP.NET Core, PostgreSQL, Redis, Docker oraz API Proxmox VE.",[10,399,401],{"id":400},"rezultat","Rezultat",[15,403,404],{},"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,406,408],{"id":407},"lessons-learned","Lessons learned",[75,410,411,414,417],{},[78,412,413],{},"Idempotentność nie jest dodatkiem — przy operacjach na infrastrukturze to warunek bezpieczeństwa.",[78,415,416],{},"Zamknięcie integracji za jednym kontraktem opłaciło się przy każdej późniejszej zmianie w Proxmox.",[78,418,419],{},"Komunikacja w czasie rzeczywistym najbardziej pomogła nie klientom, lecz administracji — skróciła czas reakcji na problemy.",[421,422,423],"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":116,"searchDepth":177,"depth":177,"links":425},[426,427,428,429,430,431,432,433],{"id":12,"depth":171,"text":13},{"id":20,"depth":171,"text":21},{"id":27,"depth":171,"text":28},{"id":72,"depth":171,"text":73},{"id":105,"depth":171,"text":106},{"id":393,"depth":171,"text":394},{"id":400,"depth":171,"text":401},{"id":407,"depth":171,"text":408},"https:\u002F\u002Fjetstreamaviation.pl\u002Fportfolio\u002Fproxmox-provider-vps-platform","Confidential","2026-07-13","md","\u002Fimages\u002Fportfolio\u002Fproxmox-provider.webp",{},{"title":5,"description":441,"type":442},"Automatyzacja cyklu życia maszyn wirtualnych na Proxmox.","article","\u002Fportfolio\u002Fproxmox-provider-vps-platform",{"title":445,"description":446,"keywords":447,"robots":453},"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.",[448,449,450,451,452,95],"Proxmox",".NET","VPS","infrastructure","Docker","index,follow",[455,448,452,95,101,456,457],".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.",[451,461,462],"backend","devops",{"card":464},"summary_large_image","Software \u002F Infrastructure","XoEMuTt59FnMAoRcEmjDmSb_DQZQwFyH1Ag8vW7t7Z0",{"id":468,"title":469,"actions":470,"areas":470,"aviationList":470,"blogChrome":471,"body":486,"canonical":470,"cards":470,"contact":470,"cta":497,"description":504,"extension":437,"eyebrow":470,"hero":470,"heroImage":470,"list":470,"meta":505,"navigation":275,"og":470,"partners":470,"path":506,"perspective":470,"portfolioChrome":507,"process":470,"sectionHeader":470,"sections":470,"seo":517,"serviceLabels":470,"services":470,"stem":519,"twitter":470,"__hash__":520},"pages\u002Fpages\u002Fglobals.md","Globalne teksty UI",null,{"readingTimeSuffix":472,"tocTitle":473,"cta":474,"related":478,"tag":481},"min czytania","Spis treści",{"eyebrow":475,"title":476,"text":477},"Współpraca","Ten temat dotyczy Twojego systemu?","Jeśli mierzysz się z podobnym problemem w praktyce — napisz. Spojrzę na konkret i podpowiem kierunek.",{"eyebrow":479,"title":480},"Czytaj dalej","Powiązane wpisy",{"eyebrow":482,"bodyPrefix":483,"backLabel":484,"emptyText":485},"Tag","Wpisy powiązane z tematem","Wróć do wszystkich wpisów →","Brak wpisów dla tego tagu.",{"type":7,"value":487,"toc":495},[488],[15,489,490,491,494],{},"Ten dokument nie ma własnej strony (",[33,492,493],{},"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":116,"searchDepth":177,"depth":177,"links":496},[],{"eyebrow":475,"title":498,"text":499,"primaryLabel":500,"primaryTo":501,"secondaryLabel":502,"secondaryTo":503},"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":508,"meta":509,"stackTitle":394,"areasTitle":513,"related":514},"Case study",{"type":510,"client":511,"date":512},"Typ","Klient","Data","Obszary",{"eyebrow":515,"title":516},"Więcej","Powiązane projekty",{"robots":518,"title":469,"description":504},"noindex,nofollow","pages\u002Fglobals","_s0SDmcqMxpaZUVz5yYm0sZItpGh6_JwnFv3nOsfADs",[522,824,937,1058],{"id":4,"title":5,"body":523,"canonical":434,"client":435,"date":436,"description":116,"extension":437,"featured":275,"image":438,"meta":817,"navigation":275,"og":818,"path":443,"seo":819,"stack":821,"stem":458,"summary":459,"tags":822,"twitter":823,"type":465,"__hash__":466},{"type":7,"value":524,"toc":807},[525,527,529,531,533,535,547,559,561,579,581,583,785,787,789,791,793,795,797,805],[10,526,13],{"id":12},[15,528,17],{},[10,530,21],{"id":20},[15,532,24],{},[10,534,28],{"id":27},[15,536,31,537,36,539,36,541,36,543,36,545,49],{},[33,538,35],{},[33,540,39],{},[33,542,42],{},[33,544,45],{},[33,546,48],{},[15,548,52,549,36,551,36,553,36,555,65,557,69],{},[33,550,55],{},[33,552,58],{},[33,554,61],{},[33,556,64],{},[33,558,68],{},[10,560,73],{"id":72},[75,562,563,567,571,575],{},[78,564,565,84],{},[81,566,83],{},[78,568,569,90],{},[81,570,89],{},[78,572,573,96],{},[81,574,95],{},[78,576,577,102],{},[81,578,101],{},[10,580,106],{"id":105},[15,582,109],{},[111,584,585],{"className":113,"code":114,"language":115,"meta":116,"style":116},[33,586,587,617,621,651,665,687,691,729,761,781],{"__ignoreMap":116},[120,588,589,591,593,595,597,599,601,603,605,607,609,611,613,615],{"class":122,"line":123},[120,590,127],{"class":126},[120,592,130],{"class":126},[120,594,134],{"class":133},[120,596,138],{"class":137},[120,598,141],{"class":133},[120,600,144],{"class":137},[120,602,148],{"class":147},[120,604,151],{"class":137},[120,606,154],{"class":133},[120,608,157],{"class":133},[120,610,36],{"class":137},[120,612,162],{"class":133},[120,614,165],{"class":133},[120,616,168],{"class":137},[120,618,619],{"class":122,"line":171},[120,620,174],{"class":137},[120,622,623,625,627,629,631,633,635,637,639,641,643,645,647,649],{"class":122,"line":177},[120,624,180],{"class":126},[120,626,184],{"class":183},[120,628,188],{"class":187},[120,630,191],{"class":137},[120,632,194],{"class":133},[120,634,197],{"class":137},[120,636,200],{"class":147},[120,638,151],{"class":137},[120,640,205],{"class":133},[120,642,197],{"class":137},[120,644,210],{"class":133},[120,646,36],{"class":137},[120,648,215],{"class":183},[120,650,218],{"class":137},[120,652,653,655,657,659,661,663],{"class":122,"line":221},[120,654,224],{"class":126},[120,656,227],{"class":137},[120,658,230],{"class":183},[120,660,233],{"class":137},[120,662,237],{"class":236},[120,664,168],{"class":137},[120,666,667,669,671,673,675,677,679,681,683,685],{"class":122,"line":242},[120,668,245],{"class":126},[120,670,248],{"class":133},[120,672,197],{"class":137},[120,674,253],{"class":147},[120,676,151],{"class":137},[120,678,230],{"class":133},[120,680,197],{"class":137},[120,682,262],{"class":133},[120,684,265],{"class":137},[120,686,269],{"class":268},[120,688,689],{"class":122,"line":272},[120,690,276],{"emptyLinePlaceholder":275},[120,692,693,695,697,699,701,703,705,707,709,711,713,715,717,719,721,723,725,727],{"class":122,"line":279},[120,694,180],{"class":126},[120,696,284],{"class":183},[120,698,188],{"class":187},[120,700,191],{"class":137},[120,702,194],{"class":133},[120,704,197],{"class":137},[120,706,295],{"class":147},[120,708,151],{"class":137},[120,710,205],{"class":133},[120,712,197],{"class":137},[120,714,304],{"class":133},[120,716,36],{"class":137},[120,718,205],{"class":133},[120,720,197],{"class":137},[120,722,210],{"class":133},[120,724,36],{"class":137},[120,726,215],{"class":183},[120,728,218],{"class":137},[120,730,731,733,735,737,739,741,743,745,747,749,751,753,755,757,759],{"class":122,"line":321},[120,732,324],{"class":137},[120,734,327],{"class":133},[120,736,197],{"class":137},[120,738,332],{"class":147},[120,740,151],{"class":137},[120,742,205],{"class":133},[120,744,197],{"class":137},[120,746,341],{"class":133},[120,748,36],{"class":137},[120,750,346],{"class":133},[120,752,197],{"class":137},[120,754,351],{"class":133},[120,756,36],{"class":137},[120,758,215],{"class":183},[120,760,218],{"class":137},[120,762,763,765,767,769,771,773,775,777,779],{"class":122,"line":360},[120,764,363],{"class":126},[120,766,248],{"class":133},[120,768,197],{"class":137},[120,770,370],{"class":147},[120,772,151],{"class":137},[120,774,375],{"class":133},[120,776,197],{"class":137},[120,778,262],{"class":133},[120,780,218],{"class":137},[120,782,783],{"class":122,"line":384},[120,784,387],{"class":137},[15,786,390],{},[10,788,394],{"id":393},[15,790,397],{},[10,792,401],{"id":400},[15,794,404],{},[10,796,408],{"id":407},[75,798,799,801,803],{},[78,800,413],{},[78,802,416],{},[78,804,419],{},[421,806,423],{},{"title":116,"searchDepth":177,"depth":177,"links":808},[809,810,811,812,813,814,815,816],{"id":12,"depth":171,"text":13},{"id":20,"depth":171,"text":21},{"id":27,"depth":171,"text":28},{"id":72,"depth":171,"text":73},{"id":105,"depth":171,"text":106},{"id":393,"depth":171,"text":394},{"id":400,"depth":171,"text":401},{"id":407,"depth":171,"text":408},{},{"title":5,"description":441,"type":442},{"title":445,"description":446,"keywords":820,"robots":453},[448,449,450,451,452,95],[455,448,452,95,101,456,457],[451,461,462],{"card":464},{"id":825,"title":826,"body":827,"canonical":907,"client":435,"date":908,"description":116,"extension":437,"featured":909,"image":910,"meta":911,"navigation":275,"og":912,"path":914,"seo":915,"stack":925,"stem":928,"summary":929,"tags":930,"twitter":934,"type":935,"__hash__":936},"portfolio\u002Fportfolio\u002Fcomarch-erp-optima-ksef-integration.md","Automatyczna integracja Comarch ERP Optima z KSeF",{"type":7,"value":828,"toc":899},[829,831,834,837,839,842,845,847,873,875,878,880,883,886,888],[10,830,13],{"id":12},[15,832,833],{},"Proces wysyłania faktur z Comarch ERP Optima do KSeF wymagał automatyzacji bez pomijania mechanizmów biznesowych i ewidencyjnych systemu ERP.",[15,835,836],{},"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,838,28],{"id":27},[15,840,841],{},"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,843,844],{},"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,846,73],{"id":72},[75,848,849,855,861,867],{},[78,850,851,854],{},[81,852,853],{},"Windows Service"," realizujący cykliczne przetwarzanie dokumentów.",[78,856,857,860],{},[81,858,859],{},"Dapper i SQL Server"," do szybkiego wyszukiwania faktur oczekujących na wysłanie.",[78,862,863,866],{},[81,864,865],{},"Comarch ERP Optima API"," do przetwarzania dokumentów i komunikacji z KSeF.",[78,868,869,872],{},[81,870,871],{},"Obsługa UPO"," wraz z zapisaniem wyniku procesu w Optimie.",[10,874,394],{"id":393},[15,876,877],{},".NET Framework 4.7.2, Windows Service, Microsoft SQL Server, Dapper oraz DLL API Comarch ERP Optima.",[10,879,401],{"id":400},[15,881,882],{},"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,884,885],{},"Użytkownicy mogą kontrolować status faktur bezpośrednio w Comarch ERP Optima, bez korzystania z dodatkowego panelu lub ręcznego uruchamiania wysyłki.",[10,887,408],{"id":407},[75,889,890,893,896],{},[78,891,892],{},"Integracja przez oficjalne API systemu ERP pozwala zachować spójność jego procesów biznesowych.",[78,894,895],{},"Usługa działająca w tle dobrze sprawdza się przy regularnym przetwarzaniu niewielkich kolejek dokumentów.",[78,897,898],{},"Rezultat integracji powinien być widoczny w systemie, z którego użytkownicy korzystają na co dzień.",{"title":116,"searchDepth":177,"depth":177,"links":900},[901,902,903,904,905,906],{"id":12,"depth":171,"text":13},{"id":27,"depth":171,"text":28},{"id":72,"depth":171,"text":73},{"id":393,"depth":171,"text":394},{"id":400,"depth":171,"text":401},{"id":407,"depth":171,"text":408},"https:\u002F\u002Fjetstreamaviation.pl\u002Fportfolio\u002Fcomarch-optima-ksef-integration","2026-04-01",false,"\u002Fimages\u002Fportfolio\u002Foptima-ksef.webp",{},{"title":826,"description":913,"type":442},"Automatyczna wysyłka faktur do KSeF i odbieranie UPO bezpośrednio w środowisku Comarch ERP Optima.","\u002Fportfolio\u002Fcomarch-erp-optima-ksef-integration",{"title":916,"description":917,"keywords":918,"robots":453},"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.",[919,920,921,922,923,924],"Comarch ERP Optima","KSeF",".NET Framework","SQL Server","Dapper","integracja ERP",[926,865,927,923,853,920],".NET Framework 4.7.2","Microsoft SQL Server","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.",[461,931,932,933],"integration","erp","automation",{"card":464},"Software \u002F ERP Integration","pLQ3RV8OBlrJ-g4BY6Lv1mG64n_dKjWPXIPhvt0PHy0",{"id":938,"title":939,"body":940,"canonical":1028,"client":435,"date":1029,"description":116,"extension":437,"featured":275,"image":1030,"meta":1031,"navigation":275,"og":1032,"path":1035,"seo":1036,"stack":1045,"stem":1050,"summary":1051,"tags":1052,"twitter":1055,"type":1056,"__hash__":1057},"portfolio\u002Fportfolio\u002Faviation-training-systems.md","Cyfryzacja procesów w organizacji szkolenia lotniczego",{"type":7,"value":941,"toc":1018},[942,944,947,949,952,954,957,960,964,990,992,995,997,1000,1002,1005,1007],[10,943,13],{"id":12},[15,945,946],{},"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,948,21],{"id":20},[15,950,951],{},"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,953,28],{"id":27},[15,955,956],{},"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,958,959],{},"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,961,963],{"id":962},"architektura-procesu","Architektura procesu",[75,965,966,972,978,984],{},[78,967,968,971],{},[81,969,970],{},"Jedno źródło prawdy"," dla ewidencji szkoleń i statusów.",[78,973,974,977],{},[81,975,976],{},"Jawne role i odpowiedzialności"," na każdym etapie obiegu.",[78,979,980,983],{},[81,981,982],{},"Checklisty"," dla kroków, w których błąd jest kosztowny.",[78,985,986,989],{},[81,987,988],{},"Ślad zmian"," — kto, co i kiedy zatwierdził.",[10,991,106],{"id":105},[15,993,994],{},"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,996,394],{"id":393},[15,998,999],{},"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,1001,401],{"id":400},[15,1003,1004],{},"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,1006,408],{"id":407},[75,1008,1009,1012,1015],{},[78,1010,1011],{},"Narzędzie nie naprawi złego procesu — najpierw proces, potem software.",[78,1013,1014],{},"Checklisty sprawdzają się w dokumentacji tak samo dobrze jak w kokpicie.",[78,1016,1017],{},"Jedno źródło prawdy to najtańsza inwestycja w spokój podczas audytu.",{"title":116,"searchDepth":177,"depth":177,"links":1019},[1020,1021,1022,1023,1024,1025,1026,1027],{"id":12,"depth":171,"text":13},{"id":20,"depth":171,"text":21},{"id":27,"depth":171,"text":28},{"id":962,"depth":171,"text":963},{"id":105,"depth":171,"text":106},{"id":393,"depth":171,"text":394},{"id":400,"depth":171,"text":401},{"id":407,"depth":171,"text":408},"https:\u002F\u002Fjetstreamaviation.pl\u002Fportfolio\u002Faviation-training-systems","2024-03-2","\u002Fimages\u002Fportfolio\u002Faviation-training-systems.jpg",{},{"title":939,"description":1033,"image":1034,"type":442},"Zgodność, powtarzalność i użyteczność operacyjna.","\u002Fimages\u002Fog\u002Faviation-training-systems.jpg","\u002Fportfolio\u002Faviation-training-systems",{"title":1037,"description":1038,"keywords":1039,"robots":453},"Cyfryzacja procesów w ATO — case study","Case study porządkowania dokumentacji i procedur w organizacji szkolenia lotniczego: zgodność, powtarzalność i użyteczność operacyjna.",[1040,1041,1042,1043,1044],"organizacja szkoleniowa","ATO","procedury lotnicze","compliance","dokumentacja",[1046,1047,1048,1049],"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ść.",[1053,1043,1054],"aviation","process",{"card":464},"Consulting \u002F Aviation IT","ivhmSUqIp6bgyU8lFp7Vuqv7lri6gNLjstlQqG0Tpcc",{"id":1059,"title":1060,"body":1061,"canonical":1238,"client":435,"date":1239,"description":116,"extension":437,"featured":909,"image":1240,"meta":1241,"navigation":275,"og":1242,"path":1245,"seo":1246,"stack":1255,"stem":1257,"summary":1258,"tags":1259,"twitter":1263,"type":1264,"__hash__":1265},"portfolio\u002Fportfolio\u002Femvo-notification-system.md","EMVO EVI — system powiadomień o zdarzeniach EMVS",{"type":7,"value":1062,"toc":1228},[1063,1065,1068,1071,1073,1076,1079,1082,1084,1087,1090,1110,1113,1115,1158,1160,1163,1166,1187,1190,1193,1195,1198,1200,1203,1206,1209,1211],[10,1064,13],{"id":12},[15,1066,1067],{},"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,1069,1070],{},"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,1072,21],{"id":20},[15,1074,1075],{},"EVI — European Medicines Verification System Information — pełniło rolę centralnego źródła informacji o zdarzeniach operacyjnych w ramach EMVS.",[15,1077,1078],{},"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,1080,1081],{},"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,1083,28],{"id":27},[15,1085,1086],{},"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,1088,1089],{},"Zdarzenie było traktowane jako obiekt posiadający własny cykl życia. Operator mógł określić między innymi:",[75,1091,1092,1095,1098,1101,1104,1107],{},[78,1093,1094],{},"kraj lub grupę krajów,",[78,1096,1097],{},"system i środowisko, którego dotyczy zdarzenie,",[78,1099,1100],{},"rodzaj oraz poziom wpływu,",[78,1102,1103],{},"czas rozpoczęcia i zakończenia,",[78,1105,1106],{},"aktualny status,",[78,1108,1109],{},"opis problemu i kolejne aktualizacje.",[15,1111,1112],{},"Po opublikowaniu lub zmianie zgłoszenia system identyfikował właściwych subskrybentów i przygotowywał dla nich odpowiednie powiadomienia.",[10,1114,73],{"id":72},[75,1116,1117,1123,1128,1134,1140,1146,1152],{},[78,1118,1119,1122],{},[81,1120,1121],{},"ASP.NET Core Web API"," jako warstwa udostępniająca operacje związane ze zgłoszeniami, użytkownikami i subskrypcjami.",[78,1124,1125,1127],{},[81,1126,927],{}," jako źródło prawdy o zdarzeniach, odbiorcach, konfiguracji subskrypcji i historii wysyłki.",[78,1129,1130,1133],{},[81,1131,1132],{},"SSO"," zapewniające kontrolowany dostęp do funkcji administracyjnych.",[78,1135,1136,1139],{},[81,1137,1138],{},"Quartz.NET"," do realizacji zadań cyklicznych i przetwarzania operacji wykonywanych poza żądaniem HTTP.",[78,1141,1142,1145],{},[81,1143,1144],{},"SendGrid"," jako usługa odpowiedzialna za dystrybucję wiadomości e-mail.",[78,1147,1148,1151],{},[81,1149,1150],{},"Microsoft Azure"," jako środowisko uruchomieniowe aplikacji i jej infrastruktury.",[78,1153,1154,1157],{},[81,1155,1156],{},"GitOps"," do kontrolowanego, powtarzalnego wdrażania zmian pomiędzy środowiskami.",[10,1159,106],{"id":105},[15,1161,1162],{},"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,1164,1165],{},"Proces obejmował:",[1167,1168,1169,1172,1175,1178,1181,1184],"ol",{},[78,1170,1171],{},"zapisanie lub aktualizację zgłoszenia,",[78,1173,1174],{},"określenie krajów, systemów i środowisk objętych zdarzeniem,",[78,1176,1177],{},"wyszukanie pasujących subskrypcji,",[78,1179,1180],{},"utworzenie powiadomień dla właściwych odbiorców,",[78,1182,1183],{},"przekazanie wiadomości do SendGrid,",[78,1185,1186],{},"zapisanie wyniku wysyłki i historii komunikacji.",[15,1188,1189],{},"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,1191,1192],{},"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,1194,394],{"id":393},[15,1196,1197],{},"ASP.NET Core Web API, Microsoft SQL Server, Microsoft Azure, Quartz.NET, SendGrid, SSO oraz proces wdrożeniowy oparty na GitOps.",[10,1199,401],{"id":400},[15,1201,1202],{},"Powstał centralny system umożliwiający uporządkowane zarządzanie informacjami o zdarzeniach operacyjnych dotyczących wielu krajów i komponentów EMVS.",[15,1204,1205],{},"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,1207,1208],{},"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,1210,408],{"id":407},[75,1212,1213,1216,1219,1222,1225],{},[78,1214,1215],{},"W systemach wielokrajowych filtrowanie odbiorców jest równie istotne jak samo publikowanie komunikatów.",[78,1217,1218],{},"Powiadomienie powinno być osobnym elementem domeny z własnym statusem, historią i możliwością ponowienia wysyłki.",[78,1220,1221],{},"Operacje zależne od zewnętrznych dostawców nie powinny blokować podstawowego procesu publikowania zgłoszenia.",[78,1223,1224],{},"Historia zmian i wysyłki jest niezbędna w systemach, w których liczy się możliwość odtworzenia pełnego przebiegu komunikacji.",[78,1226,1227],{},"GitOps ułatwia utrzymanie spójnej konfiguracji aplikacji działającej w kilku środowiskach.",{"title":116,"searchDepth":177,"depth":177,"links":1229},[1230,1231,1232,1233,1234,1235,1236,1237],{"id":12,"depth":171,"text":13},{"id":20,"depth":171,"text":21},{"id":27,"depth":171,"text":28},{"id":72,"depth":171,"text":73},{"id":105,"depth":171,"text":106},{"id":393,"depth":171,"text":394},{"id":400,"depth":171,"text":401},{"id":407,"depth":171,"text":408},"https:\u002F\u002Fjetstreamaviation.pl\u002Fportfolio\u002Femvo-notification-system","2022-05-01","\u002Fimages\u002Fportfolio\u002Femvo-evi.webp",{},{"title":1243,"description":1244,"type":442},"EMVO EVI — powiadomienia o zdarzeniach EMVS","System obsługi zgłoszeń, subskrypcji i wielokrajowej dystrybucji powiadomień dla infrastruktury weryfikacji leków.","\u002Fportfolio\u002Femvo-notification-system",{"title":1247,"description":1248,"keywords":1249,"robots":453},"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.",[1250,1251,1252,1253,1254,922,1144,1138],"EMVO","EVI","EMVS","ASP.NET Core","Azure",[1121,1256,1254,1138,1144,1132,1156],"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.",[461,1260,1261,1262,462],"healthcare","notifications","cloud",{"card":464},"Software \u002F Healthcare","N28g6OVh1jB1P1osWC_T6GYRKJxpvVqGOMbQvwz4G-c",1784052984419]