Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add SpecifiedLineTradeDelivery/ChargeFreeQuantity and PackageQuantity for ZUGFeRD Extended Profile #601

Merged
merged 6 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions ZUGFeRD.Test/ZUGFeRD22Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,35 @@ public void TestWriteTradeLineBilledQuantity()
}
}

[TestMethod]
public void TestWriteTradeLineChargeFreePackage()
{
// Read XRechnung
var path = @"..\..\..\..\demodata\xRechnung\xrechnung with trade line settlement empty.xml";
path = _makeSurePathIsCrossPlatformCompatible(path);

var fileStream = File.Open(path, FileMode.Open);
var originalInvoiceDescriptor = InvoiceDescriptor.Load(fileStream);
fileStream.Close();

// Modifiy trade line settlement data
TradeLineItem tradelineItem = originalInvoiceDescriptor.AddTradeLineItem(name: String.Empty);
tradelineItem.SetChargeFreeQuantity(10, QuantityCodes.C62);
tradelineItem.SetPackageQuantity(20, QuantityCodes.C62);

originalInvoiceDescriptor.IsTest = false;

using (var memoryStream = new MemoryStream())
{
originalInvoiceDescriptor.Save(memoryStream, ZUGFeRDVersion.Version23, Profile.Extended);
originalInvoiceDescriptor.Save(@"xrechnung with trade line settlement filled.xml", ZUGFeRDVersion.Version23, Profile.Extended);

// Load Invoice and compare to expected
var invoiceDescriptor = InvoiceDescriptor.Load(memoryStream);
Assert.AreEqual(10, invoiceDescriptor.TradeLineItems[0].ChargeFreeQuantity);
Assert.AreEqual(20, invoiceDescriptor.TradeLineItems[0].PackageQuantity);
}
}
[TestMethod]
public void TestWriteTradeLineNetUnitPrice()
{
Expand Down
11 changes: 8 additions & 3 deletions ZUGFeRD/InvoiceDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,13 @@
/// This party is optional and is written in most profiles except Minimum profile
/// </summary>
public Party ShipTo { get; set; }
public Contact ShipToContact { get; set; }

/// <summary>
/// This party is optional and only relevant for Extended profile
/// </summary>
public Party UltimateShipTo { get; set; }
public Contact UltimateShipToContact { get; set; }

/// <summary>
/// This party is optional and only relevant for Extended profile
Expand Down Expand Up @@ -800,7 +802,7 @@
/// </summary>
public void AddLogisticsServiceCharge(decimal amount, string description, TaxTypes taxTypeCode, TaxCategoryCodes taxCategoryCode, decimal taxPercent)
{
this.ServiceCharges.Add(new ServiceCharge()

Check warning on line 805 in ZUGFeRD/InvoiceDescriptor.cs

View workflow job for this annotation

GitHub Actions / build

'InvoiceDescriptor.ServiceCharges' is obsolete: 'This property will be removed in version 18.0. Please use GetLogisticsServiceCharges() instead'

Check warning on line 805 in ZUGFeRD/InvoiceDescriptor.cs

View workflow job for this annotation

GitHub Actions / build

'InvoiceDescriptor.ServiceCharges' is obsolete: 'This property will be removed in version 18.0. Please use GetLogisticsServiceCharges() instead'
{
Description = description,
Amount = amount,
Expand All @@ -816,7 +818,7 @@

public List<ServiceCharge> GetLogisticsServiceCharges()
{
return this.ServiceCharges;

Check warning on line 821 in ZUGFeRD/InvoiceDescriptor.cs

View workflow job for this annotation

GitHub Actions / build

'InvoiceDescriptor.ServiceCharges' is obsolete: 'This property will be removed in version 18.0. Please use GetLogisticsServiceCharges() instead'

Check warning on line 821 in ZUGFeRD/InvoiceDescriptor.cs

View workflow job for this annotation

GitHub Actions / build

'InvoiceDescriptor.ServiceCharges' is obsolete: 'This property will be removed in version 18.0. Please use GetLogisticsServiceCharges() instead'
} // !GetLogisticsServiceCharges()


Expand Down Expand Up @@ -1047,19 +1049,19 @@
tax.CategoryCode = categoryCode;
}

this.Taxes.Add(tax);

Check warning on line 1052 in ZUGFeRD/InvoiceDescriptor.cs

View workflow job for this annotation

GitHub Actions / build

'InvoiceDescriptor.Taxes' is obsolete: 'This property will be removed in version 18.0. Please use GetApplicableTradeTaxes() instead'

Check warning on line 1052 in ZUGFeRD/InvoiceDescriptor.cs

View workflow job for this annotation

GitHub Actions / build

'InvoiceDescriptor.Taxes' is obsolete: 'This property will be removed in version 18.0. Please use GetApplicableTradeTaxes() instead'
} // !AddApplicableTradeTax()


public List<Tax> GetApplicableTradeTaxes()
{
return this.Taxes;

Check warning on line 1058 in ZUGFeRD/InvoiceDescriptor.cs

View workflow job for this annotation

GitHub Actions / build

'InvoiceDescriptor.Taxes' is obsolete: 'This property will be removed in version 18.0. Please use GetApplicableTradeTaxes() instead'

Check warning on line 1058 in ZUGFeRD/InvoiceDescriptor.cs

View workflow job for this annotation

GitHub Actions / build

'InvoiceDescriptor.Taxes' is obsolete: 'This property will be removed in version 18.0. Please use GetApplicableTradeTaxes() instead'
} // !GetApplicableTradeTaxes()


public bool AnyApplicableTradeTaxes()
{
return this.Taxes.Any();

Check warning on line 1064 in ZUGFeRD/InvoiceDescriptor.cs

View workflow job for this annotation

GitHub Actions / build

'InvoiceDescriptor.Taxes' is obsolete: 'This property will be removed in version 18.0. Please use GetApplicableTradeTaxes() instead'

Check warning on line 1064 in ZUGFeRD/InvoiceDescriptor.cs

View workflow job for this annotation

GitHub Actions / build

'InvoiceDescriptor.Taxes' is obsolete: 'This property will be removed in version 18.0. Please use GetApplicableTradeTaxes() instead'
} // !AnyApplicableTradeTaxes()


Expand Down Expand Up @@ -1142,7 +1144,7 @@
}
else
{
if (this.TradeLineItems.Any(p => p.AssociatedDocument.LineID.Equals(lineID, StringComparison.OrdinalIgnoreCase)))

Check warning on line 1147 in ZUGFeRD/InvoiceDescriptor.cs

View workflow job for this annotation

GitHub Actions / build

'InvoiceDescriptor.TradeLineItems' is obsolete: 'This property will not be available any more with version 18.0. Please use GetTradeLineItems() instead'

Check warning on line 1147 in ZUGFeRD/InvoiceDescriptor.cs

View workflow job for this annotation

GitHub Actions / build

'InvoiceDescriptor.TradeLineItems' is obsolete: 'This property will not be available any more with version 18.0. Please use GetTradeLineItems() instead'
{
throw new ArgumentException("LineID must be unique");
}
Expand Down Expand Up @@ -1173,7 +1175,7 @@
contentCode: ContentCodes.Unknown
));

