My last
article caused a lot of questions and comments from readers. Indeed it turned out quite crumpled, did not describe everything. I want to try again to present this case, describing it based on the questions received in the comments.
So, given: a branch of a large restaurant business network specializing in the delivery of orders to the house.
Several kitchens scattered around the city. Each kitchen has an order taker, cooks, packaging of the finished product, and delivery men. Customers drop their orders through the site, a mobile application, or call the order taker. Ideally, after confirming the order in an hour, this order should be with the client. Those. the total time for preparation and packaging is 20 minutes, the delivery time is 40 minutes (here it must be said that the case takes place in the city of Millionaire, with appropriate distances and level of traffic jams). In addition to information on the actual composition of the order, the client informs the method of payment (cash / card, whether the change is required, from what amount) and the delivery address.
The main problems of the existing system can be formulated as follows:')
- Lack of proper control over employees at each stage :
- except cooks, no one knows the current load on the kitchen, except couriers - the current load on them.
- Late arrivals are not recorded (if the courier is late for more than 15 minutes - this is not a great credit for the whole company).
- It is difficult to predict how many days chefs, couriers need.
- Permanent forgetting by couriers of the second package, if the order is large, transfer to the kitchen / package, and other resulting things.
- Errors in the delivery addresses , due to which the courier does not always understand where to go: incorrect writing of the streets, house numbers that are physically absent, and other problems due to the lack of control over the correctness of this information.
- Lack of distribution of orders across the kitchens . The load on the kitchen should be about the same. That is, each order must be distributed by points based on workload and traffic jams. It is impossible to implement without implementing the control from p.
Of course, there must also be full support for the cash desk (parishioners / consumables for delivery, cash payment, all kinds of technical flights, for example, to buy food), shifts - for every period of time the point has to be responsible, who manages the cashier, orders, performs other important actions. And other little things that are probably not worth listing here. TK is great.
Many will ask: what about warehouse logistics? How to account for damaged goods, the logic of purchases, etc.? It was decided to implement in the next stage. As well as unloading consumables / ward in 1s, posting salaries through it and so on. That is, first the implementation of operational control, and then accounting.
Why it was impossible to take a ready-made implementation and integrate into this business? There is no such market. Before starting to cooperate with me, the customer was ready to pay a considerable amount of money for the finished system completed for his needs. But this was not found.
Just a special wish of the customer was that everything worked smartly. Its current system, cloudy, barely tosses in the browser on i3. And when, at peak loads, an average of 1 order comes in a minute, watching a few second lags of the interface is a real loss of customers, and, accordingly, money. But the system also had to be with a single server.
So, I took the project.
The development tool was chosen Rad Studio. The studio for some reason is not very popular, but in this case, I, firstly, wanted to get a single code on all devices, since I alone can’t afford to prescribe the same data structure several times in several development environments, but then, with any change, rule in 10 places. And secondly, I get a compiled application on all platforms, which theoretically should always work quickly. But above all, I was worried about speed at the operator’s workplace.
The first thing needed is integration with the existing system. Orders from it must fall into a new one without any delay. A small use of the sniffer showed that their program polls the server once a minute, and, if there is a new order, it prints pdf. After half a day my program was ready, which completely copied the original protocol (up to a byte, thanks that the source code for the delphi component is available and there you can easily correct the http header without bothering with the raw socket), but in addition to printing a parsil on the printer, these pdf and read out the order, loading it into the database.
There is such a wonderful program
pdftotext . Run with parameters
–enc UTF-8 –raw
And we have a .txt file that can be parsed without any problems by extracting all the necessary information.
But this method carried some limitations. Firstly, not all the necessary information was unloaded, and the operator had to refill a couple of fields (for example, change), and secondly, it was just a synchronization of orders. For example, delivery flights had to be formed both there and there. Therefore, a proxy was written, which analyzed all the information passing through it - the whole protocol was there for non-encrypted JSON and this problem was solved.
The second problem is the address base. It is clear that you need to download FIAS, load it into the database. But first, the entire base is not needed, apartments are not needed, outdated records, etc., and in fact it’s not the address itself that is interesting, but the coordinate where to take it. I decided to unload only settlements, streets and houses. And in the table of houses for each added field recording coordinates. But since, to put it mildly, not all houses are in FIAS, we had to add the ability to manually enter the house (and the streets, of course, just in case), but in a format that, in subsequent updates of the database, we could match this entry, and bind the corresponding GUID .
For geocoding, I connected Yandex. In our city he is now the best. So, the address from the PDF is first parsed on the street, house, floor, apartment, and so on, then we look in the database if there is this house, we look at the coordinates. If there are no coordinates, we will geocode, checking for ~ 30 km from the city center, since some points appear in the Far East or Siberia, and there is no settlement at all. With this glitch, Yandex did not understand.
If there is no such address, then we look at the previous uploads. Suddenly, this combination of “street-house” has already been encountered, then we take the coordinates from there. That is, if the street, for example, is written in the original database with an error, then it is enough to fix it 1 time, and not in each new order.
Displays addresses using Yandex cards via api 1.1. It does not require a developer ID for all the needs that I needed. The truth was a serious limitation for TWebBrowser (a component that displays any web page, in this case a map) in it you can run any script from the main program at the right time, but it is not possible to accept the data in any form. However, there is an interesting hack - when you need to accept data, you need to execute the script:
function ApplyPoint(){ var text = 'http://ya.ru/1.htm?&P&&'; text = text + '[' + placemark.getGeoPoint().getY(); text = text + ',' + placemark.getGeoPoint().getX()+']'; window.location = text; };
And in:Run the script:
procedure CMapElement.ApplyEditPoint; begin if not assigned(control) then exit; {$IFDEF CLIENT_BUILD} (control as TTMSFMXWebBrowser).ExecuteJavascript('ApplyPoint();'); {$ENDIF} end;
And then:
procedure CMapElement.WebBrowser1DidFinishLoad(ASender: TObject); var s,s2 : string; p : integer; co : ArrMapCoord; begin if not assigned(control) then exit; {$IFDEF CLIENT_BUILD} {$IFDEF MSWINDOWS} s :=(control as TTMSFMXWebBrowser).FWebBrowser.GetRealURL; {$ELSE} assert(false); {$endif} if (pos('ya.ru', s) > 0) and not AlreadyReload then begin p := Pos('?&',s); delete(s,1,p+1); s2 := s; p := Pos('&&',s); delete(s,1,p+1); delete(s2,p,9999); if s2 = 'P' then
That is, we begin to load the page with the parameters that are encoded in the url, and at the call-back stage, we filter this moment. Such a hack makes using a browser in Delphi very flexible: without problems, move the marker on the map, find out the length of the route, edit the delivery geopoligons, and so on.
Well, it's all necessary to somehow link. I wanted to make not just an ERP system for a specific customer. I wanted it to be quite universal, to be configured from the configuration file, and all the logic and interfaces were not tied to the executable file itself. Now the entire configuration is contained in a single xml file. This file with the entire structure of the database, with all the interfaces and scripts is read by the server, and then the necessary parts are given to the clients.
I also wrote an editor for the interfaces and event handler scripts. Separate scripts are executed when changing any field of certain tables, checking the possibility of this change, and also produce related things (generation of receivers for example). All components of their own, as they have very different properties than the standard. For example, in the columns of the tables, it is immediately indicated what to withdraw, as well as to display text or an icon, for example, card payments / cash.
The script system was written using
Pascalc . The library is over 15 years old, but it is very successful: lightweight, easily expandable and modifiable for any need. Therefore, we enable the compiler directive to follow the Pascal standard on mobile devices
{$ZEROBASEDSTRINGS OFF}
We make several edits like Destroy-> Free, remove the use of win api and here the scripts work without any problems under any OS, and the library will process such a script:
Script example CurOrderState := GetNewValueF('Orders','OrderState'); LastOrderState := GetOldValueF('Orders','OrderState'); IsCashbackOnCurier := GetNewValue('Orders','CashbackOnDeliverer') = 'True'; PayType := GetNewValueF('Orders','PayType'); OldPayType := GetOldValueF('Orders','PayType'); OrderTurn := GetNewValueF('Orders','Turn'); CurTurn := GetConstantValue('CurrentTurn'); OrderNo := GetNewValue('Orders','OrderNo'); OrderPrice := GetNewValueF('Orders','Price'); PaySum := GetNewValueF('Orders','PaySum'); LastPaySum := GetOldValueF('Orders','PaySum'); CurTurn := GetConstantValue('CurrentTurn'); CurCashbox := GetConstantValue('CurrentCashbox'); if (OrderTurn > 0) and (OrderTurn <> CurTurn) then begin ShowMessage(' . â„–'+IntToStr(OrderNo)); exit; end; if (LastOrderState = 9) then begin ShowMessage(' '); exit; end; if (LastOrderState = 8) then begin ShowMessage(' '); exit; end; if (LastOrderState <> 8) and (CurOrderState = 8) then begin TransactionCanAccept; exit; end; if (CurOrderState > 2) and (CurTurn < 1) then begin ShowMessage(' . '); exit; end; if (CurOrderState > 4) and (CurCashbox < 1) and (CurOrderState <> 8) then begin ShowMessage(' . '); exit; end;
That is, there will be a check of the shift, the ticket office, if necessary, parishioners / consumables will be discharged, etc. Also, if you change something in this algorithm, you do not need to re-recompile the program and update everything everywhere. Enough for the client to re-sign. Unfortunately, there was some haste in the case of prescribing an API for scripts. Therefore, for example, the constants specified in the tables in the databases here are just numeric values. But since this can be rewritten at any time, and the code is not complicated at all and not large, I decided to leave it for now.
The next important case is the operator’s program. All data is in the cloud, you need to receive them without delay, generate changes and so on. I decided to get away from the connection with the server in real time. Why do you need to drag the same record 100 times, if you can cache it in RAM, and apply at any time?
I preempt all actual orders, customers, addresses, etc., into memory. That is, not the whole table, which would be stupid, and the memory will end quickly, but only that part of the table that is needed. Like the cache in the processor. If there is data in it, we’ll contact you right away; if not, you’ll have to wait until the server returns it. The mechanism of “subscribing to changes” of tables was also registered: if a record of a table has changed, all subscribers will be notified about this with new data. As a result, the comfort of working with the application in terms of latency has become maximum. Reaction to changes in the filter, reordering, opening an order, etc. - all instantly.
Then a separate courier application was created. It sends the track to the server in the background. That is, the operator can observe the movement of couriers in real time. Also, to confirm delivery of the order, the courier must be within a certain radius from the point of delivery. If at this moment there is no Internet, the program will transmit information that the order is delivered as soon as the Internet appears. Naturally, you can run the Yandex-navigator from the program, which will automatically pave the way to the address; You can view all the addresses on the map or call the customer by pressing 2 buttons, and so on.
All this was tested and earned on one branch “in combat” conditions. There were no complaints. More precisely, at first there were a couple of problems with the courier application - it did not work very responsively if the Internet connection was bad. I additionally cached data, made a series of optimizations, and everything began to work without any lags.
But the project did not receive further development. The customer indicated the following reasons:
- Run “on the cold” courier application 4 seconds. Too long.
- It is necessary to change the programming language and completely rewrite everything because of claim 1.
On this we parted.
Such is the story. I now have not fully completed the system for the restaurant business with delivery and a hole in the budget. Now I think what to do with it. Throw - sorry. But the market is very narrow and quite closed.