Overview

Namespaces

  • FlexiPeeHP
    • Bricks
    • ui
  • None
  • Orderer
  • PHP
  • SpojeNet
    • System
      • ui

Classes

  • DatePeriod
  • FlexiPeeHP\Bricks\Convertor
  • FlexiPeeHP\Bricks\Customer
  • FlexiPeeHP\Bricks\HookReciever
  • FlexiPeeHP\Bricks\ParovacFaktur
  • FlexiPeeHP\Bricks\PotvrzeniUhrady
  • FlexiPeeHP\Bricks\Upominac
  • FlexiPeeHP\Bricks\Upominka
  • FlexiPeeHP\Bricks\WebHookHandler
  • FlexiPeeHP\Bricks\XSLT
  • FlexiPeeHP\ui\AdresarForm
  • FlexiPeeHP\ui\CompanyLogo
  • FlexiPeeHP\ui\ConnectionForm
  • FlexiPeeHP\ui\DownloadInvoiceButton
  • FlexiPeeHP\ui\EmbedResponsive
  • FlexiPeeHP\ui\EmbedResponsiveHTML
  • FlexiPeeHP\ui\EmbedResponsivePDF
  • FlexiPeeHP\ui\KontaktForm
  • FlexiPeeHP\ui\OrderListingItem
  • FlexiPeeHP\ui\OrdersListing
  • FlexiPeeHP\ui\RecordTypeSelect
  • FlexiPeeHP\ui\SearchBox
  • FlexiPeeHP\ui\StatusInfoBox
  • SpojeNet\System\ui\VATSwitch

Interfaces

  • Traversable

Functions

  • apiUrlToLink
  • createYearsFrom
  • exchange
  • logOperationResult
  • Orderer\deleteAllBetween
  • parseCmdline
  • SpojeNet\System\operationsMenu
  • Overview
  • Namespace
  • Class
  • Tree
   1:    2:    3:    4:    5:    6:    7:    8:    9:   10:   11:   12:   13:   14:   15:   16:   17:   18:   19:   20:   21:   22:   23:   24:   25:   26:   27:   28:   29:   30:   31:   32:   33:   34:   35:   36:   37:   38:   39:   40:   41:   42:   43:   44:   45:   46:   47:   48:   49:   50:   51:   52:   53:   54:   55:   56:   57:   58:   59:   60:   61:   62:   63:   64:   65:   66:   67:   68:   69:   70:   71:   72:   73:   74:   75:   76:   77:   78:   79:   80:   81:   82:   83:   84:   85:   86:   87:   88:   89:   90:   91:   92:   93:   94:   95:   96:   97:   98:   99:  100:  101:  102:  103:  104:  105:  106:  107:  108:  109:  110:  111:  112:  113:  114:  115:  116:  117:  118:  119:  120:  121:  122:  123:  124:  125:  126:  127:  128:  129:  130:  131:  132:  133:  134:  135:  136:  137:  138:  139:  140:  141:  142:  143:  144:  145:  146:  147:  148:  149:  150:  151:  152:  153:  154:  155:  156:  157:  158:  159:  160:  161:  162:  163:  164:  165:  166:  167:  168:  169:  170:  171:  172:  173:  174:  175:  176:  177:  178:  179:  180:  181:  182:  183:  184:  185:  186:  187:  188:  189:  190:  191:  192:  193:  194:  195:  196:  197:  198:  199:  200:  201:  202:  203:  204:  205:  206:  207:  208:  209:  210:  211:  212:  213:  214:  215:  216:  217:  218:  219:  220:  221:  222:  223:  224:  225:  226:  227:  228:  229:  230:  231:  232:  233:  234:  235:  236:  237:  238:  239:  240:  241:  242:  243:  244:  245:  246:  247:  248:  249:  250:  251:  252:  253:  254:  255:  256:  257:  258:  259:  260:  261:  262:  263:  264:  265:  266:  267:  268:  269:  270:  271:  272:  273:  274:  275:  276:  277:  278:  279:  280:  281:  282:  283:  284:  285:  286:  287:  288:  289:  290:  291:  292:  293:  294:  295:  296:  297:  298:  299:  300:  301:  302:  303:  304:  305:  306:  307:  308:  309:  310:  311:  312:  313:  314:  315:  316:  317:  318:  319:  320:  321:  322:  323:  324:  325:  326:  327:  328:  329:  330:  331:  332:  333:  334:  335:  336:  337:  338:  339:  340:  341:  342:  343:  344:  345:  346:  347:  348:  349:  350:  351:  352:  353:  354:  355:  356:  357:  358:  359:  360:  361:  362:  363:  364:  365:  366:  367:  368:  369:  370:  371:  372:  373:  374:  375:  376:  377:  378:  379:  380:  381:  382:  383:  384:  385:  386:  387:  388:  389:  390:  391:  392:  393:  394:  395:  396:  397:  398:  399:  400:  401:  402:  403:  404:  405:  406:  407:  408:  409:  410:  411:  412:  413:  414:  415:  416:  417:  418:  419:  420:  421:  422:  423:  424:  425:  426:  427:  428:  429:  430:  431:  432:  433:  434:  435:  436:  437:  438:  439:  440:  441:  442:  443:  444:  445:  446:  447:  448:  449:  450:  451:  452:  453:  454:  455:  456:  457:  458:  459:  460:  461:  462:  463:  464:  465:  466:  467:  468:  469:  470:  471:  472:  473:  474:  475:  476:  477:  478:  479:  480:  481:  482:  483:  484:  485:  486:  487:  488:  489:  490:  491:  492:  493:  494:  495:  496:  497:  498:  499:  500:  501:  502:  503:  504:  505:  506:  507:  508:  509:  510:  511:  512:  513:  514:  515:  516:  517:  518:  519:  520:  521:  522:  523:  524:  525:  526:  527:  528:  529:  530:  531:  532:  533:  534:  535:  536:  537:  538:  539:  540:  541:  542:  543:  544:  545:  546:  547:  548:  549:  550:  551:  552:  553:  554:  555:  556:  557:  558:  559:  560:  561:  562:  563:  564:  565:  566:  567:  568:  569:  570:  571:  572:  573:  574:  575:  576:  577:  578:  579:  580:  581:  582:  583:  584:  585:  586:  587:  588:  589:  590:  591:  592:  593:  594:  595:  596:  597:  598:  599:  600:  601:  602:  603:  604:  605:  606:  607:  608:  609:  610:  611:  612:  613:  614:  615:  616:  617:  618:  619:  620:  621:  622:  623:  624:  625:  626:  627:  628:  629:  630:  631:  632:  633:  634:  635:  636:  637:  638:  639:  640:  641:  642:  643:  644:  645:  646:  647:  648:  649:  650:  651:  652:  653:  654:  655:  656:  657:  658:  659:  660:  661:  662:  663:  664:  665:  666:  667:  668:  669:  670:  671:  672:  673:  674:  675:  676:  677:  678:  679:  680:  681:  682:  683:  684:  685:  686:  687:  688:  689:  690:  691:  692:  693:  694:  695:  696:  697:  698:  699:  700:  701:  702:  703:  704:  705:  706:  707:  708:  709:  710:  711:  712:  713:  714:  715:  716:  717:  718:  719:  720:  721:  722:  723:  724:  725:  726:  727:  728:  729:  730:  731:  732:  733:  734:  735:  736:  737:  738:  739:  740:  741:  742:  743:  744:  745:  746:  747:  748:  749:  750:  751:  752:  753:  754:  755:  756:  757:  758:  759:  760:  761:  762:  763:  764:  765:  766:  767:  768:  769:  770:  771:  772:  773:  774:  775:  776:  777:  778:  779:  780:  781:  782:  783:  784:  785:  786:  787:  788:  789:  790:  791:  792:  793:  794:  795:  796:  797:  798:  799:  800:  801:  802:  803:  804:  805:  806:  807:  808:  809:  810:  811:  812:  813:  814:  815:  816:  817:  818:  819:  820:  821:  822:  823:  824:  825:  826:  827:  828:  829:  830:  831:  832:  833:  834:  835:  836:  837:  838:  839:  840:  841:  842:  843:  844:  845:  846:  847:  848:  849:  850:  851:  852:  853:  854:  855:  856:  857:  858:  859:  860:  861:  862:  863:  864:  865:  866:  867:  868:  869:  870:  871:  872:  873:  874:  875:  876:  877:  878:  879:  880:  881:  882:  883:  884:  885:  886:  887:  888:  889:  890:  891:  892:  893:  894:  895:  896:  897:  898:  899:  900:  901:  902:  903:  904:  905:  906:  907:  908:  909:  910:  911:  912:  913:  914:  915:  916:  917:  918:  919:  920:  921:  922:  923:  924:  925:  926:  927:  928:  929:  930:  931:  932:  933:  934:  935:  936:  937:  938:  939:  940:  941:  942:  943:  944:  945:  946:  947:  948:  949:  950:  951:  952:  953:  954:  955:  956:  957:  958:  959:  960:  961:  962:  963:  964:  965:  966:  967:  968:  969:  970:  971:  972:  973:  974:  975:  976:  977:  978:  979:  980:  981:  982:  983:  984:  985:  986:  987:  988:  989:  990:  991:  992:  993:  994:  995:  996:  997:  998:  999: 1000: 1001: 1002: 1003: 1004: 1005: 1006: 1007: 1008: 1009: 1010: 1011: 1012: 1013: 1014: 1015: 1016: 1017: 1018: 1019: 1020: 1021: 1022: 1023: 1024: 1025: 1026: 1027: 1028: 1029: 1030: 1031: 1032: 1033: 1034: 1035: 1036: 1037: 1038: 1039: 1040: 1041: 1042: 1043: 1044: 1045: 1046: 1047: 1048: 1049: 1050: 1051: 1052: 1053: 1054: 1055: 1056: 1057: 1058: 1059: 1060: 1061: 1062: 1063: 
