Some countries’ accounting principles require invoice naming to be gapless. In the otherwise lovely ERPnext however, sales invoices are named on creation, hence leaving a gap when they are cancelled.
Ideas for possible solutions:
a) forbid cancellation of invoices per setting
b) name invoices on submission
c) a separate naming series for drafts
or
d) forbid accountants to ever hit the cancel button
A seems easiest but would cause inconsisten behaviour across DocTypes. There would not be a difference between saving and submitting anymore.
B would be elegant but make it hard to handle documents internally, again causing inconsistencies
C appears a bit far fetched, since it would only concern drafts of invoices
D they will still do it eventually
I am not sure whether the cancellation will lead to a gap. At least in our case (I have just checked), the cancelled doc keeps its invoice number and the next one created has a new number. I indeed told our accountants that they cannot delete cancelled invoices since this will indeed lead to gaps. Deleted drafts (at least in our case) will not lead to gaps.
Deleting drafts will lead to the number being reset whereas submitted ones create a gap (post-cancellation, especially if you’ve created more invoices after the deleted one).
Review your permissions and as best practice remove the delete permission and restrict only to cancellation of the invoice for audit trail.
Thanks RSA and Kenneth! I realized only after posting that cancelled invoices still are in the general ledger, only they don’t seem to go into the trial balance. So technically no gap there.
It appears that in some countries a cancelled invoice needs a second, later named or numbered document cancelling it, whereas it can be “flagged” as cancelled in others.
However, removing the delete and restriciting the cancel permission seems like the best way to go here.
@rmeyer I’m assuming it would be a minor script or something to check if it were the topmost invoice correct? With the standard setup either we allow or remove the delete permission.
def on_trash(doc, event):
if frappe.db.exists(
doc.doctype,
{
"creation": (">", doc.creation),
"name": ("!=", doc.name),
},
):
frappe.throw("Only the most recent transaction can be deleted.")
Might run into issues when working with multiple timezones (if timestamps don’t match the order of creation for some reason).
I have used a custom field called Submitted Naming Series. Normal Naming Series work by default for Draft. and on_submit hook I renamed the current document with the submitted naming series.
Some remarks on design.
There should be a difference between system timestamps and i18n displayed timestamps. Server side system timestamps are usually on the same timezone. If you generate them in the browser yourself, you are trapped.
The issue arises when matching with user input or manual logging entries created from custom userland code. Make sure you check and compare the proper system clock based values. If you are running a distributed cluster with multiple machines in different timezones, and this ends up as an issue, this may be a more general problem within the Frappe framework.
Interesting Background: The work of Lesley Lamport. Lamport was the winner of the 2013 Turing Award[3] for imposing clear, well-defined coherence on the seemingly chaotic behavior of distributed computing systems, in which several autonomous computers communicate with each other by passing messages.