📜 ⬆️ ⬇️

How I wrote my own ERP system and what came of it

Some time ago I was approached by an acquaintance, the owner of one of the branches of a large network for the delivery of sushi and pizzas. Their current ERP did not suit him at all, since there was no control over the kitchen and the delivery men, and since he has 5 kitchens in the city, this was a serious problem. He suggested that I write a new system for it. The only condition is that since he was in a franchise, and especially with golovnikov, he did not want to spoil relations, it is necessary to integrate with the existing system as covertly as possible.

The first question I have is the development tool. It was possible to take something new-fashioned NodeJS type for the server, and to give the web-interface to the clients - the easiest, but the most brake version (similar solutions require i3). You can register separately the server part, separately each application (windows / android / ios). Or NodeJS server, and C # (xamarin if more precisely) for all clients a single code. Or take the latest Rad studio and write everything on a single code base. Since I understood that different code describing the same things is a potential problem, and that I will be working on the project alone, I chose the latter option.

The second question is architecture. You can quickly type components on the form, with pens or through LiveBindings, to link everything together, and quickly get the result. But any small change in some algorithm requires rebuilding and updating of all programs, and this is a big time cost for testing, so that in another place nothing will break. So I decided to use xml to describe all the tables and interfaces, and remove all the logic in the scripts.
')
To do this, I wrote my own form editor, through RTTI I turned to all the properties and sub-properties.

image

The screenshot shows that the element is associated with the Orders table, but does not display its contents, but a link to the customer, the FirstName column (customer name). Also, when clicked, the atApplyChanges handler will be called, which will apply all changes on the form and close it. Without a single line of code! Naturally, when something changes in the Orders table, a script is called that checks the correctness of the change. Something like:

Little script
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; //  ,     . if IsCashbackOnCurier then begin if PaySum <> LastPaySum then begin ShowMessage('    '); exit; end; Price := GetNewValueF('Orders','Price'); LastPrice := GetOldValueF('Orders','Price'); if Price <> LastPrice then begin ShowMessage('    '); exit; end; end; //   if (CurOrderState >= 6) and (CurOrderState < 8) and (PayType = 1) and (not IsCashbackOnCurier) then begin if (PaySum - OrderPrice) > 0 then begin ss := '    №'+IntToStr(OrderNo); CreateCashboxDoc(ss,1,OrderPrice - PaySum); IsCashbackOnCurier := True; SetValue('Orders','CashbackOnDeliverer',True); end; end; //      if (CurOrderState < 6) and IsCashbackOnCurier then begin if (PaySum - OrderPrice) > 0 then begin CreateCashboxDoc('     №'+IntToStr(OrderNo),1,PaySum - OrderPrice); IsCashbackOnCurier := False; SetValue('Orders','CashbackOnDeliverer',False); end; end; //    if (CurOrderState >= 8) and IsCashbackOnCurier then begin if (PaySum - OrderPrice) > 0 then begin ss := '    №'+IntToStr(OrderNo); CreateCashboxDoc(ss,1,PaySum - OrderPrice); IsCashbackOnCurier := False; SetValue('Orders','CashbackOnDeliverer',False); end; end; //      if (CurOrderState = 9) and (LastOrderState <> 9) and (PayType = 1) then begin ss := '    №'+IntToStr(OrderNo); CreateCashboxDoc(ss,2,OrderPrice); end; //           if (LastOrderState < 3) and (CurOrderState >= 3) then begin if CurTurn <= 0 then begin ShowMessage('  .    .'); exit; end; SetValue('Orders','Turn',CurTurn); CurFilial := GetConstantValue('FilialID'); SetValue('Orders','Filial',CurFilial); end; if (CurOrderState <= 2) and (LastOrderState > 2) then begin SetValue('Orders','Turn',0); end; TransactionCanAccept; 


That is, there will be a check of the shift, the ticket office, if necessary, the wards / 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.

The next point, which is incredibly annoying in all current decisions, is latency. Any action results in a select from the base. Those. ping time + packet transfer + select + gzip is the minimum time after which something will happen for the operator. And when the holidays and not only that the increased load on the operator and even the server begins to slow down, this is where the real monetary losses begin. To prevent this from happening, I cached all the necessary data in RAM, and fetched it from there through requests. The server is strained only by update / insert transactions, and then it sends new and updated data to all subscribers.

With the display of information, I also had to make some interesting optimization. Since we do not have a select from 10 tables, how, for example, from the table of delivery details, find out where to take the order (my point is stored in the FIAS house table)? Like this:

  fids := CComponentBaseLink.ClassGetPrintValue('Orders_Delivery_Details','Order|Orders->CustomerAddress|Customer_address->FiasHouse|Fias_Houses->ID',orid); if not StrToInteger(fids,fid) then Continue; las := g_Base.GetTblValueByID('Fias_Houses','PointLatitude',fid); los := g_Base.GetTblValueByID('Fias_Houses','PointLongitude',fid); 

Those. We went through 3 intermediate tables and compared the order with a point on the map to draw it. Everything is very fast, and without a single request to the server. And drawn in the browser with pens through the appropriate api. That is, since Google has big problems with maps in Russia “in lock”, on android I use Yandex, in which these problems are much less pronounced. Through api 1.1, you don’t even need to receive developer code to display destination points and traffic jams for example.

Naturally, all this had to be somehow mated with (so far) the main business system. The main business system (I will not call it) prints orders to the printer. A small use of the sniffer showed that pdf is printed. 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 header without bothering with the raw socket), but besides printing a parsil printer, read these pdf and read out the order, loading it into the database. But this method carried some limitations. Firstly, not all the necessary information was unloaded, and the operator had to refill a couple of fields, and secondly, it was only order synchronization. For example, delivery flights had to be formed both there and there. Therefore, a proxy was written that analyzed all the information passing through it and this problem was solved.

Courier application.


image

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. But while this may be missing the Internet. And then 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.

In general, the system turned out to be very powerful, with a large number of features implemented, and with even more pledged. And ... was not needed. At the last moment, the customer began to change the initial requirements: he began to speak in an orderly tone, so that I would do this and that, because otherwise it would not work (by the way, these are requirements of completely rewriting everything on another PL, just "so that ") ... To the reasonable question," but it works, "" works faster than the current system, "etc. if I did not hear. On this we parted. In general, now I have an interesting, almost ready, ERP system. There are not enough reports, it would be good to finish (if it is needed) the functionality of the automatic scatter of orders across kitchens, depending on traffic jams and current load of points, etc. To finalize this, as well as to implement additional functionality, is not at all difficult. Until then, my ERP system is looking for a new customer, and I'm a new job. :)

PS Many people, I think, will be interested to know how to develop cross-platform applications in general at RAD Studio. I will express my subjective and biased opinion: it is possible to work with the last studio, with all the updates. There are some problematic components like TWebBrowser, which has difficulty removing itself under all OSs. Or problems with components like TGrid on touch devices. But this is all solved. There is also a big problem that the installed apk ~ is 100Mb. Yes, tin, I agree, but otherwise only benefits. A single code base, a bunch of components out of the box for all occasions, and under FireMonkey they have become much more flexible than vcl. So the main problem of the current Rad studio is the price. Everything else in it is very worthy.

Source: https://habr.com/ru/post/345274/


All Articles