<?php

namespace FlexiPeeHP\Bricks;

/**
 * Invoice matching class
 *
 * @copyright (c) 2018, Vítězslav Dvořák
 * @author Vítězslav Dvořák <info@vitexsoftware.cz>
 */
class ParovacFaktur extends \Ease\Sand
{
    /**
     * Invoice handler object
     * @var \FlexiPeeHP\FakturaVydana|\FlexiPeeHP\FakturaPrijata
     */
    private $invoicer;

    /**
     * account statements handler object
     * @var \FlexiPeeHP\Banka
     */
    public $banker;

    /**
     * @var Od kdy začít dohledávat doklady
     */
    public $daysBack = 1;

    /**
     * Configuration options
     * @var array 
     */
    private $config = [];

    /**
     * Requied Config Keys
     * @var array 
     */
    public $cfgRequed = ["LABEL_PREPLATEK", "LABEL_CHYBIFAKTURA", "LABEL_NEIDENTIFIKOVANO"];

    /**
     * Invoice matcher
     */
    public function __construct($configuration = [])
    {
        $this->config = array_merge($this->config, $configuration);
        foreach ($this->cfgRequed as $key) {
            if ((array_key_exists($key, $this->config) === false) || empty($this->config[$key])) {
                throw new \Ease\Exception(sprintf(_('Configuration key %s is not set'),
                    $key));
            }
        }
        parent::__construct();
        $this->banker = new \FlexiPeeHP\Banka(null, $this->config);
    }

    /**
     * Start set date
     *
     * @param int $daysBack
     */
    public function setStartDay($daysBack)
    {
        if (!is_null($daysBack)) {
            $this->addStatusMessage('Start Date '.date('Y-m-d',
                    mktime(0, 0, 0, date("m"), date("d") - $daysBack, date("Y"))));
        }
        $this->daysBack = $daysBack;
    }

    /**
     * Prepare invoice helper
     * @return \FlexiPeeHP\FakturaVydana
     */
    public function getInvoicer()
    {
        if (!is_object($this->invoicer)) {
            $this->invoicer = new \FlexiPeeHP\FakturaVydana(null, $this->config);
        }
        return $this->invoicer;
    }

