A Windows Forms point-of-sale and management application for a small coffee shop —
staff take orders at the till, managers handle products and reporting from an admin panel.
The ordering logic used to live inside the form's click handlers, wired straight to MySQL.
Pulling it into kafeSistemi/Siparis so it could be tested at all turned up three defects:
Two waiters could open two tabs on one table. Adding the first item read the table's
state, saw it free, then opened a tab and marked it occupied. Two terminals doing that at
the same moment both read free, and both opened a tab. The order screen only ever shows
the newest one (ORDER BY sipID DESC LIMIT 1), so everything written to the other tab was
never billed — the customer drank it and nobody found out. Claiming the table is now a
single conditional statement:
UPDATE Masalar SET durum = 1 WHERE masaID = @id AND durum = 0Exactly one request can affect a row; the loser is told so and writes to the winner's tab instead of opening a second one.
Closing a bill was two statements with no transaction. It freed the table, then wrote the total to the order row. If the second failed, the table was open for the next customer and the sale had never been recorded — with nothing left to say it happened. Both now run in one transaction.
Category headings mangled Turkish. ToUpper() on the machine's culture renders
Sıcak as SıCAK on any non-Turkish Windows, and ToUpperInvariant() does it everywhere.
The menu is Turkish, so the culture is now stated explicitly rather than inherited from
whatever machine the till happens to run on.
32 tests. They cover the decisions — who claimed the table, what the bill adds up to, how
the menu is grouped — against an in-memory fake. That the conditional UPDATE is genuinely
atomic is MySQL's guarantee, not something these tests prove; they prove the code asks for
it correctly.
The application is organised around three screens:
| Form | Role |
|---|---|
GirisForm |
Role selection — choose the waiter till or the manager panel |
Form1 |
Table layout — shows which tables are free and which have an open tab |
SiparisForm |
Order screen — the day-to-day point-of-sale interface |
YoneticiForm |
Manager panel — products, tables and the daily sales report |
GirisFormselects a role; it does not ask for a password. That is deliberate for a till running on a machine behind the counter, but it means the manager panel is reachable by anyone with physical access to the terminal — worth knowing before deploying it anywhere the screen is not supervised.
Data access goes through a single DatabaseHelper, which reads the connection string
from App.config and hands out MySQL connections to the forms. Every query is
parameterised — no SQL is built by string concatenation anywhere in the project.
schema.sql creates the database, four tables and some sample rows.
| Table | Purpose |
|---|---|
Masalar |
Tables in the shop; durum is 0 free / 1 occupied |
Urunler |
Products with price and category |
Siparisler |
One tab per table visit, with the running total |
SiparisDetay |
Line items — product, quantity and the price at the time of sale |
The price is copied onto the line rather than joined from Urunler when reporting.
Changing a product's price should not retroactively rewrite what yesterday's customers
were charged.
- C# on .NET Framework 4.7.2
- Windows Forms for the desktop UI
- MySQL via
MySql.Data.MySqlClient
kafeSistemi/
├── GirisForm.cs Login screen
├── SiparisForm.cs Order / point-of-sale screen
├── YoneticiForm.cs Manager panel
├── DatabaseHelper.cs MySQL connection factory
├── Program.cs Application entry point
├── packages.config NuGet dependencies
└── Siparis/ Ordering logic, kept out of the forms so it can be tested
├── Adisyon.cs A table's open bill and what it adds up to
├── Menu.cs The menu, and grouping it under category headings
├── ISiparisDeposu.cs What the ordering flow needs from the database
├── MySqlSiparisDeposu.cs The MySQL implementation
└── SiparisServisi.cs Who claimed the table, and what follows from that
kafeSistemi.Tests/ xUnit, against an in-memory fake — no MySQL needed
dotnet test kafeSistemi.Tests/kafeSistemi.Tests.csproj # 32 tests, no database requiredThe main project uses packages.config, which dotnet restore cannot read. If the build
complains that MySql cannot be found, the packages are simply not on disk yet:
nuget restore kafeSistemi/packages.config -PackagesDirectory packagesOpening the solution in Visual Studio does this for you.
Requirements
- Visual Studio 2022 (or newer) with .NET Framework 4.7.2 developer pack
- A running MySQL server
Setup
- Clone the repository and open
kafeSistemi.slnxin Visual Studio. - Create the database and seed data:
mysql -u root -p < schema.sql - Set the connection string — see below.
- Restore NuGet packages and press F5.
Connection string
Credentials live in App.config, not in source. DatabaseHelper reads the KafeDb
entry and throws a clear configuration error if it is missing, rather than failing later
with a connection error that says nothing useful:
<connectionStrings>
<add name="KafeDb"
connectionString="server=localhost;database=bmRoastery;user=YOUR_USER;password=YOUR_PASSWORD;"
providerName="MySql.Data.MySqlClient" />
</connectionStrings>The value committed here is a placeholder. Keep real credentials on your own machine —
App.configis tracked, so anything written into it is published.
Built to practise desktop application development in C#: multi-form navigation, data binding against a relational database, and separating the customer-facing till from the manager-only administration screen.
Built by Hüseyin Kutsi Balcı