Saturday, 12 September 2026

Pinned Comment Lines for Microsoft Dynamics 365 Business Central

An extension that makes important notes on customers, vendors, and items visible at the moment they matter, instead of hidden two clicks away.


The problem

Business Central already lets you record comments against a customer, a vendor, or an item. Someone careful writes down "always confirm the PO number before shipping" or "this customer disputes every invoice, copy the account manager", and the note is saved correctly.

Then nobody reads it.

Not because the note is wrong, but because of where it lives. Comments sit behind Customer > Comments, off the main card, on a page most users never open. The person picking and shipping the order has no reason to look there, and no indication that anything is waiting for them. The information is in the system; it simply isn't where the decision gets made.

What this extension does

It adds a single Pinned tick to the comment sheet you already use.

A pinned comment raises an on-screen notification whenever that customer, vendor, or item comes into play when the card is opened, when a sales or purchase document for them is opened, and when a pinned item is entered on a document line. The note travels to the point of work on its own.

Nothing about the existing comment functionality changes. This is not a replacement notes system: there is no new table, no separate editor, and no data to migrate. An existing comment becomes a pinned comment with one tick, and un-ticking it returns everything to normal.

Pinning a comment

1.  Open a customer, vendor, or item, and click Comments action.

2.  Write the comment exactly as you do today.

3.  Tick Pinned.

Three fields are added to the comment sheet:

Field

Purpose

Pinned

Marks the line to be raised as a notification.

Pin Until

Optional last date the note should appear. Leave blank to pin it indefinitely.

Pin Severity

Information for ordinary notes, Warning for notes that must not be missed.

 


Pinned lines are highlighted on the comment sheet itself, so it is obvious at a glance which notes are active, bold for Information, red for Warning.

Where the notification appears

Record

When it appears

Customer card

Every time the card is opened

Vendor card

Every time the card is opened

Item card

Every time the card is opened

Sales quote, order, invoice, credit memo, return order, blanket order

When the document is opened, and when the customer is entered

Purchase quote, order, invoice, credit memo, return order, blanket order

When the document is opened, and when the vendor is entered

Sales and purchase lines

When a pinned item is entered on the line

 


The notification reappears every time, not just the first time. If a user glances away or dismisses it, it will be there again the next time they open the record. A warning that shows once and then goes quiet is worse than no warning, because people assume they are covered.

What the notification says

All pinned comments for one record are combined into a single notification rather than one per line. Each pinned comment line appears as its own note, in comment sheet order, separated by a vertical bar:

Pinned comments (3): Always confirm the PO number before shipping | Delivery bookings need 48 hours notice | Invoices must quote the contract number

Longer messages are shortened to keep the banner readable, and blank lines are ignored. If any of the pinned lines is marked Warning, the whole notification is presented as a warning.

Every notification carries a Show comments action that opens the comment sheet filtered to that record, so the full text is always one click away.

Notes that expire on their own

Pin Until exists so temporary notes do not become permanent clutter. Set it to the date a note stops being relevant, the end of a promotion, the date a dispute is expected to close, the last day of a delivery embargo.

Once that date passes, the note simply stops appearing. Nothing needs to be tidied up, no error is raised, and the comment itself stays on the sheet as a record of what happened. Clearing the date or setting a later one brings it back.

Users stay in control

The notification is registered with Business Central's standard notification framework. Any user who does not want it can search for My Notifications and clear Pinned comment lines, alongside every other notification the system offers.

There is no separate setup page to find or learn, and no administrator involvement required. The notification is switched on by default.

Safe for automated processes

Notifications are shown only to users working in the client. Batch posting, job queue entries, API calls, and web service integrations are unaffected. A pinned comment never interrupts an automated process or causes it to fail.

No configuration is needed after installation. Existing comments are untouched until someone ticks Pinned.

At a glance

 

 

Applies to

Customers, vendors, items

Requires

Business Central 26 or later

Setup

None

New pages

None

Licence

MIT, free to use and modify

Source

https://github.com/pmohanakrishna/pinned-comment-lines

 