    /**
     * Get unmatched payments within given days and direction
     *
     * @param int    $daysBack Maximum age of payment
     * @param string $direction Incoming or outcoming payents in|out
     * 
     * @return array
     */
    public function getPaymentsToProcess($daysBack = 1, $direction = 'in')
    {
        $result                                  = [];
        $this->banker->defaultUrlParams['order'] = 'datVyst@A';
        $payments                                = $this->banker->getColumnsFromFlexibee([
            'id',
            'kod',
            'varSym',
            'specSym',
            'sumCelkem',
            'buc',
            'smerKod',
            'mena',
            'datVyst'],
            ["sparovano eq false AND typPohybuK eq '".(($direction == 'out') ? 'typPohybu.vydej'
                : 'typPohybu.prijem' )."' AND storno eq false ".
            (is_null($daysBack) ? '' :
            "AND datVyst eq '".\FlexiPeeHP\FlexiBeeRW::timestampToFlexiDate(mktime(0,
                    0, 0, date("m"), date("d") - $daysBack, date("Y")))."' ")
            ], 'id');

        if ($this->banker->lastResponseCode == 200) {
            if (empty($payments)) {
                $result = [];
            } else {
                $result = $payments;
            }
        }
        return $result;
    }

    /**
     * 
     * @param \DatePeriod $period
     * @param string  $direction
     * 
     * @return array
     */
    public function getPaymentsWithinPeriod(\DatePeriod $period,
                                            $direction = 'in')
    {
        $result                                  = [];
        $this->banker->defaultUrlParams['order'] = 'datVyst@A';

        $conds['storno']     = false;
        $conds['sparovano']  = false;
        $conds['typPohybuK'] = ($direction == 'out') ? 'typPohybu.vydej' : 'typPohybu.prijem';

        $conds['datVyst'] = $period;

        $payments = $this->banker->getColumnsFromFlexibee([
            'id',
            'kod',
            'buc',
            'smerKod',
            'varSym',
            'specSym',
            'sumCelkem',
            'mena',
            'datVyst'], $conds, 'id');

        if ($this->banker->lastResponseCode == 200) {
            if (empty($payments)) {
                $result = [];
            } else {
                $result = $payments;
            }
        }
        return $result;
    }

    /**
     * Vrací neuhrazené faktury
     *
     * @return array
     */
    public function getInvoicesToProcess()
    {
        $this->getInvoicer();
        $this->invoicer->defaultUrlParams['includes'] = '/faktura-vydana/typDokl';
        return $this->searchInvoices(["(stavUhrK is null OR stavUhrK eq 'stavUhr.castUhr') AND storno eq false"]);
    }

    /**
     * Párování odchozích faktur podle příchozích plateb v bance
     */
    public function outInvoicesMatchingByBank()
    {
        $this->getInvoicer();
        foreach ($this->getPaymentsToProcess($this->daysBack, 'in') as $paymentData) {

            $this->addStatusMessage(sprintf('Processing Payment %s %s %s vs: %s ss: %s %s',
                    $paymentData['kod'], $paymentData['sumCelkem'],
                    \FlexiPeeHP\FlexiBeeRO::uncode($paymentData['mena']),
                    $paymentData['varSym'], $paymentData['specSym'],
                    $this->banker->url.'/c/'.$this->banker->company.'/'.$this->banker->getEvidence().'/'.$paymentData['id']),
                'info');


            $invoices = $this->findInvoices($paymentData);
//  kdyz se vrati jedna faktura:
//     kdyz  je prijata castka mensi nebo rovno tak zlikviduji celou
//     kdyz sedi castka, nebo castecne
//  kdyz se vrati vic faktur  tak kdyz sedi castka uhrazuje se ta nejstarsi
//  jinak se uhrazuje castecne

            if (count($invoices) && count(current($invoices))) {
                $prijatoCelkem = floatval($paymentData['sumCelkem']);
                $payment       = new \FlexiPeeHP\Banka($paymentData,
                    $this->config);

                foreach ($invoices as $invoiceID => $invoiceData) {

                    $typDokl                = $invoiceData['typDokl'][0];
                    $docType                = $typDokl['typDoklK'];
                    $invoiceData['typDokl'] = \FlexiPeeHP\FlexiBeeRO::code($typDokl['kod']);

                    $invoice = new \FlexiPeeHP\FakturaVydana($invoiceData,
                        $this->config);

                    /*
                     *    Standardní faktura (typDokladu.faktura)
                     *    Dobropis/opravný daň. d. (typDokladu.dobropis)
                     *    Zálohová faktura (typDokladu.zalohFaktura)
                     *    Zálohový daňový doklad (typDokladu.zdd)
                     *    Dodací list (typDokladu.dodList)
                     *    Proforma (neúčetní) (typDokladu.proforma)
                     *    Pohyb Kč / Zůstatek Kč (typBanUctu.kc)
                     *    Pohyb měna / Zůstatek měna (typBanUctu.mena)
                     */
                    $matched = false;
                    switch ($docType) {
                        case 'typDokladu.zalohFaktura':
                        case 'typDokladu.faktura':
                            $matched = $this->settleInvoice($invoice, $payment);
                            break;
                        case 'typDokladu.proforma':
                            $matched = $this->settleProforma($invoice,
                                $paymentData);
                            break;
                        case 'typDokladu.dobropis':
                            $matched = $this->settleCreditNote($invoice,
                                $paymentData);
                            break;

                        default:
                            $this->addStatusMessage(
                                sprintf(_('Unsupported document type: %s %s'),
                                    $typDokl['typDoklK@showAs'].' ('.$docType.'): '.$invoiceData['typDokl'],
                                    $invoice->getApiURL()
                                ), 'warning');
                            break;
                    }

                    if ($matched && $this->savePayerAccount($invoice->getDataValue('firma'),
                            $payment)) {
                        $this->addStatusMessage(sprintf(_('new Bank account %s assigned to Address %s'),
                                $payment->getDataValue('buc').'/'.\FlexiPeeHP\FlexiBeeRO::uncode($payment->getDataValue('smerKod')),
                                $invoice->getDataValue('firma@showAs')));
                    }

                    $this->banker->loadFromFlexiBee($paymentData['id']);
                    if ($this->banker->getDataValue('sparovano') == true) {
                        break;
                    }
                }
            } else {

                if (!empty($paymentData['varSym'])) {
                    if (!empty($paymentData['varSym'])) {
                        $vInvoices = $this->searchInvoices(['varSym' => $paymentData['varSym']]);
                    }
                }
                if (!empty($paymentData['specSym'])) {
                    if (!empty($paymentData['specSym'])) {
                        $sInvoices = $this->searchInvoices(['specSym' => $paymentData['specSym']]);
                    }
                }

//                if ($vInvoices || $sInvoices) {
////                    $zdd = $this->paymentToZDD($payment);
////                    if ($zdd) {
////                        $this->addStatusMessage(sprinf(_('advance tax document created'),
////                                \FlexiPeeHP\FlexiBeeRO::uncode($zdd)));
////                    }
//
//                    $this->addStatusMessage(_('Invoice found: - overdue?'),
//                        'warning');
//                }
            }
        }
    }

