using System; using System.Collections.Generic; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.Data.SqlClient; using System.Text.Json.Nodes; using System.Net.Http; using System.IO; using System.IO.Compression; using Microsoft.Win32; namespace Sync2Server { public partial class Form1 : Form { private NotifyIcon trayIcon; private ContextMenuStrip trayMenu; private bool isExiting = false; private Timer backgroundImportTimer; public Form1() { InitializeComponent(); SetupTrayIcon(); SetupTimer(); } private void SetupTimer() { backgroundImportTimer = new Timer(); backgroundImportTimer.Interval = 1 * 60 * 1000; // 1 minuta backgroundImportTimer.Tick += async (sender, e) => await PokreniTihiUvoz(); backgroundImportTimer.Start(); } private void SetupTrayIcon() { trayMenu = new ContextMenuStrip(); trayMenu.Items.Add("Prikaži", null, OnShowClicked); trayMenu.Items.Add("Sakrij", null, OnHideClicked); trayMenu.Items.Add(new ToolStripSeparator()); trayMenu.Items.Add("Izađi", null, OnExitClicked); trayIcon = new NotifyIcon(); trayIcon.Text = "fiscal.POS Sync"; trayIcon.Icon = this.Icon; trayIcon.ContextMenuStrip = trayMenu; trayIcon.Visible = true; trayIcon.DoubleClick += TrayIcon_DoubleClick; } protected override void SetVisibleCore(bool value) { if (!this.IsHandleCreated) { CreateHandle(); value = false; } base.SetVisibleCore(value); } protected override void OnFormClosing(FormClosingEventArgs e) { if (!isExiting) { e.Cancel = true; HideApp(); } base.OnFormClosing(e); } private void TrayIcon_DoubleClick(object sender, EventArgs e) { if (this.Visible) HideApp(); else ShowApp(); } private void OnShowClicked(object sender, EventArgs e) => ShowApp(); private void OnHideClicked(object sender, EventArgs e) => HideApp(); private void OnExitClicked(object sender, EventArgs e) { isExiting = true; trayIcon.Visible = false; Application.Exit(); } private void ShowApp() { this.Show(); this.WindowState = FormWindowState.Normal; this.ShowInTaskbar = true; this.BringToFront(); } private void HideApp() { this.Hide(); this.ShowInTaskbar = false; if (backgroundImportTimer != null) { backgroundImportTimer.Stop(); backgroundImportTimer.Start(); } _ = PokreniTihiUvoz(); } // ---------- UČITAVANJE POSTAVKI ---------- private void Form1_Load(object sender, EventArgs e) { txtGuid.Text = Properties.Settings.Default.SacuvanGuid; txtConnString.Text = Properties.Settings.Default.SacuvanaBaza; } private void button1_Click(object sender, EventArgs e) { Properties.Settings.Default.SacuvanGuid = txtGuid.Text.Trim(); Properties.Settings.Default.SacuvanaBaza = txtConnString.Text.Trim(); Properties.Settings.Default.Save(); } private void button2_Click(object sender, EventArgs e) { try { string baseRegPath = @"Software\VB and VBA Program Settings\SDPos"; string guid = ""; string server = @"(local)\fiscalPOS"; string user = "sa"; string pass = "Ulaz123#"; string baza = "ZIMCA"; using (RegistryKey keyLoyalty = Registry.CurrentUser.OpenSubKey(baseRegPath + @"\Loyalty")) { if (keyLoyalty != null) { guid = keyLoyalty.GetValue("GUID", "").ToString(); } } using (RegistryKey keyPostavke = Registry.CurrentUser.OpenSubKey(baseRegPath + @"\Postavke")) { if (keyPostavke != null) { server = keyPostavke.GetValue("Srv", @"(local)\fiscalPOS").ToString(); user = keyPostavke.GetValue("cODBC_UN", "sa").ToString(); pass = keyPostavke.GetValue("cODBC_PW", "Ulaz123#").ToString(); } } txtGuid.Text = guid; txtConnString.Text = $"Server={server};Database={baza};User Id={user};Password={pass};TrustServerCertificate=True;"; MessageBox.Show("Podaci iz Registry-ja su uspješno pročitani i ubačeni!", "Učitano", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show("Došlo je do greške pri čitanju iz Registry-ja:\n" + ex.Message, "Greška", MessageBoxButtons.OK, MessageBoxIcon.Error); } } // ---------- EXPORT LOGIKA (Izvoz računa prema Web serveru) ---------- private async void btnIzvoz_Click(object sender, EventArgs e) { btnIzvoz.Enabled = false; try { DateTime datumOd = dtpOd.Value.Date; DateTime datumDo = dtpDo.Value.Date; string guid = txtGuid.Text.Trim(); string connString = txtConnString.Text.Trim(); string posId = txtPosId.Text.Trim(); if (string.IsNullOrEmpty(guid) || string.IsNullOrEmpty(connString) || string.IsNullOrEmpty(posId)) { MessageBox.Show("Molimo vas da popunite GUID, Oznaku blagajne (POS ID) i Konekcijski string prije izvoza!", "Upozorenje", MessageBoxButtons.OK, MessageBoxIcon.Warning); btnIzvoz.Enabled = true; return; } int brojDanaUPaketu = 5; DateTime trenutniOd = datumOd; int ukupnoDana = (datumDo - datumOd).Days + 1; int ukupnoPaketa = (int)Math.Ceiling((double)ukupnoDana / brojDanaUPaketu); int trenutniPaket = 1; while (trenutniOd <= datumDo) { DateTime trenutniDo = trenutniOd.AddDays(brojDanaUPaketu - 1); if (trenutniDo > datumDo) trenutniDo = datumDo; btnIzvoz.Text = $"Šaljem paket {trenutniPaket} od {ukupnoPaketa}..."; await ExportPosAsync(trenutniOd, trenutniDo, guid, posId, connString); trenutniOd = trenutniDo.AddDays(1); trenutniPaket++; } MessageBox.Show("Svi podaci su uspješno poslani u paketima!", "Uspjeh", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show("Došlo je do greške pri slanju:\n" + ex.Message, "Greška", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { btnIzvoz.Enabled = true; btnIzvoz.Text = "Izvoz (Export)"; } } private async Task ExportPosAsync(DateTime odDatum, DateTime doDatum, string guid, string posId, string connectionString) { // Dodan @posId direktno u glavni SQL upit - SQL sam kreira perfektan JSON paket string sql = @" SELECT @guid AS guid, @posId AS pos_id, (SELECT * FROM rGlava WHERE datum BETWEEN @od AND @do FOR JSON PATH, INCLUDE_NULL_VALUES) AS rGlava, (SELECT p.* FROM rPozicije p JOIN rGlava g ON g.kljuc=p.kljuc WHERE g.datum BETWEEN @od AND @do FOR JSON PATH, INCLUDE_NULL_VALUES) AS rPozicije, (SELECT b.* FROM rGlavaBlg b JOIN rGlava g ON g.kljuc=b.kljuc WHERE g.datum BETWEEN @od AND @do FOR JSON PATH, INCLUDE_NULL_VALUES) AS rGlavaBlg, (SELECT DISTINCT k.* FROM Kupci k JOIN rGlava g ON g.kupac=k.naziv WHERE g.datum BETWEEN @od AND @do FOR JSON PATH, INCLUDE_NULL_VALUES) AS Kupci FOR JSON PATH, WITHOUT_ARRAY_WRAPPER"; string tempFilePath = Path.GetTempFileName(); try { using (FileStream fs = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write)) using (GZipStream gzStream = new GZipStream(fs, CompressionMode.Compress)) using (StreamWriter writer = new StreamWriter(gzStream, new UTF8Encoding(false))) using (SqlConnection conn = new SqlConnection(connectionString)) using (SqlCommand cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@guid", guid); cmd.Parameters.AddWithValue("@posId", posId); cmd.Parameters.AddWithValue("@od", odDatum); cmd.Parameters.AddWithValue("@do", doDatum); cmd.CommandTimeout = 300; await conn.OpenAsync(); using (SqlDataReader reader = await cmd.ExecuteReaderAsync()) { while (await reader.ReadAsync()) { if (!reader.IsDBNull(0)) { await writer.WriteAsync(reader.GetString(0)); } } } } using (HttpClient client = new HttpClient()) { client.Timeout = TimeSpan.FromMinutes(10); using (FileStream fsRead = new FileStream(tempFilePath, FileMode.Open, FileAccess.Read)) { StreamContent content = new StreamContent(fsRead); content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/gzip"); HttpResponseMessage response = await client.PostAsync("http://cp.mojhosting.ba:65535/Queue/pos_sync.php", content); string responseString = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode || responseString.Trim() != "OK") throw new Exception($"Server nije prihvatio podatke. Odgovor: {responseString}"); } } } finally { if (File.Exists(tempFilePath)) File.Delete(tempFilePath); } } // ---------- IMPORT LOGIKA SA TAJMEROM I ACK-om (Prijem artikala u C#) ---------- private async Task PokreniTihiUvoz() { if (!btnImport.Enabled) return; string guid = txtGuid.Text.Trim(); string connString = txtConnString.Text.Trim(); string posId = txtPosId.Text.Trim(); if (string.IsNullOrEmpty(guid) || string.IsNullOrEmpty(connString) || string.IsNullOrEmpty(posId)) return; btnImport.Enabled = false; try { int brojUvezenih = await ImportPosAsync(guid, posId, connString); if (brojUvezenih > 0) { trayIcon.ShowBalloonTip(3000, "Uvoz završen", $"Uspješno preuzeto i upisano {brojUvezenih} zapisa sa servera.", ToolTipIcon.Info); } } catch (Exception ex) { trayIcon.ShowBalloonTip(5000, "Greška pri pozadinskom uvozu", ex.Message, ToolTipIcon.Error); } finally { btnImport.Enabled = true; } } private async void btnImport_Click(object sender, EventArgs e) { btnImport.Enabled = false; try { string guid = txtGuid.Text.Trim(); string connString = txtConnString.Text.Trim(); string posId = txtPosId.Text.Trim(); if (string.IsNullOrEmpty(guid) || string.IsNullOrEmpty(posId)) { MessageBox.Show("Niste unijeli GUID ili oznaku blagajne (POS ID)!", "Upozorenje", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } int brojUvezenih = await ImportPosAsync(guid, posId, connString); if (brojUvezenih > 0) MessageBox.Show($"Import završen uspješno! Uvezeno zapisa: {brojUvezenih}", "OK", MessageBoxButtons.OK, MessageBoxIcon.Information); else MessageBox.Show("Trenutno nema novih nesinhronizovanih podataka za ovu blagajnu.", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show(ex.Message, "Greška pri importu", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { btnImport.Enabled = true; } } private async Task ImportPosAsync(string guid, string posId, string connString) { using (HttpClient client = new HttpClient()) { string url = $"http://cp.mojhosting.ba:65535/Queue/pos_import.php?guid={guid}&pos_id={posId}"; var response = await client.GetAsync(url); response.EnsureSuccessStatusCode(); byte[] responseBytes = await response.Content.ReadAsByteArrayAsync(); if (responseBytes.Length == 0) throw new Exception("Server je vratio prazan odgovor!"); if (responseBytes.Length < 2 || responseBytes[0] != 0x1F || responseBytes[1] != 0x8B) { string serverMessage = Encoding.UTF8.GetString(responseBytes); throw new Exception("Greška na PHP serveru:\n\n" + serverMessage); } string json; using (MemoryStream ms = new MemoryStream(responseBytes)) using (GZipStream gz = new GZipStream(ms, CompressionMode.Decompress)) using (StreamReader sr = new StreamReader(gz, Encoding.UTF8)) { json = await sr.ReadToEndAsync(); } JsonNode root = JsonNode.Parse(json); int countSub = root["sub"]?.AsArray().Count ?? 0; int countArt = root["art"]?.AsArray().Count ?? 0; int countPar = root["par"]?.AsArray().Count ?? 0; int countUgcj = root["ugcj"]?.AsArray().Count ?? 0; int ukupno = countSub + countArt + countPar + countUgcj; if (ukupno > 0) { await InsertIntoDatabase(root, connString); var formContent = new FormUrlEncodedContent(new[] { new KeyValuePair("guid", guid), new KeyValuePair("pos_id", posId) }); HttpResponseMessage ackResponse = await client.PostAsync("http://cp.mojhosting.ba:65535/Queue/pos_ack2.php", formContent); string ackResult = await ackResponse.Content.ReadAsStringAsync(); if (!ackResponse.IsSuccessStatusCode || ackResult.Trim() != "OK") { throw new Exception($"Podaci su lokalno upisani, ali server nije potvrdio promjenu statusa (ACK greška: {ackResult})"); } } return ukupno; } } private async Task InsertIntoDatabase(JsonNode root, string connString) { using (SqlConnection conn = new SqlConnection(connString)) { await conn.OpenAsync(); using (SqlTransaction trans = conn.BeginTransaction()) { try { await CallStoredProcedureAsync(conn, trans, "ArtikliSub", "_sd_artiklisub", root["sub"]?.AsArray(), "ident"); await CallStoredProcedureAsync(conn, trans, "Artikli", "_sd_artikli2", root["art"]?.AsArray(), "ident", true); await CallStoredProcedureAsync(conn, trans, "Kupci", "_sd_kupci", root["par"]?.AsArray(), "acSubject"); await CallStoredProcedureAsync(conn, trans, "tFP_SetSubjPriceItem", "_fp_uvoz_ugcj", root["ugcj"]?.AsArray(), "acSubject"); trans.Commit(); } catch (Exception ex) { trans.Rollback(); throw new Exception("Greška pri upisu u bazu: " + ex.Message); } } } } private async Task CallStoredProcedureAsync(SqlConnection conn, SqlTransaction trans, string tableName, string spName, JsonArray dataArray, string primaryKey, bool hasDocTypeParam = false) { if (dataArray == null || dataArray.Count == 0) return; if (dataArray[0][primaryKey]?.ToString().Trim() == "!!!BRISANJE!!!") { using (SqlCommand cmdDel = new SqlCommand($"DELETE FROM [{tableName}]", conn, trans)) { await cmdDel.ExecuteNonQueryAsync(); } dataArray.RemoveAt(0); } if (dataArray.Count > 0) { string jsonString = dataArray.ToJsonString(); using (SqlCommand cmdSp = new SqlCommand(spName, conn, trans)) { cmdSp.CommandType = CommandType.StoredProcedure; cmdSp.Parameters.AddWithValue("@JSON", jsonString); if (hasDocTypeParam) { cmdSp.Parameters.AddWithValue("@acDocType2", "3200;"); } cmdSp.CommandTimeout = 300; await cmdSp.ExecuteNonQueryAsync(); } } } private void txtPosId_TextChanged(object sender, EventArgs e) { } } }