mirror of
https://github.com/grocy/grocy.git
synced 2026-04-09 14:06:16 +02:00
Add form to upload kroger receipt data
Add a new menu option to "Upload JSON" that will accept Kroger grocery store json receipt data.
This commit is contained in:
parent
53f1321e19
commit
aa91418364
|
|
@ -139,6 +139,7 @@ Setting('FEATURE_FLAG_TASKS', true);
|
||||||
Setting('FEATURE_FLAG_BATTERIES', true);
|
Setting('FEATURE_FLAG_BATTERIES', true);
|
||||||
Setting('FEATURE_FLAG_EQUIPMENT', true);
|
Setting('FEATURE_FLAG_EQUIPMENT', true);
|
||||||
Setting('FEATURE_FLAG_CALENDAR', true);
|
Setting('FEATURE_FLAG_CALENDAR', true);
|
||||||
|
Setting('FEATURE_FLAG_UPLOAD_JSON', true);
|
||||||
|
|
||||||
|
|
||||||
# Sub feature flags
|
# Sub feature flags
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,44 @@ class StockApiController extends BaseApiController
|
||||||
return $this->GenericErrorResponse($response, $ex->getMessage());
|
return $this->GenericErrorResponse($response, $ex->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function UploadJson(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||||
|
{
|
||||||
|
$requestBody = $request->getParsedBody();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if ($requestBody === null)
|
||||||
|
{
|
||||||
|
throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!array_key_exists('json-data', $requestBody))
|
||||||
|
{
|
||||||
|
throw new \Exception('JSON data is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$default_location_id = $this->getUsersService()->GetUserSetting(GROCY_USER_ID, 'product_presets_location_id');
|
||||||
|
$default_qu_id = $this->getUsersService()->GetUserSetting(GROCY_USER_ID, 'product_presets_qu_id');
|
||||||
|
|
||||||
|
if (!$default_location_id)
|
||||||
|
$default_location_id = $this->getDatabase()->locations()->limit(1)->fetch()['id'];
|
||||||
|
|
||||||
|
if (!$default_qu_id)
|
||||||
|
$default_qu_id = $this->getDatabase()->quantity_units()->limit(1)->fetch()['id'];
|
||||||
|
|
||||||
|
$shopping_location_id = array_key_exists('shopping_location_id', $requestBody) ? $requestBody['shopping_location_id'] : null;
|
||||||
|
$parsedData = json_decode($requestBody['json-data'], true);
|
||||||
|
|
||||||
|
$lastInsertId = $this->getStockService()->AddMultipleProducts($parsedData, $default_qu_id,
|
||||||
|
$default_location_id, $requestBody['dont_add_to_stock'] == "1", $shopping_location_id);
|
||||||
|
return $this->ApiResponse($response, $lastInsertId);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->GenericErrorResponse($response, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function InventoryProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
public function InventoryProduct(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,29 @@ class StockController extends BaseController
|
||||||
'locations' => $this->getDatabase()->locations()->orderBy('name')
|
'locations' => $this->getDatabase()->locations()->orderBy('name')
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function UploadJson(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||||
|
{
|
||||||
|
$location_id = $this->getUsersService()->GetUserSetting(GROCY_USER_ID, 'product_presets_location_id');
|
||||||
|
$location = null;
|
||||||
|
if ($location_id > 0)
|
||||||
|
$location = $this->getDatabase()->locations()->where('id', $location_id).fetch();
|
||||||
|
else
|
||||||
|
$location = $this->getDatabase()->locations()->limit(1)->fetch();
|
||||||
|
|
||||||
|
$qu_id = $this->getUsersService()->GetUserSetting(GROCY_USER_ID, 'product_presets_location_id');
|
||||||
|
$quantity_unit = null;
|
||||||
|
if ($qu_id > 0)
|
||||||
|
$quantity_unit = $this->getDatabase()->quantity_units()->where('id', $qui_id).fetch();
|
||||||
|
else
|
||||||
|
$quantity_unit = $this->getDatabase()->quantity_units()->limit(1)->fetch();
|
||||||
|
|
||||||
|
return $this->renderPage($response, 'uploadjson', [
|
||||||
|
'location' => $location,
|
||||||
|
'quantityunit' => $quantity_unit,
|
||||||
|
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function Inventory(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
public function Inventory(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1760,14 +1760,66 @@ msgstr ""
|
||||||
msgid "Group ingredients by their product group"
|
msgid "Group ingredients by their product group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Unknown store"
|
msgid "Upload json"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Upload Json"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Upload Purchase Data As JSON"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Error while saving, probably an error in the json"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Default Quantity unit purchase"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Don't add old purchases to stock"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "JSON Data"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"Kroger grocery stores allow manual access to past purchases in json form \r\n"
|
||||||
|
"\t\t\tthrough a browser's developer tools."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
"To get this json, open developer tools\r\n"
|
||||||
|
"\t\t\tand navigate to https://www.qfc.com/mypurchases (or another Kroger grocer,\r\n"
|
||||||
|
"\t\t\tsuch as https://www.fredmeyer.com/mypurchases) and look for a call to \r\n"
|
||||||
|
"\t\t\t/mypurchases/api/v1/receipt/details. The response will contain data for the\r\n"
|
||||||
|
"\t\t\tlast five receipts."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Store"
|
msgid "Store"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Default shopping location"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Transaction successfully undone"
|
msgid "Transaction successfully undone"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Unknown store"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Default store"
|
msgid "Default store"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Default location "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Change this value in user settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Default Quantity unit "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Don't add to stock"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
|
|
||||||
27
public/viewjs/uploadjson.js
Normal file
27
public/viewjs/uploadjson.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
$('#upload-json-button').on('click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var redirectDestination = U('/stockoverview');
|
||||||
|
var returnTo = GetUriParam('returnto');
|
||||||
|
if (returnTo !== undefined)
|
||||||
|
{
|
||||||
|
redirectDestination = U(returnTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonData = $('#json-form').serializeJSON({ checkboxUncheckedValue: "0" });
|
||||||
|
|
||||||
|
Grocy.FrontendHelpers.BeginUiBusy("json-form");
|
||||||
|
|
||||||
|
Grocy.Api.Post('uploadjson', jsonData,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = redirectDestination
|
||||||
|
},
|
||||||
|
function (xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.EndUiBusy("json-form");
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably an error in the json', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
12
routes.php
12
routes.php
|
|
@ -128,6 +128,12 @@ $app->group('', function(RouteCollectorProxy $group)
|
||||||
$group->get('/calendar', '\Grocy\Controllers\CalendarController:Overview');
|
$group->get('/calendar', '\Grocy\Controllers\CalendarController:Overview');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Upload json routes
|
||||||
|
if (GROCY_FEATURE_FLAG_UPLOAD_JSON)
|
||||||
|
{
|
||||||
|
$group->get('/uploadjson', '\Grocy\Controllers\StockController:UploadJson');
|
||||||
|
}
|
||||||
|
|
||||||
// OpenAPI routes
|
// OpenAPI routes
|
||||||
$group->get('/api', '\Grocy\Controllers\OpenApiController:DocumentationUi');
|
$group->get('/api', '\Grocy\Controllers\OpenApiController:DocumentationUi');
|
||||||
$group->get('/manageapikeys', '\Grocy\Controllers\OpenApiController:ApiKeysList');
|
$group->get('/manageapikeys', '\Grocy\Controllers\OpenApiController:ApiKeysList');
|
||||||
|
|
@ -249,6 +255,12 @@ $app->group('/api', function(RouteCollectorProxy $group)
|
||||||
$group->get('/calendar/ical', '\Grocy\Controllers\CalendarApiController:Ical')->setName('calendar-ical');
|
$group->get('/calendar/ical', '\Grocy\Controllers\CalendarApiController:Ical')->setName('calendar-ical');
|
||||||
$group->get('/calendar/ical/sharing-link', '\Grocy\Controllers\CalendarApiController:IcalSharingLink');
|
$group->get('/calendar/ical/sharing-link', '\Grocy\Controllers\CalendarApiController:IcalSharingLink');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Upload json routes
|
||||||
|
if (GROCY_FEATURE_FLAG_UPLOAD_JSON)
|
||||||
|
{
|
||||||
|
$group->post('/uploadjson', '\Grocy\Controllers\StockApiController:UploadJson');
|
||||||
|
}
|
||||||
})->add(new CorsMiddleware([
|
})->add(new CorsMiddleware([
|
||||||
'origin' => ["*"],
|
'origin' => ["*"],
|
||||||
'methods' => ["GET", "POST"],
|
'methods' => ["GET", "POST"],
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace Grocy\Services;
|
namespace Grocy\Services;
|
||||||
|
|
||||||
|
use Grocy\Helpers\KrogerToGrocyConverter;
|
||||||
|
|
||||||
class StockService extends BaseService
|
class StockService extends BaseService
|
||||||
{
|
{
|
||||||
const TRANSACTION_TYPE_PURCHASE = 'purchase';
|
const TRANSACTION_TYPE_PURCHASE = 'purchase';
|
||||||
|
|
@ -72,8 +74,8 @@ class StockService extends BaseService
|
||||||
{
|
{
|
||||||
return $this->getDatabase()->stock_current_locations()->where('product_id', $productId)->fetchAll();
|
return $this->getDatabase()->stock_current_locations()->where('product_id', $productId)->fetchAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetProductIdFromBarcode(string $barcode)
|
public function GetProductFromBarcode(string $barcode)
|
||||||
{
|
{
|
||||||
$potentialProduct = $this->getDatabase()->products()->where("',' || barcode || ',' LIKE '%,' || :1 || ',%' AND IFNULL(barcode, '') != ''", $barcode)->limit(1)->fetch();
|
$potentialProduct = $this->getDatabase()->products()->where("',' || barcode || ',' LIKE '%,' || :1 || ',%' AND IFNULL(barcode, '') != ''", $barcode)->limit(1)->fetch();
|
||||||
|
|
||||||
|
|
@ -82,6 +84,25 @@ class StockService extends BaseService
|
||||||
throw new \Exception("No product with barcode $barcode found");
|
throw new \Exception("No product with barcode $barcode found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $potentialProduct;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetProductFromName(string $name)
|
||||||
|
{
|
||||||
|
$potentialProduct = $this->getDatabase()->products()->where("',' || name || ',' LIKE '%,' || :1 || ',%'", $name)->limit(1)->fetch();
|
||||||
|
|
||||||
|
if ($potentialProduct === null)
|
||||||
|
{
|
||||||
|
throw new \Exception("No product with name $name found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $potentialProduct;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetProductIdFromBarcode(string $barcode)
|
||||||
|
{
|
||||||
|
$potentialProduct = $this->GetProductFromBarcode($barcode);
|
||||||
|
|
||||||
return intval($potentialProduct->id);
|
return intval($potentialProduct->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -598,6 +619,72 @@ class StockService extends BaseService
|
||||||
return $this->getDatabase()->lastInsertId();
|
return $this->getDatabase()->lastInsertId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function AddMultipleProducts($data, $defaultQuantityUnits, $defaultLocation, $dontAddToStock, $shoppingLocation)
|
||||||
|
{
|
||||||
|
$products = KrogerToGrocyConverter::ConvertJson($data, $defaultQuantityUnits, $defaultLocation);
|
||||||
|
|
||||||
|
foreach ($products as &$product)
|
||||||
|
{
|
||||||
|
$existingProduct = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$existingProduct = $this->GetProductFromBarcode($product["barcode"]);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$existingProduct = $this->GetProductFromName($product["name"]);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
if (defined(GROCY_STOCK_BARCODE_LOOKUP_PLUGIN) && GROCY_STOCK_BARCODE_LOOKUP_PLUGIN != "DemoBarcodeLookupPlugin")
|
||||||
|
{
|
||||||
|
$existingProduct = $this->ExternalBarcodeLookup($product["barcode"], true /*addFoundProduct*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existingProduct == null)
|
||||||
|
{
|
||||||
|
$existingProduct = array(
|
||||||
|
'name' => $product['name'],
|
||||||
|
'location_id' => $product['location_id'],
|
||||||
|
'qu_id_purchase' => $product['qu_id_purchase'],
|
||||||
|
'qu_id_stock' => $product['qu_id_stock'],
|
||||||
|
'qu_factor_purchase_to_stock' => $product['qu_factor_purchase_to_stock'],
|
||||||
|
'default_best_before_days' => $product['default_best_before_days'],
|
||||||
|
'barcode' => $product['barcode'],
|
||||||
|
'picture_url' => $product['picture_url'],
|
||||||
|
'shopping_location_id' => $shoppingLocation,
|
||||||
|
'cumulate_min_stock_amount_of_sub_products' => 1,
|
||||||
|
'min_stock_amount' => 1
|
||||||
|
);
|
||||||
|
$newRow = $this->getDatabase()->products()->createRow($existingProduct);
|
||||||
|
$newRow->save();
|
||||||
|
|
||||||
|
$existingProduct['id'] = $newRow->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$bestBeforeDays = -1;
|
||||||
|
if ($existingProduct['default_best_before_days'] > -1)
|
||||||
|
{
|
||||||
|
$bestBeforeDays = $existingProduct['default_best_before_days'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->AddProduct($existingProduct['id'], $product["quantity"], null /*newBestBeforeDate*/, StockService::TRANSACTION_TYPE_PURCHASE,
|
||||||
|
$product["transaction_date"], $product["price_paid"], $existingProduct['location_id'], $existingProduct['shopping_location_id']);
|
||||||
|
|
||||||
|
|
||||||
|
if ($dontAddToStock)
|
||||||
|
{
|
||||||
|
$this->ConsumeProduct($existingProduct['id'], $product['quantity'], false /*spoiled*/, self::TRANSACTION_TYPE_INVENTORY_CORRECTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getDatabase()->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
public function EditStockEntry(int $stockRowId, int $amount, $bestBeforeDate, $locationId, $shoppingLocationId, $price, $open, $purchasedDate)
|
public function EditStockEntry(int $stockRowId, int $amount, $bestBeforeDate, $locationId, $shoppingLocationId, $price, $open, $purchasedDate)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"Version": "2.6.2",
|
"Version": "2.6.3",
|
||||||
"ReleaseDate": "2020-03-29"
|
"ReleaseDate": "2020-03-29"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,14 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@endif
|
@endif
|
||||||
|
@if(GROCY_FEATURE_FLAG_UPLOAD_JSON)
|
||||||
|
<li class="nav-item nav-item-sidebar" data-toggle="tooltip" data-placement="right" title="{{ $__t('Upload json') }}" data-nav-for-page="upload-json">
|
||||||
|
<a class="nav-link discrete-link" href="{{ $U('/uploadjson') }}">
|
||||||
|
<i class="fas fa-fire"></i>
|
||||||
|
<span class="nav-link-text">{{ $__t('Upload Json') }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
@endif
|
||||||
|
|
||||||
@php $firstUserentity = true; @endphp
|
@php $firstUserentity = true; @endphp
|
||||||
@foreach($userentitiesForSidebar as $userentity)
|
@foreach($userentitiesForSidebar as $userentity)
|
||||||
|
|
|
||||||
71
views/uploadjson.blade.php
Normal file
71
views/uploadjson.blade.php
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
@extends('layout.default')
|
||||||
|
|
||||||
|
@section('title', $__t('Upload Purchase Data As JSON'))
|
||||||
|
@section('activeNav', 'uploadjson')
|
||||||
|
@section('viewJsName', 'uploadjson')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h1>
|
||||||
|
@yield('title')
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6 col-xs-12">
|
||||||
|
<form id="json-form" novalidate>
|
||||||
|
|
||||||
|
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="location_id">{{ $__t('Default location ') . $location->name }}</label>
|
||||||
|
<p class="small text-muted">{{ $__t('Change this value in user settings') }}</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="qu_id_purchase">{{ $__t('Default Quantity unit ') . $quantityunit->name }}</label>
|
||||||
|
<p class="small text-muted">{{ $__t('Change this value in user settings') }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="shopping_location_id">{{ $__t('Default store') }}</label>
|
||||||
|
<select class="form-control input-group-qu" id="shopping_location_id" name="shopping_location_id">
|
||||||
|
<option></option>
|
||||||
|
@foreach($shoppinglocations as $location)
|
||||||
|
<option value="{{ $location->id }}">{{ $location->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="dont_add_to_stock" name="dont_add_to_stock" value="1">
|
||||||
|
<label class="form-check-label" for="dont_add_to_stock">{{ $__t("Don't add to stock") }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="json-data">{{ $__t('JSON Data') }}</label>
|
||||||
|
<textarea class="form-control" rows="75" id="json-data" name="json-data" placeholder="{'data': [{'items': [{}]}] }"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="upload-json-button" class="btn btn-success d-block">{{ $__t('OK') }}</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6 col-xs-12">
|
||||||
|
<p>
|
||||||
|
{{ $__t("Kroger grocery stores allow manual access to past purchases in json form
|
||||||
|
through a browser's developer tools.") }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ $__t("To get this json, open developer tools
|
||||||
|
and navigate to https://www.qfc.com/mypurchases (or another Kroger grocer,
|
||||||
|
such as https://www.fredmeyer.com/mypurchases) and look for a call to
|
||||||
|
/mypurchases/api/v1/receipt/details. The response will contain data for the
|
||||||
|
last five receipts.") }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@stop
|
||||||
Loading…
Reference in New Issue
Block a user