    public function paymentToZDD($invoiceData)
    {
        $return = $this->invoiceCopy($invoiceData, 'ZDD');
    }

    /**
     * Párování prichozich faktur podle odchozich plateb v bance
     * 
     * @param  $name Description
     * 
     */
    public function inInvoicesMatchingByBank(\DatePeriod $range = null)
    {
        $this->invoicer = new \FlexiPeeHP\FakturaPrijata(null, $this->config);
        foreach ($this->getPaymentsWithinPeriod($range, 'out') as $outPaymentId => $outPaymentData) {
            $this->banker->setData($outPaymentData, true);
            $this->banker->setMyKey($outPaymentId);
            $this->addStatusMessage(sprintf('Processing Outcoming Payment %s %s %s vs: %s ss: %s %s',
                    $outPaymentData['kod'], $outPaymentData['sumCelkem'],
                    \FlexiPeeHP\FlexiBeeRO::uncode($outPaymentData['mena']),
                    $outPaymentData['varSym'], $outPaymentData['specSym'],
                    $this->banker->getApiURL()), 'info');


            $inInvoicesToMatch = $this->findInvoices($outPaymentData);
//  kdyz se vrati jedna faktura:
//     kdyz  je prijata castka mensi nebo rovno tak zlikviduji celou
//     kdyz sedi castka, nebo castecne
//  kdyz se vrati vic faktur  tak kdyz sedi castka uhrazuje se ta nejstarsi
//  jinak se uhrazuje castecne



            switch (count($inInvoicesToMatch)) {
                case 0:
                    $this->addStatusMessage(_('No incoming invoice found for outcoming payment'));
                    break;
                case 1:
                    $invoiceData = current($inInvoicesToMatch);
                    $invoiceID   = key($inInvoicesToMatch);
                    $inInvoice   = new \FlexiPeeHP\FakturaVydana($invoiceData,
                        array_merge($this->config,
                            ['evidence' => 'faktura-prijata']));
                    if ($this->settleInvoice($inInvoice, $this->banker)) {
                        //Post match action here
                    }
                    break;
                default :
                    if (self::isSameCompany($inInvoicesToMatch)) {
                        foreach ($inInvoicesToMatch as $invoiceID => $invoiceData) {
                            $inInvoice = new \FlexiPeeHP\FakturaVydana($invoiceData,
                                array_merge($this->config,
                                    ['evidence' => 'faktura-prijata']));
                            if ($this->settleInvoice($inInvoice, $this->banker)) {
                                
                            }
                        }
                    } else {
                        $this->addStatusMessage(_('Match by bank here'));
                        foreach ($inInvoicesToMatch as $invoiceID => $invoiceData) {
                            $inInvoice = new \FlexiPeeHP\FakturaVydana($invoiceData,
                                array_merge($this->config,
                                    ['evidence' => 'faktura-prijata']));
                        }
                    }
                    break;
            }


            if (count($inInvoicesToMatch) && count(current($inInvoicesToMatch))) {
                $uhrazenoCelkem = floatval($outPaymentData['sumCelkem']);
                $payment        = new \FlexiPeeHP\Banka($outPaymentData,
                    $this->config);
            }
        }
    }

    /**
     * Obtain FlexiBee company code for given bank account number
     * 
     * @param string $account
     * @param string $bankCode
     * 
     * @return string Company Code
     */
    public function getCompanyForBUC($account, $bankCode = null)
    {
        $bucer      = new \FlexiPeeHP\FlexiBeeRW(null,
            ['evidence' => 'adresar-bankovni-ucet']);
        $companyRaw = $bucer->getColumnsFromFlexibee(['firma'],
            empty($bankCode) ? ['buc' => $account] : ['buc' => $account, 'smerKod' => $bankCode]);
        return array_key_exists(0, $companyRaw) ? $companyRaw[0]['firma'] : null;
    }

    /**
     * Check for common company
     * 
     * @param array $documents invoices or payments data
     * 
     * @return boolean All records have same company
     */
    public static function isSameCompany($documents)
    {
        return count(self::reindexArrayBy($documents, 'firma')) == 1;
    }

    /**
     * Check for common bank account
     * 
     * @param array $documents invoices or payments data
     * 
     * @return boolean All records have same bank account
     */
    public static function isSameAccount($documents)
    {
        return count(self::reindexArrayBy($documents, 'buc')) == 1;
    }

    /**
     * Párování faktur dle nezaplacenych faktur
     */
    public function invoicesMatchingByInvoices()
    {
        foreach ($this->getInvoicesToProcess() as $invoiceData) {
            $payments = $this->findPayments($invoiceData);
            if (!empty($payments) && count(current($payments))) {
                $typDokl                = $invoiceData['typDokl'][0];
                $docType                = $typDokl['typDoklK'];
                $invoiceData['typDokl'] = \FlexiPeeHP\FlexiBeeRO::code($typDokl['kod']);
                $invoice                = new \FlexiPeeHP\FakturaVydana($invoiceData,
                    $this->config);
                $this->invoicer->setMyKey($invoiceData['id']);
                /*
                 *    Standardní faktura (typDokladu.faktura)
                 *    Dobropis/opravný daň. d. (typDokladu.dobropis)
                 *    Zálohová faktura (typDokladu.zalohFaktura)
                 *    Zálohový daňový doklad (typDokladu.zdd)
                 *    Dodací list (typDokladu.dodList)
                 *    Proforma (neúčetní) (typDokladu.proforma)
                 *    Pohyb Kč / Zůstatek Kč (typBanUctu.kc)
                 *    Pohyb měna / Zůstatek měna (typBanUctu.mena)
                 */

                foreach ($payments as $paymentData) {
                    $payment = new \FlexiPeeHP\Banka($paymentData, $this->config);
                    switch ($docType) {
                        case 'typDokladu.zalohFaktura':
                        case 'typDokladu.faktura':
                            if ($this->settleInvoice($invoice, $payment)) {
                                
                            }
                            break;
                        case 'typDokladu.proforma':
                            $this->settleProforma($invoice, $payments);
                            break;
                        case 'typDokladu.dobropis':
                            $this->settleCreditNote($invoice, $payments);
                            break;
                        default:
                            $this->addStatusMessage(
                                sprintf(_('Unsupported document type: %s %s'),
                                    $typDokl['typDoklK@showAs'].' ('.$docType.'): '.$invoiceData['typDokl'],
                                    $invoice->getApiURL()
                                ), 'warning');
                            break;
                    }
                }
            }
        }
    }

