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; using System.Net.Http; using System.IO; using System.IO.Compression; using Microsoft.Win32; namespace Sync2Client { public partial class Form1 : Form { private NotifyIcon trayIcon; private ContextMenuStrip trayMenu; private bool isExiting = false; private Timer backgroundTimer; public Form1() { InitializeComponent(); SetupTrayIcon(); SetupTimer(); } private void SetupTimer() { backgroundTimer = new Timer(); backgroundTimer.Interval = 5 * 60 * 1000; backgroundTimer.Tick += async (sender, e) => await PokreniUvoz(true); backgroundTimer.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); } protected override void OnResize(EventArgs e) { base.OnResize(e); if (this.WindowState == FormWindowState.Minimized) { HideApp(); } } 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 (backgroundTimer != null) { backgroundTimer.Stop(); backgroundTimer.Start(); } _ = PokreniUvoz(true); } // ---------- 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 tnCitajRegistry_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!", "Učitano", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show("Greška pri čitanju iz Registry-ja:\n" + ex.Message, "Greška", MessageBoxButtons.OK, MessageBoxIcon.Error); } } // ---------- EXPORT RAČUNA (rGlava, rPozicije...) ---------- private async void btnIzvoz_Click(object sender, EventArgs e) { if (btnIzvoz != null) { btnIzvoz.Enabled = false; btnIzvoz.Text = "Izvoz u toku..."; } try { // Uzimamo period (npr. unazad 7 dana) ili čitamo sa dtp kontrola ako ih imaš DateTime datumOd = DateTime.Now.AddDays(-7).Date; DateTime datumDo = DateTime.Now.Date; string guid = txtGuid.Text.Trim(); string connString = txtConnString.Text.Trim(); // PAŽNJA: Ovdje stavi stvarni POS ID blagajne (npr. txtPosId.Text) string posId = "KASA1"; if (string.IsNullOrEmpty(guid) || string.IsNullOrEmpty(connString)) { MessageBox.Show("Molimo vas da popunite GUID i Konekcijski string prije izvoza!", "Upozorenje", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } int brojDanaUPaketu = 15; 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; if (btnIzvoz != null) btnIzvoz.Text = $"Šaljem paket {trenutniPaket} od {ukupnoPaketa}..."; await ExportPosAsync(trenutniOd, trenutniDo, guid, posId, connString); trenutniOd = trenutniDo.AddDays(1); trenutniPaket++; } MessageBox.Show("Svi računi su uspješno kompresovani i poslani na server!", "Uspjeh", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { MessageBox.Show("Došlo je do greške pri izvozu:\n" + ex.Message, "Greška", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { if (btnIzvoz != null) { btnIzvoz.Enabled = true; btnIzvoz.Text = "Izvoz"; } } } private async Task ExportPosAsync(DateTime odDatum, DateTime doDatum, string guid, string posId, string connectionString) { 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"); // Šalje na pos_sync.php API (zadužen za račune) 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 (Zadržan tvoj stari generic kod) ---------- private async void btnUvoz_Click(object sender, EventArgs e) { await PokreniUvoz(false); } private async Task PokreniUvoz(bool isSilent) { if (!btnUvoz.Enabled) return; btnUvoz.Enabled = false; string oldText = btnUvoz.Text; btnUvoz.Text = "Provjeravam server..."; if (isSilent) { trayIcon.ShowBalloonTip(2000, "fiscal.POS Sync", "Provjeravam podatke na serveru...", ToolTipIcon.Info); } try { string guid = txtGuid.Text.Trim(); string connString = txtConnString.Text.Trim(); if (string.IsNullOrEmpty(guid) || string.IsNullOrEmpty(connString)) { if (!isSilent) MessageBox.Show("Molimo vas da popunite GUID i Konekcijski string!", "Upozorenje", MessageBoxButtons.OK, MessageBoxIcon.Warning); else trayIcon.ShowBalloonTip(3000, "Upozorenje", "Podesite GUID i konekcijski string!", ToolTipIcon.Warning); return; } await ImportPosAsync(guid, connString, isSilent); } catch (Exception ex) { if (!isSilent) MessageBox.Show("Došlo je do greške pri uvozu:\n" + ex.Message, "Greška", MessageBoxButtons.OK, MessageBoxIcon.Error); else trayIcon.ShowBalloonTip(5000, "Greška pri sinhronizaciji", ex.Message, ToolTipIcon.Error); } finally { btnUvoz.Enabled = true; btnUvoz.Text = oldText; } } private async Task ImportPosAsync(string guid, string connectionString, bool isSilent) { using (HttpClient client = new HttpClient()) { client.Timeout = TimeSpan.FromMinutes(15); string getUrl = $"http://cp.mojhosting.ba:65535/Queue/pos_get.php?guid={guid}"; using (HttpResponseMessage response = await client.GetAsync(getUrl, HttpCompletionOption.ResponseHeadersRead)) { response.EnsureSuccessStatusCode(); using (Stream stream = await response.Content.ReadAsStreamAsync()) using (JsonDocument doc = await JsonDocument.ParseAsync(stream)) { if (doc.RootElement.ValueKind != JsonValueKind.Array) { if (!isSilent) MessageBox.Show("Server nije vratio validan niz podataka.", "Greška", MessageBoxButtons.OK, MessageBoxIcon.Error); else trayIcon.ShowBalloonTip(3000, "Greška", "Server nije vratio validan niz podataka.", ToolTipIcon.Error); return; } int brojPaketa = doc.RootElement.GetArrayLength(); if (brojPaketa == 0) { if (!isSilent) MessageBox.Show("Server kaže da nema novih nesinhronizovanih podataka.", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); else trayIcon.ShowBalloonTip(2000, "Nema novih podataka", "Svi podaci su već sinhronizovani.", ToolTipIcon.Info); return; } int ukupnoUpisanoRedova = 0; using (SqlConnection conn = new SqlConnection(connectionString)) { await conn.OpenAsync(); foreach (JsonElement rowElement in doc.RootElement.EnumerateArray()) { int serverId = rowElement.GetProperty("id").GetInt32(); string tableName = rowElement.GetProperty("table_name").GetString().Trim(); JsonElement jsonProp = rowElement.GetProperty("json"); string tableJsonData = jsonProp.ValueKind == JsonValueKind.String ? jsonProp.GetString() : jsonProp.GetRawText(); if (string.IsNullOrWhiteSpace(tableJsonData)) continue; List primaryKeys = new List(); string tNameLower = tableName.ToLower(); if (tNameLower == "rglava") primaryKeys.Add("kljuc"); else if (tNameLower == "rpozicije" || tNameLower == "rglavablg") { primaryKeys.Add("kljuc"); primaryKeys.Add("poz"); } else if (tNameLower == "kupci") primaryKeys.Add("naziv"); else primaryKeys.Add("kljuc"); string onClause = string.Join(" AND ", primaryKeys.Select(pk => $"Target.[{pk}] COLLATE DATABASE_DEFAULT = Source.[{pk}] COLLATE DATABASE_DEFAULT")); using (JsonDocument tableDoc = JsonDocument.Parse(tableJsonData)) { if (tableDoc.RootElement.ValueKind == JsonValueKind.Array) { using (SqlTransaction transaction = conn.BeginTransaction()) { try { foreach (JsonElement dataRow in tableDoc.RootElement.EnumerateArray()) { List columns = new List(); List parameters = new List(); List updateMapping = new List(); using (SqlCommand cmdMerge = new SqlCommand()) { cmdMerge.Connection = conn; cmdMerge.Transaction = transaction; foreach (JsonProperty prop in dataRow.EnumerateObject()) { string colName = prop.Name; if (colName.ToLower() == "id") continue; string paramName = "@" + colName.Replace(" ", "_"); columns.Add($"[{colName}]"); parameters.Add(paramName); if (!primaryKeys.Contains(colName.ToLower())) updateMapping.Add($"Target.[{colName}] = Source.[{colName}]"); if (prop.Value.ValueKind == JsonValueKind.Null) cmdMerge.Parameters.AddWithValue(paramName, DBNull.Value); else cmdMerge.Parameters.AddWithValue(paramName, prop.Value.ToString().Trim()); } string updateClause = updateMapping.Count > 0 ? $"WHEN MATCHED THEN UPDATE SET {string.Join(", ", updateMapping)}" : ""; string mergeSql = $@" MERGE [{tableName}] AS Target USING (SELECT {string.Join(", ", parameters.Select((p, i) => p + " AS [" + columns[i].Trim('[', ']') + "]"))}) AS Source ON ({onClause}) {updateClause} WHEN NOT MATCHED THEN INSERT ({string.Join(", ", columns)}) VALUES ({string.Join(", ", parameters)});"; cmdMerge.CommandText = mergeSql; await cmdMerge.ExecuteNonQueryAsync(); ukupnoUpisanoRedova++; } } transaction.Commit(); } catch (Exception ex) { transaction.Rollback(); throw new Exception($"Greška u tabeli {tableName}: {ex.Message}"); } } } } var formContent = new FormUrlEncodedContent(new[] { new KeyValuePair("id", serverId.ToString()) }); HttpResponseMessage ackResponse = await client.PostAsync("http://cp.mojhosting.ba:65535/Queue/pos_ack.php", formContent); string ackResult = await ackResponse.Content.ReadAsStringAsync(); if (!ackResponse.IsSuccessStatusCode || ackResult.Trim() != "OK") { throw new Exception($"ID {serverId} upisan lokalno, ali server nije vratio potvrdu!"); } } if (ukupnoUpisanoRedova > 0) { using (SqlCommand cmdSp = new SqlCommand("_sin_detaljno_dl", conn)) { cmdSp.CommandType = System.Data.CommandType.StoredProcedure; await cmdSp.ExecuteNonQueryAsync(); } } if (!isSilent) MessageBox.Show($"Sinhronizacija uspješna!\nObrađeno paketa: {brojPaketa}\nFizički upisano/ažurirano redova u bazi: {ukupnoUpisanoRedova}", "Gotovo", MessageBoxButtons.OK, MessageBoxIcon.Information); else trayIcon.ShowBalloonTip(3000, "Sinhronizacija uspješna!", $"Preuzeto paketa: {brojPaketa}\nAžurirano redova: {ukupnoUpisanoRedova}", ToolTipIcon.Info); } } } } } } }