KSEF

https://web2tr-ksef.mf.gov.pl/web/  – strona logowania

– uwierzytelnienie za pomocą podpisu zaufanego, podpisanie dokumentu

// ============================================
// KROK 2: IMPLEMENTACJA KSEFCLIENT I AUTORYZACJA 
// ============================================
//
// W KSeF API 2.0 proces uwierzytelniania składa się z 3 kroków:
// 1. Pobranie challenge (POST /auth/challenge)
// 2. Przygotowanie żądania autoryzacyjnego z tokenem i challenge
// 3. Uzyskanie JWT accessToken

// ============================================
// Plik: src/KSeF/KSeFClient.php
// ============================================
# composer require guzzlehttp/guzzle: ^7.10
# composer require phpseclib/phpseclib:~3.0
Upewnij się że PHP ma włączone rozszerzenie OpenSSL:
# php -m | grep openssl
(powinno pokazać "openssl")

odsumowanie Kroku 2:

Co zbudowaliśmy:

  • KSeFClient.php – pełna implementacja uwierzytelnienia
  • Szyfrowanie RSA-OAEP SHA-256 (phpseclib + OpenSSL fallback)
  • Proces 3-krokowy KSeF 2.0:
    1. POST /auth/challenge
    2. POST /auth/ksef-token
    3. GET /auth/{referenceNumber} (polling)
    4. POST /auth/token/redeem
  • Zarządzanie JWT tokenami
  • Kontroler testowy do łatwego testowania: src/Controller/KsefTestController.php

Kluczowe nauki:

  • KSeF 2.0 wymaga RSA-OAEP z SHA-256 (nie SHA-1!)
  • Format szyfrowania: token|timestampMs (bez challenge)
  • Struktura JSON: płaska (bez credentials)
  • Proces jest asynchroniczny – wymaga pollingu

CO DALEJ? – KROK 3

Teraz mamy accessToken i możemy przejść do wysyłania faktur!

Krok 3 będzie obejmował:

  1. Budowanie faktury w formacie FA(3) XML
  2. Szyfrowanie faktury (AES-256-CBC)
  3. Otwarcie sesji online
  4. Wysłanie faktury do KSeF
  5. Pobranie UPO (Urzędowego Poświadczenia Odbioru)
  6. Pobieranie faktur z KSeF

Moduł wysyłania faktur!

To będzie obejmowało:

  • Klasę FakturaBuilder do tworzenia XML FA(3)
  • Szyfrowanie AES-256-CBC
  • Sesje online
  • Wysyłkę faktur

user filter – lastEvents

Dołącza podzbiór zdarzeń na kliencie i znajduje ostatnie zdarzenie, zaraportowane, starsze niż obecna data (przyszłe zdarzenia nie miały miejsca to tylko plany). Jeśli ostatnie zdarzenie jest starsze niż 3 miesiące ustawia alarm.

public function findLastEvent($query, $options)
{
  $query = $query->contain([
      'Events' => function($q){
                     return $q->select([
                       'id', 'company_id', 'last_visit' => 'MAX(start_date)', 
                       'alarm' => $q->newExpr()->addCase(
                        [ $q->newExpr()->lt('MAX(start_date)', 
                                     $q->func()->dateAdd('CURRENT_DATE', -3, 'MONTH')) 
                            ],
                        [1,0],
                        ['integer', 'integer']
                      )  // addCase
                 ])  // select
                ->where(['report <>' => '']) // Zaraportowane
                ->where(['start_date <=' => new \DateTime('now')])
                ->order(['start_date' => 'DESC'])
                ->group(['Events.id']);
         }]);
   return $query;
}

Przełączanie URL – query string – filtrowanie po wielu parametrach

W filtrowaniu treści – wybieramy z listy opcję – wybrane ID, nazwy przekazujemy jako parametery query string. Pozwala to na filtrowanie po wielu parametrach niezależnie.

let url = new URL(location.href);
let onChangePostion = function(){

    url.searchParams.delete('page');                              // 1 strona

    var position_name = $(this).find(':selected').text().trim();  // Nazwa
    var position_id   = $(this).val();                            // id

    if(position_id) {
        url.searchParams.set('position_id', position_id);
    } else {
        url.searchParams.delete('position_id');
    }
    location.href = url.toString();
    return false;
};
$("#filter-position").on('change', onChangePostion);

Czytaj dalej Przełączanie URL – query string – filtrowanie po wielu parametrach

CakpPHP – wgrywanie plików na serwer

Tabele MySQL

CREATE TABLE files ( 
  id           INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 
  file_type_id INT UNSIGNED, 
  project_id   INT UNSIGNED, 
  orygname     VARCHAR(255) NOT NULL, 
  dir          VARCHAR(255), 
  filename     VARCHAR(255) NOT NULL, 
  ext          CHAR(9), 
  description  TEXT, 
  module       ENUM ('project')
  is_active    TINYINT(1) DEFAULT 1, 
  user_id      INT UNSIGNED NOT NULL, 
  created      DATETIME,   
  modified     DATETIME, 
);
CREATE TABLE file_types ( 
  id          INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 
  name        VARCHAR(255) NOT NULL,
  description TEXT
);

CakePHP MySQL – procedury składowane

Utworzenie procedury składowanej w MySQL

DELIMITER $$
CREATE PROCEDURE low_amount()
BEGIN
SELECT ip.id, ip.product_id, ip.brand_id, ip.amount_alert, ip.is_ordered, SUM(ip.amount) AS suma, 
       p.name AS product, b.initials, b.name AS brand
FROM item_packs ip 
    INNER JOIN products p ON p.id=ip.product_id
    INNER JOIN brands   b ON b.id=ip.brand_id
GROUP BY ip.product_id, ip.brand_id
HAVING suma <= ip.amount_alert;
END $$
DELIMITER ;
SHOW PROCEDURE STATUS    -- wszystkie procedury

SHOW PROCEDURE STATUS [LIKE 'my_procedure' | WHERE search_condition]

SHOW CREATE PROCEDURE my_procedure;
DROP PROCEDURE [IF EXISTS] my_procedure;

SHOW PROCEDURE STATUS WHERE Db='myDababaseName' \G
DELIMITER $
CREATE PROCEDURE user_projects(p_user_id INT)
BEGIN
  SELECT p.id, p.status_id, COUNT(*) AS count,
        SUM(p.price) AS total, SUM(p.price * (ch.percent / 100)) AS weighted
  FROM projects p
   LEFT JOIN chances ch ON p.chance_id=ch.id
  WHERE p.manager_id = p_user_id
   AND p.status_id <> 3                        -- bez odrzuconych
  GROUP BY p.status_id ;
END $
DELIMITER ;

Wywołanie procedury w CakePHP 3

$connection = \Cake\Datasource\ConnectionManager::get('default');

$results = $connection->execute('CALL low_amount()')->fetchAll('assoc');

$this->set(compact('results'));