    /**
     * Provede "Zaplacení" vydaného dobropisu
     *
     * @param \FlexiPeeHP\FakturaVydana $invoice
     * @param \FlexiPeeHP\Banka $payment
     *
     * @return int vysledek 0 = chyba, 1 = sparovano
     */
    public function settleCreditNote($invoice, $payment)
    {
        $success       = 0;
        $prijataCastka = (float) $payment->getDataValue('sumCelkem');

        if ($prijataCastka < $invoice->getDataValue('zbyvaUhradit')) { //Castecna uhrada
            $this->addStatusMessages(sprinf(_('Castecna uhrada - DOBROPIS: prijato: %s ma byt zaplaceno %s'),
                    $prijataCastka, $invoice->getDataValue('zbyvaUhradit')),
                'warning');
        }
        if ($prijataCastka > $invoice->getDataValue('zbyvaUhradit')) { //Castecna uhrada
            $this->addStatusMessages(sprinf(_('Přeplatek - DOBROPIS: prijato: %s ma byt zaplaceno %s'),
                    $prijataCastka, $invoice->getDataValue('zbyvaUhradit')),
                'warning');

            $this->banker->dataReset();
            $this->banker->setDataValue('id', $payment['id']);
            $this->banker->setDataValue('stitky',
                $this->config['LABEL_PREPLATEK']);
            $this->banker->insertToFlexiBee();
        }

        if ($invoice->sparujPlatbu($payment, 'castecnaUhrada')) { //Jak se ma FlexiBee zachovat pri preplatku/nedoplatku
            $success = 1;
            $invoice->addStatusMessage(sprintf(_('Platba %s  %s byla sparovana s dobropisem %s'),
                    (string) $payment, $prijataCastka, (string) $invoice),
                'success');
            //PDF Danoveho dokladu priloz k nemu samemu
            //PDF Danoveho dokladu odesli mailem zakaznikovi y FLEXIBEE( nasledne pouzit tabulku Mail/Gandalf)
        }

        return $success;
    }

    /**
     * Provede "Zaplacení" vydané zalohove faktury
     *
     * @param \FlexiPeeHP\FakturaVydana $zaloha
     * @param array $payment
     * 
     * @return int vysledek 0 = chyba, 1 = sparovano, 2 sparovano a vytvorena faktura, -1 sparovnano ale chyba vytvoreni faktury
     */
    public function settleProforma($zaloha, $payment)
    {
        $success       = 0;
        $prijataCastka = (float) $payment['sumCelkem'];

        $platba = new \FlexiPeeHP\Banka((int) $payment['id'], $this->config);

        if ($zaloha->sparujPlatbu($platba, 'castecnaUhrada')) {
            $success = 1;
            $zaloha->addStatusMessage(sprintf(_('Platba %s  %s %s byla sparovana s zalohou %s'),
                    \FlexiPeeHP\FlexiBeeRO::uncode($platba), $prijataCastka,
                    \FlexiPeeHP\FlexiBeeRO::uncode($payment['mena']),
                    (string) $zaloha), 'success');

            if ($zaloha->getDataValue('zbyvaUhradit') > $prijataCastka) { // Castecna Uhrada
//                //Castecna uhrada
//                //Vytvorit ZDD ve vysi payment
//                $zdd = new \FlexiPeeHP\FakturaVydana(['firma' => $zaloha->getDataValue('firma'),
//                    'zavTxt' => $zaloha->getDataValue('zavTxt').' DOPLNIT!!! ',
//                    'varSym' => $zaloha->getDataValue('varSym'),
//                    'popis' => 'Částečná úhrada '.$zaloha->getDataValue('kod')
//                ]);
//
//                $zdd->setDataValue('typDokl', 'code:ZDD');
////                $zdd->setDataValue('zbyvaUhradit', 0); //Mozna nemusime resit -vymazat
////                $zdd->setDataValue('sumCelkem', $prijataCastka);
//                $zdd->setDataValue('szbDphZakl',
//                    $zaloha->getDataValue('szbDphZakl'));
//                $zdd->setDataValue('bezPolozek', true);
////                $zdd->setDataValue('stavUhrK', '');
//                $zdd->unsetDataValue('polozkyFaktury');
//
//                // ---------- Tady se resi sazby - nahrdit objektem pro praci s castkami --------------//
//                // DPH21
//                if ((float) $zaloha->getDataValue('sumCelkZakl')) {
//                    $sumZklZakl = $prijataCastka / ( 1 + (float) $zaloha->getDataValue('szbDphZakl')
//                        / 100 );
//
////                    $zdd->setDataValue('sumZklZakl', round($sumZklZakl, 2));
////                    $zdd->setDataValue('sumDphZakl',
////                        round($prijataCastka - $sumZklZakl, 2));
//                    $zdd->setDataValue('sumCelkZakl', round($prijataCastka, 2));
//                    // DPH00
//                } else {
//                    if ((float) $zaloha->getDataValue('sumOsv')) {
////                        $zdd->setDataValue('sumOsv', round($prijataCastka),
////                            2);
//                    }
//                }
//                $result = $zdd->insertToFlexiBee();
//
//                $zdd->loadFromFlexiBee();
//                $zaloha->debug = true;
//                $zdd->debug    = true;
//
//
//                $targt      = $platba->apiURL.'/vytvor-zdd.json';
//                $zauctovani = '01-02';
//                $value      = $zaloha->getDataValue('kod').'^^^'.$zauctovani;
//                $sender     = new \FlexiPeeHP\FlexiBeeRW();
//                $sender->setPostFields(['zalohaACleneni' => $value]);
//                $result     = $sender->performRequest($targt, 'POST', 'json');
//
//                $result = $zdd->odpocetZDD($zaloha,
//                    ['castkaMen' => $prijataCastka]);
//                if (isset($result['success']) && ($result['success'] == 'true')) {
//                    $success = 2;
//                    $zaloha->addStatusMessage(sprintf(_('Faktura #%s byla sparovana se ZDD'),
//                            $kod), 'success');
//                } else {
//                    $success = -1;
//                    $zaloha->addStatusMessage(sprintf(_('Faktura #%s nebyla sparovana se ZDD'),
//                            $kod), 'error');
//                }
                $zaloha->addStatusMessage(sprintf(_('Částečná úhrada %s'),
                        self::apiUrlToLink($zaloha->apiURL)), 'warning');

                $zaloha->addStatusMessage(sprintf(_('Vytvoř ZDD: %s'),
                        self::apiUrlToLink($platba->apiURL.'/vytvor-zdd')),
                    'debug');
            } else {

                if ($prijataCastka > $zaloha->getDataValue('zbyvaUhradit')) { // Preplatek
                    $zaloha->addStatusMessage(sprintf(_('Přeplatek %s'),
                            self::apiUrlToLink($platba->apiURL)), 'warning');
                }

                //Plna uhrada
                //$toCopy['sumCelkem'] = $payment->getDataValue('sumCelkem');
                //Dopsat pro vsechny mozne sazby dane - vytvorit objekt

                $faktura2 = $this->invoiceCopy($zaloha,
                    ['duzpUcto' => $platba->getDataValue('datVyst'), 'datVyst' => $platba->getDataValue('datVyst')]);
                $id       = (int) $faktura2->getLastInsertedId();
                $faktura2->loadFromFlexiBee($id);
                $kod      = $faktura2->getDataValue('kod');
                $faktura2->dataReset();
                $faktura2->setDataValue('id', 'code:'.$kod);
                $faktura2->setDataValue('typDokl', 'code:FAKTURA');

                $result = $faktura2->odpocetZalohy($zaloha);
                if (isset($result['success']) && ($result['success'] == 'true')) {
                    $success = 2;
                    $zaloha->addStatusMessage(sprintf(_('Faktura #%s byla sparovana'),
                            $kod), 'success');
                } else {
                    $success = -1;
                    $zaloha->addStatusMessage(sprintf(_('Faktura #%s nebyla sparovana'),
                            $kod), 'error');
                }
            }

            //PDF Danoveho dokladu priloz k nemu samemu
            //PDF Danoveho dokladu odesli mailem zakaznikovi y FLEXIBEE( nasledne pouzit tabulku Mail/Gandalf)
        }
        return $success;
    }

