Терминальный проект КиберПлат [open source]
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

526 lines
15KB

  1. /* @file Обработчик команд работы с устройствами выдачи наличных. */
  2. // STL
  3. #include <algorithm>
  4. #include <numeric>
  5. // Qt
  6. #include <Common/QtHeadersBegin.h>
  7. #include <QtCore/QVector>
  8. #include <QtCore/QtAlgorithms>
  9. #include <Common/QtHeadersEnd.h>
  10. // PaymentProcessor SDK
  11. #include <SDK/PaymentProcessor/Components.h>
  12. #include <SDK/PaymentProcessor/Settings/TerminalSettings.h>
  13. // Driver SDK
  14. #include <SDK/Drivers/WarningLevel.h>
  15. #include <SDK/Drivers/HardwareConstants.h>
  16. #include <SDK/Drivers/Components.h>
  17. #include <SDK/Drivers/IDispenser.h>
  18. // Project
  19. #include "System/IApplication.h"
  20. #include "Services/SettingsService.h"
  21. #include "Services/DatabaseService.h"
  22. #include "Services/DeviceService.h"
  23. #include "Services/PaymentService.h"
  24. #include "DatabaseUtils/IHardwareDatabaseUtils.h"
  25. #include "FundsService.h"
  26. #include "CashDispenserManager.h"
  27. namespace PPSDK = SDK::PaymentProcessor;
  28. //---------------------------------------------------------------------------
  29. CashDispenserManager::CashDispenserManager(IApplication * aApplication) :
  30. ILogable(CFundsService::LogName),
  31. mApplication(aApplication),
  32. mDeviceService(nullptr),
  33. mDatabase(nullptr),
  34. mPaymentDatabase(nullptr)
  35. {
  36. }
  37. //---------------------------------------------------------------------------
  38. bool CashDispenserManager::initialize(IPaymentDatabaseUtils * aDatabase)
  39. {
  40. mDeviceService = DeviceService::instance(mApplication);
  41. mDatabase = DatabaseService::instance(mApplication)->getDatabaseUtils<IHardwareDatabaseUtils>();
  42. mPaymentDatabase = aDatabase;
  43. mDispensers.clear();
  44. // Получаем настройки терминала
  45. PPSDK::TerminalSettings * settings = SettingsService::instance(mApplication)->getAdapter<PPSDK::TerminalSettings>();
  46. mCurrencyName = settings->getCurrencySettings().name;
  47. if (mCurrencyName.isEmpty())
  48. {
  49. toLog(LogLevel::Error, "Currency is not set for funds service!");
  50. return false;
  51. }
  52. updateHardwareConfiguration();
  53. connect(mDeviceService, SIGNAL(configurationUpdated()), this, SLOT(updateHardwareConfiguration()));
  54. return true;
  55. }
  56. //---------------------------------------------------------------------------
  57. void CashDispenserManager::updateHardwareConfiguration()
  58. {
  59. mDispensers.clear();
  60. // Получаем настройки терминала
  61. PPSDK::TerminalSettings * settings = SettingsService::instance(mApplication)->getAdapter<PPSDK::TerminalSettings>();
  62. // Получаем список всех доступных устройств.
  63. QStringList deviceList = settings->getDeviceList().filter(QRegExp(QString("(%1)").arg(DSDK::CComponents::Dispenser)));
  64. foreach (const QString & configurationName, deviceList)
  65. {
  66. DSDK::IDispenser * device = dynamic_cast<DSDK::IDispenser *>(mDeviceService->acquireDevice(configurationName));
  67. if (device)
  68. {
  69. // Подписываемся на сигналы.
  70. device->subscribe(SDK::Driver::IDispenser::UnitsDefinedSignal, this, SLOT(onUnitsDefined()));
  71. device->subscribe(SDK::Driver::IDispenser::DispensedSignal, this, SLOT(onDispensed(int, int)));
  72. device->subscribe(SDK::Driver::IDispenser::RejectedSignal, this, SLOT(onRejected(int, int)));
  73. device->subscribe(SDK::Driver::IDispenser::UnitEmptySignal, this, SLOT(onUnitEmpty(int)));
  74. device->subscribe(SDK::Driver::IDevice::StatusSignal, this, SLOT(onStatusChanged(SDK::Driver::EWarningLevel::Enum, const QString &, int)));
  75. mDispensers.insert(device, configurationName);
  76. }
  77. else
  78. {
  79. toLog(LogLevel::Error, QString("Failed to acquire cash dispenser %1.").arg(configurationName));
  80. }
  81. }
  82. // Грузим список загруженных купюр
  83. loadCashList();
  84. onUnitsDefined();
  85. }
  86. //---------------------------------------------------------------------------
  87. void CashDispenserManager::shutdown()
  88. {
  89. // Сохраняем состояние по купюрам
  90. saveCashCount();
  91. foreach (DSDK::IDispenser * dispenser, mDispensers.keys())
  92. {
  93. mDeviceService->releaseDevice(dispenser);
  94. }
  95. mDispensers.clear();
  96. }
  97. //---------------------------------------------------------------------------
  98. void CashDispenserManager::onStatusChanged(DSDK::EWarningLevel::Enum aLevel, const QString & aTranslation, int /*aStatus*/)
  99. {
  100. DSDK::IDispenser * dispenser = dynamic_cast<DSDK::IDispenser *>(sender());
  101. if (!dispenser)
  102. {
  103. return;
  104. }
  105. if (aLevel == DSDK::EWarningLevel::Error)
  106. {
  107. mFailedDispensers.insert(dispenser);
  108. emit error(aTranslation);
  109. }
  110. else
  111. {
  112. mFailedDispensers.remove(dispenser);
  113. emit activity();
  114. }
  115. }
  116. //---------------------------------------------------------------------------
  117. PPSDK::SCashUnit * CashDispenserManager::checkSignal(QObject * aSender, const QString & aSignalName, int aUnit)
  118. {
  119. DSDK::IDispenser * dispenser = dynamic_cast<DSDK::IDispenser *>(aSender);
  120. if (!dispenser)
  121. {
  122. toLog(LogLevel::Error, QString("Receive %1 signal, but sender not have DSDK::IDispenser interface.").arg(aSignalName));
  123. return nullptr;
  124. }
  125. QString configurationName = mDispensers.value(dispenser);
  126. int unitCount = mCurrencyCashList[configurationName].size();
  127. if (unitCount <= aUnit)
  128. {
  129. toLog(LogLevel::Error, QString("Wrong unit number = %1, need max %2.").arg(aUnit).arg(unitCount - 1));
  130. return nullptr;
  131. }
  132. return &mCurrencyCashList[configurationName][aUnit];
  133. }
  134. //---------------------------------------------------------------------------
  135. bool CashDispenserManager::handleSignal(QObject * aSender, const QString & aSignalName, int aUnit, int aItems, PPSDK::TPaymentAmount & aAmount)
  136. {
  137. PPSDK::SCashUnit * cashUnit = checkSignal(aSender, aSignalName, aUnit);
  138. if (!cashUnit)
  139. {
  140. return false;
  141. }
  142. cashUnit->count -= qMin(aItems, cashUnit->count);
  143. int nominal = cashUnit->nominal;
  144. aAmount = nominal * aItems;
  145. toLog(LogLevel::Normal, QString("%1 %2 notes (nominal %3) AMOUNT = %4").arg(aSignalName).arg(aItems).arg(nominal).arg(aAmount, 0, 'f', 2));
  146. if (!cashUnit->count)
  147. {
  148. DSDK::IDispenser * dispenser = dynamic_cast<DSDK::IDispenser *>(aSender);
  149. setCashList(dispenser);
  150. }
  151. saveCashCount();
  152. return true;
  153. }
  154. //---------------------------------------------------------------------------
  155. void CashDispenserManager::setCashList(DSDK::IDispenser * aDispenser)
  156. {
  157. DSDK::TUnitData unitData;
  158. foreach(auto cashUnit, mCurrencyCashList[mDispensers[aDispenser]])
  159. {
  160. unitData << cashUnit.count;
  161. }
  162. aDispenser->setCashList(unitData);
  163. }
  164. //---------------------------------------------------------------------------
  165. void CashDispenserManager::onUnitsDefined()
  166. {
  167. DSDK::IDispenser * dispenser = dynamic_cast<DSDK::IDispenser *>(sender());
  168. TDispensers dispensers;
  169. if (dispenser)
  170. {
  171. dispensers << dispenser;
  172. }
  173. else
  174. {
  175. dispensers = mDispensers.keys().toSet();
  176. }
  177. /// Проверяем и обновляем список доступных купюр
  178. /// если загруженных купюр нет - создаем и сохраняем в БД пустой список
  179. foreach(DSDK::IDispenser * dispenser, dispensers)
  180. {
  181. QString configPath = mDispensers.value(dispenser);
  182. int units = dispenser->units();
  183. if (units)
  184. {
  185. int currentUnits = mCurrencyCashList.contains(configPath) ? mCurrencyCashList[configPath].size() : 0;
  186. if (currentUnits < units)
  187. {
  188. PPSDK::TCashUnitList cashUnitList = PPSDK::TCashUnitList(units - currentUnits, PPSDK::SCashUnit(mCurrencyName, 0, 0));
  189. mCurrencyCashList[configPath] << cashUnitList;
  190. }
  191. else if (currentUnits > units)
  192. {
  193. PPSDK::TCashUnitList & cashUnitList = mCurrencyCashList[configPath];
  194. cashUnitList = cashUnitList.mid(0, units);
  195. }
  196. if (currentUnits != units)
  197. {
  198. saveCashCount();
  199. }
  200. }
  201. }
  202. }
  203. //---------------------------------------------------------------------------
  204. void CashDispenserManager::onDispensed(int aUnit, int aItems)
  205. {
  206. PPSDK::TPaymentAmount amount = 0;
  207. if (handleSignal(sender(), "Dispensed", aUnit, aItems, amount))
  208. {
  209. storeNotes(sender(), aUnit, aItems);
  210. mAmounts += SAmounts(-amount, amount);
  211. if (canDispense(mAmounts.toDispensing))
  212. {
  213. emit activity();
  214. dispense(mAmounts.toDispensing);
  215. }
  216. else
  217. {
  218. toLog(LogLevel::Normal, QString("Send dispensed total amount = %1").arg(mAmounts.dispensed, 0, 'f', 2));
  219. emit dispensed(mAmounts.dispensed);
  220. mAmounts = SAmounts(0, 0);
  221. }
  222. }
  223. else
  224. {
  225. toLog(LogLevel::Warning, "Send dispensed total amount = 0 due to error in handling notes info");
  226. emit dispensed(0);
  227. mAmounts = SAmounts(0, 0);
  228. }
  229. }
  230. //---------------------------------------------------------------------------
  231. void CashDispenserManager::onRejected(int aUnit, int aItems)
  232. {
  233. PPSDK::TPaymentAmount amount;
  234. handleSignal(sender(), "Rejected", aUnit, aItems, amount);
  235. }
  236. //---------------------------------------------------------------------------
  237. void CashDispenserManager::onUnitEmpty(int aUnit)
  238. {
  239. PPSDK::SCashUnit * cashUnit = checkSignal(sender(), "unitEmpty", aUnit);
  240. if (cashUnit)
  241. {
  242. cashUnit->count = 0;
  243. }
  244. }
  245. //---------------------------------------------------------------------------
  246. CashDispenserManager::TItemDataSet CashDispenserManager::getItemDataSet(PPSDK::TPaymentAmount aAmount)
  247. {
  248. CashDispenserManager::TItemDataSet result;
  249. foreach (auto dispenser, mDispensers.keys())
  250. {
  251. if (!mFailedDispensers.contains(dispenser))
  252. {
  253. auto cashList = mCurrencyCashList[mDispensers[dispenser]];
  254. for (int i = 0; i < cashList.size(); i++)
  255. {
  256. if (dispenser->isDeviceReady(i) && cashList[i].count && cashList[i].nominal && (cashList[i].nominal <= aAmount))
  257. {
  258. int count = (cashList[i].count > 0) ? cashList[i].count : 0;
  259. result[cashList[i].nominal] << SItemData(dispenser, i, count);
  260. }
  261. }
  262. }
  263. }
  264. return result;
  265. }
  266. //---------------------------------------------------------------------------
  267. void CashDispenserManager::saveCashCount()
  268. {
  269. foreach (auto configName, mCurrencyCashList.keys())
  270. {
  271. QStringList parameterValueList;
  272. foreach (auto cash, mCurrencyCashList.value(configName))
  273. {
  274. parameterValueList << QString("%1:%2:%3").arg(cash.currencyName).arg(cash.nominal).arg(cash.count);
  275. }
  276. mDatabase->setDeviceParam(configName, PPSDK::CDatabaseConstants::Parameters::CashUnits, parameterValueList.join(";"));
  277. }
  278. }
  279. //---------------------------------------------------------------------------
  280. void CashDispenserManager::loadCashList()
  281. {
  282. mCurrencyCashList.clear();
  283. for (auto it = mDispensers.begin(); it != mDispensers.end(); ++it)
  284. {
  285. QString configPath = *it;
  286. QString cashUnitData = mDatabase->getDeviceParam(configPath, PPSDK::CDatabaseConstants::Parameters::CashUnits).toString();
  287. QStringList cashUnits = cashUnitData.split(";", QString::SkipEmptyParts);
  288. for (int i = 0; i < cashUnits.size(); ++i)
  289. {
  290. QStringList unit = cashUnits[i].split(":");
  291. int count = unit[2].toInt();
  292. PPSDK::SCashUnit cashUnit(unit[0], unit[1].toInt(), count);
  293. mCurrencyCashList[configPath] << cashUnit;
  294. }
  295. setCashList(it.key());
  296. }
  297. }
  298. //---------------------------------------------------------------------------
  299. bool CashDispenserManager::getItemData(SDK::PaymentProcessor::TPaymentAmount aAmount, TItemDataSet & aItemData, TItemDataSetIt & aItemDataSetIt)
  300. {
  301. if (aItemData.isEmpty())
  302. {
  303. return false;
  304. }
  305. QList<int> nominals = aItemData.keys();
  306. if (*std::min_element(nominals.begin(), nominals.end()) > aAmount)
  307. {
  308. return false;
  309. }
  310. int nominal = *std::max_element(nominals.begin(), nominals.end());
  311. if (nominal > aAmount)
  312. {
  313. qSort(nominals);
  314. QList<int>::iterator it = qLowerBound(nominals.begin(), nominals.end(), aAmount);
  315. nominal = *it;
  316. if (nominal > aAmount && it != nominals.begin())
  317. {
  318. it--;
  319. nominal = *it;
  320. }
  321. }
  322. aItemDataSetIt = TItemDataSetIt(aItemData.find(nominal));
  323. return true;
  324. }
  325. //---------------------------------------------------------------------------
  326. PPSDK::TPaymentAmount CashDispenserManager::canDispense(PPSDK::TPaymentAmount aRequiredAmount)
  327. {
  328. TItemDataSet itemDataSet = getItemDataSet(aRequiredAmount);
  329. TItemDataSetIt itemDataSetIt;
  330. PPSDK::TPaymentAmount dispensingAmount = 0;
  331. while (getItemData(aRequiredAmount - dispensingAmount, itemDataSet, itemDataSetIt))
  332. {
  333. int nominal = itemDataSetIt.key();
  334. int requiredCount = int(aRequiredAmount - dispensingAmount) / nominal;
  335. int availableCount = std::accumulate(itemDataSetIt->begin(), itemDataSetIt->end(), 0, [] (int aCount, const SItemData & data) -> int { return aCount + data.count; });
  336. int count = qMin(requiredCount, availableCount);
  337. dispensingAmount += count * nominal;
  338. if (count == availableCount)
  339. {
  340. itemDataSet.erase(itemDataSetIt);
  341. }
  342. }
  343. return dispensingAmount;
  344. }
  345. //---------------------------------------------------------------------------
  346. void CashDispenserManager::dispense(PPSDK::TPaymentAmount aAmount)
  347. {
  348. if (qFuzzyIsNull(mAmounts.toDispensing) && qFuzzyIsNull(mAmounts.dispensed))
  349. {
  350. mAmounts.toDispensing = aAmount;
  351. toLog(LogLevel::Normal, QString("Amount to dispensing = %1").arg(aAmount, 0, 'f', 2));
  352. }
  353. TItemDataSet itemDataSet = getItemDataSet(aAmount);
  354. TItemDataSetIt itemDataSetIt;
  355. if (!getItemData(aAmount, itemDataSet, itemDataSetIt))
  356. {
  357. mAmounts = SAmounts(0, 0);
  358. toLog(LogLevel::Warning, "Send dispensed total amount = 0 due to absence of available dispensing resources");
  359. emit dispensed(0);
  360. }
  361. else
  362. {
  363. int requiredCount = int(aAmount) / itemDataSetIt.key();
  364. SItemData & data = *itemDataSetIt->begin();
  365. int count = qMin(data.count, requiredCount);
  366. data.dispenser->dispense(data.unit, count);
  367. }
  368. }
  369. //---------------------------------------------------------------------------
  370. PPSDK::TCashUnitsState CashDispenserManager::getCashUnitsState()
  371. {
  372. return mCurrencyCashList;
  373. }
  374. //---------------------------------------------------------------------------
  375. bool CashDispenserManager::setCashUnitsState(const QString & aDeviceConfigurationName, const PPSDK::TCashUnitList & aCashUnitList)
  376. {
  377. if (!mCurrencyCashList.contains(aDeviceConfigurationName))
  378. {
  379. toLog(LogLevel::Error, QString("Unknown cash dispenser device %1.").arg(aDeviceConfigurationName));
  380. return false;
  381. }
  382. if (mCurrencyCashList.value(aDeviceConfigurationName).size() != aCashUnitList.size())
  383. {
  384. toLog(LogLevel::Error, QString("Incorrect cash unit count for device %1.").arg(aDeviceConfigurationName));
  385. return false;
  386. }
  387. mCurrencyCashList[aDeviceConfigurationName] = aCashUnitList;
  388. setCashList(mDispensers.key(aDeviceConfigurationName));
  389. saveCashCount();
  390. return true;
  391. }
  392. //---------------------------------------------------------------------------
  393. bool CashDispenserManager::storeNotes(QObject * aSender, int aUnit, int aItems)
  394. {
  395. DSDK::IDispenser * dispenser = dynamic_cast<DSDK::IDispenser *>(aSender);
  396. if (!dispenser)
  397. {
  398. toLog(LogLevel::Error, "Receive dispense signal, but sender not have DSDK::IDispenser interface.");
  399. return false;
  400. }
  401. QString configurationName = mDispensers.value(dispenser);
  402. PPSDK::SCashUnit * cashUnit = &mCurrencyCashList[configurationName][aUnit];
  403. PPSDK::TerminalSettings * settings = SettingsService::instance(mApplication)->getAdapter<PPSDK::TerminalSettings>();
  404. QList<PPSDK::SNote> notes;
  405. for (int i = 0; i < aItems; i++)
  406. {
  407. notes.push_back(PPSDK::SNote(PPSDK::EAmountType::Bill, cashUnit->nominal, settings->getCurrencySettings().id, ""));
  408. }
  409. return mPaymentDatabase->addChangeNote(PaymentService::instance(mApplication)->getChangeSessionRef(), notes);
  410. }
  411. //---------------------------------------------------------------------------