📜 ⬆️ ⬇️

JSON API My Warehouse, self-learning

There is such a way of self-learning - as the implementation of test tasks. Its advantage is that the volume of the task is finite, the time limits are limited. It does not allow you to stretch the tires to infinity or selflessly draw the twists and curls of architectural delights.

A nice bonus of this skill training is to get acquainted with new technologies and business processes, which among other things are in demand in real-world tasks.

This time it was necessary to make a page for the formation of the order of the buyer in the service "My warehouse". For me, this is like a flight to the moon: in web development I’m a little less than a newbie, I know the front-end only by hearsay, and here you need to develop a whole page, oh, you Jojik!
Any criticism and advice are welcome.

There are a lot of curses in comments, my decision is so awful that for him they did refactoring into something decent:
michael_vostrikov
From nothing to do, I did a little refactoring of this task (although there is a lot more that can be changed), not so much for you, as for those who later find this article in the search:
commits , markup , form submission .

Go!


First of all, of course google, only the link to the documentation , tutorials, examples - zero.
')
Still googled: “JSON API is available for subscribers on all rates, except Free” Wooops! Of course, no one gave me a paid one, I didn’t buy it, but I thought that if they gave such a task, then probably on Free it is functioning there and continued to work.

And of course, “moysklad-client — npm — a JavaScript client for comfortable work with the API of the MySklad service,” but with JS exclusively on “You”, and according to the terms of the task, you need to write in PHP. So even did not understand what can be done there on JS.

The first


The first thing to do is to get acquainted with the documentation. I got acquainted.
The second is to make a plan. Made up.
Plan, start.
The first step is authorization.
The second step is to show the Nomenclature list.
The third step is to add a buyer's order.
The fourth action is to add Positions to the Buyer's Order.
The goal is reached, the end of the plan.

Authorization


I saw the code in which cUrl was used to communicate with the API. I don’t know what cUrl is, I don’t know how to communicate with the API, but if there is code that can be copied, then its suitability is not difficult. I copied pasted, processed with a file - it turned out.

I will not bore you with intimate details about the friendship of a file with copy-paste, here's the working code:

function setupCurl($apiSettings) { $curl = curl_init(); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false); $userName = $apiSettings[MOYSKLAD_USERNAME]; $userPassword = $apiSettings[MOYSKLAD_PASSWORD]; curl_setopt($curl, CURLOPT_USERPWD, "$userName:$userPassword"); curl_setopt($curl, CURLOPT_USERAGENT, $apiSettings[MOYSKLAD_USER_AGENT]); return $curl; } 

Curl options:


The remaining options do not know why we need stupid copy-paste.
It’s not a fact that authorization headers should be sent in every request, although it’s not a fact that they were not reset after the first sending ... who can tell?

So that was the initialization of the curl object for messaging with the API server.

Using:

 function curlExec($curlObject) { $response = curl_exec($curlObject); $curlErrorNumber = curl_errno($curlObject); if ($curlErrorNumber) { throw new Exception(curl_error($curlObject)); } return $response; } 

Pure copy-paste, don't ask me why.

Show the list of Nomenclature


Some items were not enough, for the order of the buyer, you must specify the legal entity of the supplier and the counterparty of the buyer. The owner of the My Warehouse account may have several legal entities, counterparties - 100500 is clearly understandable, but the specific order of the buyer is the order of a specific Counterparty to a specific Legal entity.

Therefore, with the nomenclatures we pitch, we will deal with the parties to the "contract" - the deal.

Legal entities

 $curl = setCurl( $curl, $apiSettings[MOYSKLAD_API_URL] . $apiSettings[MOYSKLAD_GET_JURIDICAL_PERSON], $apiSettings[MOYSKLAD_GET_JURIDICAL_PERSON_METHOD]); $persons = getJuridicalPerson($curl); function setCurl(&$curlObject, $uri, $method) { curl_setopt($curlObject, CURLOPT_URL, $uri); curl_setopt($curlObject, CURLOPT_HTTPGET, true); switch ($method) { case MOYSKLAD_METHOD_GET: break; case MOYSKLAD_METHOD_POST: curl_setopt($curlObject, CURLOPT_POST, true); break; case MOYSKLAD_METHOD_PUT: curl_setopt($curlObject, CURLOPT_PUT, true); break; } return $curlObject; } function getJuridicalPerson($curlObject) { $response = curlExec($curlObject); $data = json_decode($response, true); $result = $data['rows']; return $result; } 