    /**
     * Provede "Zaplacení" vydané faktury
     *
     * @param \FlexiPeeHP\FakturaVydana $invoice Invoice to settle
     * @param \FlexiPeeHP\Banka         $payment Payment to settle by
     *
     * @return int vysledek 0 = chyba, 1 = sparovano
     */
    public function settleInvoice($invoice, $payment)
    {
        $success       = 0;
        $zbytek        = 'ne';
        $prijataCastka = (float) $payment->getDataValue('sumCelkem');
        $zbyvaUhradit  = $invoice->getDataValue('zbyvaUhradit');

        if ($prijataCastka < $zbyvaUhradit) { //Castecna uhrada
            $this->addStatusMessage(sprintf(_('Castecna uhrada - FAKTURA: prijato: %s %s ma byt zaplaceno %s %s'),
                    $prijataCastka,
                    \FlexiPeeHP\FlexiBeeRO::uncode($payment->getDataValue('mena')),
                    $zbyvaUhradit,
                    \FlexiPeeHP\FlexiBeeRO::uncode($invoice->getDataValue('mena'))),
                'warning');
            $zbytek = 'castecnaUhrada';
        }
        if ($prijataCastka > $zbyvaUhradit) { //Castecna uhrada
            $this->addStatusMessage(sprintf(_('Přeplatek - FAKTURA: prijato: %s %s ma byt zaplaceno %s %s'),
                    $prijataCastka,
                    \FlexiPeeHP\FlexiBeeRO::uncode($payment->getDataValue('mena')),
                    $zbyvaUhradit,
                    \FlexiPeeHP\FlexiBeeRO::uncode($invoice->getDataValue('mena'))),
                'warning');

            //$this->banker->insertToFlexiBee(['id'=>$payment->getDataValue('id'), 'stitky'=>$this->config['LABEL_CASTECNAUHRADA']]);
            $zbytek = 'ignorovat';
        }

        if ($invoice->sparujPlatbu($payment, $zbytek)) { //Jak se ma FlexiBee zachovat pri preplatku/nedoplatku
            $success = 1;
            $invoice->insertToFlexiBee(['id' => (string) $invoice, 'stavMailK' => 'stavMail.odeslat']);
            $invoice->addStatusMessage(sprintf(_('Platba %s  %s %s byla sparovana s fakturou %s'),
                    \FlexiPeeHP\FlexiBeeRO::uncode($payment->getRecordIdent()),
                    $prijataCastka,
                    \FlexiPeeHP\FlexiBeeRO::uncode($payment->getDataValue('mena')),
                    \FlexiPeeHP\FlexiBeeRO::uncode($invoice->getRecordIdent())),
                'success');
        }

        return $success;
    }

