Skip to content

Commit 6c5f84b

Browse files
authored
Merge pull request #1002 from meparis/fix/issue-1000-support-ItemSellerTradeParty-in-line-level
Fix #1000 : support ItemSellerTradeParty in line level
2 parents 53623cf + 1efc525 commit 6c5f84b

File tree

4 files changed

+138
-2
lines changed

4 files changed

+138
-2
lines changed

library/src/main/java/org/mustangproject/Item.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public class Item implements IZUGFeRDExportableItem {
4545
protected String accountingReference;
4646
protected String parentLineID = null;
4747
protected String lineStatusReasonCode = null;
48+
protected TradeParty lineSeller;
4849
//protected HashMap<String, String> attributes = new HashMap<>();
4950

5051
/***
@@ -681,4 +682,19 @@ public Item setLineStatusReasonCode(String lineStatusReasonCode) {
681682
this.lineStatusReasonCode = lineStatusReasonCode;
682683
return this;
683684
}
685+
686+
/***
687+
* For line seller
688+
* @param seller The line seller
689+
* @return fluent setter
690+
*/
691+
public Item setLineSeller(TradeParty seller) {
692+
this.lineSeller = seller;
693+
return this;
694+
}
695+
@Override
696+
public TradeParty getLineSeller() {
697+
return this.lineSeller;
698+
}
699+
684700
}

library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
import org.mustangproject.IncludedNote;
3434
import org.mustangproject.Item;
35+
import org.mustangproject.TradeParty;
3536

3637
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
3738

@@ -237,4 +238,13 @@ default String getAccountingReference() {
237238
}
238239

239240
default LineCalculator getCalculation() {return new LineCalculator(this); };
241+
/***
242+
* For line seller
243+
* @return the seller
244+
*/
245+
246+
default TradeParty getLineSeller() {
247+
return null;
248+
}
249+
240250
}