I apologize for the terrible names of the constants, but I’m not going to confuse with such calm, as if with nothing. Yes, I know that case (switch) has a default branch, but it’s easier for me to embed the default value in the code and not to hope for the opposite of fate with the case.

Each API command has its own address and its own method, setCurl - sets the address and method.

To get the list of legal entities, we set the appropriate address and method (the address and method are set in the settings, the settings are loaded by the function method getSettings () {$ apiConfig = include ('moysklad_curl_details.php'); return $ apiConfig;}).

After that, we use the getJuridicalPerson method to execute curl, get the response in JSON, and only take the 'rows' array from the response. Received, saved, postponed.

We are doing the same with Counterparties: setCurl => getCounterparty, Nomenclature using the same algorithm: setCurl => getNomenclature.

If it was not a test for two evenings after work, but for two days of an unemployed specialist, then it would be possible to automate it, but it was a test in the style - “if only it worked,” so I didn’t bother.

For me, the purpose of the test was to sip and taste the JSON API, draw beauty - there was no goal.

The data received is not a question at all, it is a stupid thing - it is not a tricky business, it was more interesting to take it to a page and then pick it up from a page, that was a problem.

Frontend


I do not know how correctly, I did this:

 echo '<form action="#" onsubmit="return false;" id="orderForm" ><p>  :<br />'; foreach ($persons as $key => $person) { $personId = $person['id']; echo '<label for="' . $personId . '">' . $person['name'] . '</label><input type="radio" data-organization-type="1" id="' . $personId . '" name="organization"><br />'; } echo ' :<br />'; foreach ($counterparty as $key => $person) { $personId = $person['id']; echo '<label for="' . $personId . '">' . $person['name'] . '</label><input type="radio" data-counterparty-type="1" id="' . $personId . '" name="counterparty"><br />'; } echo ' :<br />'; foreach ($nomenclature as $key => $position) { $positionId = $position['id']; echo '<label for="' . $positionId . '">' . $position['name'] . ',    => </label><input type="text" id="' . $positionId . '" data-position-type="1"><br />'; } echo ' <input type="submit" name="  " onclick="sendOrder();"><br /></p></form>' 

General algorithm:

  1. write the name of the section
  2. write the name of the position
  3. write the input tag, in the id attribute we write the identifier obtained from the API,
  4. we write the corresponding attribute data-organization-type / data-counterparty-type / data-position-type, because to get the attribute you need to assign a value, then assign it

(in general, of course, you can use attributes without values, but I did not figure out how).

By clicking on the button “Generate customer order”, the form is not sent - “return false;”, but the function is called - “sendOrder ();”.

send an order


 echo ' <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" type="text/javascript"></script> <script type="text/javascript"> function sendOrder(){ var $text_field = $('#orderForm :input:text'); var position = {}; $text_field.each(function() { var this_val = $(this).val(); var may_assign = this_val>0 || this_val !=""; var is_it_position = $(this).data('position-type'); if ( may_assign && is_it_position > 0){ position[this.id] = this_val; } }); var $radio_field = $('#orderForm :input:radio:checked'); var counterparty = {}; var organization = {}; $radio_field.each(function() { var this_val = $(this).val(); var is_it_counterparty = $(this).data('counterparty-type'); var is_it_organization = $(this).data('organization-type'); var may_assign = this_val>0 || this_val !=""; if ( may_assign && is_it_counterparty > 0){ counterparty[this.id] = this_val; } if ( may_assign && is_it_organization > 0){ organization[this.id] = this_val; } }); 

