Skip to content

Commit c7ce715

Browse files
committed
release 3.0.18
1 parent 186d24b commit c7ce715

8 files changed

Lines changed: 374 additions & 14 deletions

File tree

RELEASE_NOTES.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1887,3 +1887,33 @@
18871887
| 🔧 zmienione | 16 |
18881888
| ➖ usunięte | 0 |
18891889

1890+
1891+
# Changelog zmian - `## 3.0.18 (2026-02-26)`- `API: 2.1.2`
1892+
1893+
## 1. ksef-client
1894+
- **build.gradle.kts**: 🔧 dodanie budowanie paczki ze źródłami - `withSourcesJar()`
1895+
1896+
### 1.1 client.model
1897+
- **auth/TokenPermissionType.java**: 🔧 dodano wartość enuma `INTROSPECTION("Introspection")`
1898+
- **ApiException.java**: 🔧 dodanie `message` do metody `toString`
1899+
1900+
### 1.2 api.client.interfaces
1901+
- **CryptographyService.java**: 🔧 usunięcie błędnych opisów metod
1902+
-
1903+
## 2. demo-web-app
1904+
1905+
### 2.1 integrationTest
1906+
- **RrInvoiceIntegrationTest.java.java**: ➕ dodano scenariusz testowy dla faktury VAT RR
1907+
1908+
### 2.1.1 integrationTest.resources
1909+
- **invoice-template-fa-rr-1.xml**: ➕ dodano plik zawierający przykładową fakturę VAT RR
1910+
1911+
---
1912+
## 3. Podsumowanie
1913+
1914+
| Typ zmiany | Liczba plików |
1915+
|-------------|---------------|
1916+
| ➕ dodane | 2 |
1917+
| 🔧 zmienione | 4 |
1918+
| ➖ usunięte | 0 |
1919+

demo-web-app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ plugins {
88
}
99

1010
group = "pl.akmf.ksef"
11-
version = "3.0.17"
11+
version = "3.0.18"
1212

