ZUGFeRD export: common modifications

ZUGFeRD export: common modifications

Reflect header charges (markups)

The standard sales invoice in D365 only shows the total of all miscellaneous charges (markups), but not the single markup transactions. No information about header charges is disclosed – unless we are in Finland: the Finnish localisation ensures disclosure of detailed charges on invoices, which is why the invoice data provider collects markup details only when the ISO country code is “FI”. For all other countries, this logic is skipped in standard D365.

Data for the ZUGFeRD e-invoice is calculated in D365 by the same Data Provider as with the original SSRS sales invoice: SalesInvoiceDP. Without extending the DP or introducing alternative data sources, Electronic Reporting alone cannot retrieve missing charge details. The challenge and a possible no-code solution is best described in the article “ER: Add charge details to the Sales invoice report – CleverAX” written by a fellow ER expert.

An alternative is a small X++ customisation:

				
					[ExtensionOf(classStr(SalesInvoiceDPBase))]
final class SalesInvoiceDPBase_Extension
{
    #ISOCountryRegionCodes
    public void createData()
    {
        next createData();
        if (isoCountryCode != #isoFI)
        {
            this.insertMarkupSpec(custInvoiceJour);
        }
    }
}
				
			

This extension effectively enables header charge extraction for all countries; in the Lasernet application, this behaviour is activated by the feature “SalesInvoice.Report creates temporary MarkupTmpTrans_FI“. In the Docentric ecosystem, you need the above customisation plus another one.    

If no detailed charge records are available in the data model, our e-invoice falls back to displaying the total:

      <ram:SpecifiedTradeAllowanceCharge>
        <ram:ChargeIndicator>
          <udt:Indicator>true</udt:Indicator>
        </ram:ChargeIndicator>
        <ram:ActualAmount>123.99</ram:ActualAmount>
        <ram:Reason>Summe der Zuschläge</ram:Reason>
        <ram:CategoryTradeTax>
          <ram:TypeCode>VAT</ram:TypeCode>
          <ram:CategoryCode>S</ram:CategoryCode>
          <ram:RateApplicablePercent>19</ram:RateApplicablePercent>
        </ram:CategoryTradeTax>
      </ram:SpecifiedTradeAllowanceCharge>

With the above customisation in place, the XML output expands to include individual charge entries:

      <ram:SpecifiedTradeAllowanceCharge>
        <ram:ChargeIndicator>
          <udt:Indicator>true</udt:Indicator>
        </ram:ChargeIndicator>
        <ram:ActualAmount>99.99</ram:ActualAmount>
        <ram:Reason>FREIGHT</ram:Reason>
        <ram:CategoryTradeTax>
          <ram:TypeCode>VAT</ram:TypeCode>
          <ram:CategoryCode>S</ram:CategoryCode>
          <ram:RateApplicablePercent>19</ram:RateApplicablePercent>
        </ram:CategoryTradeTax>
      </ram:SpecifiedTradeAllowanceCharge>
      <ram:SpecifiedTradeAllowanceCharge>
        <ram:ChargeIndicator>
          <udt:Indicator>true</udt:Indicator>
        </ram:ChargeIndicator>
        <ram:ActualAmount>24.0</ram:ActualAmount>
        <ram:Reason>HANDLING</ram:Reason>
        <ram:CategoryTradeTax>
          <ram:TypeCode>VAT</ram:TypeCode>
          <ram:CategoryCode>S</ram:CategoryCode>
          <ram:RateApplicablePercent>19</ram:RateApplicablePercent>
        </ram:CategoryTradeTax>
      </ram:SpecifiedTradeAllowanceCharge>

Reflect line charges

The above customisation to Dynamics 365 for Finance only solves header-level charges. Even in Finland, the SalesInvoiceTmp record representing the surcharge does not have a reference to the invoice line: the line-level charges are never disclosed in standard D365.

Fortunately, the Retail Call Center (“MCR”) configuration key (it is usually active) helps: the related MCRMarkupCalculatedAmount field keeps the total of all line charges which affect the invoice amount. This data source is leveraged in our ZUGFeRD configuration to reflect the sum of all charges per line:

      <ram:SpecifiedTradeAllowanceCharge>
        <ram:ChargeIndicator>
          <udt:Indicator>true</udt:Indicator>
        </ram:ChargeIndicator>
        <ram:ActualAmount>12.3</ram:ActualAmount>
        <ram:Reason>Belastung</ram:Reason>
      </ram:SpecifiedTradeAllowanceCharge>

If a detailed list of line-level charges broken down by code/description is required, then you need an adaptation of the ZUGFeRD ER report and mapping. Refer to the the Docentric’s article “Add Charges to Sales Invoice Lines through ER” first.

If you do not have the Docentric code installed, the following X++ snippet must be deployed, plus an extension of the SalesInvoiceTmp table with the field JourTransRecId_DR, contact us for more details:

				
					[ExtensionOf(classStr(SalesInvoiceDP))]
final class SalesInvoiceDP_Extension
{
    protected void populateSalesInvoiceTmp(CustInvoiceJour _custInvoiceJour, CustInvoiceTrans _custInvoiceTrans, TaxSpec _taxSpec,
        CustPaymSchedLine _custPaymSchedLine, CustTrans _prepaymentCustTrans, TaxTrans _prepaymentTaxTrans)
    {
        next populateSalesInvoiceTmp(_custInvoiceJour,
            _custInvoiceTrans,
            _taxSpec,
            _custPaymSchedLine,
            _prepaymentCustTrans,
            _prepaymentTaxTrans);

        salesInvoiceTmp.JourTransRecId_DR = _custInvoiceTrans.RecId;
    }
}
				
			

Derive a custom mapping from the “Invoice model mapping (ZUGFeRD)“, and derive a custom format from the “ZUGFeRD Sales invoice“.

As suggested by Sanja Kolundzija, add

$SalesInvoiceTmp_Lines.$CustInvoiceTransFromTableDef asTables.CustInvoiceTransTable.'findRecId()'(@.JourTransRecId_DR),

to the derived Mapping, then add a nested record list

$CustInvoiceTransFromTableDef.$MarkupTransWithFilter as

FILTER(Tables.MarkupTransRecords,
AND(Tables.MarkupTransRecords.TransTableId=TABLENAME2ID("CustInvoiceTrans"),
Tables.MarkupTransRecords.TransRecId=@.RecId))

The InvoiceLines.MarkupTransaction list in not bound in the Microsoft’s base mapping, you have to bind it as shown:

Additionally, you have to set the MCRMarkupCalculatedAmount element to zero to avoid doubling of line charge amounts: Charge(InvoiceLines.MCRMarkupCalculatedAmount) = @.MCRMarkupCalculatedAmount*0.0

In the derived “ZUGFeRD Sales invoice“, add a new <ram:SpecifiedTradeAllowanceCharge> at the line and bind it  toInvoiceLines.MarkupTransaction as shown below:

This is not all. In accordance with the ZUGFeRD specification, this markup amount is separated from the header level charges, and it increases the total line amount before VAT <ram:LineTotalAmount>. Dynamics 365 in contrary adds all header and line charges and prints them at the and of the invoice. To satisfy the CII scheme, the line charges must be subtracted from the total charge amount given by Dynamics.

In our ZUGFeRD configuration, a collection named $SumMCRMarkupCalculatedAmount2 is used to hold the total of all line charges across all invoice lines. This is why $SumMCRMarkupCalculatedAmount2.Collect() wraps and adds to an internal counter the amounts @.’$CUSTOM_SumOfLineMarkups’, which is a shortcut for the totals of all markup transactions per line: @.’$CUSTOM_SumOfLineMarkups’ = @.’$CUSTOM_MarkupTransGrpLines’.aggregated.SumChargeCalculatedValue


The @.'$CUSTOM_MarkupTransGrpLines' is a Group By element:

Technical debt of customised SSRS Invoices

This chapter concerns Dynamics users who utilised SSRS in the past, or intend to further use their SSRS invoices with the Dynamity Group’s SSRS-to-ZUGFeRD convertor.

In these installations, we often see the SSRS Report Data Provider (RDP) customised to a certain extent to make the printed invoice show information that the standard invoice layout did not expose conveniently. For example, the abovementioned markup/charge details was a common requirement, there the charges started appearing among the invoice lines and not in a separate section as intended by Microsoft; namely, the typical legacy approach was to inject additional records into SalesInvoiceTmp that behaved like invoice lines from the SSRS report’s perspective. This made it easy for the report designer to render the information in the same repeating line section as normal sales lines. Such customisations could also carry other display-only information that was useful on the printed invoice.

This creates a problem when the same SalesInvoiceTmp dataset is reused as the source for ZUGFeRD/Factur-X:

  • SSRS asks essentially: “What should I display on the printed invoice?”
  • ZUGFeRD/ER asks: “What are the actual commercial invoice lines and their tax/amount semantics?”

With the RDP customised, those are not necessarily the same. Consequently, a record that was perfectly useful as a display-only pseudo-line in an SSRS report can look like a malformed invoice line to CII/ZUGFeRD. It may have an amount or description but lack the quantity, item, tax code or other information expected for a genuine IncludedSupplyChainTradeLineItem.

To resolve this abiguity, the SalesInvoiceTmp data had to be analysed first, the malformed or unintended records identified, and then…

  1. either the upstream/customised D365 logic corrected,
  2. or the rogue SalesInvoiceTmp filtered out so that ZUGFeRD receives a clean dataset,
  3. or the ER binding made more tolerant to these additional records.

The options 2 and 3 are elaborated below on two real-world examples.

Case 2: remove the pseudo-lines from the lines list. In the “Invoice model mapping” there is already a filter of invoice lines to separate true invoice lines from markup records. The latter bear no quantity:

$SalesInvoiceTmp_Lines as WHERE('$SalesInvoiceTmp', '$SalesInvoiceTmp'.Qty <> 0)

This is exactly the place where you can start adding criteria to filter out other pseudo-lines.

Case 3: the developers added records to SalesInvoiceTmp representing order-level surcharges. Unlike the previous cases, we decided not to remove them from SalesInvoiceTmp. They were intended to remain visible in the electronic invoice as IncludedSupplyChainTradeLineItem lines.

The problem was that these pseudo-lines did not contain the same tax information as regular invoice lines. In particular, the expected TaxWriteCode was not available to the ER data source, resulting in an empty value being passed to the tax-category mapping.

As a solution, where no Print code/tax code was available, the tax category was derived from the first relevant tax transaction, based on the assumption that the invoice contains either zero-rated tax or the applicable standard/reduced German VAT rates, but not a mixture of zero and taxable rates.

A second adjustment was required because the surcharge records were effectively represented twice in the generated XML: once as invoice lines and again as charges. The pseudo-charge lines therefore had to be excluded from the charge calculation so that ChargeTotalAmount remained zero.

Here again, the collection $SumMCRMarkupCalculatedAmount2 was leveraged. When looping through invoice lines and encountering a line representing a charge, the charge amount was recoded to get subtracted at the end. This was achieved with the following dirty trick:
IF(@.TaxWriteCode="",
model.InvoiceBase.’$FirstTaxTrans’.’$TaxWriteCode2Category’&

LEFT(TEXT(model.InvoiceBase.’$SumMCRMarkupCalculatedAmount2′.Collect(@.LineBase.Amount)*0),0),

@.’$TaxWriteCode2Category’)

The amount of the charge has nothing to do with the VAT category, but at that tag the charge amount was evaluated, added to the collection, then reduced to nil with LEFT(TEXT(…),0) = “” .

Extensibility of the Heavy version

To implement a custom invoice design with a corporate logo, a certain colour palette or fonts, extend the ER configuration “ZUGFeRD Sales invoice” as any other configurable business document. Derive an own configuration, then extract, amend and replace the PDF template attached to the ER configuration version.
 

Please bear in mind that PDF forms do not contain dynamic, repeatable ranges or cells (refer to Filling PDF Forms with Electronic Reporting in Dynamics 365 for Finance for more details). This is the reason why the number of invoice lines is limited by the PDF template; change the constants Paginator.#LinesMax, #LinesOnFirstPage and #LinesOnNextPages in accordance with the template to let Dynamics control the number of pages.

 

The “output actions” i.e. different transmission options are extendable; new output actions may be offered upon request at a discounted price. You may even add your own class ERFileOutputActionXXX_ZUGFeRD. If inherited from the ERFileOutputActionPersist_ZUGFeRD, it will commit the synchronous transmission, setting the SentElectronically flag at the invoice in the process. The interface ERIDocuManagementAttachable_ZUGFeRD interacts with the Archive electronic reporting destination, replacing the file to attach to the ER job record.

The method sendFile() must be implemented. It receives a memory stream with the hybrid invoice and transmits it to the final destination.

The method makeVisitor() must be implemented, too. Its result – a ERFileOutputActionXmlVisitor object – subscribes to the post-processor to extract information from the XML. By default, it receives the file name of the PDF. The input parameter of the method andVisitNode() is the name of a tag in the CII invoice (without the “ram” or “rsm” schema prefix). For example, if instantiated with return visitor.andVisitNode(“ExchangedDocument”), the Visitor object will also receive the invoice number.

				
					/// <summary>
/// Send the resulting PDF to a browser window interactively
/// </summary>
[ERFileOutputAttribute_ZUGFeRD(SRSPrintMediumType::Screen)]
class ERFileOutputActionScreen_ZUGFeRD extends ERFileOutputAction_ZUGFeRD
{
    ERFileOutputActionXmlVisitor_ZUGFeRD visitor;
    /// <summary>
    /// The screen destination does not need to extract any contact info
    /// </summary>
    /// <returns>An empty visitor class</returns>
    public ERFileOutputActionXmlVisitor_ZUGFeRD makeVisitor()
    {
        visitor = new ERFileOutputActionXmlVisitor_ZUGFeRD();
        return visitor;
    }