    /**
     * Provizorní zkopírování faktury
     *
     * @link https://www.flexibee.eu/podpora/Tickets/Ticket/View/28848 Chyba při Provádění akcí přes REST API JSON
     * @param \FlexiPeeHP\FakturaVydana $invoice
     * @param array                     $extraValues Extra hodnoty pro kopii faktury
     *
     * @return \FlexiPeeHP\FakturaVydana
     */
    function invoiceCopy($invoice, $extraValues = [])
    {
        $invoice2 = new \FlexiPeeHP\FakturaVydana(array_merge($invoice->getData(),
                array_merge($this->config, $extraValues)));
//        $invoice2->debug = true;
        $invoice2->setDataValue('typDokl', 'code:FAKTURA');
        $invoice2->unsetDataValue('id');
        $invoice2->unsetDataValue('kod');
        if ($invoice2->getDataValue('stavUhrK') != 'stavUhr.uhrazenoRucne') {
            $invoice2->unsetDataValue('stavUhrK');
        }
        $polozky = $invoice2->getDataValue('polozkyFaktury');
        if (count($polozky)) {
            foreach ($polozky as $pid => $polozka) {
                unset($polozky[$pid]['id']);
                unset($polozky[$pid]['datUcto']);
                unset($polozky[$pid]['doklFak']);
                unset($polozky[$pid]['doklFak@showAs']);
                unset($polozky[$pid]['doklFak@ref']);
                $polozky[$pid]['ucetni'] = true;
            }
        }
        $invoice2->setDataValue('polozkyFaktury', $polozky);

        $invoice2->unsetDataValue('external-ids');
//              $invoice2->unsetDataValue('duzpUcto');

        if (isset($extraValues['datVyst'])) {
            $today = $extraValues['datVyst'];
        } else {
            $today = date('Y-m-d');
        }
        $invoice2->setDataValue('duzpPuv', $today);
        $invoice2->setDataValue('duzpUcto', $today);
        $invoice2->setDataValue('datUcto', $today);
        $invoice2->setDataValue('stavMailK', 'stavMail.odeslat');
        $invoice2->insertToFlexiBee();
        if ($invoice2->lastResponseCode == 201) {
            $invoice->addStatusMessage(sprintf(_('Faktura %s byla vytvořena z dokladu %s'),
                    self::apiUrlToLink($invoice2->apiURL),
                    self::apiUrlToLink($invoice->apiURL)), 'success');
        }
        return $invoice2;
    }

    function hotfixDeductionOfAdvances()
    {
        
    }

    /**
     * 
     * @param array $vInvoices new invoices
     * @param array $invoices current invoices
     */
    static public function unifyInvoices($vInvoices, &$invoices)
    {
        if (!empty($vInvoices) && count($vInvoices)) {
            foreach ($vInvoices as $invoiceID => $invoice) {
                if (!array_key_exists($invoiceID, $invoices)) {
                    $invoices[$invoiceID] = $invoice;
                }
            }
        }
    }

    /**
     * Najde vydané faktury
     *
     * @param array $paymentData
     * 
     * @return array
     */
    public function findInvoices($paymentData)
    {
        $invoices  = [];
        $vInvoices = [];
        $sInvoices = [];
        $uInvoices = [];
        $bInvoices = [];

        if (!empty($paymentData['varSym'])) {
            $vInvoices = $this->findInvoice(['varSym' => $paymentData['varSym']]);
        }

        if (empty($vInvoices)) {

            if (!empty($paymentData['specSym'])) {

                // Faktury vydane "firma":"code:02100",
                // Adresar: ext:lms.cstmr:2365
                $uInvoices = $this->findInvoice(['firma' => sprintf("code:%05s",
                        $paymentData['specSym'])]);
            }

            if (!empty($paymentData['specSym'])) {
                $sInvoices = $this->findInvoice(['specSym' => $paymentData['specSym']]);
            }

            if ($paymentData['buc']) {
                $bInvoices = $this->findInvoice(['buc' => $paymentData['buc']]);
            }
        }

        self::unifyInvoices($vInvoices, $invoices);
        self::unifyInvoices($uInvoices, $invoices);
        self::unifyInvoices($sInvoices, $invoices);
        self::unifyInvoices($bInvoices, $invoices);

        $invoices = self::reorderInvoicesByAge($invoices);

        if (empty($paymentData['varSym']) && empty($paymentData['specSym'])) {
            $this->banker->dataReset();
            $this->banker->setDataValue('id', $paymentData['id']);
            $this->banker->setDataValue('stitky', 'NEIDENTIFIKOVANO');
            $this->addStatusMessage(_('Neidentifikovaná platba').': '.$this->banker->getApiURL(),
                'warning');
            $this->banker->insertToFlexiBee();
        } elseif (count($invoices) == 0) {
            $this->banker->dataReset();
            $this->banker->setDataValue('id', $paymentData['id']);
            $this->banker->setDataValue('stitky', 'CHYBIFAKTURA');
            $this->addStatusMessage(_('Platba bez faktury').': '.$this->banker->getApiURL(),
                'warning');
            $this->banker->insertToFlexiBee();
        }

        return $invoices;
    }

    /**
     * Reorder invoices by Age. 
     * 
     * @param array $invoices
     * 
     * @return array Older First sorted invoices
     */
    public static function reorderInvoicesByAge($invoices)
    {
        $invoicesByAge    = [];
        $invoicesByAgeRaw = [];
        foreach ($invoices as $invoiceData) {
            $invoicesByAgeRaw[\FlexiPeeHP\FlexiBeeRW::flexiDateToDateTime($invoiceData['datVyst'])->getTimestamp()]
                = $invoiceData;
        }
        ksort($invoicesByAgeRaw);
        foreach ($invoicesByAgeRaw as $invoiceData) {
            $invoicesByAge[$invoiceData['kod']] = $invoiceData;
        }
        return $invoicesByAge;
    }

    /**
     * Najde příchozí platby
     *
     * @param array $invoiceData
     * @return array
     */
    public function findPayments($invoiceData)
    {
        $pays  = [];
        $sPays = [];
        $bPays = [];

        if (array_key_exists('varSym', $invoiceData) && !empty($invoiceData['varSym'])) {
            $sPays = $this->findPayment(['varSym' => $invoiceData['varSym']]);
            if (is_array($sPays)) {
                $pays = $sPays;
            }
        }

        if (array_key_exists('specSym', $invoiceData) && !empty($invoiceData['specSym'])) {
            $sPays = $this->findPayment(['specSym' => $invoiceData['specSym']]);
            if (is_array($bPays)) {
                $pays = $bPays;
            }
        }

        if (array_key_exists('buc', $invoiceData) && !empty($invoiceData['buc'])) {
            $bPays = $this->findPayment(['buc' => $invoiceData['buc']]);
            if ($bPays) {
                foreach ($bPays as $payID => $payment) {
                    if (!array_key_exists($payID, $pays)) {
                        $pays[$payID] = $payment;
                    }
                }
            }
        }

        return $pays;
    }

