Терминальный проект КиберПлат [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.

1599 lines
49KB

  1. /* @file Базовый ФР на протоколе АТОЛ. */
  2. // Qt
  3. #include <Common/QtHeadersBegin.h>
  4. #include <QtCore/qmath.h>
  5. #include <Common/QtHeadersEnd.h>
  6. // Project
  7. #include "AtolFRBase.h"
  8. using namespace SDK::Driver;
  9. using namespace ProtocolUtils;
  10. //--------------------------------------------------------------------------------
  11. AtolFRBase::AtolFRBase()
  12. {
  13. using namespace SDK::Driver::IOPort::COM;
  14. // данные порта
  15. mPortParameters[EParameters::BaudRate].append(EBaudRate::BR115200); // default
  16. mPortParameters[EParameters::BaudRate].append(EBaudRate::BR4800); // default after resetting to zero
  17. mPortParameters[EParameters::BaudRate].append(EBaudRate::BR57600);
  18. mPortParameters[EParameters::BaudRate].append(EBaudRate::BR38400);
  19. mPortParameters[EParameters::BaudRate].append(EBaudRate::BR19200);
  20. mPortParameters[EParameters::BaudRate].append(EBaudRate::BR9600);
  21. mPortParameters[EParameters::Parity].append(EParity::No);
  22. // теги
  23. mTagEngine = Tags::PEngine(new CAtolFR::TagEngine());
  24. // данные устройства
  25. mLineFeed = false;
  26. mMode = CAtolFR::InnerModes::NoMode;
  27. mSubmode = CAtolFR::InnerSubmodes::NoSubmode;
  28. mLocked = false;
  29. mNonNullableAmount = 0;
  30. // количество строк шапки (по умолчанию 4) - изменено для корректной печати на фискализированных аппаратах (как и было раньше)
  31. mDocumentCapStrings = 6;
  32. // регистры
  33. mRegisterData.add(CAtolFR::Registers::PaymentAmount, '\x03', 6);
  34. mRegisterData.add(CAtolFR::Registers::PaymentCount, '\x06', 2);
  35. mRegisterData.add(CAtolFR::Registers::MoneyInCash, '\x0A', 7);
  36. mRegisterData.add(CAtolFR::Registers::CurrentDateTime, '\x11', 6);
  37. mRegisterData.add(CAtolFR::Registers::SessionInfo, '\x12', 7);
  38. mRegisterData.add(CAtolFR::Registers::PrintingSettings, '\x18', 1);
  39. // команды
  40. using namespace CAtolFR::Commands;
  41. mCommandData.add(GetModelInfo, true, false, false);
  42. mCommandData.add(GetLongStatus, false, true);
  43. mCommandData.add(GetShortStatus, false, true);
  44. mCommandData.add(EnterToMode, true, true);
  45. mCommandData.add(GetFRRegister, 30 * 1000);
  46. mCommandData.add(OpenDocument, 5 * 1000);
  47. mCommandData.add(CloseDocument, 20 * 1000);
  48. mCommandData.add(Encashment, 10 * 1000);
  49. mCommandData.add(CancelDocument, 20 * 1000);
  50. mCommandData.add(ZReport, 30 * 1000);
  51. mCommandData.add(ExitFromMode, 10 * 1000);
  52. mCommandData.add(OpenFRSession, 10 * 1000);
  53. // ошибки
  54. mUnprocessedErrorData.add(CAtolFR::Commands::GetFRParameter, CAtolFR::Errors::WrongSeriesNumber);
  55. // налоги
  56. mTaxData.add(10, 2);
  57. mTaxData.add(18, 3);
  58. mTaxData.add( 0, 4);
  59. }
  60. //--------------------------------------------------------------------------------
  61. void AtolFRBase::setInitialData()
  62. {
  63. TSerialFRBase::setInitialData();
  64. mFRBuild = 0;
  65. }
  66. //--------------------------------------------------------------------------------
  67. QDateTime AtolFRBase::getDateTime()
  68. {
  69. QByteArray data;
  70. if (getLongStatus(data))
  71. {
  72. QString dateDataTime = data.mid(3, 6).toHex().prepend("20");
  73. return QDateTime::fromString(dateDataTime, CAtolFR::DateTimeFormat);
  74. }
  75. return QDateTime();
  76. }
  77. //--------------------------------------------------------------------------------
  78. bool AtolFRBase::checkTaxValue(TVAT aVAT, CFR::Taxes::SData & aData, const CAtolFR::FRParameters::TData & aFRParameterData, bool aCanCorrectTaxValue)
  79. {
  80. QByteArray taxData;
  81. if (!getFRParameter(aFRParameterData(aData.group), taxData))
  82. {
  83. toLog(LogLevel::Error, mDeviceName + QString(": Failed to get tax value for %1 tax group").arg(aData.group));
  84. return false;
  85. }
  86. bool OK;
  87. int FRValue = TVAT(taxData.toHex().toInt(&OK));
  88. if (!OK)
  89. {
  90. FRValue = -1;
  91. }
  92. else
  93. {
  94. aData.deviceVAT = FRValue / 100;
  95. }
  96. int value = int(aVAT * 100);
  97. if (value != FRValue)
  98. {
  99. QString log = QString("%1% (%2 tax group)").arg(value/100.0, 5, 'f', 2, ASCII::Zero).arg(aData.group);
  100. if (!aCanCorrectTaxValue)
  101. {
  102. toLog(LogLevel::Error, mDeviceName + ": Cannot to set tax value = " + log);
  103. return false;
  104. }
  105. else if (!setFRParameter(aFRParameterData(aData.group), getBCD(double(aVAT) * 100.0, 2)))
  106. {
  107. toLog(LogLevel::Error, mDeviceName + ": Failed to set tax value = " + log);
  108. return false;
  109. }
  110. }
  111. return true;
  112. }
  113. //--------------------------------------------------------------------------------
  114. bool AtolFRBase::updateParameters()
  115. {
  116. exitInnerMode();
  117. processDeviceData();
  118. setConfigParameter(CHardware::Printer::FeedingAmount, mModelData.feedingAmount);
  119. // если у нас Меркурий - открываем руками смену
  120. if ((mDeviceName == CAtolFR::Models::Mercury140F) && !openFRSession())
  121. {
  122. toLog(LogLevel::Error, "AtolFR: Failed to open fiscal session");
  123. return false;
  124. }
  125. // программируем параметры модели
  126. if (!enterInnerMode(CAtolFR::InnerModes::Programming) || !setFRParameters())
  127. {
  128. toLog(LogLevel::Error, "AtolFR: Failed to set FR parameters");
  129. return false;
  130. }
  131. if (!getPrintingSettings())
  132. {
  133. return false;
  134. }
  135. if (!isFiscal())
  136. {
  137. return true;
  138. }
  139. TLoadSectionName loadSectionName = [&] (int aIndex, QByteArray & aData) -> bool { return getFRParameter(CAtolFR::FRParameters::SectionName(aIndex), aData); };
  140. if (mOperatorPresence && !loadSectionNames(loadSectionName))
  141. {
  142. return false;
  143. }
  144. mZBufferError = !enterExtendedMode();
  145. return checkTaxes();
  146. }
  147. //--------------------------------------------------------------------------------
  148. void AtolFRBase::finaliseInitialization()
  149. {
  150. if (!mCanProcessZBuffer)
  151. {
  152. enterInnerMode(CAtolFR::InnerModes::Register);
  153. }
  154. exitInnerMode();
  155. TSerialFRBase::finaliseInitialization();
  156. }
  157. //--------------------------------------------------------------------------------
  158. bool AtolFRBase::isConnected()
  159. {
  160. QByteArray answer;
  161. if (!processCommand(CAtolFR::Commands::GetModelInfo, &answer))
  162. {
  163. return false;
  164. }
  165. CAtolFR::TModelKey modelKey = getModelKey(answer);
  166. CAtolFR::CModelData modelData;
  167. if (!modelData.data().keys().contains(modelKey))
  168. {
  169. toLog(LogLevel::Error, "AtolFR: Unknown model");
  170. }
  171. mModelData = modelData[modelKey];
  172. mDeviceName = mModelData.name;
  173. mVerified = mModelData.verified;
  174. mCanProcessZBuffer = mModelData.ZBufferSize;
  175. mModelCompatibility = mSupportedModels.contains(mDeviceName);
  176. setConfigParameter(CHardware::Printer::NeedCutting, mModelData.cutter);
  177. return true;
  178. }
  179. //--------------------------------------------------------------------------------
  180. bool AtolFRBase::getPrintingSettings()
  181. {
  182. if ((mDeviceName == CAtolFR::Models::TriumF) ||
  183. (mDeviceName == CAtolFR::Models::FelixRF) ||
  184. (mDeviceName == CAtolFR::Models::Felix02K) ||
  185. (mDeviceName == CAtolFR::Models::Mercury140F) ||
  186. (mDeviceName == CAtolFR::Models::Mercury130) ||
  187. (mDeviceName == CAtolFR::Models::MicroFR01K) ||
  188. (mDeviceName == CAtolFR::Models::Flaton11K))
  189. {
  190. toLog(LogLevel::Normal, mDeviceName + ": Cannot get printing settings");
  191. return true;
  192. }
  193. QByteArray data;
  194. if (!getFRParameter(CAtolFR::FRParameters::PrintingSettings, data))
  195. {
  196. toLog(LogLevel::Error, mDeviceName + ": Failed to get get printing settings");
  197. return true;
  198. }
  199. int FRTypeLength = data.toHex().toInt();
  200. int modelTypeLength = mModelData.maxStringSize;
  201. mLineSize = ((FRTypeLength <= modelTypeLength) && FRTypeLength) ? FRTypeLength : modelTypeLength;
  202. return true;
  203. }
  204. //--------------------------------------------------------------------------------
  205. QByteArray AtolFRBase::getBCD(double aValue, int aSize, int aPrecision, int aMantissa) const
  206. {
  207. qint64 factor = qint64(qPow(10.0, abs(aPrecision + 1)));
  208. aValue = (aPrecision >= 0) ? (double(qRound64(aValue * factor)) / factor) : (qint64(aValue / factor) * factor);
  209. aMantissa = aMantissa > aPrecision ? aMantissa : aPrecision;
  210. qint64 value = qRound64(aValue * qPow(10.0, aMantissa));
  211. QString stringValue = QString("%1").arg(value, aSize * 2, 10, QChar(ASCII::Zero));
  212. QByteArray result;
  213. for (int i = 0; i < aSize; ++i)
  214. {
  215. result.append(uchar(stringValue.mid(2 * i, 2).toInt(0, 16)));
  216. }
  217. return result;
  218. }
  219. //--------------------------------------------------------------------------------
  220. TResult AtolFRBase::execCommand(const QByteArray & aCommand, const QByteArray & aCommandData, QByteArray * aAnswer)
  221. {
  222. // для пересчета ошибки виртуального конца буфера Z-отчетов:
  223. // если открываем чек, либо делаем выплату (- операции, открывающие смену) и сессия сейчас закрыта, то
  224. // после открытия чека она будет открыта открытием чека. Тогда потом просто обновим время последнего открытия смены.
  225. bool isSessionInZBufferOpened = false;
  226. if (mCanProcessZBuffer && ((aCommand[0] == CAtolFR::Commands::OpenDocument) || (aCommand[0] == CAtolFR::Commands::Encashment)))
  227. {
  228. isSessionInZBufferOpened = getSessionState() == ESessionState::Closed;
  229. }
  230. QByteArray commandData = aCommand + aCommandData;
  231. QByteArray answer;
  232. mLastCommandResult = performCommand(commandData, answer, mCommandData[aCommand].timeout);
  233. if ((mLastCommandResult != CommandResult::Transport) && aAnswer)
  234. {
  235. *aAnswer = answer;
  236. }
  237. if (!mLastCommandResult)
  238. {
  239. return mLastCommandResult;
  240. }
  241. CAtolFR::SCommadData commandInfo = mCommandData[aCommand];
  242. int minAnswerSize = int(commandInfo.error) + int(commandInfo.prefix);
  243. if (answer.size() < minAnswerSize)
  244. {
  245. toLog(LogLevel::Error, "AtolFR: Data in packet is less than " + QString::number(minAnswerSize));
  246. return CommandResult::Answer;
  247. }
  248. char error = getError(aCommand, answer);
  249. if (uchar(error) < CAtolFR::MinErrorCode)
  250. {
  251. // смена открыта, а ранее она была закрыта - обновляем метку времени открытия смены
  252. if (isSessionInZBufferOpened)
  253. {
  254. mLastOpenSession = QDateTime::currentDateTime();
  255. }
  256. return CommandResult::OK;
  257. }
  258. mLastError = error;
  259. mLastCommand = aCommand;
  260. toLog(LogLevel::Error, mDeviceName + ": Error: " + mErrorData->value(mLastError).description);
  261. if (!isErrorUnprocessed(aCommand, mLastError))
  262. {
  263. setErrorFlags(aCommand, mLastError);
  264. }
  265. if (!mProcessingErrors.isEmpty() && (mProcessingErrors.last() == mLastError))
  266. {
  267. return CommandResult::Device;
  268. }
  269. if (isErrorUnprocessed(aCommand, error) || !processAnswer(aCommand, error))
  270. {
  271. mLastError = error;
  272. mLastCommand = aCommand;
  273. return CommandResult::Device;
  274. }
  275. TResult result = processCommand(aCommand, aCommandData, aAnswer);
  276. if (result)
  277. {
  278. mProcessingErrors.pop_back();
  279. }
  280. return result;
  281. }
  282. //--------------------------------------------------------------------------------
  283. char AtolFRBase::getError(char aCommand, const QByteArray & aAnswer)
  284. {
  285. return getError(QByteArray(1, aCommand), aAnswer);
  286. }
  287. //--------------------------------------------------------------------------------
  288. char AtolFRBase::getError(const QByteArray & aCommand, const QByteArray & aAnswer)
  289. {
  290. CAtolFR::SCommadData commandData = mCommandData[aCommand];
  291. if (!commandData.error) return ASCII::NUL;
  292. else if (commandData.prefix) return aAnswer[1];
  293. return aAnswer[0];
  294. }
  295. //--------------------------------------------------------------------------------
  296. void AtolFRBase::setErrorFlags(const QByteArray & /*aCommand*/, char aError)
  297. {
  298. if (aError == CAtolFR::Errors::PrinterHeadOverheat)
  299. {
  300. mPrinterCollapse = true;
  301. }
  302. }
  303. //--------------------------------------------------------------------------------
  304. bool AtolFRBase::openSession()
  305. {
  306. char mode = mMode;
  307. if (!enterInnerMode(CAtolFR::InnerModes::Register))
  308. {
  309. return false;
  310. }
  311. QByteArray commandData(1, CAtolFR::FiscalFlags::ExecutionMode);
  312. bool result = processCommand(CAtolFR::Commands::OpenFRSession, commandData);
  313. enterInnerMode(mode);
  314. return result;
  315. }
  316. //--------------------------------------------------------------------------------
  317. bool AtolFRBase::processAnswer(const QByteArray & aCommand, char aError)
  318. {
  319. switch (aError)
  320. {
  321. // эта модель не может выполнить эту команду.
  322. case CAtolFR::Errors::CannotExecCommand :
  323. case CAtolFR::Errors::BadModeForCommand :
  324. {
  325. /*
  326. Если еще не было ни 1 запроса статуса (значит, идет инициализация), но
  327. не можем выполнить команду, значит, что-то не так либо с ЭКЛЗ, либо с ФП, и ККМ фискализирована.
  328. Последующие запросы статуса должны прояснить ситуацию.
  329. */
  330. if ((aError == CAtolFR::Errors::BadModeForCommand) &&
  331. (mMode == CAtolFR::InnerModes::NoMode) &&
  332. (mSubmode == CAtolFR::InnerSubmodes::NoSubmode))
  333. {
  334. mFiscalized = true;
  335. mFiscalCollapse = true;
  336. }
  337. //если режим - не выбор, поэтому, возможно, не выполнили команду, т.к. уже находимся в режиме
  338. //тогда выходим из режима и снова заходим в нужный режим
  339. if ((aCommand[0] == CAtolFR::Commands::EnterToMode) ||
  340. (aCommand[0] == CAtolFR::Commands::PrintString) ||
  341. (aCommand[0] == CAtolFR::Commands::Cut))
  342. {
  343. mProcessingErrors.append(aError);
  344. return exitInnerMode(true);
  345. }
  346. //TODO: разобраться со спецификой модели Феликс80к, сделать завязку на эту модель
  347. break;
  348. }
  349. //--------------------------------------------------------------------------------
  350. // необходимо закрыть чек - отменяем
  351. case CAtolFR::Errors::NeedCloseDocument :
  352. case CAtolFR::Errors::NeedCloseSaleDocument :
  353. {
  354. mProcessingErrors.append(aError);
  355. return processCommand(CAtolFR::Commands::CancelDocument);
  356. }
  357. //--------------------------------------------------------------------------------
  358. // со времени открытия смены прошло более 24 часа - надо закрыть смену
  359. //TODO: проверить на предмет обработки ошибок выполнения отдельных команд
  360. //TODO: для Меркурия-140Ф надо руками открывать смену, автооткрытия по первому чеку у него нет.
  361. //самое удобное - сделать это сразу после команды закрытия смены
  362. case CAtolFR::Errors::NeedZReport :
  363. case CAtolFR::Errors::NeedCloseSession :
  364. {
  365. mProcessingErrors.append(aError);
  366. return execZReport(true);
  367. }
  368. //--------------------------------------------------------------------------------
  369. // нет бамаги - это совсем не значит, что бумаги нет. Это может означать, к примеру, что забился буфер Z-отчетов.
  370. // или бумага замялась. или головка перегрелась. или еще что-то...
  371. case CAtolFR::Errors::NoPaper :
  372. {
  373. if (!mCommandData[aCommand].status)
  374. {
  375. simplePoll();
  376. if ((aCommand[0] == CAtolFR::Commands::ZReport) && mCanProcessZBuffer)
  377. {
  378. bool & ZBufferFlag = (getSessionState() == ESessionState::Expired) ? mZBufferOverflow : mZBufferFull;
  379. ZBufferFlag = true;
  380. }
  381. else
  382. {
  383. mProcessingErrors.append(aError);
  384. }
  385. }
  386. else
  387. {
  388. mProcessingErrors.append(aError);
  389. }
  390. break;
  391. }
  392. //--------------------------------------------------------------------------------
  393. // если не смогли открыть чек с ошибкой "Вход в режим заблокирован" - ФР залочен
  394. case CAtolFR::Errors::EnterToModeIsLocked :
  395. {
  396. mLocked = mLocked || (aCommand[0] == CAtolFR::Commands::OpenDocument);
  397. break;
  398. }
  399. }
  400. return false;
  401. }
  402. //---------------------------------------------------------------------------
  403. bool AtolFRBase::checkTaxes()
  404. {
  405. char mode = mMode;
  406. if (!enterInnerMode(CAtolFR::InnerModes::Programming))
  407. {
  408. return false;
  409. }
  410. bool result = TSerialFRBase::checkTaxes();
  411. enterInnerMode(mode);
  412. return result;
  413. }
  414. //--------------------------------------------------------------------------------
  415. bool AtolFRBase::printLine(const QByteArray & aString)
  416. {
  417. if (mProcessingErrors.contains(CAtolFR::Errors::NoPaper))
  418. {
  419. return false;
  420. }
  421. if (!processCommand(CAtolFR::Commands::PrintString, aString))
  422. {
  423. toLog(LogLevel::Error, "AtolFR: Failed to process print line command for AtolFR");
  424. return false;
  425. }
  426. return true;
  427. }
  428. //--------------------------------------------------------------------------------
  429. bool AtolFRBase::processReceipt(const QStringList & aReceipt, bool aProcessing)
  430. {
  431. bool result = TSerialFRBase::processReceipt(aReceipt, aProcessing);
  432. if (aProcessing)
  433. {
  434. SleepHelper::msleep(CAtolFR::Timeouts::EndNotFiscalPrint);
  435. }
  436. return result;
  437. }
  438. //--------------------------------------------------------------------------------
  439. bool AtolFRBase::getCommonStatus(TStatusCodes & aStatusCodes)
  440. {
  441. if (!getShortStatus(aStatusCodes))
  442. {
  443. return false;
  444. }
  445. QByteArray data = performStatus(aStatusCodes, CAtolFR::Commands::GetLongStatus, 9);
  446. if (data == CFR::Result::Fail)
  447. {
  448. return false;
  449. }
  450. else if ((data != CFR::Result::Error) && (data[9] & CAtolFR::States::CoverIsOpened))
  451. {
  452. aStatusCodes.insert(DeviceStatusCode::Error::CoverIsOpened);
  453. }
  454. return true;
  455. }
  456. //--------------------------------------------------------------------------------
  457. bool AtolFRBase::getStatus(TStatusCodes & aStatusCodes)
  458. {
  459. // если находимся в ошибке сбоя буфера Z-отчета - пытаемся исправиться
  460. // на случай, если принтер отваливается, но потом опять появляется, не используем последние статусы
  461. if (mZBufferError)
  462. {
  463. mZBufferError = !enterExtendedMode();
  464. }
  465. if (mCanProcessZBuffer)
  466. {
  467. checkZBufferState();
  468. // если Z-буфер есть и уже заполнен - запрашиваем время, чтобы понять - переполнен ли он.
  469. if (mZBufferFull && !mZBufferOverflow)
  470. {
  471. int sessionInterval = mLastOpenSession.secsTo(QDateTime::currentDateTime()) / 60;
  472. mZBufferOverflow = sessionInterval >= (24 * 60 - CPrinters::ZBufferVirtualOverflow);
  473. }
  474. }
  475. if (!getCommonStatus(aStatusCodes))
  476. {
  477. return false;
  478. }
  479. bool buildOK = !mFRBuild || (mFRBuild >= mModelData.build);
  480. if (!buildOK)
  481. {
  482. aStatusCodes.insert(DeviceStatusCode::Warning::Firmware);
  483. }
  484. if (mProcessingErrors.contains(CAtolFR::Errors::NeedZReport))
  485. {
  486. if (!mCanProcessZBuffer)
  487. {
  488. mNeedCloseSession = true;
  489. }
  490. else if (!mWhiteSpaceZBuffer)
  491. {
  492. mZBufferOverflow = true;
  493. }
  494. }
  495. return true;
  496. }
  497. //--------------------------------------------------------------------------------
  498. void AtolFRBase::execTags(Tags::SLexeme & aTagLexeme, QVariant & aLine)
  499. {
  500. QByteArray data = mCodec->fromUnicode(aTagLexeme.data);
  501. if (aTagLexeme.tags.contains(Tags::Type::DoubleWidth))
  502. {
  503. Tags::TTypes types;
  504. types.insert(Tags::Type::DoubleWidth);
  505. for (int i = 0; i < data.size(); i = i + 2)
  506. {
  507. data.insert(i, mTagEngine->getTag(types, Tags::Direction::Open));
  508. }
  509. }
  510. aLine = aLine.toByteArray() + data;
  511. //TODO: реализовать теги командой Печать поля (87h):
  512. // для PayVKP-80 : inverse, bold и underline
  513. // для PayPPU-700 : inverse, bold
  514. // для PayCTS-2000 : inverse
  515. }
  516. //--------------------------------------------------------------------------------
  517. bool AtolFRBase::performFiscal(const QStringList & aReceipt, const SPaymentData & aPaymentData, quint32 * /*aFDNumber*/)
  518. {
  519. if (!enterInnerMode(CAtolFR::InnerModes::Register) || (!openDocument(aPaymentData.payOffType) && !mLocked))
  520. {
  521. return false;
  522. }
  523. bool result = processReceipt(aReceipt, false);
  524. if (result)
  525. {
  526. foreach (auto unitData, aPaymentData.unitDataList)
  527. {
  528. result = result && sale(unitData);
  529. }
  530. if (result)
  531. {
  532. result = setOFDParameters() && closeDocument(aPaymentData.payType);
  533. }
  534. else if (aPaymentData.back() && (mLastError == CAtolFR::Errors::NoMoneyForPayout))
  535. {
  536. emitStatusCode(FRStatusCode::Error::NoMoney, EFRStatus::NoMoneyForSellingBack);
  537. }
  538. }
  539. if (!result)
  540. {
  541. processCommand(CAtolFR::Commands::CancelDocument);
  542. }
  543. exitInnerMode();
  544. return result;
  545. }
  546. //--------------------------------------------------------------------------------
  547. bool AtolFRBase::isFiscalReady(bool aOnline, EFiscalPrinterCommand::Enum aCommand)
  548. {
  549. if (!TSerialFRBase::isFiscalReady(aOnline, aCommand))
  550. {
  551. return false;
  552. }
  553. else if (aCommand == EFiscalPrinterCommand::Encashment)
  554. {
  555. int sessionInterval = mLastOpenSession.secsTo(QDateTime::currentDateTime()) / 60;
  556. if (sessionInterval >= 24 * 60)
  557. {
  558. return false;
  559. }
  560. MutexLocker locker(&mExternalMutex);
  561. return enterInnerMode(CAtolFR::InnerModes::Register);
  562. }
  563. return true;
  564. }
  565. //--------------------------------------------------------------------------------
  566. double AtolFRBase::getAmountInCash()
  567. {
  568. QByteArray data;
  569. if (!getRegister(CAtolFR::Registers::MoneyInCash, data))
  570. {
  571. return -1;
  572. }
  573. bool OK;
  574. double result = data.toHex().toInt(&OK) / 100.0;
  575. return OK ? result : -1;
  576. }
  577. //--------------------------------------------------------------------------------
  578. bool AtolFRBase::processPayout(double aAmount)
  579. {
  580. char innerMode = mMode;
  581. if (!enterInnerMode(CAtolFR::InnerModes::Register))
  582. {
  583. return false;
  584. }
  585. QByteArray commandData;
  586. commandData.append(CAtolFR::FiscalFlags::ExecutionMode);
  587. commandData.append(getBCD(aAmount / 10.0, 5, 2, 3));
  588. bool result = processCommand(CAtolFR::Commands::Encashment, commandData);
  589. enterInnerMode(innerMode);
  590. return result;
  591. }
  592. //--------------------------------------------------------------------------------
  593. bool AtolFRBase::cut()
  594. {
  595. return processCommand(CAtolFR::Commands::Cut, QByteArray(1, CAtolFR::FullCutting));
  596. }
  597. //--------------------------------------------------------------------------------
  598. bool AtolFRBase::printDeferredZReports()
  599. {
  600. if (!exitInnerMode())
  601. {
  602. return false;
  603. }
  604. int command = getConfigParameter(CHardware::FR::Commands::PrintingDeferredZReports).toInt();
  605. if (!processCommand(char(command)))
  606. {
  607. toLog(LogLevel::Error, "AtolFR: Failed to print deferred Z-reports");
  608. return false;
  609. }
  610. if (!waitForChangeZReportMode())
  611. {
  612. toLog(LogLevel::Error, "AtolFR: Waiting error when printing deferred Z-reports");
  613. return false;
  614. }
  615. return true;
  616. }
  617. //--------------------------------------------------------------------------------
  618. bool AtolFRBase::performZReport(bool aPrintDeferredReports)
  619. {
  620. toLog(LogLevel::Normal, "AtolFR: Processing Z-report");
  621. bool printDeferredZReportSuccess = true;
  622. // если ККМ работает в расширенном режиме - печатаем отложенные Z-отчеты
  623. if (mCanProcessZBuffer && aPrintDeferredReports)
  624. {
  625. toLog(LogLevel::Normal, "AtolFR: Printing deferred Z-reports");
  626. printDeferredZReportSuccess = printDeferredZReports();
  627. }
  628. bool ZReportSuccess = execZReport(false);
  629. // ошибки наполнения и переполнения буфера Z-Отчетов можно сбросить
  630. checkZBufferState();
  631. exitInnerMode();
  632. bool result = ZReportSuccess || (printDeferredZReportSuccess && aPrintDeferredReports);
  633. if ((ZReportSuccess || (printDeferredZReportSuccess && aPrintDeferredReports)) || (mWhiteSpaceZBuffer > 1))
  634. {
  635. mZBufferOverflow = false;
  636. }
  637. return result;
  638. }
  639. //--------------------------------------------------------------------------------
  640. bool AtolFRBase::openDocument(EPayOffTypes::Enum aPayOffType)
  641. {
  642. QByteArray commandData;
  643. commandData.append(CAtolFR::FiscalFlags::ExecutionMode);
  644. commandData.append(CAtolFR::PayOffTypeData[aPayOffType]);
  645. if (processCommand(CAtolFR::Commands::OpenDocument, commandData))
  646. {
  647. return true;
  648. }
  649. if (mLocked)
  650. {
  651. toLog(LogLevel::Normal, "AtolFR: FR is locked.");
  652. return false;
  653. }
  654. EFiscalDocumentState::Enum state;
  655. if (!getFiscalDocumentState(state) || (state != EFiscalDocumentState::Opened))
  656. {
  657. toLog(LogLevel::Error, "AtolFR: Failed to open document.");
  658. return false;
  659. }
  660. toLog(LogLevel::Normal, "AtolFR: There was an error at the check opening, but the document is opened");
  661. return true;
  662. }
  663. //--------------------------------------------------------------------------------
  664. bool AtolFRBase::closeDocument(EPayTypes::Enum aPayType)
  665. {
  666. QByteArray commandData;
  667. commandData.append(CAtolFR::FiscalFlags::ExecutionMode);
  668. commandData.append(mPayTypeData[aPayType].value);
  669. commandData.append(QByteArray(5, ASCII::NUL)); // сумма = 0, т.к. сдачу терминал не дает
  670. if (!processCommand(CAtolFR::Commands::CloseDocument, commandData))
  671. {
  672. EFiscalDocumentState::Enum state;
  673. if (!getFiscalDocumentState(state) || (state != EFiscalDocumentState::Closed))
  674. {
  675. toLog(LogLevel::Error, "AtolFR: Failed to close document");
  676. cancelDocument(true);
  677. return false;
  678. }
  679. }
  680. return true;
  681. }
  682. //--------------------------------------------------------------------------------
  683. bool AtolFRBase::sale(const SUnitData & aUnitData)
  684. {
  685. if (!aUnitData.name.isEmpty())
  686. {
  687. printLine(mCodec->fromUnicode(aUnitData.name));
  688. }
  689. if (!aUnitData.VAT)
  690. {
  691. QString withoutTaxes = getConfigParameter(CHardware::FR::Strings::WithoutTaxes).toString();
  692. printLine(mCodec->fromUnicode(withoutTaxes));
  693. }
  694. int taxGroup = (aUnitData.section == -1) ? mTaxData[aUnitData.VAT].group : aUnitData.section;
  695. QByteArray commandData;
  696. commandData.append(CAtolFR::SaleFlags); // флаги
  697. commandData.append(getBCD(aUnitData.sum / 10.0, 5, 2, 3)); // сумма
  698. commandData.append(getBCD(10, 5, 2)); // количество = 1 штука
  699. commandData.append(uchar(taxGroup)); // отдел (== налоговая ставка)
  700. if (processCommand(CAtolFR::Commands::Sale, commandData))
  701. {
  702. return true;
  703. }
  704. toLog(LogLevel::Error, QString("%1: Failed to sale for %2 (%3, VAT = %4)").arg(mDeviceName).arg(aUnitData.sum, 0, 'f', 2).arg(aUnitData.name).arg(aUnitData.VAT));
  705. EFiscalDocumentState::Enum state = EFiscalDocumentState::Other;
  706. bool documentOpened = !getFiscalDocumentState(state) || (state == EFiscalDocumentState::Opened) || (state == EFiscalDocumentState::Sale);
  707. cancelDocument(documentOpened);
  708. return false;
  709. }
  710. //--------------------------------------------------------------------------------
  711. bool AtolFRBase::setDocumentCapAmount(char aAmount)
  712. {
  713. // войдем в режим программирования
  714. if (!enterInnerMode(CAtolFR::InnerModes::Programming))
  715. {
  716. return false;
  717. }
  718. // ставим количество строк шапки
  719. if (!setFRParameter(CAtolFR::FRParameters::DocumentCapStringsAmount, char(aAmount)))
  720. {
  721. toLog(LogLevel::Error, "AtolFR: Failed to set document cap strings amount");
  722. return false;
  723. }
  724. return true;
  725. }
  726. //--------------------------------------------------------------------------------
  727. void AtolFRBase::cancelDocument(bool aDocumentIsOpened)
  728. {
  729. bool result = true;
  730. //если фискальный документ открыт - пытаемся аннулировать. Не вышло или не открыт - проматываем, если надо, и режем
  731. if (aDocumentIsOpened)
  732. {
  733. //аннулируем фискальный документ
  734. result = processCommand(CAtolFR::Commands::CancelDocument);
  735. }
  736. if (!(aDocumentIsOpened && result))
  737. {
  738. receiptProcessing();
  739. }
  740. // выходим из режима
  741. exitInnerMode();
  742. }
  743. //--------------------------------------------------------------------------------
  744. bool AtolFRBase::getLastSessionDT(QDateTime & aLastSessionDT)
  745. {
  746. QByteArray data;
  747. // Делаем запрос состояния смены только если ЭКЛЗ активирована, иначе вернется мусор
  748. if (!mFiscalized || !getRegister(CAtolFR::Registers::SessionInfo, data))
  749. {
  750. return false;
  751. }
  752. QString dateDataTime = data.mid(1, 6).toHex().insert(4, "20");
  753. aLastSessionDT = QDateTime::fromString(dateDataTime, CAtolFR::SessionDTFormat);
  754. return aLastSessionDT.isValid();
  755. }
  756. //--------------------------------------------------------------------------------
  757. void AtolFRBase::processDeviceData()
  758. {
  759. setDeviceParameter(CDeviceData::ModelNumber, mModelData.modelNumber);
  760. // Вытаскиваем инфо по софта ФР и БЛ запросами версий софта
  761. CAtolFR::SSoftInfo softInfo;
  762. removeDeviceParameter(CDeviceData::Firmware);
  763. removeDeviceParameter(CDeviceData::BootFirmware);
  764. if (getSoftVersion(CAtolFR::FRSubSystems::FR, softInfo))
  765. {
  766. setDeviceParameter(CDeviceData::FR::Language, softInfo.language);
  767. setDeviceParameter(CDeviceData::Version, softInfo.version, CDeviceData::Firmware);
  768. setDeviceParameter(CDeviceData::Build, softInfo.build, CDeviceData::Firmware);
  769. }
  770. if (getSoftVersion(CAtolFR::FRSubSystems::BL, softInfo))
  771. {
  772. setDeviceParameter(CDeviceData::Version, softInfo.version, CDeviceData::BootFirmware);
  773. setDeviceParameter(CDeviceData::Build, softInfo.build, CDeviceData::BootFirmware);
  774. }
  775. //запрашиваем статус для получения режима.подрежима
  776. TStatusCodes statusCodes;
  777. getShortStatus(statusCodes);
  778. QByteArray data;
  779. QDateTime FRDateTime = getDateTime();
  780. if (FRDateTime.isValid())
  781. {
  782. setDeviceParameter(CDeviceData::FR::CurrentDate, FRDateTime.toString(CFR::DateTimeLogFormat));
  783. }
  784. if (getRegister(CAtolFR::Registers::SerialNumber, data))
  785. {
  786. mSerial = CFR::serialToString(data.toHex());
  787. }
  788. removeDeviceParameter(CDeviceData::FR::Session);
  789. QDateTime lastSessionDT;
  790. if (getLastSessionDT(lastSessionDT) && getLongStatus(data))
  791. {
  792. QString sessionDT = lastSessionDT.toString(CFR::DateTimeLogFormat);
  793. if (data[9] & CAtolFR::States::SessionOpened)
  794. {
  795. mLastOpenSession = lastSessionDT.addDays(-1);
  796. setDeviceParameter(CDeviceData::FR::Session, CDeviceData::Values::Opened);
  797. setDeviceParameter(CDeviceData::FR::FutureClosingDate, sessionDT, CDeviceData::FR::Session);
  798. }
  799. else
  800. {
  801. setDeviceParameter(CDeviceData::FR::Session, CDeviceData::Values::Closed);
  802. setDeviceParameter(CDeviceData::FR::LastClosingDate, sessionDT, CDeviceData::FR::Session);
  803. }
  804. }
  805. }
  806. //--------------------------------------------------------------------------------
  807. bool AtolFRBase::setFRParameter(const CAtolFR::FRParameters::SData & aData, const QVariant & aValue)
  808. {
  809. QByteArray commandData;
  810. commandData.append(aData.table);
  811. commandData.append(uchar(aData.series << 8));
  812. commandData.append(uchar(aData.series << 0));
  813. commandData.append(aData.field);
  814. if (aValue.type() == QVariant::ByteArray) commandData.append(aValue.toByteArray());
  815. else if (aValue.type() == QVariant::String) commandData.append(mCodec->fromUnicode(aValue.toString()));
  816. else commandData.append(char(aValue.toInt()));
  817. if (!processCommand(CAtolFR::Commands::SetFRParameters, commandData))
  818. {
  819. toLog(LogLevel::Error, mDeviceName + QString(": Failed to set %1 = %2").arg(aData.log()).arg(aValue.toString()));
  820. return false;
  821. }
  822. return true;
  823. }
  824. //--------------------------------------------------------------------------------
  825. bool AtolFRBase::setFRParameters()
  826. {
  827. if (!setFRParameter(CAtolFR::FRParameters::TaxType, CAtolFR::CustomSaleTax) || // тип налога - на каждую покупку
  828. !setFRParameter(CAtolFR::FRParameters::Cut, true) || // автоотрезка фискального чека - да. Нефискальный - режем сами
  829. !setFRParameter(CAtolFR::FRParameters::PrintNotFiscalData, true)) // печатать текст командой Печать строки - да
  830. {
  831. return false;
  832. }
  833. QByteArray data;
  834. if (!getFRParameter(CAtolFR::FRParameters::ReportMode, data))
  835. {
  836. return false;
  837. }
  838. char FRReportMode = data[0];
  839. char reportMode = CAtolFR::ReportMode;
  840. QString nullingSumInCash = getConfigParameter(CHardwareSDK::FR::NullingSumInCash).toString();
  841. if (nullingSumInCash == CHardwareSDK::Values::Auto)
  842. {
  843. reportMode &= ~CAtolFR::NullingSumInCashMask;
  844. reportMode |= FRReportMode & CAtolFR::NullingSumInCashMask;
  845. }
  846. else if (nullingSumInCash == CHardwareSDK::Values::Use)
  847. {
  848. reportMode |= CAtolFR::NullingSumInCashMask;
  849. }
  850. else if (nullingSumInCash == CHardwareSDK::Values::NotUse)
  851. {
  852. reportMode &= ~CAtolFR::NullingSumInCashMask;
  853. }
  854. if (mOperatorPresence)
  855. {
  856. reportMode |= CAtolFR::LongReportMask;
  857. }
  858. if (reportMode != FRReportMode)
  859. {
  860. // выставляем параметры формата печати Z-отчета
  861. setFRParameter(CAtolFR::FRParameters::ReportMode, reportMode);
  862. }
  863. // размеры "ИТОГ" на чеке
  864. setFRParameter(CAtolFR::FRParameters::ResumeSize, CAtolFR::ResumeSize);
  865. // печатать имя кассира
  866. setFRParameter(CAtolFR::FRParameters::PrintCashier, true);
  867. // сквозная нумерация на чеке - нет
  868. setFRParameter(CAtolFR::FRParameters::ContinuousDocumentNumbering, false);
  869. // обнулять счетчик чеков при закрытии смены - нет
  870. setFRParameter(CAtolFR::FRParameters::AutoNullingChequeCounter, false);
  871. // печатать номер секции - нет
  872. setFRParameter(CAtolFR::FRParameters::PrintSectionNumber, false);
  873. // печатать название секции - в зависимости от наличия оператора
  874. setFRParameter(CAtolFR::FRParameters::PrintSectionName, mOperatorPresence);
  875. // печатать документ открытия смены - нет
  876. setFRParameter(CAtolFR::FRParameters::OpeningSessionDocument, false);
  877. // количество строк шапки
  878. setFRParameter(CAtolFR::FRParameters::DocumentCapStringsAmount, mDocumentCapStrings);
  879. // межстрочный интервал
  880. if (mModelData.lineSpacing && containsConfigParameter(CHardware::Printer::Settings::LineSpacing))
  881. {
  882. int linespacing = getConfigParameter(CHardware::Printer::Settings::LineSpacing).toInt();
  883. setFRParameter(CAtolFR::FRParameters::LineSpacing, char(linespacing));
  884. }
  885. return true;
  886. }
  887. //--------------------------------------------------------------------------------
  888. bool AtolFRBase::getFRParameter(const CAtolFR::FRParameters::SData & aData, QByteArray & aValue)
  889. {
  890. QByteArray commandData;
  891. commandData.append(aData.table);
  892. commandData.append(uchar(aData.series << 8));
  893. commandData.append(uchar(aData.series << 0));
  894. commandData.append(aData.field);
  895. if (!processCommand(CAtolFR::Commands::GetFRParameter, commandData, &aValue))
  896. {
  897. toLog(LogLevel::Error, mDeviceName + ": Failed to get " + aData.log());
  898. return false;
  899. }
  900. aValue = aValue.mid(2);
  901. return true;
  902. }
  903. //--------------------------------------------------------------------------------
  904. TResult AtolFRBase::getRegister(const QString & aRegister, QByteArray & aData, char aParameter1, char aParameter2)
  905. {
  906. CAtolFR::Registers::SData registerData = mRegisterData[aRegister];
  907. toLog(LogLevel::Normal, mDeviceName + QString(": Begin to get FR register %1 (%2)").arg(toHexLog(registerData.number)).arg(aRegister));
  908. QByteArray commandData;
  909. commandData.append(registerData.number);
  910. commandData.append(aParameter1);
  911. commandData.append(aParameter2);
  912. TResult result = processCommand(CAtolFR::Commands::GetFRRegister, commandData, &aData);
  913. if (!CORRECT(result) || (result == CommandResult::Device))
  914. {
  915. return result;
  916. }
  917. if (aData.size() < (2 + registerData.size))
  918. {
  919. toLog(LogLevel::Error, mDeviceName + QString(": invalid answer size for register %1: %2, need %3 minimum")
  920. .arg(toHexLog(registerData.number)).arg(aData.size()).arg(2 + registerData.size));
  921. return CommandResult::Answer;
  922. }
  923. aData = aData.mid(2);
  924. return CommandResult::OK;
  925. }
  926. //--------------------------------------------------------------------------------
  927. bool AtolFRBase::waitForChangeXReportMode()
  928. {
  929. //запускаем цикл опроса ККМ, ставим таймер на 20 с
  930. //(код состояния = 2.2 - печатается X отчет)
  931. QTime clockTimer;
  932. clockTimer.start();
  933. do
  934. {
  935. QTime clock = QTime::currentTime();
  936. TStatusCodes statusCodes;
  937. // Получаем статус, пауза 0.5 секунды
  938. if (getShortStatus(statusCodes))
  939. {
  940. if (getStatusCollection(statusCodes).size(EWarningLevel::Error))
  941. {
  942. // при распечатке отчета произошла ошибка, выходим с ошибкой
  943. toLog(LogLevel::Error, "AtolFR: unexpected error occured when printing Z-report, exit!");
  944. return false;
  945. }
  946. else if ((mMode == CAtolFR::InnerModes::Choice) ||
  947. ((mMode == CAtolFR::InnerModes::NotCancel) && (mSubmode == 0)))
  948. {
  949. // все нормально, выходим
  950. return true;
  951. }
  952. else if ((mMode == CAtolFR::InnerModes::NotCancel) && (mSubmode == 2))
  953. {
  954. toLog(LogLevel::Normal, "AtolFR: service X-report process, wait...");
  955. }
  956. else
  957. {
  958. // режим не тот, который ожидаем в соответствии с протоколом, выходим с ошибкой
  959. toLog(LogLevel::Error, QString("AtolFR: X-report, unknown mode.submode = %1.%2").arg(int(mMode)).arg(int(mSubmode)));
  960. return false;
  961. }
  962. }
  963. //спим до периода опроса
  964. int sleepTime = CAtolFR::Timeouts::XReportPoll - abs(clock.msecsTo(QTime::currentTime()));
  965. if (sleepTime > 0)
  966. {
  967. SleepHelper::msleep(sleepTime);
  968. }
  969. }
  970. while(clockTimer.elapsed() < CAtolFR::Timeouts::XReportNoAnswer);
  971. //вышли по таймауту, значит, не смогли дождаться нужного режима/подрежима
  972. return false;
  973. }
  974. //--------------------------------------------------------------------------------
  975. bool AtolFRBase::waitForChangeZReportMode()
  976. {
  977. //запускаем цикл опроса ККМ, ставим таймер на 20 с
  978. //(код состояния = 3.2 - печатается Z отчет) -> (код состояния = 7.1 - гасятся регистры)
  979. QTime clockTimer;
  980. clockTimer.start();
  981. do
  982. {
  983. QTime clock = QTime::currentTime();
  984. TStatusCodes statusCodes;
  985. // Получаем статус, пауза 0.5 секунды
  986. if (getShortStatus(statusCodes))
  987. {
  988. if (getStatusCollection(statusCodes).size(EWarningLevel::Error))
  989. {
  990. // при распечатке отчета(ов) произошла ошибка, выходим с ошибкой
  991. toLog(LogLevel::Error, "AtolFR: unexpected error occured when printing Z-report, exit!");
  992. return false;
  993. }
  994. else if ((mMode == CAtolFR::InnerModes::Choice) ||
  995. ((mMode == CAtolFR::InnerModes::Cancel) && (mSubmode == 0)))
  996. {
  997. // все нормально, выходим
  998. return true;
  999. }
  1000. else if (((mMode == CAtolFR::InnerModes::ExtraCommand) && (mSubmode == 1)) ||
  1001. (((mMode == CAtolFR::InnerModes::Cancel) ||
  1002. (mMode == CAtolFR::InnerModes::NotCancel)) && (mSubmode == 2)))
  1003. {
  1004. toLog(LogLevel::Normal, "AtolFR: service Z-report process, wait...");
  1005. }
  1006. else
  1007. {
  1008. // режим не тот, который ожидаем в соответствии с протоколом, выходим с ошибкой
  1009. toLog(LogLevel::Error, QString("AtolFR: Z-report, unknown mode.submode = %1.%2").arg(int(mMode)).arg(int(mSubmode)));
  1010. return false;
  1011. }
  1012. }
  1013. //спим до периода опроса
  1014. int sleepTime = CAtolFR::Timeouts::ZReportPoll - abs(clock.msecsTo(QTime::currentTime()));
  1015. if (sleepTime > 0)
  1016. {
  1017. SleepHelper::msleep(sleepTime);
  1018. }
  1019. }
  1020. while(clockTimer.elapsed() < CAtolFR::Timeouts::ZReportNoAnswer);
  1021. //вышли по таймауту, значит, не смогли дождаться нужного режима/подрежима
  1022. return false;
  1023. }
  1024. //--------------------------------------------------------------------------------
  1025. bool AtolFRBase::enterInnerMode(char aInnerMode)
  1026. {
  1027. // если хотим войти в такой же режим, в котором сейчас находимся - выходим
  1028. if (mMode == aInnerMode)
  1029. {
  1030. return true;
  1031. }
  1032. else if (mMode)
  1033. {
  1034. bool result = exitInnerMode();
  1035. // если хотим войти в режим выбора, либо не вышло выйти из режима - выходим
  1036. if (!result || (aInnerMode == CAtolFR::InnerModes::Choice))
  1037. {
  1038. return result;
  1039. }
  1040. }
  1041. toLog(LogLevel::Normal, QString("AtolFR: Entering to %1 mode").arg(int(aInnerMode)));
  1042. QByteArray commandData;
  1043. commandData.append(aInnerMode);
  1044. commandData.append(ASCII::NUL);
  1045. commandData.append(ASCII::NUL);
  1046. commandData.append(ASCII::NUL);
  1047. commandData.append(CAtolFR::Users::SysAdmin);
  1048. bool result = processCommand(CAtolFR::Commands::EnterToMode, commandData);
  1049. if (result)
  1050. {
  1051. mMode = aInnerMode;
  1052. }
  1053. return result;
  1054. }
  1055. //--------------------------------------------------------------------------------
  1056. bool AtolFRBase::exitInnerMode(bool aForce)
  1057. {
  1058. //выходим, если выходить дальше некуда
  1059. if (!aForce && (mMode == CAtolFR::InnerModes::Choice))
  1060. {
  1061. return true;
  1062. }
  1063. toLog(LogLevel::Normal, "AtolFR: Exiting from mode");
  1064. if (!processCommand(CAtolFR::Commands::ExitFromMode))
  1065. {
  1066. return false;
  1067. }
  1068. mMode = CAtolFR::InnerModes::Choice;
  1069. return true;
  1070. }
  1071. //--------------------------------------------------------------------------------
  1072. bool AtolFRBase::getSoftVersion(char aSoftType, CAtolFR::SSoftInfo & aSoftInfo)
  1073. {
  1074. QByteArray answer;
  1075. if (!processCommand(CAtolFR::Commands::GetSoftInfo, QByteArray(1, aSoftType), &answer))
  1076. {
  1077. return false;
  1078. }
  1079. aSoftInfo.version = QString("%1.%2")
  1080. .arg(uchar(answer.mid(2, 1).toHex().toUShort()), 1, 10, QChar(ASCII::Zero))
  1081. .arg(uchar(answer.mid(3, 1).toHex().toUShort()), 2, 10, QChar(ASCII::Zero));
  1082. aSoftInfo.build = answer.mid(5, 2).toHex().toUShort();
  1083. if (aSoftType == CAtolFR::FRSubSystems::FR)
  1084. {
  1085. aSoftInfo.language = CAtolFR::Languages[uchar(answer.mid(4, 1).toHex().toUShort())];
  1086. if (!mIsOnline)
  1087. {
  1088. mFRBuild = aSoftInfo.build;
  1089. }
  1090. }
  1091. return true;
  1092. }
  1093. //--------------------------------------------------------------------------------
  1094. bool AtolFRBase::processXReport()
  1095. {
  1096. // запомнили режим
  1097. char innerMode = mMode;
  1098. // заходим в режим гашения. не можем зайти - что-то критичное, выходим
  1099. if (!enterInnerMode(CAtolFR::InnerModes::NotCancel))
  1100. {
  1101. return false;
  1102. }
  1103. bool result = false;
  1104. if (processCommand(CAtolFR::Commands::XReport, QByteArray(1, CAtolFR::Balances::XReport)))
  1105. {
  1106. result = waitForChangeXReportMode();
  1107. }
  1108. enterInnerMode(innerMode);
  1109. return result;
  1110. }
  1111. //--------------------------------------------------------------------------------
  1112. bool AtolFRBase::execZReport(bool aAuto)
  1113. {
  1114. toLog(LogLevel::Normal, mDeviceName + QString(": Begin processing %1Z-report").arg(aAuto ? "auto-" : ""));
  1115. ESessionState::Enum sessionState = getSessionState();
  1116. ExitAction exitAction([&] () { mZBufferError = !enterExtendedMode(); });
  1117. if (sessionState == ESessionState::Error) return false;
  1118. else if (sessionState == ESessionState::Closed) return true;
  1119. bool cannotAutoZReport = !mCanProcessZBuffer || (mOperatorPresence && !getConfigParameter(CHardware::FR::ForcePerformZReport).toBool());
  1120. if (aAuto && cannotAutoZReport)
  1121. {
  1122. toLog(LogLevel::Error, mDeviceName + (mOperatorPresence ?
  1123. ": Failed to process auto-Z-report due to presence of the operator." :
  1124. ": has no Z-buffer, so it is impossible to perform auto-Z-report."));
  1125. mNeedCloseSession = mNeedCloseSession || (sessionState == ESessionState::Expired);
  1126. return false;
  1127. }
  1128. char innerMode = mMode;
  1129. if (!enterInnerMode(CAtolFR::InnerModes::Cancel))
  1130. {
  1131. return false;
  1132. }
  1133. QVariantMap outData;
  1134. QByteArray data;
  1135. char FDType = CAtolFR::PayOffTypeData[EPayOffTypes::Debit];
  1136. if (getRegister(CAtolFR::Registers::PaymentCount, data, FDType))
  1137. {
  1138. outData.insert(CFiscalPrinter::PaymentCount, data.toHex().toUShort());
  1139. }
  1140. double paymentAmount = 0;
  1141. if (getRegister(CAtolFR::Registers::PaymentAmount, data, FDType, CAtolFR::PaymentSource::Cash))
  1142. {
  1143. paymentAmount = qlonglong(data.toHex().toULongLong()) / 100.0;
  1144. outData.insert(CFiscalPrinter::PaymentAmount, paymentAmount);
  1145. }
  1146. mNonNullableAmount += paymentAmount;
  1147. outData.insert(CFiscalPrinter::NonNullableAmount, mNonNullableAmount);
  1148. if (getLongStatus(data))
  1149. {
  1150. int closedSessionNumber = data.mid(20, 2).toHex().toUShort();
  1151. outData.insert(CFiscalPrinter::ZReportNumber, closedSessionNumber + 1);
  1152. }
  1153. outData.insert(CFiscalPrinter::Serial, mSerial);
  1154. outData.insert(CFiscalPrinter::RNM, mRNM);
  1155. QDateTime FRDateTime = getDateTime();
  1156. if (FRDateTime.isValid())
  1157. {
  1158. outData.insert(CFiscalPrinter::FRDateTime, FRDateTime.toString(CFR::DateTimeLogFormat));
  1159. }
  1160. QString systemDateTime = QDateTime::currentDateTime().toString(CFR::DateTimeLogFormat);
  1161. outData.insert(CFiscalPrinter::SystemDateTime, systemDateTime);
  1162. // делаем закрытие смены и ждем смены состояний
  1163. mNeedCloseSession = false;
  1164. bool success = processCommand(CAtolFR::Commands::ZReport) && waitForChangeZReportMode();
  1165. mNeedCloseSession = getSessionState() == ESessionState::Expired;
  1166. if (!mNeedCloseSession)
  1167. {
  1168. mProcessingErrors.removeAll(CAtolFR::Errors::NeedZReport);
  1169. }
  1170. if (success)
  1171. {
  1172. emit FRSessionClosed(outData);
  1173. }
  1174. enterInnerMode(innerMode);
  1175. return success && exitAction.reset();
  1176. }
  1177. //--------------------------------------------------------------------------------
  1178. void AtolFRBase::checkZBufferState()
  1179. {
  1180. if (mCanProcessZBuffer)
  1181. {
  1182. if (!mWhiteSpaceZBuffer)
  1183. {
  1184. mZBufferFull = true;
  1185. }
  1186. else if (mWhiteSpaceZBuffer > 1)
  1187. {
  1188. mZBufferFull = false;
  1189. }
  1190. }
  1191. }
  1192. //--------------------------------------------------------------------------------
  1193. ESessionState::Enum AtolFRBase::getSessionState()
  1194. {
  1195. QByteArray data;
  1196. if (!getLongStatus(data))
  1197. {
  1198. return ESessionState::Error;
  1199. }
  1200. if (~data[9] & CAtolFR::States::SessionOpened)
  1201. {
  1202. return ESessionState::Closed;
  1203. }
  1204. QString dateTimeData = data.mid(3, 6).toHex().prepend("20");
  1205. QDateTime currentDT = QDateTime::fromString(dateTimeData, CAtolFR::DateTimeFormat);
  1206. QDateTime lastSessionDT;
  1207. if (!currentDT.isValid() || !getLastSessionDT(lastSessionDT))
  1208. {
  1209. return ESessionState::Error;
  1210. }
  1211. return (lastSessionDT.addDays(1) <= currentDT) ? ESessionState::Expired : ESessionState::Opened;
  1212. }
  1213. //--------------------------------------------------------------------------------
  1214. bool AtolFRBase::getFiscalDocumentState(EFiscalDocumentState::Enum & aState)
  1215. {
  1216. QByteArray data;
  1217. if (!getLongStatus(data))
  1218. {
  1219. return false;
  1220. }
  1221. char fiscalState = data[22] & 0x07;
  1222. switch (fiscalState)
  1223. {
  1224. case 0: aState = EFiscalDocumentState::Closed; break;
  1225. case 1: aState = EFiscalDocumentState::Opened; break;
  1226. case 3: aState = EFiscalDocumentState::Cancellation; break;
  1227. default: aState = EFiscalDocumentState::Other; break;
  1228. }
  1229. qlonglong sum = data.mid(23, 5).toHex().toULongLong();
  1230. if (sum && (aState == EFiscalDocumentState::Opened))
  1231. {
  1232. aState = EFiscalDocumentState::Sale;
  1233. }
  1234. return true;
  1235. }
  1236. //--------------------------------------------------------------------------------
  1237. EDocumentState::Enum AtolFRBase::getDocumentState()
  1238. {
  1239. EFiscalDocumentState::Enum state;
  1240. if (!getFiscalDocumentState(state))
  1241. {
  1242. return EDocumentState::Error;
  1243. }
  1244. return (state == EFiscalDocumentState::Closed) ? EDocumentState::Closed : EDocumentState::Opened;
  1245. }
  1246. //--------------------------------------------------------------------------------
  1247. bool AtolFRBase::getLongStatus(QByteArray & aData)
  1248. {
  1249. return processCommand(CAtolFR::Commands::GetLongStatus, &aData) && (aData.size() >= 28);
  1250. }
  1251. //--------------------------------------------------------------------------------
  1252. bool AtolFRBase::getShortStatus(TStatusCodes & aStatusCodes)
  1253. {
  1254. QByteArray data = performStatus(aStatusCodes, CAtolFR::Commands::GetShortStatus, 2);
  1255. if (data == CFR::Result::Fail)
  1256. {
  1257. return false;
  1258. }
  1259. else if (data != CFR::Result::Error)
  1260. {
  1261. mMode = (data[1] >> 0) & CAtolFR::ModeMask;
  1262. mSubmode = (data[1] >> 4) & CAtolFR::ModeMask;
  1263. parseShortStatusFlags(data[2], aStatusCodes);
  1264. }
  1265. return true;
  1266. }
  1267. //--------------------------------------------------------------------------------
  1268. void AtolFRBase::parseShortStatusFlags(char aFlags, TStatusCodes & aStatusCodes)
  1269. {
  1270. for (auto it = CAtolFR::ShortFlags.data().begin(); it != CAtolFR::ShortFlags.data().end(); ++it)
  1271. {
  1272. if (aFlags & it.key())
  1273. {
  1274. aStatusCodes.insert(it.value());
  1275. }
  1276. }
  1277. }
  1278. //--------------------------------------------------------------------------------