Friday, 11 September 2026

You can't see your stale documents, and that's why they're stale

Open the sales order list in Business Central and filter by date. Now do the same for quotes. Now for unposted invoices. Three lists, three filters, no way to see them together, and no way to answer the only question that matters in a Monday pipeline meeting: what is stale, and whose is it?

So the documents pile up. Quotes nobody chased. Orders shipped three weeks ago and never invoiced. Invoices sitting unposted since last month because the person who raised them went on leave. Every partner I've worked with has fielded this request, and the usual answer is a manual Excel pull that someone does once, shows in a meeting, and never does again.

I built Unposted Document Aging to replace that Excel pull with a page.

One list, aged into bands

The extension adds a single read-only list that pulls sales quotes, orders and unposted invoices and optionally purchase documents into one view, with the age of each document and the person who owns it.

The important part isn't the list. It's the bands.

A flat list of 400 stale documents gets opened once. An order created yesterday with nothing shipped is not a problem; an order fully shipped three weeks ago and never invoiced is money sitting on the floor. Those two things should not look the same on screen. So every row lands in one of six bands, sorted so the money is at the top:



Band

Rule

Shipped Not Invoiced

Order completely shipped, or with any line showing a shipped quantity

Quote Expired

Quote whose Quote Valid To Date has passed

Invoice Unposted

Unposted invoice older than the invoice threshold

Order Stale

Order past the order threshold with nothing shipped

Quote Stale

Quote past the quote threshold, not yet expired

Other

Fallback

Shipped Not Invoiced and Quote Expired are highlighted in red. Thresholds default to 30 days for quotes, 14 for orders and 7 for unposted invoices, and are configurable per company.


The argument you're going to have about dates

Here's the design decision that determines whether anyone trusts the page: which date defines "old"?

Document Date is what people mean when they say a quote is old. It is also backdated regularly, which is precisely why some people won't trust a report built on it.

Created uses the record's SystemCreatedAt and tells you when the document genuinely appeared in the system. Harder to argue with, less intuitive.

I made it a setup switch and defaulted to Document Date, because that's the one people will argue about in the meeting and they should be able to flip it and see both.

No stored data

The list is a temporary buffer built on demand by a codeunit, with the page bound to it using SourceTableTemporary. Nothing is stored. There's no job queue keeping a table warm, nothing to go stale, no upgrade code, and the numbers always reflect the live document tables. Hit Refresh and it rebuilds.

A query object can't union across Sales Header and Purchase Header, so the buffer was the only sensible route anyway but the absence of stored state turned out to be the best property of the design.

Performance was the thing I worried about most, since building a buffer from scratch on every page open is exactly how you write a slow app. What keeps it honest:

  • Every threshold is turned into a cutoff date in AL and applied as a SetFilter on the header, so SQL does the filtering. There is no loop over open documents testing ages in code.
  • One pass per document type, so each gets its own cutoff rather than a lowest-common-denominator filter.
  • SetLoadFields on both header loops.
  • Amount Including VAT is a FlowField, so CalcFields runs only on rows that already made it into the buffer never before filtering. There's a Calculate Amounts toggle to turn it off entirely on very large databases.

Amounts are converted to LCY so a mixed-currency list totals meaningfully, with the currency code kept for display. Choosing a document number opens the actual document, whatever type it is, via a stored RecordId.

The part that actually changes behaviour

A page helps the person who opens it. A digest helps everyone else.

The extension can email a daily digest, and the feature worth having is that it's per salesperson. Rows are grouped by salesperson code, each person's address is resolved from their Salesperson/Purchaser record, and each person gets only their own rows. A digest that goes to everyone gets filtered into a folder and ignored. One that says "you have 6 quotes over 30 days old" gets read.

Managers and shared mailboxes can be added as separate recipients, optionally filtered to a subset of salespeople. It sends through the modern email module with its own Email Scenario, so an admin can point it at a dedicated account on the Email Accounts page. One bad address doesn't abort the run, failures are collected and shown on the setup page. There's a button that creates the recurring job queue entry so nobody has to know a codeunit ID.


