Production order should not get Released on material shortage

Production order should not get Released on material shortage

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:

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

}

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

You are welcome!

ZUGFeRD 2.1/ Factur-X 1.0 for Dynamics 365 FO

ZUGFeRD 2.1/ Factur-X 1.0 for Dynamics 365 FO

We are excited to announce the release of the common German-French electronic invoice ZUGFeRD / Factur-X in Dynamics 365 for Finance and SCM. This plug-and-play solution in its “Light” version does not require any customisation and can be easily installed in the standard Electronic Reporting module of D365. It supports the ZUGFeRD 2.1.1/Factur-X 1.0 profile EN 16931, also known as the “COMFORT” profile, which meets all tax law requirements and must be accepted by customers unless explicitly denied.

In the ZUGFeRD/Factur-X standard, the invoicing information is included in an XML payload attached to a human-readable PDF file (see https://de.wikipedia.org/wiki/ZUGFeRD). This is in contrast to the Italian electronic invoicing system, where a PDF is embedded in a Base64 encoded block within an XML. The PDF document is just a visualisation of the electronic content, what counts is the XML attachment. The XML file is always named factur-x.xml and it is attached to at the PDF document level, not to a single PDF page:

Here is an example: factur-x.xml

Our solution, which utilizes Configurable business documents in D365, supports Sales invoices with separate buyer, seller, and order accounts. Other types of invoices such as the Free text invoice or any of the 3 Project invoices, can be provided upon request at a discounted price. Our solution also handles form notes, line and header notes and footers, line discounts, total discounts, and cash discounts, document level charges, properly reflecting VAT for domestic, EU, and export sales. The settlement instructions orchestrate a SEPA credit transfer or a SEPA direct debit withdrawal; partially prepaid invoices are properly interpreted. The solution is available in French, German, and British English.

Price

The solution is offered at a fixed fee per end customer / D365 tenant:
Version Price Deliverables Notes
Light €890 A ZUGFeRD Sales invoice ER configuration, the XML file only This option does not require any X++ customisations or 3rd party software and can be installed and used right away.
Light+ €1200 Same as the Light version but with 5 hours of prepaid support and installation  
Heavy €2200 Contains the full solution, including a PDF template, and a PDF post-processor with 8 hours of prepaid support. Includes the iTextSharp 5.5.13.2 .NET library under the GNU Affero General Public License
Includes the BouncyCastle 1.8.9 .NET cryptographic library under a variant of the MIT license.

Setup and use

 
  1.  Import the Electronic configuration “ZUGFeRD Sales invoice” provided to you (Organisation administration > Electronic reporting > Configurations, button Exchange / Load from XML file). For the ease of installation, configuration and integration, the standard Invoice model Invoice model v 277 and the standard Invoice model mapping 277.258 are used, make sure they have been imported upfront.
  2. Beware: the standard Invoice model mapping from Microsoft has a number of shortcomings. For instance, the buyer and seller addresses are passed in a de-normalized form as multi line strings. As a walkaround, some heuristics and regular expressions have been applied to extract the ISO2 code of the country and the zip (postal) code. A well-known modification to the standard Invoice model mapping will fix that and reinforce the report. Futhermore, the current Microsoft Invoice model mapping does not pass the EU Tax directives to the report. At the moment, the report relies on the non-EU Tax exempt codes in the same way as the standard Excel invoice does. To leverage the Tax directives, fix the model mapping, then modify the ZipFolder/InvoicePDF/SalesTax/SalesTax* mappings in ZUGFeRD Sales invoice format like this:
    IF(OR(@.Value.TaxExemptDescription<>"",@.Value.TaxDirective<>""),
    IF(@.Value.TaxExemptDescription<>"",@.Value.TaxExemptDescription,@.Value.TaxDirective),
    @"GER_LABEL:PlusVAT")
  3. Under the button Configurations / Application specific parameters / Setup, provide a mapping of the VAT (en-us: Sales tax) Print codes to the ZUGFeRD categories S, K, AE, G, O (for example, the 19% and 7% VAT belong to the “S” category). Also provide a mapping of the D365 Unit of measure codes to the UNECE Recommendation No. 20 list called “Codes for Units of Measure Used in International Trade” (see https://tfig.unece.org/contents/recommendation-20.htm). For instance, a piece has the standard code H87. A sample parameter setup can be downloaded here: 03-01-2023 03.29.45 pm_ZUGFeRD Sales invoice.277.11
  4. Pay attention to the sample VAT print codes on the screenshot above: the standard Invoice model mapping does not pass the tax rates to the report, and the VAT Print code must contain the VAT rate in numbers, if applicable. In other words, they should look like “19%” and “7%” in Germany or “20%” and “5,5%”in France. The mapping of the sales units of measure is against the language-specific, translated unit code (if exists), not the UoM symbol used in D365 globally, i.e. not pcs but Stk. Once both the mappings have been established, set the Application specific parameters State to Completed.
  5. Open Sales ledger (en-us: Accounts receivable) > Setup > Forms > Form setup. Make sure that the VAT specification option is set to Registration currency. You will also probably Print VAT number on invoice, show some invoice line inventory dimensions and/or notes, and display external item numbers (Item number in forms = Both).
  6. Finally, open Print management and select the Report format = ZUGFeRD Sales invoice at the Customer invoice node. The Light version setup ends here.
  7. For the Heavy version, first deploy the X++ customisations and libraries provided.
  8. Create a new File document type in the Organisation administration > Document management > Document types list. Name it exactly like this: ZUGFeRD.
  9. Create a new ER destination (Organisation administration > Electronic reporting > Electronic reporting destination) for the report ZUGFeRD Sales invoice. Under File destination, add a new line and divert the ZipFolder file component to the Archive: click Settings, then Archive, set Enabled and Save in job archive, and choose file Type = ZUGFeRD. The Archive destination is namely the only one to plug in/subscribe to.
  10.  Make sure that the feature Configure action-dependent ER destinations is turned OFF: https://learn.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/analytics/er-action-dependent-destinations. It doesn’t work well anyway.
  11. The steps to configure the Heavy version end here. The Electronic Reporting engine will fill out the PDF form, populate an XML file and put both into a ZIP (i.e. a folder). This ZIP file is intercepted, unpacked, and the XML part is embedded into the PDF, adding some links and attributes required. The resulting PDF is rendered on the screen in a separate browser window. Other output destinations than the Screen may be offered upon request at a discounted price. The PDF file is not fully PDF-A/3 compliant, but it can be read and parsed, the XML payload passes all validations. Please bear in mind that PDF forms never contain repeatable ranges or cells. This is the reason why the number of invoice lines is currently limited by 10, this number will be extended on demand; see also Design ER configurations to fill in PDF templates – Finance & Operations | Dynamics 365 | Microsoft Learn.
  12. Print or reprint an invoice with the option Use print management turned on.
  13. The resulting PDF document is displayed on the screen (the Light version offers an XML file to download). Here is a sample, you buy what you see:



Useful links:

Batch jobs in D365: a Russian roulette

Batch jobs in D365: a Russian roulette

Real world production scenarios include automated batch tasks executed in the background with some cadence: Batch processing overview – Finance & Operations. They apply to sets of documents fetched at the run time, for example to sales orders delivered, but not invoiced yet. Obviously, every day the query with the documents should return a different set with the recently despatched sales orders. It is called Late selection: Late selection in D365FO – D365Tour. So far so known.

As the complexity of the business grows, so does the complexity of the batch jobs. You’ll need jobs consisting of multiple tasks, executed one after another. It is called task dependency, or task Constraints. One of the latest unpleasant discoveries was the fact, that you cannot always rely on the sequence of steps set in the batch job: the most useful tasks, such as the SalesFormLetter_Invoice and its likes (used to update sales and purchase orders with order confirmations, delivery notes and invoices) act as orchestrators and spin off child batch tasks for simultaneous, multi-threaded sales order, purchase order, production order processing. These child tasks are not constrained (!) and get executed at a first come, first served basis. They may still run while the master task SalesFormLetter_Invoice seemingly ends and relays the baton to the next task. This leads to interference and racing conditions. You better evaluate the average execution time of the master controller task as a whole, put these controllers into different batch jobs, schedule them at a safe time distance from one another.

Heterogenous batch jobs as the one shown below pose a different challenge.

Here is the how-to instruction:

  1. Spin off the first task from the main menu (here: Production control > Periodic tasks > Production order status update) and choose Run in the background / Batch processing. This will be your starting point, a template for the batch job.
  2. Locate the batch job in the My batch jobs list, set the status to Withheld and start editing.
  3. Every task within a batch job needs a Class name. Now you know the class name of the first task: ProdMultiCostEstimation. To add the next task, you have to manually specify its Class name. The owners of this framework provided a lookup list, but it is slow and completely dysfunctional: it shows just a fraction of the possible executable RunBase or SysOperation classes, sometimes it crashes if there are some bad customizations in the source code. If you don’t know the class name, repeat the step 1 to reveal it.
  4. Add the next task, type the class name in. The first time it will take the system up to 20-30 seconds to accept and retrieve the Class description.
  5. Most batch tasks need a query. Click Parameters to populate it. Here you experience another nasty trait of the batch framework: the query shown is the last one you used in the system elsewhere. It is not necessarily the one really persisting at the batch task. Another user is not going to see your query criteria at all: only the owner/creator has the control. *1) *2) *3)
  6. On the Batch task details tab, Constraints, choose the predecessor task to build a chain. You many need to change the Expected status to Ended or error if the batch job has to continue with the next task on an error somewhere in the chain.
  7. Check the recurrence, remove the Alerts if needed, and put the Batch job back to Waiting.

1) Lessons learned: take a screenshot of every query and parameter, document it thoroughly to pass the maintenance to the Dynamics system administrator.

2) In certain cases a manually added task does not let edit the execution parameters and/or the query. Sometimes(e.g. on sales order updates) it helps to execute the same periodic task in the normal user session once. This is the reason why it is often hard to pre-configure the batch jobs in a “Golden Config” environment: it may need real transactions to execute against. Sometimes it wishes to be initialised from the main menu and nowhere else because badly programmed. In such a case, start your step 1 – the key task – from this critical periodic task. Should there be 2 such bitchy tasks in one batch job chain, it is over. They must be separated into different batch jobs.  

3) As a really bad surprize came the fact that even the owner has no control over the query: if you use the Late selection, in a chain of nearly identical tasks as the ones below, the QUERY OF THE LAST TASK IS APPLIED TO EVERY TASK of the kind, at least in the case of the production order status updates.

You should let the bloody job run one first time, then put the batch job back on hold and adjust the queries in the tasks once more, then save and re-activate the batch job.