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.
No comments:
Post a Comment