    /**
     * Vrací neuhrazene faktury odpovídající zadaným parametrům
     *
     * @param array $what
     * @return array
     */
    public function findInvoice($what)
    {
        return $this->searchInvoices(["(".\FlexiPeeHP\FlexiBeeRO::flexiUrl($what,
                    'or').") AND (stavUhrK is null OR stavUhrK eq 'stavUhr.castUhr') AND storno eq false"]);
    }

    /**
     * Vrací neuhrazene faktury odpovídající zadaným parametrům
     *
     * @param array $what
     * 
     * @return array
     */
    public function searchInvoices($what)
    {
        $result                                       = null;
        $this->invoicer->defaultUrlParams['order']    = 'datVyst@A';
        $this->invoicer->defaultUrlParams['includes'] = '/faktura-vydana/typDokl';
        $invoices                                     = $this->invoicer->getColumnsFromFlexibee([
            'id',
            'kod',
            'stavUhrK',
            'zbyvaUhradit',
            'firma',
            'buc',
            'mena',
            'varSym',
            'specSym',
            'typDokl(typDoklK,kod)',
            'sumCelkem',
            'duzpPuv',
            'stitky',
            'typDokl',
            'datVyst'
            ], $what, 'id');

        if ($this->invoicer->lastResponseCode == 200) {
            $result = $invoices;
        }
        unset($this->invoicer->defaultUrlParams['includes']);
        return $result;
    }

    /**
     * Vrací nesparovane platby odpovídající zadaným parametrům
     *
     * @param array $what
     * @return array
     */
    public function findPayment($what)
    {
        $result                                  = null;
        $this->banker->defaultUrlParams['order'] = 'datVyst@A';
        $payments                                = $this->banker->getColumnsFromFlexibee([
            'id',
            'varSym',
            'specSym',
            'buc',
            'sumCelkem',
            'mena',
            'stitky',
            'datVyst'],
            ["(".\FlexiPeeHP\FlexiBeeRO::flexiUrl($what, 'or').") AND sparovano eq 'false'"],
            'id');
        if ($this->banker->lastResponseCode == 200) {
            $result = $payments;
        }

        return $result;
    }

    /**
     * Najde nejlepší platbu pro danou fakturu
     *
     * @param array $payments pole příchozích plateb
     * @param \FlexiPeeHP\FakturaVydana $invoice  faktura ke spárování
     * 
     * @return \FlexiPeeHP\Banka Bankovní pohyb
     */
    public function findBestPayment($payments, $invoice)
    {
        $value = $invoice->getDataValue('sumCelkem');
        foreach ($payments as $paymentID => $payment) {
            if ($payment['sumCelkem'] == $value) {
                return new \FlexiPeeHP\Banka(\FlexiPeeHP\FlexiBeeRO::code($payments[$paymentID]['kod']),
                    $this->config);
            }
        }

        $symbol = $invoice->getDataValue('specSym');

        $this->addStatusMessage(sprintf(_('Platba pro fakturu %s nebyla dohledána'),
                self::apiUrlToLink($invoice->apiURL)), 'warning');

        return null;
    }

    /**
     * Change url to html link
     *
     * @param string $apiURL
     * 
     * @return string
     */
    public static function apiUrlToLink($apiURL)
    {
        return str_replace('.json?limit=0', '',
            preg_replace("#(^|[\n ])([\w]+?://[\w\#$%&~/.\-;:=,?@\[\]+]*)#is",
                "\\1<a href=\"\\2\" target=\"_blank\" rel=\"nofollow\">\\2</a>",
                $apiURL));
    }

    /**
     * Return Document original type
     * 
     * @param string $typDokl
     * 
     * @return string typDokladu.faktura|typDokladu.dobropis|
     *                typDokladu.zalohFaktura|typDokladu.zdd|
     *                typDokladu.dodList|typDokladu.proforma|
     *                typBanUctu.kc|typBanUctu.mena
     */
    public function getOriginDocumentType($typDokl)
    {
        if (empty($this->docTypes)) {
            $this->docTypes = $this->getDocumentTypes();
        }
        $documentType = \FlexiPeeHP\FlexiBeeRO::uncode($typDokl);
        return array_key_exists($documentType, $this->docTypes) ? $this->docTypes[$documentType]
                : 'typDokladu.neznamy';
    }

    /**
     * Assign Bank Account to Address 
     * 
     * @param \FlexiPeeHP\Adresar|string $payer    Object or code: identier
     * @param \FlexiPeeHP\Banka          $payment  Payment object
     * 
     * @return boolean account was assigned to Address
     */
    public function savePayerAccount($payer, $payment)
    {
        $result = null;
        $buc    = $payment->getDataValue('buc');
        if (!empty($buc) && !empty($payer) && $this->isKnownBankAccountForAddress($payer,
                $buc)) {
            $result = $this->assignBankAccountToAddress($payer, $payment);
        }
        return $result;
    }

    public function isKnownBankAccountForAddress($address, $buc)
    {
        $result      = null;
        $accounts    = [];
        $bucer       = new \FlexiPeeHP\FlexiBeeRW(null,
            ['evidence' => 'adresar-bankovni-ucet']);
        $accountsRaw = $bucer->getColumnsFromFlexibee(['buc', 'smerKod'],
            ['firma' => $address]);
        if (!empty($accountsRaw)) {
            $accounts = self::reindexArrayBy($accountsRaw, 'buc');
        }
        return !array_key_exists($buc, $accounts);
    }

    /**
     * Assign Bank Account to Address
     * 
     * @param \FlexiPeeHP\Adresar|string $address Object or code: identier
     * @param \FlexiPeeHP\Banka                   $payment
     * 
     * @return boolean added ?
     */
    public function assignBankAccountToAddress($address, $payment)
    {
        $bucer = new \FlexiPeeHP\FlexiBeeRW(null,
            ['evidence' => 'adresar-bankovni-ucet']);
        $bucer->insertToFlexiBee(['firma' => $address, 'buc' => $payment->getDataValue('buc'),
            'smerKod' => $payment->getDataValue('smerKod'), 'poznam' => _('Added by script')]);
        return $bucer->lastResponseCode == 201;
    }
}
FlexiPeeHP-Bricks API documentation generated by ApiGen