C JS seems to me more than transparent:

  1. $ ('# orderForm: input: text'); - selected all input tags with the text type inside the tag with the orderForm identifier
  2. $ text_field.each - for each element we perform an anonymous function
  3. var this_val = $ (this) .val (); - retained value
  4. var may_assign = this_val> 0 || this_val! = ""; - calculated that the value is not empty
  5. var is_it_position = $ (this) .data ('position-type'); - calculated that the data-position-type attribute is set
  6. if (may_assign && is_it_position> 0) {position [this.id] = this_val; } - if the value is not empty and this input corresponds to the position, then we add the corresponding element to the position array, the identifier as an index guarantees uniqueness.

We perform the same acrobatics with legal entities and counter agents, with the difference that for the analysis we select all inputs with the type “radio” in the “checked” state:

$ ('# orderForm: input: radio: checked'), and besides, we don’t need the value of the input element, we just need to know who (one) from the entire list was selected by our Buyer.

Now that the data to be sent to processing is ready, you need to create a request:

  var postData = JSON.stringify({position : position, counterparty : counterparty , organization : organization}); console.log(postData); $.ajax({ type: "POST", url: "moyskald_add_order.php", data: postData, contentType: "application/json; charset=utf-8", dataType: "text", timeout: 10000, error: function(){ alert("  "); }, success: function(data){alert(data);}, failure: function(errMsg) { alert(errMsg); } }); 

I have nothing to add to this copy-paste, the sending method is “POST”, the processing will be executed - “moyskald_add_order.php”, the data is sent in the format - “application / json; charset = utf-8 ", come in a format -" text ".

The data is not needed at all, although of course it is a good tone to inform the user “Your order is accepted” or “Failed to add an order”, and ok.

We go further, the next point of arrival - "Processing".

Treatment


There was a misfire with the treatment. I am a lamer enikeyschik and for development I use XAMPP (under Win10), which I once set up and forgot. And here something is configured there, that I get GET requests to the PHP script as a normal person, and POST requests, only through one place.

But GET is not Camille, because GET is cached, and it’s not the fact that the server’s response will be exactly the same as last time, especially when you are developing, and the server logic changes every five minutes.

Therefore, we had to accept the use of black magic in the form of file_get_contents ("php: // input"), because, whatever I did, but var_export ($ POST) consistently produced "array ()". By the way, I will be grateful for a series of kicks in the right direction.

And then everything is simple:

 $data = json_decode($rawData, true); $rawPosition = $data['position']; $rawCounterparty = $data['counterparty']; $rawOrganization = $data['organization']; 

Disassembled input data.

Legal entity and contractor pulled:

 const FIRST_INDEX = 0; $counterpartyId = $counterpartyIdCollection[FIRST_INDEX]; $organizationId = $organizationIdCollection[FIRST_INDEX]; 