    /// <summary>
    /// Display  the PDF file formed by ERFileDestinationPostProcessor_ZUGFeRD on the screen 
    /// </summary>
    /// <param name = "_outputPDF">Stream with a PDF file, with a CII XML attached</param>
    /// <returns>Success or failure to display</returns>
    public boolean sendFile(System.IO.MemoryStream _outputPDF)
    {
        Browser br = new Browser();
        
        str downloadUrl = File::SendFileToTempStore(_outputPDF,
                                                    ERConstants_ZUGFeRD::ZUGFeRD_Filename,
                                                    classstr(FileUploadTemporaryStorageStrategy),
                                                    true);
        if (downloadUrl != '')
        {
            br.navigate(downloadUrl, true, false);
        }
        else
        {
            return checkFailed("@ApplicationPlatform:DownloadFailed");
        }
        return true;
    }

}
				
			

ZUGFeRD 2.2-2.5 import for Dynamics 365 for Finance

ZUGFeRD 2.2-2.5 import for Dynamics 365 for Finance

ZUGFeRD (Zentraler User Guide des Forums elektronische Rechnung Deutschland) is an e-invoicing format combining a human-readable PDF/A-3 and an embedded XML file using the CII (Cross-Industry Invoice) syntax. The CII syntax may also be used in the xRechnung, which is a pure XML version of the German e-Invoice. From 2025, the ability to receive and process electronic invoices becomes mandatory for B2B transactions in Germany.

Microsoft’s current solution in Dynamics 365 for Finance provides support specifically for the UBL (Universal Business Language) format of the xRechnung: Import vendor electronic invoices – Finance | Dynamics 365 | Microsoft Learn. It enables the import of vendor invoices received in this format, mapping them as pending vendor invoices in D365. The invoice may be uploaded directly into the system or placed in a SharePoint folder for unattended processing.

We are happy to announce the release of the CII (Cross-Industry Invoice) dialect of the xRechnung / ZUGFeRD electronic invoice. This solution is implemented via Electronic Reporting (ER) configurations, which means it does not require any customisations and can be easily installed across all active versions of D365. It sits on top of the standard Invoice model v280 v322, Vendor Invoice Mapping to Destination v280.46 v322.47 , side by side with the configuration Vendor Invoice Import v280.7 v280.9 v322.10 (state: Sep. 2026).

The functionality aligns with the standard Microsoft UBL import, utilizing the key data elements for processing and ensuring PO matching:

  • Header: Invoice ID, Invoice date, Due date, Vendor ID, Currency code, PO number, a header-level note and header attachments (in XML embedded MIME documents).
  • Lines: Line number, Item ID, Quantity, Sales unit of measure, Sales price, Discount (percentage discounts are converted to absolute values as the XML schema doesn’t support percentages), Price unit, Name, Description, and Line amount (excluding VAT).

The solution supports the ZUGFeRD 2.1, ZUGFeRD 2.2, 2.3.x or ZUGFeRD 2.4 / Factur-X 1.0x with the profile EN16931 (also known as the “COMFORT” profile), XRECHNUNG profile or the EXTENDED profile. All three meet the tax law requirements and must be accepted by e-invoice recipients.

In contrary, ZUGFeRD 1.0 (easily recognised by the <rsm:CrossIndustryDocument> root node instead of the proper <rsm:CrossIndustryInvoice>) is no longer permitted in Germany when it comes to complying with the current legal requirements for electronic invoices, and therefore not supported. The same applies to the BASIC and BASIC WL profiles of the ZUGFeRD 2.x, announced by the <ram:GuidelineSpecifiedDocumentContextParameter> tag in the header.

Price

The solution is offered at a fixed fee per end customer / D365 tenant:
VersionPriceDeliverablesNotes
Light€890The Vendor invoice import CII Electronic Reporting configurationThis option does not require any X++ customisations or 3rd party software and can be installed and used right away.
Light+€1200Same as the Light version but with 5 hours of prepaid support and installation 

You can acquire the solution on the official Microsoft AppSource marketplace: ZUGFeRD CII import (microsoft.com)

 

Setup and use

 
    1. Follow the steps outlined in the Microsoft’s guidance Import vendor electronic invoices – Finance | Dynamics 365 | Microsoft Learn:
    2. Import the same three configurations from Microsoft (Prerequisites);
    3. Import the configuration Vendor invoice import CII.xml provided to you (menu Organisation administration > Electronic reporting > Configurations, button Exchange / Load from XML file);
    4. Open Accounts payable > Setup > Accounts payable parameters; on the Electronic documents tab, select the imported Vendor invoice import CII format.
    5. To run either one of the formats – UBL or CII – by choice, you may download and import a complimentary X++ extension with an additional parameter CII vendor invoice and an own menu item AP > Periodic tasks > Import vendor invoices CII underneath the standard one: VendImportInvoice_CII.axpp. In the batch execution mode this does not matter, because each of the formats becomes its own Electronic source with a set of SharePoint folders etc.
    6. Prepare the master data: make sure the vendors are stored with proper VAT numbers in Dynamics 365 for Finance. They must have the proper country prefix “DE” and may not contain any spaces or special characters.
    7. Make sure that the vendor’s external item ID mapping exists. Any Buyer Assigned ID (our item number) potentially sent by the vendor is neglected by the Microsoft’s mapping, and the conversion to the internal Item ID is always handled via the external item IDs. Under these conditions, the import works as expected and the PO lines may be matched.
    8. Make sure that the units of measure are properly mapped to the UNECE Recommendation No. 20 list called “Codes for Units of Measure Used in International Trade”. For instance, the UNECE unit H87 should be mapped to PCS or STK, while C62 may become EA in our system; see also a longer list in https://erconsult.eu/de/blog/aktueller-stand-der-erechnung-in-d365-for-finance-deutschland/. You may need to introduce new units of measure, because the External codes table (ExtCodeValueTable in particular) assumes the 1:1 relation between the system units in D365 and the external unit codes.
    9. Perform an interactive test upload by using the function Accounts payable > Periodic tasks > Import vendor invoices (or Import vendor invoices CII if you installed and configured the above extension from step 5). Beware: contrary to the UBL format, the CII (“COMFORT”) invoice does not include any line-by-line reference to the purchase order lines. Just the PO number is sent in the header. We assume that the numeration of the invoice lines mostly follows the PO lines numeration. If not, the standard mapping will fall back to the matching by the line purchase price.
    10. Configure the batch mode with Electronic reporting sources i.e. SharePoint folders as outlined in the above Microsoft’s guidance: Import vendor electronic invoices – Configure the sources to import files in batch mode.
    11. The standard UBL import from Microsoft and our CII import may be “daisy-chained”: set the error folder (Electronic reporting sources -> Settings / Document type for failed files) of the CII import the same as the input folder (Settings / Document type for input sources) for the UBL. If the XML starts with the <rsm:CrossIndustryInvoice> tag and features the <rsm:>, <ram:> namespaces, then it is a ZUGFeRD and it will be processed regularly by the CII import. If the XML starts with the <ubl:Invoice> or <ubl:CreditNote> tag and features <cbc:>, <cac:> namespaces, the CII parsing is going to fail early, and the XML file will be passed to the UBL parser, which takes it from there.

Cardinality of the external item number

In Dynamics 365 Supply Chain Management, the relationship between an external item number and an internal item ID is strictly 1:1. Each external ID points to exactly one internal product. This can cause issues when using Microsoft’s Vendor invoice mapping to destination, especially if Procurement categories were converted into service items during the vendor invoice import implementation. For example, spare parts that were once all booked under the procurement category “Maintenance and repair” now require separate released products, and every type of stationery once grouped under “Office supplies” must be split into multiple products. With mapping based on vendor external item numbers, this means creating a separate released product record for every sheet of paper or pen type.

To reduce this complexity, we recommend a small extension to the CustVendExternalItem table. Vendor invoice mapping to destination relies on the method CustVendExternalItem.getItemIdForInvoiceAccount_BR() , which could theoretically return the same internal item ID for different external IDs. However, the table’s unique index blocks multiple external numbers from being linked to the same product and vendor. Fortunately, Microsoft provided an extension point fieldIdsToHash() that allows you to add new fields into the uniqueness constraint, compressed into a hash. The code below extends this index with the Description field, so multiple vendor external item numbers can point to the same internal product.

				
					/// <summary>
/// Allow for internal ItemId 1 : N ExternalItemId
/// </summary>
[ExtensionOf(tableStr(CustVendExternalItem))]
final public class CustVendExternalItem_CII_Extension
{
    /// <summary>
    /// Add the Description to the list of fields that define the uniqueness of a record
    /// </summary>
    /// <returns>The set of field Ids.</returns>
    protected Set fieldIdsToHash()
    {
        Set hashFields = next fieldIdsToHash();

        if (this.ModuleType != ModuleInventPurchSalesVendCustGroup::Vend && 
            this.ModuleType != ModuleInventPurchSalesVendCustGroup::VendGroup)
        {
            return hashFields;
        }
        if (! this.Description)
        {
            return hashFields;
        }

        PurchParameters purchParameters = PurchParameters::find();
        if (purchParameters.ERModelMappingVendInvoice != 0 ||
            purchParameters.ERModelMappingVendInvoiceCII != 0)
        {
            hashFields.add(fieldNum(CustVendExternalItem, Description));
        }
        return hashFields;
    }

}
				
			

Use this code carefully: if you already rely on the Description field for other purposes, conflicts may arise. Records with an empty description will still behave as in the standard system and act as a fallback when matching external vendor numbers.

Procurement categories matching

Recently we successfully extended the Microsoft’s existing Vendor invoice Mapping to destination (v322.47) to enable supplier invoice matching with purchase orders lines with procurement categories. We added two targeted calculated fields to match purchase order lines by line number or net line price, and incorporated them as additional fallbacks in the PO-line matching logic. We then bound the procurement category and adjusted the ItemId logic to avoid conflicts when category lines are used.

In addition, a purchase order may contain a mix of positive and negative lines to uptake a credit note or a corrective invoice. The standard matching logic ignores the sign of the quantity in the eInvoice. We’ve hardened the logic to only look for PO lines with the same quantity sign as in the invoice line.

Both UBL and CII users benefit from this improvement. You may download it here: Vendor invoice Mapping to destination (ERC) 280.46.2 or the newer Vendor invoice Mapping to destination (ERC) 322.47.3; contact us for more details.

Useful links

Production order should not get Released on material shortage

Production order should not get Released on material shortage

Update 27.08.2024: The below issue has ultimately been solved by Microsoft in Update 38, see What’s new or changed in Dynamics 365 Supply Chain Management 10.0.38 (February 2024) – Supply Chain Management | Dynamics 365 | Microsoft Learn. The feature “Prevent production orders to be released when full material is not available” is described as follows: “Fixes an issue where the production control parameter Requirement for material reservation is set to Require full reservation. Previously, when a production order was released while this option was selected, its status was always changed from Scheduled to Released, even if all material couldn’t be fully reserved. When this feature is enabled, when a production order is released with this option selected, the production order will only change status once all material is fully available for reservation.”

There is an evergreen issue in Dynamics 365 material reservation for production: despite the policy “Require full reservation“, the production order is released even if there is a shortage of materials:

The issue is very well known, I have been combating it since 2018. It has been reported to Microsoft on numerous occasions by my colleagues around the world: Production order should not get status Released if raw material release to warehouse fails.

The consequences are dire. A released order’s jobs will appear on the Shop floor control terminal and animate the worker to start the order. In the course of the assembly the worker is going to be forced to interrupt his/her work and fetch the parts from the warehouse. Later, at the report as finished / material backflushing stage at the Production floor terminal, it is going to prevent the online material consumption if the backflushing principle is set to Finish.

Apparently, the BOM line and the warehouse work generation are not in the same transaction scope as the overall production order release. Indeed, the ProdWHSRelease call is performed outside of the ttsbegin/ttscommit pair in ProdUpdRelease

Since Microsoft refuses to fix this problem, let me publish a programmatic solution. We put the ProdWHSRelease call into the endUpdateProduction() method if the order requires full reservation. Then we prevent the execution of ProdWHSRelease for the second time by temporarily changing one parameter and then reverting it back:

DontReleaseOnMaterialShortage.axpp

				
					                ttscommit;
                this.printout();
            }
        }
        catch (Exception::Deadlock)
        {
            retry;
        }
...
        prodTable.reread();
        if (ProdParametersDim::find(prodTable.InventDimId).ProductionLineRelease == WHSProductionLineRelease::OnProdOrderRelease)
        {
            ProdWHSRelease::newFromProdParmRelease(prodParmRelease).whsReleaseSingleOrder(prodTable);
        }