library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -525,8 +525,13 @@ public void generateXML(IExportableTransaction trans) {
525525
+ "</ram:ChargeAmount>" // currencyID=\"EUR\"
526526
+ "<ram:BasisQuantity unitCode=\"" + XMLTools.encodeXML(currentItem.getProduct().getUnit())
527527
+ "\">" + quantityFormat(currentItem.getBasisQuantity()) + "</ram:BasisQuantity>"
528-
+ "</ram:NetPriceProductTradePrice>"
529-
+ "</ram:SpecifiedLineTradeAgreement>"
528+
+ "</ram:NetPriceProductTradePrice>";
529+
530+
if(currentItem.getLineSeller()!=null) {
531+
xml += "<ram:ItemSellerTradeParty>" + getTradePartyAsXML(currentItem.getLineSeller(), true, false) + "</ram:ItemSellerTradeParty>";
532+
533+
}
534+
xml += "</ram:SpecifiedLineTradeAgreement>"
530535
+ "<ram:SpecifiedLineTradeDelivery>"
531536
+ "<ram:BilledQuantity unitCode=\"" + XMLTools.encodeXML(currentItem.getProduct().getUnit()) + "\">"
532537
+ quantityFormat(currentItem.getQuantity()) + "</ram:BilledQuantity>"
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package org.mustangproject.ZUGFeRD;
2+
3+
import org.junit.Test;
4+
import org.mustangproject.Invoice;
5+
import org.mustangproject.Item;
6+
import org.mustangproject.Product;
7+
import org.mustangproject.TradeParty;
8+
9+
import java.math.BigDecimal;
10+
import java.nio.charset.StandardCharsets;
11+
import java.util.Date;
12+
13+
import static org.xmlunit.assertj.XmlAssert.assertThat;
14+
15+
public class ItemSellerTradePartyTest {
16+
17+
/**
18+
* Item de test qui force un ItemSellerTradeParty au niveau ligne
19+
* via la nouvelle méthode IZUGFeRDExportableItem#getItemSellerTradeParty().
20+
*/
21+
private static class ItemWithItemSellerTradeParty extends Item {
22+
private final TradeParty itemSeller;
23+
24+
ItemWithItemSellerTradeParty(Product product, BigDecimal price, BigDecimal quantity, TradeParty itemSeller) {
25+
super(product, price, quantity);
26+
this.itemSeller = itemSeller;
27+
}
28+
29+
@Override
30+
public TradeParty getLineSeller() {
31+
return itemSeller;
32+
}
33+
}
34+
35+
@Test
36+
public void testLineLevelItemSellerTradePartyIsExportedOnlyWhenProvided() {
37+
// Header seller = vendeur contractuel (collectivité émettrice)
38+
TradeParty contractualSeller = new TradeParty("Mairie A", "1 rue A", "75001", "Paris", "FR");
39+
// Line-level "actual service provider" (autre collectivité)
40+
TradeParty actualServiceProvider = new TradeParty("Mairie B", "2 rue B", "69001", "Lyon", "FR");
41+
// Buyer
42+
TradeParty buyer = new TradeParty("Client X", "3 rue C", "33000", "Bordeaux", "FR");
43+
44+
Product service = new Product("Prestation", "Service intercommunal", "C62", new BigDecimal("20.00"));
45+
46+
// Ligne 1 : avec ItemSellerTradeParty
47+
Item line1 = new ItemWithItemSellerTradeParty(
48+
service,
49+
new BigDecimal("100.00"),
50+
new BigDecimal("1"),
51+
actualServiceProvider
52+
);
53+
54+
// Ligne 2 : sans ItemSellerTradeParty (null via default getter)
55+
Item line2 = new Item(
56+
service,
57+
new BigDecimal("50.00"),
58+
new BigDecimal("1")
59+
);
60+
61+
Invoice inv = new Invoice()
62+
.setNumber("INV-ITEM-SELLER-001")
63+
.setIssueDate(new Date())
64+
.setDeliveryDate(new Date())
65+
.setDueDate(new Date())
66+
.setSender(contractualSeller)
67+
.setRecipient(buyer)
68+
.setCurrency("EUR")
69+
.addItem(line1)
70+
.addItem(line2);
71+
72+
ZUGFeRD2PullProvider provider = new ZUGFeRD2PullProvider();
73+
provider.setProfile(Profiles.getByName("EXTENDED"));
74+
provider.generateXML(inv);
75+
76+
String xml = new String(provider.getXML(), StandardCharsets.UTF_8);
77+
78+
// 1) Le Seller header doit rester "Mairie A"
79+
assertThat(xml)
80+
.valueByXPath("//*[local-name()='ApplicableHeaderTradeAgreement']" +
81+
"/*[local-name()='SellerTradeParty']" +
82+
"/*[local-name()='Name']")
83+
.isEqualTo("Mairie A");
84+
85+
// 2) Ligne 1 : ItemSellerTradeParty présent, et correspond à "Mairie B"
86+
assertThat(xml)
87+
.nodesByXPath("(//*[local-name()='IncludedSupplyChainTradeLineItem'])[1]" +
88+
"//*[local-name()='SpecifiedLineTradeAgreement']" +
89+
"/*[local-name()='ItemSellerTradeParty']")
90+
.exist();
91+
92+
assertThat(xml)
93+
.valueByXPath("(//*[local-name()='IncludedSupplyChainTradeLineItem'])[1]" +
94+
"//*[local-name()='ItemSellerTradeParty']" +
95+
"/*[local-name()='Name']")
96+
.isEqualTo("Mairie B");
97+
98+
// 3) Ligne 2 : ItemSellerTradeParty absent
99+
assertThat(xml)
100+
.nodesByXPath("(//*[local-name()='IncludedSupplyChainTradeLineItem'])[2]" +
101+
"//*[local-name()='SpecifiedLineTradeAgreement']" +
102+
"/*[local-name()='ItemSellerTradeParty']").isEmpty();
103+
104+
}
105+
}

0 commit comments

Comments
 (0)