Formed query fields:

 $textAddCustomerOrder = ' { "name": "' . time() . '", "organization": { "meta": { "href": "https://online.moysklad.ru/api/remap/1.1/entity/organization/' . $organizationId . '", "type": "organization", "mediaType": "application/json" } }, "agent": { "meta": { "href": "https://online.moysklad.ru/api/remap/1.1/entity/counterparty/' . $counterpartyId . '", "type": "counterparty", "mediaType": "application/json" } } } '; $apiSettings = getSettings(); $curl = setupCurl($apiSettings); $curl = setCurl( $curl, $apiSettings[MOYSKLAD_API_URL] . $apiSettings[MOYSKLAD_ADD_CUSTOMER_ORDER], $apiSettings[MOYSKLAD_ADD_CUSTOMER_ORDER_METHOD]); curl_setopt($curl, CURLOPT_POSTFIELDS, $textAddCustomerOrder); curl_setopt($curl, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json', 'Content-Length: ' . strlen($textAddCustomerOrder)) ); 

Then it turned out to shuffle and fill in JSON without json_encode, stupidly paste the necessary identifier into the right places. Be sure to do POSTFIELDS - $ textAddCustomerOrder, HTTPHEADER - 'Content-Length:'. strlen ($ textAddCustomerOrder).

send a request for processing to the API server: $ customerOrderId = setCustomerOrder ($ curl), in the response we take away 'id'.

Order has been added.

Add Items to Buyer’s Order


With this item of the Plan, there are no problems, except the need to use json_encode to format the request text and floatval for the amount of goods in the position. Without floatval, the API server generates a format error for the “quantity” field (and “reserve”, respectively).

 $isPositionArray = is_array($rawPosition); $orderPositions= array(); if ($isPositionArray) { foreach ($rawPosition as $id => $quantity) { $positionQuantity=floatval($quantity); $orderPositions[] = [ "quantity" =>$positionQuantity, "price"=>0, "discount"=>0, "vat"=>0, "assortment" =>[ "meta"=>[ "href"=>"https://online.moysklad.ru/api/remap/1.1/entity/product/$id", "type"=>"product", "mediaType"=>"application/json" ] ], "reserve"=>$positionQuantity, ]; } } 

Remark: foreach ($ rawPosition as $ id => $ quantity), in the keys of the array there are position identifiers, in the values ​​of the array elements - the quantity for the order.

My feng shui requires creating each array separately and adding all the "elementary" arrays to the general heap, but ... I think it will be excessive fanaticism.

I'm not sure that a reserve should be set up in the buyer's order, but I think this is more a business requirement than the mechanics of working with the API.

With the price it was ridiculous, the task required to display a list of nomenclatures, for me it’s just a list of items and corresponding TTX, in which the price is not included, but if you think about it, the price is the very first TTX in such an application, but I have no price on the order form , therefore - passed.

With the sending of the API request, now everything seems to me very clear:

 $jsonResponse = 'empty'; $isContainPosition = count($orderPositions)>0; if($isContainPosition ){ $jsonOrderPositions= json_encode($orderPositions); $curl = setupCurl($apiSettings); $curl = setCurl( $curl, $apiSettings[MOYSKLAD_API_URL] . $apiSettings[MOYSKLAD_ADD_ORDER_POSITION_PREFIX] . $customerOrderId . $apiSettings[MOYSKLAD_ADD_ORDER_POSITION_SUFFIX], $apiSettings[MOYSKLAD_ADD_ORDER_POSITION_METHOD]); curl_setopt($curl, CURLOPT_POSTFIELDS, $jsonOrderPositions); curl_setopt($curl, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json', 'Content-Length: ' . strlen($jsonOrderPositions)) ); $jsonResponse = setCustomerOrderPosition($curl); } 

The address command of the request is “calculated” in a somewhat strange way:
 $apiSettings[MOYSKLAD_API_URL] . $apiSettings[MOYSKLAD_ADD_ORDER_POSITION_PREFIX] . $customerOrderId . $apiSettings[MOYSKLAD_ADD_ORDER_POSITION_SUFFIX] 

The documentation should be: "/ entity / customerorder / {id} / positions".

Even a native PHP line type, take it straight and write - {id} to substitute itself, but I can’t allow a hardcode to write an API command, of course, there is no command that the command will not change at the time of writing the test, but Feng Shui requires you to put such global things into constants Amen. Although when I look at the sources of all open source frameworks, I see that the hardcode is pretty much there, but they do it, and I do it, and I don’t.

Epilogue


That's all. The API, at the Free Rate, handles requests last, so sometimes a timeout of 10 seconds is not enough. The rest is stable. I checked.
But our goal, of course, was not the case; as a result of the assignment, we learned how to work with the JSON Web API and learned how to muddle the front-end with the output of information in the form and passing the user input for processing to the server script.
Hooray :)

PostScriptum


Before writing the article, I went a little more closely on the topic “php json api my warehouse” and found a lot of implementations, but not for JSON, but for XML. Of the dozen found, the couple looks like a real worker, but this is not relevant because the XML API is threatened to be disabled from March 31, 2017.

Or maybe everything will be the same as with “JSON API is available for subscribers on all tariffs, except Free”.

Constructive criticism and links to best practics WELCOME! I'm not ashamed to be a collective farmer, but it would not be nice if it is better?

Links


  1. Git

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


All Articles