</> NextXbase
1 /* ===========================================================================
2 POSAlaska.prg -- Full-Screen Touch POS Terminal (Alaska Xbase++ 2.0)
3
4 Pure Alaska Xbase++ XbpParts OOP syntax:
5 - XbpDialog():new( AppDesktop(), , aPos, aSize ):create()
6 - Bottom-left origin coordinate system { x, y } on oDlg:drawingArea
7 - XbpStatic(), :caption, :setFontCompoundName(), :setColorFG(), :setColorBG()
8 - GraMakeRGBColor( { r, g, b } ), :itemSelected / :lbClick slots, MsgBox()
9 =========================================================================== */
10
11 #define XBPSTATIC_TEXT_RIGHT 2
12
13 STATIC s_aHeld := {}
14
15 PROCEDURE Main()
16 LOCAL oDlg, oDa, oSay
17 LOCAL oGrid, oCart, oPad
18 LOCAL aCat, aPay, n
19 LOCAL nNavy, nLightBlue, nWhite
20
21 nNavy := GraMakeRGBColor( { 30, 58, 110 } )
22 nLightBlue := GraMakeRGBColor( { 191, 219, 254 } )
23 nWhite := GraMakeRGBColor( { 255, 255, 255 } )
24
25 oDlg := XbpDialog():new( AppDesktop(), , { 0, 0 }, { 1580, 800 } )
26 oDlg:title := "NextXbase -- Touch POS (Alaska Xbase++ dialect)"
27 oDlg:create()
28
29 oDa := oDlg:drawingArea
30
31 /* ---- Header Bar (y measured UP from bottom of 800px drawingArea) ---- */
32 oSay := XbpStatic():new( oDa, , { 0, 740 }, { 1580, 60 } )
33 oSay:caption := ""
34 oSay:setColorBG( nNavy )
35 oSay:create()
36
37 oSay := XbpStatic():new( oDa, , { 24, 764 }, { 400, 28 } )
38 oSay:caption := "NextXbase POS"
39 oSay:setFontCompoundName( "22.Segoe UI.bold" )
40 oSay:setColorFG( nWhite )
41 oSay:setColorBG( nNavy )
42 oSay:create()
43
44 oSay := XbpStatic():new( oDa, , { 24, 746 }, { 400, 18 } )
45 oSay:caption := "Point of Sale Terminal"
46 oSay:setFontCompoundName( "13.Segoe UI" )
47 oSay:setColorFG( nLightBlue )
48 oSay:setColorBG( nNavy )
49 oSay:create()
50
51 oSay := XbpStatic():new( oDa, , { 1150, 768 }, { 220, 20 } )
52 oSay:caption := "Admin"
53 oSay:options := XBPSTATIC_TEXT_RIGHT
54 oSay:setFontCompoundName( "13.Segoe UI" )
55 oSay:setColorFG( nWhite )
56 oSay:setColorBG( nNavy )
57 oSay:create()
58
59 oSay := XbpStatic():new( oDa, , { 1150, 750 }, { 220, 18 } )
60 oSay:caption := "Manager - Terminal 01"
61 oSay:options := XBPSTATIC_TEXT_RIGHT
62 oSay:setFontCompoundName( "13.Segoe UI" )
63 oSay:setColorFG( nLightBlue )
64 oSay:setColorBG( nNavy )
65 oSay:create()
66
67 /* ---- Product Catalog Grid ({ 12, 62 }, { 1150, 574 } -> top 164) ---- */
68 oGrid := TPosProductGrid():new( oDa, , { 12, 62 }, { 1150, 574 } )
69 oGrid:nTileMinWidth := 140
70 oGrid:nImageSize := 96
71 oGrid:setColorBG( GraMakeRGBColor( { 241, 245, 249 } ) )
72 oGrid:create()
73
74 oGrid:addProduct( "d1", "Cola", 2.50, "img/cola.png", "drinks" )
75 oGrid:addProduct( "d2", "Lemon Soda", 2.50, "img/lemon_soda.png", "drinks" )
76 oGrid:addProduct( "d3", "Water", 1.50, "img/water.png", "drinks" )
77 oGrid:addProduct( "b1", "Coffee", 3.00, "img/coffee.png", "beverages" )
78 oGrid:addProduct( "b2", "Hot Tea", 2.25, "img/hot_tea.png", "beverages" )
79 oGrid:addProduct( "f1", "Burger", 8.99, "img/burger.png", "food" )
80 oGrid:addProduct( "f2", "Fries", 3.50, "img/fries.png", "food" )
81 oGrid:addProduct( "f3", "Pizza Slice", 5.99, "img/pizza_slice.png", "food" )
82 oGrid:addProduct( "f4", "Salad", 6.50, "img/salad.png", "food" )
83 oGrid:addProduct( "s1", "Chips", 2.00, "img/chips.png", "snacks" )
84 oGrid:addProduct( "s2", "Candy Bar", 1.50, "img/candy_bar.png", "snacks" )
85 oGrid:addProduct( "x1", "Ice Cream", 4.50, "img/ice_cream.png", "desserts" )
86 oGrid:addProduct( "x2", "Cake Slice", 5.50, "img/cake_slice.png", "desserts" )
87 oGrid:addProduct( "k1", "Croissant", 3.00, "img/croissant.png", "bakery" )
88 oGrid:addProduct( "k2", "Sourdough Loaf", 5.25, "img/sourdough_loaf.png", "bakery" )
89
90 /* ---- Category Filter Tiles (y = 648, height = 80 -> top 72) --------- */
91 aCat := { { "", "All", "bi-grid-3x3-gap-fill", { 107, 114, 128 } }, ;
92 { "drinks", "Drinks", "bi-cup-straw", { 59, 130, 246 } }, ;
93 { "food", "Food", "bi-egg-fried", { 34, 197, 94 } }, ;
94 { "snacks", "Snacks", "bi-basket-fill", { 234, 179, 8 } }, ;
95 { "desserts", "Desserts", "bi-cake2-fill", { 236, 72, 153 } }, ;
96 { "beverages", "Beverages", "bi-cup-hot-fill", { 168, 85, 247 } }, ;
97 { "bakery", "Bakery", "bi-shop", { 249, 115, 22 } } }
98 FOR n := 1 TO Len( aCat )
99 BuildCatTile( oDa, oGrid, aCat[ n ], 12 + ( n - 1 ) * 150 )
100 NEXT
101
102 /* ---- Shopping Cart ({ 1176, 342 }, { 264, 390 } -> top 68) ---------- */
103 oCart := TPosCart():new( oDa, , { 1176, 342 }, { 264, 390 } )
104 oCart:nTaxRate := 0.08
105 oCart:cTitle := "Shopping Cart"
106 oCart:cCheckoutText := "Complete Order"
107 oCart:setColorBG( GraMakeRGBColor( { 10, 10, 10 } ) )
108 oCart:create()
109
110 /* ---- Touch Numerical Pinpad ({ 1176, 62 }, { 264, 270 } -> top 468) -- */
111 oPad := TPosNumpad():new( oDa, , { 1176, 62 }, { 264, 270 } )
112 oPad:setColorBG( GraMakeRGBColor( { 30, 41, 59 } ) )
113 oPad:create()
114
115 /* ---- Payment & Action Tiles (x = 1452, y = 644 - (n-1)*96) ---------- */
116 aPay := { { "CASH", "bi-cash-stack", { 16, 185, 129 } }, ;
117 { "CARD", "bi-credit-card-fill", { 59, 130, 246 } }, ;
118 { "SPLIT", "bi-scissors", { 168, 85, 247 } }, ;
119 { "DISC", "bi-tag-fill", { 245, 158, 11 } }, ;
120 { "HOLD", "bi-pause-fill", { 51, 65, 85 } }, ;
121 { "VOID", "bi-slash-circle", { 239, 68, 68 } }, ;
122 { "CANCEL", "bi-x-lg", { 244, 63, 94 } } }
123 FOR n := 1 TO Len( aPay )
124 BuildPayTile( oDa, oCart, aPay[ n ], 644 - ( n - 1 ) * 96 )
125 NEXT
126
127 /* ---- Callback Slots ------------------------------------------------- */
128 oGrid:itemSelected := { | hProd | oCart:addItem( hProd[ "id" ], hProd[ "title" ], ;
129 hProd[ "price" ], Max( 1, Int( oPad:nValue ) ) ), ;
130 oPad:cEntry := "", oPad:nValue := 0 }
131 oCart:lbClick := { || SettleOrder( oCart, "CARD" ) }
132
133 oDlg:show()
134
135 RETURN
136
137 STATIC FUNCTION BuildCatTile( oDa, oGrid, aC, nLeft )
138 LOCAL oTile, cCatId := aC[ 1 ]
139 oTile := TTile():new( oDa, , { nLeft, 648 }, { 140, 80 } )
140 oTile:caption := aC[ 2 ]
141 oTile:icon := aC[ 3 ]
142 oTile:setColorBG( GraMakeRGBColor( aC[ 4 ] ) )
143 oTile:lbClick := { || oGrid:cFilter := cCatId, oGrid:refresh() }
144 oTile:create()
145 RETURN oTile
146
147 STATIC FUNCTION BuildPayTile( oDa, oCart, aP, nBottomY )
148 LOCAL oTile, cKey := aP[ 1 ]
149 oTile := TTile():new( oDa, , { 1452, nBottomY }, { 96, 88 } )
150 oTile:caption := cKey
151 oTile:icon := aP[ 2 ]
152 oTile:setColorBG( GraMakeRGBColor( aP[ 3 ] ) )
153 oTile:lbClick := { || SettleOrder( oCart, cKey ) }
154 oTile:create()
155 RETURN oTile
156
157 STATIC FUNCTION OrderTotal( oCart )
158 LOCAL nSum := 0, h
159 FOR EACH h IN oCart:aItems
160 nSum += h[ "price" ] * h[ "qty" ]
161 NEXT
162 RETURN nSum * ( 1 + oCart:nTaxRate )
163
164 STATIC FUNCTION SettleOrder( oCart, cKey )
165 LOCAL nTotal := OrderTotal( oCart )
166 LOCAL cTotal := "$" + LTrim( Str( nTotal, 12, 2 ) )
167 LOCAL nSub := 0, h
168
169 DO CASE
170 CASE cKey == "HOLD"
171 IF Empty( oCart:aItems ) .AND. ! Empty( s_aHeld )
172 oCart:aItems := s_aHeld
173 s_aHeld := {}
174 ELSEIF ! Empty( oCart:aItems )
175 s_aHeld := oCart:aItems
176 oCart:aItems := {}
177 ENDIF
178
179 CASE Empty( oCart:aItems )
180 MsgBox( "The order is empty.", cKey )
181 RETURN NIL
182
183 CASE cKey == "CASH" .OR. cKey == "CARD"
184 MsgBox( "Paid " + cTotal + " by " + Lower( cKey ) + ".", "Order complete" )
185 oCart:aItems := {}
186
187 CASE cKey == "SPLIT"
188 MsgBox( "Two ways: $" + LTrim( Str( nTotal / 2, 12, 2 ) ) + " each.", "Split " + cTotal )
189
190 CASE cKey == "DISC"
191 IF AScan( oCart:aItems, { | x | x[ "id" ] == "disc" } ) == 0
192 FOR EACH h IN oCart:aItems
193 nSub += h[ "price" ] * h[ "qty" ]
194 NEXT
195 oCart:addItem( "disc", "Discount 10%", - Round( nSub * 0.10, 2 ), 1 )
196 ENDIF
197
198 CASE cKey == "VOID"
199 ASize( oCart:aItems, Len( oCart:aItems ) - 1 )
200