this.TradeLineItems.Add(item);

Check warning on line 1178 in ZUGFeRD/InvoiceDescriptor.cs

View workflow job for this annotation

GitHub Actions / build

'InvoiceDescriptor.TradeLineItems' is obsolete: 'This property will not be available any more with version 18.0. Please use GetTradeLineItems() instead'

Check warning on line 1178 in ZUGFeRD/InvoiceDescriptor.cs

View workflow job for this annotation

GitHub Actions / build

'InvoiceDescriptor.TradeLineItems' is obsolete: 'This property will not be available any more with version 18.0. Please use GetTradeLineItems() instead'
return item;
} // !AddTradeLineCommentItem()

Expand Down Expand Up @@ -1225,7 +1227,8 @@
string sellerAssignedID = "", string buyerAssignedID = "",
string deliveryNoteID = "", DateTime? deliveryNoteDate = null,
string buyerOrderLineID = "", string buyerOrderID = "", DateTime? buyerOrderDate = null,
DateTime? billingPeriodStart = null, DateTime? billingPeriodEnd = null)
DateTime? billingPeriodStart = null, DateTime? billingPeriodEnd = null
)
{
return AddTradeLineItem(lineID: _getNextLineId(),
name: name,
Expand All @@ -1249,7 +1252,8 @@
buyerOrderID: buyerOrderID, // Extended!
buyerOrderDate: buyerOrderDate,
billingPeriodStart: billingPeriodStart,
billingPeriodEnd: billingPeriodEnd);
billingPeriodEnd: billingPeriodEnd
);
} // !AddTradeLineItem()


