diff --git a/library/src/main/java/org/mustangproject/Item.java b/library/src/main/java/org/mustangproject/Item.java index b385ee84..5c9d8976 100644 --- a/library/src/main/java/org/mustangproject/Item.java +++ b/library/src/main/java/org/mustangproject/Item.java @@ -45,6 +45,7 @@ public class Item implements IZUGFeRDExportableItem { protected String accountingReference; protected String parentLineID = null; protected String lineStatusReasonCode = null; + protected TradeParty lineSeller; //protected HashMap attributes = new HashMap<>(); /*** @@ -681,4 +682,19 @@ public class Item implements IZUGFeRDExportableItem { this.lineStatusReasonCode = lineStatusReasonCode; return this; } + + /*** + * For line seller + * @param seller The line seller + * @return fluent setter + */ + public Item setLineSeller(TradeParty seller) { + this.lineSeller = seller; + return this; + } + @Override + public TradeParty getLineSeller() { + return this.lineSeller; + } + } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java index 7b48ab5f..38ab882b 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/IZUGFeRDExportableItem.java @@ -32,6 +32,7 @@ import java.util.List; import org.mustangproject.IncludedNote; import org.mustangproject.Item; +import org.mustangproject.TradeParty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -237,4 +238,13 @@ public interface IZUGFeRDExportableItem extends IAbsoluteValueProvider{ } default LineCalculator getCalculation() {return new LineCalculator(this); }; + /*** + * For line seller + * @return the seller + */ + + default TradeParty getLineSeller() { + return null; + } + } diff --git a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java index c47f3669..7d8cd0fd 100644 --- a/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java +++ b/library/src/main/java/org/mustangproject/ZUGFeRD/ZUGFeRD2PullProvider.java @@ -525,8 +525,13 @@ public class ZUGFeRD2PullProvider implements IXMLProvider { + "" // currencyID=\"EUR\" + "" + quantityFormat(currentItem.getBasisQuantity()) + "" - + "" - + "" + + ""; + + if(currentItem.getLineSeller()!=null) { + xml += "" + getTradePartyAsXML(currentItem.getLineSeller(), true, false) + ""; + + } + xml += "" + "" + "" + quantityFormat(currentItem.getQuantity()) + "" diff --git a/library/src/test/java/org/mustangproject/ZUGFeRD/ItemSellerTradePartyTest.java b/library/src/test/java/org/mustangproject/ZUGFeRD/ItemSellerTradePartyTest.java new file mode 100644 index 00000000..12af7493 --- /dev/null +++ b/library/src/test/java/org/mustangproject/ZUGFeRD/ItemSellerTradePartyTest.java @@ -0,0 +1,105 @@ +package org.mustangproject.ZUGFeRD; + +import org.junit.Test; +import org.mustangproject.Invoice; +import org.mustangproject.Item; +import org.mustangproject.Product; +import org.mustangproject.TradeParty; + +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +import static org.xmlunit.assertj.XmlAssert.assertThat; + +public class ItemSellerTradePartyTest { + + /** + * Item de test qui force un ItemSellerTradeParty au niveau ligne + * via la nouvelle méthode IZUGFeRDExportableItem#getItemSellerTradeParty(). + */ + private static class ItemWithItemSellerTradeParty extends Item { + private final TradeParty itemSeller; + + ItemWithItemSellerTradeParty(Product product, BigDecimal price, BigDecimal quantity, TradeParty itemSeller) { + super(product, price, quantity); + this.itemSeller = itemSeller; + } + + @Override + public TradeParty getLineSeller() { + return itemSeller; + } + } + + @Test + public void testLineLevelItemSellerTradePartyIsExportedOnlyWhenProvided() { + // Header seller = vendeur contractuel (collectivité émettrice) + TradeParty contractualSeller = new TradeParty("Mairie A", "1 rue A", "75001", "Paris", "FR"); + // Line-level "actual service provider" (autre collectivité) + TradeParty actualServiceProvider = new TradeParty("Mairie B", "2 rue B", "69001", "Lyon", "FR"); + // Buyer + TradeParty buyer = new TradeParty("Client X", "3 rue C", "33000", "Bordeaux", "FR"); + + Product service = new Product("Prestation", "Service intercommunal", "C62", new BigDecimal("20.00")); + + // Ligne 1 : avec ItemSellerTradeParty + Item line1 = new ItemWithItemSellerTradeParty( + service, + new BigDecimal("100.00"), + new BigDecimal("1"), + actualServiceProvider + ); + + // Ligne 2 : sans ItemSellerTradeParty (null via default getter) + Item line2 = new Item( + service, + new BigDecimal("50.00"), + new BigDecimal("1") + ); + + Invoice inv = new Invoice() + .setNumber("INV-ITEM-SELLER-001") + .setIssueDate(new Date()) + .setDeliveryDate(new Date()) + .setDueDate(new Date()) + .setSender(contractualSeller) + .setRecipient(buyer) + .setCurrency("EUR") + .addItem(line1) + .addItem(line2); + + ZUGFeRD2PullProvider provider = new ZUGFeRD2PullProvider(); + provider.setProfile(Profiles.getByName("EXTENDED")); + provider.generateXML(inv); + + String xml = new String(provider.getXML(), StandardCharsets.UTF_8); + + // 1) Le Seller header doit rester "Mairie A" + assertThat(xml) + .valueByXPath("//*[local-name()='ApplicableHeaderTradeAgreement']" + + "/*[local-name()='SellerTradeParty']" + + "/*[local-name()='Name']") + .isEqualTo("Mairie A"); + + // 2) Ligne 1 : ItemSellerTradeParty présent, et correspond à "Mairie B" + assertThat(xml) + .nodesByXPath("(//*[local-name()='IncludedSupplyChainTradeLineItem'])[1]" + + "//*[local-name()='SpecifiedLineTradeAgreement']" + + "/*[local-name()='ItemSellerTradeParty']") + .exist(); + + assertThat(xml) + .valueByXPath("(//*[local-name()='IncludedSupplyChainTradeLineItem'])[1]" + + "//*[local-name()='ItemSellerTradeParty']" + + "/*[local-name()='Name']") + .isEqualTo("Mairie B"); + + // 3) Ligne 2 : ItemSellerTradeParty absent + assertThat(xml) + .nodesByXPath("(//*[local-name()='IncludedSupplyChainTradeLineItem'])[2]" + + "//*[local-name()='SpecifiedLineTradeAgreement']" + + "/*[local-name()='ItemSellerTradeParty']").isEmpty(); + + } +}