1313
java {
1414
toolchain {
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
package pl.akmf.ksef.sdk;
2+
3+
import jakarta.xml.bind.JAXBException;
4+
import org.junit.jupiter.api.Assertions;
5+
import org.junit.jupiter.api.Test;
6+
import org.springframework.beans.factory.annotation.Autowired;
7+
import pl.akmf.ksef.sdk.api.builders.permission.entity.EntityAuthorizationPermissionsQueryRequestBuilder;
8+
import pl.akmf.ksef.sdk.api.builders.permission.proxy.GrantAuthorizationPermissionsRequestBuilder;
9+
import pl.akmf.ksef.sdk.api.builders.session.OpenOnlineSessionRequestBuilder;
10+
import pl.akmf.ksef.sdk.api.builders.session.SendInvoiceOnlineSessionRequestBuilder;
11+
import pl.akmf.ksef.sdk.api.services.DefaultCryptographyService;
12+
import pl.akmf.ksef.sdk.client.model.ApiException;
13+
import pl.akmf.ksef.sdk.client.model.UpoVersion;
14+
import pl.akmf.ksef.sdk.client.model.permission.OperationResponse;
15+
import pl.akmf.ksef.sdk.client.model.permission.PermissionStatusInfo;
16+
import pl.akmf.ksef.sdk.client.model.permission.proxy.GrantAuthorizationPermissionsRequest;
17+
import pl.akmf.ksef.sdk.client.model.permission.proxy.SubjectIdentifier;
18+
import pl.akmf.ksef.sdk.client.model.permission.search.EntityAuthorizationGrant;
19+
import pl.akmf.ksef.sdk.client.model.permission.search.EntityAuthorizationPermissionsQueryRequest;
20+
import pl.akmf.ksef.sdk.client.model.permission.search.EntityAuthorizationsAuthorizedEntityIdentifier;
21+
import pl.akmf.ksef.sdk.client.model.permission.search.EntityAuthorizationsAuthorizingEntityIdentifier;
22+
import pl.akmf.ksef.sdk.client.model.permission.search.InvoicePermissionType;
23+
import pl.akmf.ksef.sdk.client.model.permission.search.QueryEntityAuthorizationPermissionsResponse;
24+
import pl.akmf.ksef.sdk.client.model.permission.search.QueryType;
25+
import pl.akmf.ksef.sdk.client.model.session.EncryptionData;
26+
import pl.akmf.ksef.sdk.client.model.session.FileMetadata;
27+
import pl.akmf.ksef.sdk.client.model.session.FormCode;
28+
import pl.akmf.ksef.sdk.client.model.session.SchemaVersion;
29+
import pl.akmf.ksef.sdk.client.model.session.SessionStatusResponse;
30+
import pl.akmf.ksef.sdk.client.model.session.SessionValue;
31+
import pl.akmf.ksef.sdk.client.model.session.SystemCode;
32+
import pl.akmf.ksef.sdk.client.model.session.online.OpenOnlineSessionRequest;
33+
import pl.akmf.ksef.sdk.client.model.session.online.OpenOnlineSessionResponse;
34+
import pl.akmf.ksef.sdk.client.model.session.online.SendInvoiceOnlineSessionRequest;
35+
import pl.akmf.ksef.sdk.client.model.session.online.SendInvoiceResponse;
36+
import pl.akmf.ksef.sdk.client.model.testdata.SubjectTypeTestData;
37+
import pl.akmf.ksef.sdk.client.model.testdata.TestDataSubjectCreateRequest;
38+
import pl.akmf.ksef.sdk.client.model.testdata.TestDataSubjectRemoveRequest;
39+
import pl.akmf.ksef.sdk.configuration.BaseIntegrationTest;
40+
import pl.akmf.ksef.sdk.util.IdentifierGeneratorUtils;
41+
42+
import java.io.IOException;
43+
import java.nio.charset.StandardCharsets;
44+
import java.util.Base64;
45+
import java.util.List;
46+
import java.util.UUID;
47+
48+
import static java.util.concurrent.TimeUnit.SECONDS;
49+
import static org.awaitility.Awaitility.await;
50+
51+
public class RrInvoiceIntegrationTest extends BaseIntegrationTest {
52+
53+
@Autowired
54+
private DefaultCryptographyService defaultCryptographyService;
55+
private EncryptionData encryptionData;
56+
57+
private static final String templateFileName = "/xml/invoices/sample/invoice-template-fa-rr-1.xml";
58+
private static final String grantorDescriptionPrefix = "E2E-RR-Grantor";
59+
private static final String authorizedDescriptionPrefix = "E2E-RR-Authorized";
60+
private static final String permissionDescriptionPrefix = "E2E-RRInvoicing";
61+
private static final String authorizedSubjectFullName = "Podmiot Testowy RR";
62+
private static final int expectedSuccessfulInvoiceCount = 1;
63+
private static final int pageOffset = 0;
64+
private static final int pageSize = 50;
65+
66+
// Weryfikacja poprawności nadawania uprawnienia RRInvoicing i wysyłania faktury FA-RR.
67+
// Scenariusz:
68+
// 1. Utworzenie dwóch podmiotów testowych (grantor i authorized)
69+
// 2. Nadanie uprawnienia RRInvoicing przez grantor dla authorized
70+
// 3. Otwarcie sesji online przez authorized z kodem systemu FA_RR
71+
// 4. Wysłanie faktury FA-RR przez authorized w imieniu grantor
72+
// 5. Weryfikacja przetworzenia faktury
73+
// 6. Zamknięcie sesji i wyszukanie nadanego uprawnienia
74+
// 7. Odebranie uprawnienia RRInvoicing
75+
// 8. Usunięcie podmiotów testowych
76+
77+
@Test
78+
void sendingFaRrInvoiceWithGrantPermission() throws JAXBException, IOException, ApiException {
79+
// Przygotowanie podmiotów testowych
80+
String grantorNip = IdentifierGeneratorUtils.generateRandomNIP();
81+
String authorizedNip = IdentifierGeneratorUtils.generateRandomNIP();
82+
83+
TestDataSubjectCreateRequest grantorRequest = new TestDataSubjectCreateRequest();
84+
grantorRequest.setSubjectNip(grantorNip);
85+
grantorRequest.setSubjectType(SubjectTypeTestData.ENFORCEMENT_AUTHORITY);
86+
grantorRequest.setDescription(grantorDescriptionPrefix + "-" + grantorNip);
87+
ksefClient.createTestSubject(grantorRequest);
88+
89+
TestDataSubjectCreateRequest authorizedRequest = new TestDataSubjectCreateRequest();
90+
authorizedRequest.setSubjectNip(authorizedNip);
91+
authorizedRequest.setSubjectType(SubjectTypeTestData.ENFORCEMENT_AUTHORITY);
92+
authorizedRequest.setDescription(authorizedDescriptionPrefix + "-" + authorizedNip);
93+
ksefClient.createTestSubject(authorizedRequest);
94+
95+
// Uwierzytelnienie jako podmiot nadający uprawnienie (grantor)
96+
String grantorAccessToken = authWithCustomNip(grantorNip, grantorNip).accessToken();
97+
98+
// Nadanie uprawnienia RRInvoicing
99+
grantRRPermission(authorizedNip, grantorAccessToken);
100+
101+
// Uwierzytelnienie jako podmiot uprawniony (authorized)
102+
String authorizedAccessToken = authWithCustomNip(authorizedNip, authorizedNip).accessToken();
103+
104+
encryptionData = defaultCryptographyService.getEncryptionData();
105+
106+
// Otwarcie sesji online z kodem systemu FA_RR
107+
String sessionReferenceNumber = openOnlineSession(encryptionData, SystemCode.FA_RR, SchemaVersion.VERSION_1_0E, SessionValue.RR, authorizedAccessToken);
108+
109+
// Wysłanie faktury FA-RR
110+
sendRrInvoice(sessionReferenceNumber, encryptionData, grantorNip, authorizedNip, templateFileName, authorizedAccessToken);
111+
112+
// Weryfikacja przetworzenia faktury
113+
await().atMost(30, SECONDS)
114+
.pollInterval(5, SECONDS)
115+
.until(() -> isInvoicesInSessionProcessed(sessionReferenceNumber, authorizedAccessToken));
116+
117+
// Zamknięcie sesji online
118+
closeSession(sessionReferenceNumber, authorizedAccessToken);
119+
120+
// Wyszukanie nadanego uprawnienia RRInvoicing
121+
List<String> permissionIds = searchRrPermissions(grantorNip, authorizedNip, 1, grantorAccessToken);
122+
123+
// Odebranie uprawnienia RRInvoicing
124+
permissionIds.forEach(e -> {
125+
String revokeReferenceNumber = revokePermission(e, grantorAccessToken);
126+
127+
// Weryfikacja odebrania uprawnienia
128+
await().atMost(30, SECONDS)
129+
.pollInterval(2, SECONDS)
130+
.until(() -> isPermissionStatusReady(revokeReferenceNumber, grantorAccessToken));
131+
});
132+
searchRrPermissions(grantorNip, authorizedNip, 0, grantorAccessToken);
133+
134+
// Usunięcie podmiotów testowych
135+
ksefClient.removeTestSubject(new TestDataSubjectRemoveRequest(authorizedNip));
136+
ksefClient.removeTestSubject(new TestDataSubjectRemoveRequest(grantorNip));
137+
}
138+
139+
private void grantRRPermission(String authorizedNip, String grantorAccessToken) throws ApiException {
140+
GrantAuthorizationPermissionsRequest.PermissionsAuthorizationSubjectDetails subjectDetails =
141+
new GrantAuthorizationPermissionsRequest.PermissionsAuthorizationSubjectDetails();
142+
subjectDetails.setFullName(authorizedSubjectFullName);
143+
GrantAuthorizationPermissionsRequest grantRequest =
144+
new GrantAuthorizationPermissionsRequestBuilder()
145+
.withSubjectIdentifier(new SubjectIdentifier(SubjectIdentifier.IdentifierType.NIP, authorizedNip))
146+
.withPermission(InvoicePermissionType.RR_INVOICING)
147+
.withDescription(permissionDescriptionPrefix + "-" + authorizedNip)
148+
.withSubjectDetails(subjectDetails)
149+
.build();
150+
151+
OperationResponse grantOperation = ksefClient.grantsPermissionsProxyEntity(grantRequest, grantorAccessToken);
152+
153+
await().atMost(10, SECONDS)
154+
.pollInterval(5, SECONDS)
155+
.until(() -> isPermissionStatusReady(grantOperation.getReferenceNumber(), grantorAccessToken));
156+
}
157+
158+
private String openOnlineSession(EncryptionData encryptionData, SystemCode systemCode, SchemaVersion schemaVersion, SessionValue value, String accessToken) throws ApiException {
159+
OpenOnlineSessionRequest request = new OpenOnlineSessionRequestBuilder()
160+
.withFormCode(new FormCode(systemCode, schemaVersion, value))
161+
.withEncryptionInfo(encryptionData.encryptionInfo())
162+
.build();
163+
164+
OpenOnlineSessionResponse openOnlineSessionResponse = ksefClient.openOnlineSession(request, UpoVersion.UPO_4_3, accessToken);
165+
Assertions.assertNotNull(openOnlineSessionResponse);
166+
Assertions.assertNotNull(openOnlineSessionResponse.getReferenceNumber());
167+
return openOnlineSessionResponse.getReferenceNumber();
168+
}
169+
170+
private String sendRrInvoice(String sessionReferenceNumber, EncryptionData encryptionData,
171+
String supplierNip, String buyerNip, String path, String accessToken) throws IOException, ApiException {
172+
String invoiceTemplate = new String(readBytesFromPath(path), StandardCharsets.UTF_8)
173+
.replace("#nip#", supplierNip)
174+
.replace("#invoice_number#", UUID.randomUUID().toString())
175+
.replace("#buyer_nip#", buyerNip);
176+
177+
byte[] invoice = invoiceTemplate.getBytes(StandardCharsets.UTF_8);
178+
179+
byte[] encryptedInvoice = defaultCryptographyService.encryptBytesWithAES256(invoice,
180+
encryptionData.cipherKey(),
181+
encryptionData.cipherIv());
182+
183+
FileMetadata invoiceMetadata = defaultCryptographyService.getMetaData(invoice);
184+
FileMetadata encryptedInvoiceMetadata = defaultCryptographyService.getMetaData(encryptedInvoice);
185+
186+
SendInvoiceOnlineSessionRequest sendInvoiceOnlineSessionRequest = new SendInvoiceOnlineSessionRequestBuilder()
187+
.withInvoiceHash(invoiceMetadata.getHashSHA())
188+
.withInvoiceSize(invoiceMetadata.getFileSize())
189+
.withEncryptedInvoiceHash(encryptedInvoiceMetadata.getHashSHA())
190+
.withEncryptedInvoiceSize(encryptedInvoiceMetadata.getFileSize())
191+
.withEncryptedInvoiceContent(Base64.getEncoder().encodeToString(encryptedInvoice))
192+
.build();
193+
194+
SendInvoiceResponse sendInvoiceResponse = ksefClient.onlineSessionSendInvoice(sessionReferenceNumber, sendInvoiceOnlineSessionRequest, accessToken);
195+
Assertions.assertNotNull(sendInvoiceResponse);
196+
Assertions.assertNotNull(sendInvoiceResponse.getReferenceNumber());
197+
198+
return sendInvoiceResponse.getReferenceNumber();
199+
}
200+
201+
private boolean isInvoicesInSessionProcessed(String sessionReferenceNumber, String accessToken) {
202+
try {
203+
SessionStatusResponse statusResponse = ksefClient.getSessionStatus(sessionReferenceNumber, accessToken);
204+
return statusResponse != null &&
205+
statusResponse.getFailedInvoiceCount() == null &&
206+
statusResponse.getSuccessfulInvoiceCount() != null &&
207+
statusResponse.getSuccessfulInvoiceCount() > 0 &&
208+
statusResponse.getSuccessfulInvoiceCount() == expectedSuccessfulInvoiceCount;
209+
} catch (Exception e) {
210+
return false;
211+
}
212+
}
213+
214+
private void closeSession(String sessionReferenceNumber, String accessToken) throws ApiException {
215+
ksefClient.closeOnlineSession(sessionReferenceNumber, accessToken);
216+
}
217+
218+
private Boolean isPermissionStatusReady(String grantReferenceNumber, String accessToken) throws ApiException {
219+
PermissionStatusInfo status = ksefClient.permissionOperationStatus(grantReferenceNumber, accessToken);
220+
return status != null && status.getStatus().getCode() == 200;
221+
}
222+
223+
private String revokePermission(String operationId, String accessToken) {
224+
try {
225+
return ksefClient.revokeAuthorizationsPermission(operationId, accessToken).getReferenceNumber();
226+
} catch (ApiException e) {
227+
Assertions.fail(e.getMessage());
228+
}
229+
return null;
230+
}
231+
232+
private List<String> searchRrPermissions(String grantorNip, String authorizedNip, int expectedPermissionCount, String accessToken) throws ApiException {
233+
EntityAuthorizationPermissionsQueryRequest request = new EntityAuthorizationPermissionsQueryRequestBuilder()
234+
.withAuthorizingIdentifier(new EntityAuthorizationsAuthorizingEntityIdentifier(EntityAuthorizationsAuthorizingEntityIdentifier.IdentifierType.NIP, grantorNip))
235+
.withAuthorizedIdentifier(new EntityAuthorizationsAuthorizedEntityIdentifier(EntityAuthorizationsAuthorizedEntityIdentifier.IdentifierType.NIP, authorizedNip))
236+
.withQueryType(QueryType.GRANTED)
237+
.withPermissionTypes(List.of(InvoicePermissionType.RR_INVOICING))
238+
.build();
239+
240+
QueryEntityAuthorizationPermissionsResponse response = ksefClient.searchEntityAuthorizationGrants(request, pageOffset, pageSize, accessToken);
241+
242+
Assertions.assertNotNull(response);
243+
Assertions.assertEquals(expectedPermissionCount, response.getAuthorizationGrants().size());
244+
245+
// Weryfikacja znalezienia nadanego uprawnienia
246+
boolean marching = response.getAuthorizationGrants().stream()
247+
.anyMatch(g -> g.getAuthorizationScope().equals(InvoicePermissionType.RR_INVOICING)
248+
&& g.getAuthorizedEntityIdentifier().getValue().equals(authorizedNip)
249+
);
250+
if (expectedPermissionCount > 0) {
251+
Assertions.assertTrue(marching);
252+
}
253+
254+
return response.getAuthorizationGrants()
255+
.stream()
256+
.map(EntityAuthorizationGrant::getId)
257+
.toList();
258+
}
259+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Faktura xmlns:etd="http://crd.gov.pl/xml/schematy/dziedzinowe/mf/2022/01/05/eD/DefinicjeTypy/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="http://crd.gov.pl/wzor/2026/02/17/14164/">
4+
<Naglowek>
5+
<KodFormularza kodSystemowy="FA_RR(1)" wersjaSchemy="1-0E">FA_RR</KodFormularza>
6+
<WariantFormularza>1</WariantFormularza>
7+
<DataWytworzeniaFa>2026-02-23T10:37:00Z</DataWytworzeniaFa>
8+
<SystemInfo>SamploFaktur</SystemInfo>
9+
</Naglowek>
10+
<Podmiot1>
11+
<DaneIdentyfikacyjne>
12+
<NIP>#nip#</NIP>
13+
<Nazwa>Elektrownia S.A.</Nazwa>
14+
</DaneIdentyfikacyjne>
15+
<Adres>
16+
<KodKraju>PL</KodKraju>
17+
<AdresL1>84-100 Zielona Wieś 12</AdresL1>
18+
</Adres>
19+
</Podmiot1>
20+
<Podmiot2>
21+
<DaneIdentyfikacyjne>
22+
<NIP>#buyer_nip#</NIP>
23+
<Nazwa>F.H.U. Jan Kowalski</Nazwa>
24+
</DaneIdentyfikacyjne>
25+
<Adres>
26+
<KodKraju>PL</KodKraju>
27+
<AdresL1>80-001 Gdańsk, ul. Handlowa 7</AdresL1>
28+
</Adres>
29+
</Podmiot2>
30+
<FakturaRR>
31+
<KodWaluty>PLN</KodWaluty>
32+
<P_4A>2026-02-20</P_4A>
33+
<P_4B>2026-02-23</P_4B>
34+
<P_4C>#invoice_number#</P_4C>
35+
<P_11_1>2000.00</P_11_1>
36+
<P_11_2>140.00</P_11_2>
37+
<P_12_1>2140.00</P_12_1>
38+
<P_12_2>dwa tysiące sto czterdzieści złotych 00/100</P_12_2>
39+
<RodzajFaktury>VAT_RR</RodzajFaktury>
40+
41+
<FakturaRRWiersz>
42+
<NrWierszaFa>1</NrWierszaFa>
43+
<P_5>ziemniaki jadalne odmiana irga (partia A)</P_5>
44+
<P_6A>kg</P_6A>
45+
<P_6B>300.000</P_6B>
46+
<P_6C>klasa I</P_6C>
47+
<P_7>2.50</P_7>
48+
<P_8>750.00</P_8>
49+
<P_9>7.0</P_9>
50+
<P_10>52.50</P_10>
51+
<P_11>802.50</P_11>
52+
</FakturaRRWiersz>
53+
<FakturaRRWiersz>
54+
<NrWierszaFa>2</NrWierszaFa>
55+
<P_5>marchew świeża (partia B)</P_5>
56+
<P_6A>kg</P_6A>
57+
<P_6B>500.000</P_6B>
58+
<P_6C>myta, luz</P_6C>
59+
<P_7>2.50</P_7>
60+
<P_8>1250.00</P_8>
61+
<P_9>7.0</P_9>
62+
<P_10>87.50</P_10>
63+
<P_11>1337.50</P_11>
64+
</FakturaRRWiersz>
65+
66+
<Platnosc>
67+
<FormaPlatnosci>1</FormaPlatnosci>
68+
<RachunekBankowy1>
69+
<NrRB>83102055550000987654321012</NrRB>
70+
<NazwaBanku>Bank Alfa Test</NazwaBanku>
71+
</RachunekBankowy1>
72+
<RachunekBankowy2>
73+
<NrRB>12114033330000123456789012</NrRB>
74+
<NazwaBanku>Bank Beta Test</NazwaBanku>
75+
</RachunekBankowy2>
76+
</Platnosc>
77+
</FakturaRR>
78+
</Faktura>

ksef-client/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ plugins {
44
}
55

66

7-
val appVersion = "3.0.17"
7+
val appVersion = "3.0.18"
88
val artifactName = "ksef-client"
99

1010
val githubRepositoryToken = "token"
@@ -19,6 +19,7 @@ version = cleanVersion
1919

2020

2121
java {
22+
withSourcesJar()
2223
toolchain {
2324
languageVersion = JavaLanguageVersion.of(21)
2425
}

0 commit comments

Comments
 (0)