Expand All @@ -1274,7 +1278,8 @@
string sellerAssignedID = "", string buyerAssignedID = "",
string deliveryNoteID = "", DateTime? deliveryNoteDate = null,
string buyerOrderLineID = "", string buyerOrderID = "", DateTime? buyerOrderDate = null,
DateTime? billingPeriodStart = null, DateTime? billingPeriodEnd = null)
DateTime? billingPeriodStart = null, DateTime? billingPeriodEnd = null
)
{
if (String.IsNullOrWhiteSpace(lineID))
{
Expand All @@ -1282,7 +1287,7 @@
}
else
{
if (this.TradeLineItems.Any(p => p.AssociatedDocument.LineID.Equals(lineID, StringComparison.OrdinalIgnoreCase)))

Check warning on line 1290 in ZUGFeRD/InvoiceDescriptor.cs

View workflow job for this annotation

GitHub Actions / build

'InvoiceDescriptor.TradeLineItems' is obsolete: 'This property will not be available any more with version 18.0. Please use GetTradeLineItems() instead'

Check warning on line 1290 in ZUGFeRD/InvoiceDescriptor.cs

View workflow job for this annotation

GitHub Actions / build

'InvoiceDescriptor.TradeLineItems' is obsolete: 'This property will not be available any more with version 18.0. Please use GetTradeLineItems() instead'
{
throw new ArgumentException("LineID must be unique");
}
Expand Down
30 changes: 30 additions & 0 deletions ZUGFeRD/InvoiceDescriptor23CIIReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ public override InvoiceDescriptor Load(Stream stream)
}

retval.ShipTo = _nodeAsParty(doc.DocumentElement, "//ram:ApplicableHeaderTradeDelivery/ram:ShipToTradeParty", nsmgr);
retval.ShipToContact = _nodeAsContact(doc.DocumentElement, "//ram:ApplicableHeaderTradeDelivery/ram:ShipToTradeParty", nsmgr);
retval.UltimateShipTo = _nodeAsParty(doc.DocumentElement, "//ram:ApplicableHeaderTradeDelivery/ram:UltimateShipToTradeParty", nsmgr);
retval.UltimateShipToContact = _nodeAsContact(doc.DocumentElement, "//ram:ApplicableHeaderTradeDelivery/ram:UltimateShipToTradeParty", nsmgr);
retval.ShipFrom = _nodeAsParty(doc.DocumentElement, "//ram:ApplicableHeaderTradeDelivery/ram:ShipFromTradeParty", nsmgr);
retval.ActualDeliveryDate = XmlUtils.NodeAsDateTime(doc.DocumentElement, "//ram:ApplicableHeaderTradeDelivery/ram:ActualDeliverySupplyChainEvent/ram:OccurrenceDateTime/udt:DateTimeString", nsmgr);

Expand Down Expand Up @@ -435,13 +437,17 @@ private static TradeLineItem _parseTradeLineItem(XmlNode tradeLineItem, XmlNames
BilledQuantity = XmlUtils.NodeAsDecimal(tradeLineItem, ".//ram:BilledQuantity", nsmgr, 0).Value,
ShipTo = _nodeAsParty(tradeLineItem, ".//ram:SpecifiedLineTradeDelivery/ram:ShipToTradeParty", nsmgr),
UltimateShipTo = _nodeAsParty(tradeLineItem, ".//ram:SpecifiedLineTradeDelivery/ram:UltimateShipToTradeParty", nsmgr),
ChargeFreeQuantity = XmlUtils.NodeAsDecimal(tradeLineItem, ".//ram:ChargeFreeQuantity", nsmgr, 0).Value,
PackageQuantity = XmlUtils.NodeAsDecimal(tradeLineItem, ".//ram:PackageQuantity", nsmgr, 0).Value,
LineTotalAmount = XmlUtils.NodeAsDecimal(tradeLineItem, ".//ram:LineTotalAmount", nsmgr, 0),
TaxCategoryCode = default(TaxCategoryCodes).FromString(XmlUtils.NodeAsString(tradeLineItem, ".//ram:ApplicableTradeTax/ram:CategoryCode", nsmgr)),
TaxType = default(TaxTypes).FromString(XmlUtils.NodeAsString(tradeLineItem, ".//ram:ApplicableTradeTax/ram:TypeCode", nsmgr)),
TaxPercent = XmlUtils.NodeAsDecimal(tradeLineItem, ".//ram:ApplicableTradeTax/ram:RateApplicablePercent", nsmgr, 0).Value,
NetUnitPrice = XmlUtils.NodeAsDecimal(tradeLineItem, ".//ram:NetPriceProductTradePrice/ram:ChargeAmount", nsmgr, 0).Value,
GrossUnitPrice = XmlUtils.NodeAsDecimal(tradeLineItem, ".//ram:GrossPriceProductTradePrice/ram:ChargeAmount", nsmgr, 0).Value,
UnitCode = default(QuantityCodes).FromString(XmlUtils.NodeAsString(tradeLineItem, ".//ram:BilledQuantity/@unitCode", nsmgr)),
ChargeFreeUnitCode = default(QuantityCodes).FromString(XmlUtils.NodeAsString(tradeLineItem, ".//ram:ChargeFreeQuantity/@unitCode", nsmgr)),
PackageUnitCode = default(QuantityCodes).FromString(XmlUtils.NodeAsString(tradeLineItem, ".//ram:PackageQuantity/@unitCode", nsmgr)),
BillingPeriodStart = XmlUtils.NodeAsDateTime(tradeLineItem, ".//ram:BillingSpecifiedPeriod/ram:StartDateTime/udt:DateTimeString", nsmgr),
BillingPeriodEnd = XmlUtils.NodeAsDateTime(tradeLineItem, ".//ram:BillingSpecifiedPeriod/ram:EndDateTime/udt:DateTimeString", nsmgr),
};
Expand Down Expand Up @@ -639,6 +645,29 @@ private static LegalOrganization _nodeAsLegalOrganization(XmlNode baseNode, stri
return retval;
}

private static Contact _nodeAsContact(XmlNode baseNode, string xpath, XmlNamespaceManager nsmgr = null)
{
if (baseNode == null)
{
return null;
}

XmlNode node = baseNode.SelectSingleNode(xpath, nsmgr);
if (node == null)
{
return null;
}

Contact retval = new Contact()
{
Name = XmlUtils.NodeAsString(node, "./ram:PersonName", nsmgr),
OrgUnit = XmlUtils.NodeAsString(node, "./ram:DepartmentName", nsmgr),
PhoneNo = XmlUtils.NodeAsString(node, "./ram:TelephoneUniversalCommunication/ram:CompleteNumber", nsmgr),
FaxNo = XmlUtils.NodeAsString(node, "./ram:FaxUniversalCommunication/ram:CompleteNumber", nsmgr),
EmailAddress = XmlUtils.NodeAsString(node, "./ram:EmailURIUniversalCommunication/ram:URIID", nsmgr)
};
return retval;
}

private static Party _nodeAsParty(XmlNode baseNode, string xpath, XmlNamespaceManager nsmgr = null)
{
Expand All @@ -665,6 +694,7 @@ private static Party _nodeAsParty(XmlNode baseNode, string xpath, XmlNamespaceMa
City = XmlUtils.NodeAsString(node, "./ram:PostalTradeAddress/ram:CityName", nsmgr),
Country = default(CountryCodes).FromString(XmlUtils.NodeAsString(node, "./ram:PostalTradeAddress/ram:CountryID", nsmgr)),
SpecifiedLegalOrganization = _nodeAsLegalOrganization(node, "./ram:SpecifiedLegalOrganization", nsmgr),

};

string lineOne = XmlUtils.NodeAsString(node, "./ram:PostalTradeAddress/ram:LineOne", nsmgr);
Expand Down
16 changes: 10 additions & 6 deletions ZUGFeRD/InvoiceDescriptor23CIIWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,14 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream, ZUGFeRDFo
#region SpecifiedLineTradeDelivery (Basic, Comfort, Extended)
Writer.WriteStartElement("ram", "SpecifiedLineTradeDelivery", Profile.Basic | Profile.Comfort | Profile.Extended | Profile.XRechnung1 | Profile.XRechnung);
_writeElementWithAttributeWithPrefix(Writer, "ram", "BilledQuantity", "unitCode", tradeLineItem.UnitCode.EnumToString(), _formatDecimal(tradeLineItem.BilledQuantity, 4));

if (tradeLineItem.ChargeFreeQuantity.HasValue)
{
_writeElementWithAttributeWithPrefix(Writer, "ram", "ChargeFreeQuantity", "unitCode", tradeLineItem.ChargeFreeUnitCode.EnumToString(), _formatDecimal(tradeLineItem.ChargeFreeQuantity, 4), Profile.Extended);
}
if (tradeLineItem.PackageQuantity.HasValue)
{
_writeElementWithAttributeWithPrefix(Writer, "ram", "PackageQuantity", "unitCode", tradeLineItem.PackageUnitCode.EnumToString(), _formatDecimal(tradeLineItem.PackageQuantity, 4), Profile.Extended);
}
if (tradeLineItem.ShipTo != null)
{
_writeOptionalParty(Writer, PartyTypes.ShipToTradeParty, tradeLineItem.ShipTo, Profile.Extended);
Expand Down Expand Up @@ -461,9 +468,6 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream, ZUGFeRDFo
Writer.WriteEndElement(); // !ram:DeliveryNoteReferencedDocument
}

/// TODO: Add ShipToTradeParty
/// TODO: Add UltimateShipToTradeParty

Writer.WriteEndElement(); // !ram:SpecifiedLineTradeDelivery
#endregion

Expand Down Expand Up @@ -746,8 +750,8 @@ public override void Save(InvoiceDescriptor descriptor, Stream stream, ZUGFeRDFo

#region ApplicableHeaderTradeDelivery
Writer.WriteStartElement("ram", "ApplicableHeaderTradeDelivery"); // Pflichteintrag
_writeOptionalParty(Writer, PartyTypes.ShipToTradeParty, this.Descriptor.ShipTo, ALL_PROFILES ^ Profile.Minimum);
_writeOptionalParty(Writer, PartyTypes.UltimateShipToTradeParty, this.Descriptor.UltimateShipTo, Profile.Extended | Profile.XRechnung1 | Profile.XRechnung);
_writeOptionalParty(Writer, PartyTypes.ShipToTradeParty, this.Descriptor.ShipTo, ALL_PROFILES ^ Profile.Minimum, this.Descriptor.ShipToContact);
_writeOptionalParty(Writer, PartyTypes.UltimateShipToTradeParty, this.Descriptor.UltimateShipTo, Profile.Extended | Profile.XRechnung1 | Profile.XRechnung, this.Descriptor.UltimateShipToContact);
_writeOptionalParty(Writer, PartyTypes.ShipFromTradeParty, this.Descriptor.ShipFrom, Profile.Extended);

#region ActualDeliverySupplyChainEvent
Expand Down
55 changes: 55 additions & 0 deletions ZUGFeRD/TradeLineItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,20 @@
/// </summary>
public decimal BilledQuantity { get; set; }

/// <summary>
/// No charge quantity
///
/// BT-X-46
/// </summary>
public decimal? ChargeFreeQuantity { get; set; }

/// <summary>
/// Package quantity
///
/// BT-X-47
/// </summary>
public decimal? PackageQuantity { get; set; }

/// <summary>
/// Invoice line net amount including (!) trade allowance charges for the line item
///
Expand Down Expand Up @@ -155,6 +169,20 @@
/// </summary>
public QuantityCodes UnitCode { get; set; }

/// <summary>
/// Charge Free Quantity Unit Code
///
/// BT-X-46-0
/// </summary>
public QuantityCodes ChargeFreeUnitCode { get; set; }

/// <summary>
/// Package Quantity Unit Code
///
/// BT-X-47-0
/// </summary>
public QuantityCodes PackageUnitCode { get; set; }

/// <summary>
/// Identifier of the invoice line item
///
Expand Down Expand Up @@ -596,5 +624,32 @@
{
return this.DesignatedProductClassifications.Where(c => c.ClassCode.Equals(classCode)).ToList();
} // !GetDesignatedProductClassificationsByClassCode()

/// sets the quantity, at line level, free of charge, in this trade delivery.
/// </summary>

Check warning on line 629 in ZUGFeRD/TradeLineItem.cs

View workflow job for this annotation

GitHub Actions / build

XML comment has badly formed XML -- 'End tag was not expected at this location.'

Check warning on line 629 in ZUGFeRD/TradeLineItem.cs

View workflow job for this annotation

GitHub Actions / build

XML comment has badly formed XML -- 'End tag was not expected at this location.'
/// <param name="chargeFreeQuantity">Quantity of the included charge free product</param>
/// <param name="chargeFreeUnitCode">Unit code for the quantity</param>
/// </summary>
/// <returns></returns>
public void SetChargeFreeQuantity(decimal chargeFreeQuantity, QuantityCodes chargeFreeUnitCode)
{
ChargeFreeQuantity = chargeFreeQuantity;
ChargeFreeUnitCode = chargeFreeUnitCode;

} // !SetChargeFreeQuantity()

/// sets the number of packages, at line level, in this trade delivery.
/// </summary>

Check warning on line 642 in ZUGFeRD/TradeLineItem.cs

View workflow job for this annotation

GitHub Actions / build

XML comment has badly formed XML -- 'End tag was not expected at this location.'

Check warning on line 642 in ZUGFeRD/TradeLineItem.cs

View workflow job for this annotation

GitHub Actions / build

XML comment has badly formed XML -- 'End tag was not expected at this location.'
/// <param name="packageQuantity">Quantity of the included charge free product</param>
/// <param name="packageUnitCode">Unit code for the quantity</param>
/// </summary>
/// <returns></returns>
public void SetPackageQuantity(decimal packageQuantity, QuantityCodes packageUnitCode)
{
PackageQuantity = packageQuantity;
PackageUnitCode = packageUnitCode;

} // !SetPackageQuantity()

}
}