It's a per-tenant extension targeting BC 26 and later, and it compiles clean under CodeCop and UICop.

Source: https://github.com/pmohanakrishna/Unposted-Document-Aging

If you install it and the bands don't match how your team actually triages work, that's the most useful thing you could tell me.

Thursday, 10 September 2026

Managing Apps in the Business Central Admin Center

A BC environment is really a stack of apps: Base Application, Microsoft first-party apps, ISV apps, and your own per-tenant extensions (PTEs). The Manage Apps page in the admin center is where you keep all of that under control. Here's what it actually lets you do.



Getting there

Environments > select the environment > Apps. Use the dropdown to switch between Global, Per-Tenant Extension, and Dev Extension apps. Click an app name to open App Details for the actions on that specific app.

What you can do, by app type

App type

View

Upload

Install

Update

Uninstall

Global (marketplace)

Yes

No

No

Yes

Yes

Per-Tenant Extension

Yes

Yes

Yes

Yes

Yes

Dev Extension

Yes

No

No

No

Yes

The one to remember: you can't install a new global app from here, only update or uninstall one already on the environment. New global apps come from marketplace.microsoft.com (the site Microsoft used to call AppSource).

Per-tenant extensions

Choose Install Extension from Manage Apps or App Details:

1.      Select the .app file: BC figures out if it's new or an update.

2.      Pick a Deployment schedule (immediately, next update window, or next minor/major update: new PTEs only get the first two).

3.      Pick a Sync Mode. (Add or Force Sync)

4.      Confirm, and track progress on the Operations page.



A few rules to keep in mind:

  • Don't mix this with Extension Management in the client. Scheduled installs from one won't show in the other until they finish.
  • Extension Management is being phased out as an upload surface. Move PTE deployment to the admin center or the Admin Center API now, since Microsoft has said the client-side upload option is expected to go away in 2027 release wave 1.
  • You can queue different versions of the same PTE against different schedules, but not two versions against the same schedule.

Uninstalling an app

Test uninstalls in a sandbox copy of production first. You'll find the Uninstall action on Manage Apps or App Details; apps like Base Application that can't be removed show N/A.



Two things to watch:

  • You can choose to delete the app's data too. If you do, your only way back is a point-in-time restore (28 days), so export the database first if you need a longer safety net.
  • Dependent apps get uninstalled too, and BC will list them for you to confirm.




Quick checklist

  • Review Manage Apps on a schedule, not just when something breaks
  • Use the update window instead of pushing updates mid-day
  • Set Apps Update Cadence on purpose
  • Move PTE deployment off Extension Management now
  • Rehearse uninstalls in sandbox, and export data before any deletion
  • Always grab the Operation ID on a failed update

Wednesday, 9 September 2026

Direct transfers from warehouse-enabled locations in Business Central 2026 wave 2

Direct transfers have always been the shortcut option for moving stock between locations. No in-transit location, no separate ship and receive steps, one posting and you are done. The catch was that the moment your source location had any real warehouse configuration on it, the shortcut stopped feeling like a shortcut.

Update 29.0 (2026 release wave 2) addresses two related things: direct transfers now work properly with outbound warehouse handling at the source location, and the posting method is no longer a single company-wide switch. You can set it per transfer route.

 

Quick refresher on the two posting methods

A direct transfer skips the in-transit location. What happens behind the scenes depends on which posting method is in play, and the difference matters more than the name suggests.

Direct Transfer posts one Posted Direct Transfer document. Business Central posts the full transfer line quantity. No transfer shipment, no transfer receipt. Per the current documentation, this produces two item ledger entries: transfer from the source location and transfer to the destination location.

Shipment and Receipt posts a transfer shipment and a transfer receipt together, so you end up with both a posted transfer shipment and a posted transfer receipt. Qty. to Ship must equal Qty. to Receive. Per the current documentation, this produces four item ledger entries, routed through a blank location rather than a real in-transit location.


What changed in the setup

