<?php
namespace ApplicationBundle\Modules\Api\Controller;
use ApplicationBundle\ApplicationBundle;
use ApplicationBundle\Entity\BrandCompany;
use ApplicationBundle\Entity\InvProductCategories;
use ApplicationBundle\Entity\InvProducts;
use ApplicationBundle\Modules\Api\Constants\ApiConstants;
use ApplicationBundle\Constants\GeneralConstant;
use ApplicationBundle\Controller\GenericController;
use ApplicationBundle\Helper\Generic;
use ApplicationBundle\Interfaces\PublicApiInterface;
use ApplicationBundle\Modules\Authentication\Constants\UserConstants;
use ApplicationBundle\Modules\Inventory\Inventory;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
class PublicApiController extends GenericController implements PublicApiInterface
{
public function DecodeQrAction(Request $request)
{
$qrTypes = [
'_APPLICANT_', '_COMPANY_', '_DOCUMENT_'
];
$applicantId = $request->request->get('applicant_id');
$encData = $request->request->get('data', $request->request->get('query', ''));
$getRelData = $request->request->get('getRelData', 1);
$qrType = $request->request->get('qrType', '_UNSET_');
$decryptedData = [
'relData' => [],
];
//////decode qr data
$tvp = $this->get('url_encryptor')->decrypt($encData);
$decryptedData = json_decode($tvp, true);
if ($decryptedData == null) $decryptedData = [];
$decryptedData['relData'] = [];
if ($qrType == '_UNSET_')
$qrType = $decryptedData['qrType'] ?? '_APPLICANT_';
if ($qrType == '_APPLICANT_') {
$urlToCall = GeneralConstant::HONEYBEE_CENTRAL_SERVER . '/get_applicant_data_central';
$applicantId = $decryptedData['applicantId'] ?? $applicantId;
$data = [
'applicantId' => $applicantId
];
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_POST => 1,
CURLOPT_URL => $urlToCall,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_HTTPHEADER => array(),
CURLOPT_POSTFIELDS => $data
));
$retData = curl_exec($curl);
$errData = curl_error($curl);
curl_close($curl);
$retDataObj = json_decode($retData, true);
$decryptedData['centralData'] = $retDataObj['centralData'];
$decryptedData['relData'] = $retDataObj['centralData'];
return new JsonResponse($retDataObj['centralData']);
}
// return $decryptedData;
return new JsonResponse($decryptedData
);
}
public function EncodeQrAction(Request $request)
{
$data = $request->request->get('data', $request->request->get('query', ''));
$getRelData = $request->request->get('getRelData', 1);
$qrType = $request->request->get('qrType', '_UNSET_');
if (is_array($data) || is_object($data)) $data = json_encode($data);
$encryptedData = $this->get('url_encryptor')->encrypt($data);
return new JsonResponse(
$encryptedData
);
}
public function ProductSkuNextCodeAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$prefix = trim((string)$request->request->get('prefix', ''));
$categoryId = (int)$request->request->get('category_id', 0);
$currentProductId = (int)$request->request->get('product_id', 0);
if ($prefix === '') {
return new JsonResponse([
'success' => false,
'message' => 'Category prefix is required.'
]);
}
$qb = $em->getRepository('ApplicationBundle\\Entity\\InvProducts')
->createQueryBuilder('p')
->select('p.skuCode')
->where('p.skuCode IS NOT NULL')
->andWhere('p.skuCode <> :emptySku')
->andWhere('p.skuCode LIKE :skuPrefix')
->setParameter('emptySku', '')
->setParameter('skuPrefix', $prefix . '%');
if ($currentProductId > 0) {
$qb->andWhere('p.id <> :currentProductId')
->setParameter('currentProductId', $currentProductId);
}
$skuRows = $qb->getQuery()->getArrayResult();
$maxSerial = 0;
foreach ($skuRows as $skuRow) {
$skuCode = trim((string)($skuRow['skuCode'] ?? ''));
if ($skuCode === '') {
continue;
}
if (preg_match('/^' . preg_quote($prefix, '/') . '(\d+)$/', $skuCode, $matches)) {
$serial = (int)$matches[1];
if ($serial > $maxSerial) {
$maxSerial = $serial;
}
}
}
return new JsonResponse([
'success' => true,
'prefix' => $prefix,
'category_id' => $categoryId,
'nextNumber' => $maxSerial + 1,
'skuCode' => $prefix . ($maxSerial + 1),
]);
}
public function ConvertMlParsedProductInfoToLocalAction(Request $request, $id)
{
$systemType = $this->container->getParameter('system_type') ?: '_ERP_';
$em_goc = $this->getDoctrine()->getManager();
$em = $this->getDoctrine()->getManager();
$success = true;
$data = $request->request->get('data', Inventory::GetImmutableKeysForProductSyncFromCentralToLocal());
if ($systemType == '_ERP_') {
//first send to central server to see if match found
$urlToCall = GeneralConstant::HONEYBEE_CENTRAL_SERVER . '/api/convertMlParsedProductInfoToLocal';
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_URL => $urlToCall,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_HTTPHEADER => [],
CURLOPT_POSTFIELDS => http_build_query([
'data' => $data
])
]);
$retData = curl_exec($curl);
$errData = curl_error($curl);
curl_close($curl);
if ($errData) {
return new JsonResponse(['success' => false, 'data' => []]);
}
$retData = json_decode($retData, true);
$data = $retData['data'];
$em = $this->getDoctrine()->getManager('company_group');
$em->getConnection()->connect();
$connected = $em->getConnection()->isConnected();
$session = $request->getSession();
$appIdFromSession = $session->get(UserConstants::USER_APP_ID, 0);
$gocDataList = [];
$gocDataListByAppId = [];
$retDataDebug = array();
$appId = $request->get('appId', $appIdFromSession);
if ($connected) {
$findByQuery = array(
'active' => 1
);
if ($appId !== '_UNSET_')
$findByQuery['appId'] = $appId;
$entry = $this->getDoctrine()->getManager('company_group')
->getRepository("CompanyGroupBundle\\Entity\\CompanyGroup")
->findOneBy($findByQuery);
$d = array(
'name' => $entry->getName(),
'id' => $entry->getId(),
'image' => $entry->getImage(),
'companyGroupHash' => $entry->getCompanyGroupHash(),
'dbName' => $entry->getDbName(),
'dbUser' => $entry->getDbUser(),
'dbPass' => $entry->getDbPass(),
'dbHost' => $entry->getDbHost(),
'appId' => $entry->getAppId(),
'companyRemaining' => $entry->getCompanyRemaining(),
'companyAllowed' => $entry->getCompanyAllowed(),
);
$gocDataList[$entry->getId()] = $d;
$gocId = $entry->getId();
$gocDataListByAppId[$entry->getAppId()] = $d;
$connector = $this->container->get('application_connector');
$connector->resetConnection(
'default',
$gocDataList[$gocId]['dbName'],
$gocDataList[$gocId]['dbUser'],
$gocDataList[$gocId]['dbPass'],
$gocDataList[$gocId]['dbHost'],
$reset = true);
$em = $this->getDoctrine()->getManager();
$data = Inventory::TryAndMatchParsedProductData($em, $em_goc, $systemType, $data);
}
} // CENTRAL SYSTEM PROCESSING
else if ($systemType == '_CENTRAL_') {
$data = Inventory::TryAndMatchParsedProductData($em, $em_goc, $systemType, $data);
}
return new JsonResponse([
'success' => $success,
'data' => $data,
]);
}
public function SyncItemGroupAction(Request $request, $id)
{
$systemType = $this->container->getParameter('system_type') ?: '_ERP_';
$em_goc = $this->getDoctrine()->getManager();
$em = $this->getDoctrine()->getManager();
$success = true;
$data = $request->request->get('data', [
'mode' => "UPDATE",
'igData' => [
'ids' => [$id],
'globalIds' => [0],
'indexBy' => 'globalId', //or id
'info' => [
$id => [
"id" => $id,
"globalId" => 0,
]
]
],
'brandData' => [
],
]);
if ($systemType == '_ERP_') {
$em = $this->getDoctrine()->getManager('company_group');
$em->getConnection()->connect();
$connected = $em->getConnection()->isConnected();
$gocDataList = [];
$gocDataListByAppId = [];
$retDataDebug = array();
$appIds = $request->get('appIds', '_UNSET_');
if ($connected) {
$findByQuery = array(
'active' => 1
);
if ($appIds !== '_UNSET_')
$findByQuery['appId'] = $appIds;
$gocList = $this->getDoctrine()->getManager('company_group')
->getRepository("CompanyGroupBundle\\Entity\\CompanyGroup")
->findBy($findByQuery);
foreach ($gocList as $entry) {
$d = array(
'name' => $entry->getName(),
'id' => $entry->getId(),
'image' => $entry->getImage(),
'companyGroupHash' => $entry->getCompanyGroupHash(),
'dbName' => $entry->getDbName(),
'dbUser' => $entry->getDbUser(),
'dbPass' => $entry->getDbPass(),
'dbHost' => $entry->getDbHost(),
'appId' => $entry->getAppId(),
'companyRemaining' => $entry->getCompanyRemaining(),
'companyAllowed' => $entry->getCompanyAllowed(),
);
$gocDataList[$entry->getId()] = $d;
$gocDataListByAppId[$entry->getAppId()] = $d;
}
foreach ($gocDataList as $gocId => $entry) {
$connector = $this->container->get('application_connector');
$connector->resetConnection(
'default',
$gocDataList[$gocId]['dbName'],
$gocDataList[$gocId]['dbUser'],
$gocDataList[$gocId]['dbPass'],
$gocDataList[$gocId]['dbHost'],
$reset = true);
$em = $this->getDoctrine()->getManager();
Inventory::SyncItemGroup($em, $em_goc, $systemType, $data);
}
}
} // CENTRAL SYSTEM PROCESSING
else if ($systemType == '_CENTRAL_') {
$data = Inventory::SyncItemGroup($em, $em_goc, $systemType, $data);
}
return new JsonResponse([
'success' => $success,
'data' => $data,
]);
}
public function CreateProductSpecAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
if ($request->isMethod('POST')) {
$systemType = $this->container->getParameter('system_type') ?: '_ERP_';
$spec_data = Inventory::CreateProductSpecification($this->getDoctrine()->getManager(),
$systemType,
$request->request->get('name', '_UNSET_'),
$request->request->get('unitText', '_UNSET_'),
$request->request->get('uniqueHash', '_UNSET_'),
$request->request->get('markers', '_UNSET_'),
$request->request->get('tags', '_UNSET_'),
$request->request->get('globalId', 0),
$request->getSession()->get(UserConstants::USER_LOGIN_ID));
if ($spec_data['id'] != '')
return new JsonResponse(array("success" => true, 'spec_data' => $spec_data));
}
return new JsonResponse(array("success" => false, 'spec_data' => []));
// return $this->redirectToRoute("create_product");
}
public function getIgBrandSpecListAction(Request $request, $id)
{
$systemType = $this->container->getParameter('system_type') ?: '_ERP_';
$em_goc = $this->getDoctrine()->getManager();
$em = $this->getDoctrine()->getManager();
$data = array(
'igList' => [],
'specList' => [],
);
$igs = $em
->getRepository("ApplicationBundle\\Entity\\InvItemGroup")
->findBy(
array(
'type' => 1
)
);
foreach ($igs as $ig) {
$data['igList'][] = array(
'id' => $ig->getId(),
'name' => $ig->getName(),
);
}
$specs = $em
->getRepository("ApplicationBundle\\Entity\\SpecType")
->findAll();
foreach ($specs as $spec) {
$data['specList'][] = array(
'id' => $spec->getId(),
'name' => $spec->getName(),
'unitText' => $spec->getUnitText(),
'uniqueHash' => $spec->getUniqueHash(),
);
}
return new JsonResponse([
'success' => true,
'data' => $data,
]);
}
public function SyncProductFromCentralAction(Request $request)
{
$systemType = $this->container->getParameter('system_type') ?: '_ERP_';
if ($systemType !== '_ERP_') {
return new JsonResponse(['success' => false, 'message' => 'Not an ERP system'], 403);
}
$globalId = (int) $request->request->get('globalId', 0);
$data = $request->request->get('data', []);
if (!is_array($data)) { $data = []; }
if (!$globalId) {
return new JsonResponse(['success' => false, 'message' => 'globalId is required'], 400);
}
// The central "Push" is authoritative — overwrite even locally-edited fields.
$force = (int) $request->request->get('force', 1) === 1;
// This server hosts MANY tenants. Apply the sync to every local tenant that
// holds the product. Each tenant DB is localhost to THIS server, so we just try
// every active tenant in the registry: those whose DB exists here update; the
// rest (other servers' tenants) fail to connect and are skipped harmlessly —
// which is also why we DON'T filter by server address (that filter was too
// brittle and could exclude this server's own tenants). Previously only the
// single default tenant was touched, so most tenants here never got the change.
$updated = 0; $checked = 0; $perTenant = [];
$gocList = [];
try {
$em_goc = $this->getDoctrine()->getManager('company_group');
$gocList = $em_goc->getRepository('CompanyGroupBundle\\Entity\\CompanyGroup')->findBy(['active' => 1]);
} catch (\Throwable $e) { $gocList = []; }
if (!empty($gocList)) {
$connector = $this->container->get('application_connector');
foreach ($gocList as $entry) {
$dbName = $entry->getDbName();
if (!$dbName) { continue; }
$checked++;
try {
$connector->resetConnection('default', $dbName, $entry->getDbUser(), $entry->getDbPass(), $entry->getDbHost(), true);
$tem = $this->getDoctrine()->getManager();
$tem->clear();
$res = Inventory::SyncProductFromCentral($tem, $globalId, $data, $force);
if (!empty($res['updatedCount'])) { $updated++; $perTenant[$dbName] = (int) $res['updatedCount']; }
} catch (\Throwable $e) { /* DB not on this server / unreachable — skip */ }
}
try { $connector->resetConnectionToDefault('default', true); } catch (\Throwable $e) { /* ignore */ }
} else {
// fallback: no tenant registry — update the default tenant only
$res = Inventory::SyncProductFromCentral($this->getDoctrine()->getManager(), $globalId, $data, $force);
if (!empty($res['updatedCount'])) { $updated = 1; }
$perTenant['(default)'] = $res;
}
return new JsonResponse([
'success' => true,
'tenants_checked' => $checked,
'tenants_updated' => $updated,
'results' => $perTenant,
], 200);
}
/**
* Receive a bulk global-ID remap from the central server after a product merge:
* for each {oldGlobalId => newGlobalId}, re-point local products that reference the old global id.
* Idempotent (re-running with the same map is a no-op). _ERP_-only; guarded by a shared secret.
*/
public function RemapGlobalIdAction(Request $request)
{
$systemType = $this->container->getParameter('system_type') ?: '_ERP_';
if ($systemType === '_CENTRAL_') {
return new JsonResponse(['success' => false, 'message' => 'Not applicable on central'], 403);
}
// Shared-secret guard — this call rewrites tenant data, so it must be authenticated.
$expected = $this->container->hasParameter('central_sync_secret') ? (string) $this->container->getParameter('central_sync_secret') : '';
$provided = (string) ($request->headers->get('X-Central-Secret') ?: $request->request->get('secret', ''));
if ($expected === '' || !hash_equals($expected, $provided)) {
return new JsonResponse(['success' => false, 'message' => 'Unauthorized'], 401);
}
$mapRaw = $request->request->get('map', '');
$map = is_array($mapRaw) ? $mapRaw : json_decode((string) $mapRaw, true);
if (!is_array($map) || empty($map)) {
return new JsonResponse(['success' => false, 'message' => 'map (oldGlobalId => newGlobalId) is required'], 400);
}
$conn = $this->getDoctrine()->getManager()->getConnection();
$remapped = 0;
$pairs = 0;
foreach ($map as $old => $new) {
$old = (int) $old;
$new = (int) $new;
if ($old <= 0 || $new <= 0 || $old === $new) {
continue;
}
$pairs++;
try {
$remapped += (int) $conn->executeStatement(
"UPDATE inv_products SET global_id = :new WHERE global_id = :old",
['new' => $new, 'old' => $old]
);
} catch (\Throwable $e) { /* skip a bad pair, keep going */ }
}
return new JsonResponse(['success' => true, 'pairs' => $pairs, 'productsRemapped' => $remapped]);
}
public function RetryFailedProductSyncAction(Request $request)
{
$systemType = $this->container->getParameter('system_type') ?: '_ERP_';
if ($systemType !== '_ERP_') {
return new JsonResponse(['success' => false, 'message' => 'Not an ERP system'], 403);
}
$em = $this->getDoctrine()->getManager();
$result = Inventory::RetrySyncFailedProducts($em, [], $systemType);
return new JsonResponse(['success' => true] + $result);
}
public function ProductDependenciesAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$productId = (int) $request->request->get('product_id', $request->request->get('productId', 0));
$qty = (float) $request->request->get('qty', 0);
$result = array();
if ($productId <= 0 || $qty <= 0) {
return new JsonResponse(array(
'success' => true,
'data' => $result,
));
}
$dependencies = $em->getRepository('ApplicationBundle\\Entity\\InvProductDependencies')->findBy(
array(
'parentProductId' => $productId,
),
array(
'id' => 'ASC',
)
);
foreach ($dependencies as $dependency) {
$childProductId = (int) $dependency->getChildProductId();
if ($childProductId <= 0 || $childProductId === $productId) {
continue;
}
$repeatationThreshold = (int) $dependency->getRepeatationThreshold();
if ($repeatationThreshold <= 0) {
$repeatationThreshold = 1;
}
$multiplier = floor($qty / $repeatationThreshold);
if ($multiplier <= 0) {
continue;
}
$repeatQty = $dependency->getRepeatQty();
if ($repeatQty !== null && $repeatQty !== '') {
$finalQty = $multiplier * (float) $repeatQty;
} else {
$finalQty = $multiplier * $qty;
}
if ($finalQty <= 0) {
continue;
}
$result[] = array(
'product_id' => $childProductId,
'qty' => $finalQty,
'required' => $dependency->getRequired() ? 1 : 0,
);
}
return new JsonResponse(array(
'success' => true,
'data' => $result,
));
}
}