Previously, one setting on the Inventory Setup page decided how Business Central posted every direct transfer in the company. If you had a mix of simple store-to-store moves and warehouse-controlled moves, you picked one method and lived with it.

Now there are two levels:

  • Default Direct Transfer Posting on the Inventory Setup page sets the company-wide default.
  • Direct Transfer Posting on the Transfer Routes page overrides the default for a specific route.

The transfer order uses the route setting when one exists, and falls back to the Inventory Setup default when it does not. You can also change Direct Transfer Posting on an open transfer order when a one-off needs different treatment.

Outbound warehouse scenarios

This is the part that unlocks the feature for warehouse-enabled sites. How you complete the transfer depends on how the transfer-from location is configured.

Transfer-from location setup

How you complete the transfer

No warehouse requirements

Post from the Transfer Order

Require Pick without Require Shipment

Create and post an Inventory Pick

Require Shipment without Require Pick

Create and post a Warehouse Shipment

Require Pick and Require Shipment

Register the Warehouse Pick, then post the Warehouse Shipment

Bin Mandatory

Enter valid source and destination bins, then post from whichever document the other warehouse settings require

Directed Put-away and Pick at the source

Register the warehouse pick and post the warehouse shipment using the advanced warehouse process

Partial posting is supported when you post from a warehouse shipment under the Shipment and Receipt method. Everywhere else, expect the full line quantity.

Walkthrough

1. Set the company default

Search for Inventory Setup and set Default Direct Transfer Posting. Pick the method that fits the majority of your transfers, because everything without a route-level override inherits this.



2. Override the method on a transfer route

Search for Transfer Routes. For the route you want to treat differently, turn on Direct Transfer and set Direct Transfer Posting.



3. Confirm the source location setup

Open the Location Card for the transfer-from location and check Require Pick, Require Shipment, Bin Mandatory, and Directed Put-away and Pick on the Warehouse FastTab. This combination decides which document you post from later, so note it before you create the order.



4. Create the transfer order

Search for Transfer Orders and create a new one. Fill in Transfer-from Code and Transfer-to Code, then turn on Direct Transfer. Confirm that Direct Transfer Posting picked up the value you expect from the route or the setup default.



5. Add lines and bin codes

Add the item lines. If either location uses bins, fill in Transfer-From Bin Code and Transfer-To Bin Code. A destination that uses bins needs a valid Transfer-To Bin Code or posting will fail.

You can build lines by hand, use Select items for several at once, or pull from Get Bin Content or Get Receipt Lines.



6. Post according to the source setup

No warehouse requirements. Choose Post on the transfer order.

Require Pick only. Choose Create Inventory Put-away/Pick on the released transfer order, open the inventory pick, enter the handled quantities and bins, then post it.

Require Shipment only. Choose Create Warehouse Shipment, open the warehouse shipment, set the quantities, and post. This is the path where partial posting works under Shipment and Receipt.

Require Pick and Require Shipment. Create the warehouse shipment, create the pick from it, register the warehouse pick, then post the warehouse shipment. Registering the pick does not post the transfer, it just makes the quantity available to the shipment.

Directed Put-away and Pick at the source. Same as above, using the standard advanced warehouse flow.

7. Check what got posted

With Direct Transfer, look for the Posted Direct Transfer document. With Shipment and Receipt, look for the posted transfer shipment and the posted transfer receipt.



Then open the item ledger entries for the item and confirm the entry count and the location codes match what you expect.



Limitations to design around

These are stated in the preview documentation and they shape your location setup more than anything else in the feature:

  • Direct transfer posting does not create inbound warehouse receipts or inventory put-aways.
  • The transfer-to location cannot use Directed Put-away and Pick.
  • With Shipment and Receipt, the destination also cannot require warehouse receipt or put-away.
  • A destination that uses bins requires a valid Transfer-To Bin Code.

Outbound warehouse handling at the source is now in scope. 

If the receiving side needs warehouse receipt or directed put-away control, the configuration that supports full warehouse handling on both ends is a transfer order with the Direct Transfer toggle off and an in-transit location.