Help on Sales Order for Services Items

Hello Guys,
Context:
We have a software development company and do not have stock. We have start using ERPNext.
All our items where created with the “Maintain Stock” unchecked.
I have created a Sales Order (Sales type) for a services we made to one client. Also create an invoice.

Problem:
The Sale Order have an “Overdue” status, so i read that i have to create a Delivery Note. But the problem is that the Delivery Note ask me to move stock (wich i don’t have).

My Atempts to solve it:
First i saw this topic

But it didn’t help, because the first one was what i made, and the second one has the same problem.
Second i saw this article:
https://docs.erpnext.com/docs/v13/user/manual/en/selling/articles/erpnext-for-services-organization
So, i create a new Sales Order but Maintenance type, and create a Maintenance Visit, but the Sales Order status continues on “Overdue”.

Any idea to solve this?
Thanks

Hi msalim79, thanks for asking,
Because I’m using the ERPNext sales process and it says that after a Quotation you have to create a Sale Order.
https://docs.erpnext.com/docs/v13/user/manual/en/selling/quotation

Hello msalim79, please don’t take this the wrong way, but I find it hard to believe that ERPNext does not support Sales Orders for the service industry. There has to be a way to use them.

@msalim79 that is not the solution.

The issue you mention is something that has been discussed several times in ERPNext.

The solution is that if you do not have the stock checkbox, it does not force you to create a delivery note, so it does not apply the Overdue status.

In addition, it also applies to an intangible, the theory that when you confirm the quotation, it is like an order, this is a Sales Order in the ERP, leaving to the accounting team the invoicing and payment in the order they want.

@marduh In our case that we are also a software company, what we do is to close the sales order (Close button) once the invoice is done and you do not need to make any delivery of goods

ERPNext does support service industry :smile:
For our service items its just quotes and sales invoices.
You will need to see what works for your software company.

Simply in OUR experience, the example you mention does not work.

With the OV we can manage better if the payment comes before the invoice, leave the quotation in the right state (converted), even if the invoice takes 15 days to be done, it allows us to associate it to the project, fundamental in the software industry, among other things.

Can you be more specific about what’s happening here? Delivery Notes work for non-stock items too, so if you’re being asked to move stock something’s off.

There are a few different ways to bypass delivery notes, but it looks like there’s a problem with how your sales orders are getting set up that is probably worth fixing first.

2 Likes

The problem is that it asks me to associate a warehouse and an account. But we don’t have warehouses, and we don’t have inventory accounting accounts.

For now the temporary solution was to create a warehouse and an account.
From what I see, making a delivery note does not generate an accounting entry.

I operate mostly as a service company but also include tangible goods. I don’t maintain inventory (considered mostly cash basis business vs accrual).

I have the desire to use sales orders when I know the customer won’t pay within the calendar year (tax period when the work was performed).

This is not tax advice nor a guarantee of a bug-free process :wink:

Use at your own risk:

With the help of ChatGPT I create a server script and client script that adds a button to submitted sales orders that will allow me to set the delivery date and mark as 100% delivered. This should not have negative impact in my system as ALL of my items have “maintain stock = no”. I also default sales orders and sales invoices “update stock = no” (again, I don’t maintain any inventory).

Perhaps others may be in a similar situation and find these scripts useful. I was tired of going into the databse backend and manually changing these values.

Client script creates the button and calls the server script:

DocType = Sales Order

Apply to = Form

frappe.ui.form.on("Sales Order", {
  refresh(frm) {
    if (frm.doc.docstatus === 1 && frm.doc.per_delivered !== 100) {
      frm.add_custom_button(__("Set Delivery"), function() {
        frappe.prompt(
          [
            {
              fieldname: "delivery_date",
              label: "Delivery Date",
              fieldtype: "Date",
              reqd: 1,
              default: frm.doc.delivery_date || frappe.datetime.now_date()
            }
          ],
          (values) => {
            frappe.call({
              method: "set_as_delivered_100",
              args: {
                sales_order_name: frm.doc.name,
                delivery_date: values.delivery_date
              },
              freeze: true,
              freeze_message: __("Updating delivery status"),
              callback(r) {
                if (r.message && !r.message.error) {
                  frappe.msgprint(__("Sales Order marked as 100% Delivered on {0}", [values.delivery_date]));
                  frm.reload_doc();
                } else if (r.message && r.message.error) {
                  frappe.msgprint(__("Error: {0}", [r.message.error]));
                }
              }
            });
          },
          __("Set Delivery Date"),
          __("Mark Delivered")
        );
      });
    }
  }
});

Server script allows modifying the submitted Sales Order.

Script type = API

API Method = set_as_delivered_100

try:
    sales_order = frappe.get_doc('Sales Order', frappe.form_dict.sales_order_name)

    if not sales_order:
        frappe.response["message"] = {"error": "Sales Order not found"}

    # Set delivery date from dialog
    frappe.db.set_value("Sales Order", sales_order.name, "delivery_date", frappe.form_dict.delivery_date)

    # Set delivered_qty = ordered qty for each item
    for item in sales_order.items:
        if item.qty:
            frappe.db.set_value("Sales Order Item", item.name, "delivered_qty", item.qty)

    # Mark as 100% delivered
    frappe.db.set_value("Sales Order", sales_order.name, "per_delivered", 100)
    frappe.db.set_value("Sales Order", sales_order.name, "delivery_status", "Delivered")

    frappe.response["message"] = {"status": "ok"}

except Exception as e:
    frappe.response["message"] = {"error": str(e)}

EDIT: This does not update tabSales Order Item table with the delivery date for the items, not sure I care :slight_smile:

If you need to simulate delivery status, your script is a good approach.

There’s also a field called skip_delivery_note in Sales Orders. It’s hidden by default, but if you check it I believe it should bypass the delivery note check in the list view to mark sales orders as complete as soon as the sales invoice/payment is issued.

I can’t confirm that’s true. I looks like I’ve already customized Sales Orders to set the default value of skip_delivery_note = 1, yet I still need a mechanism to mark them as delivered.

If you need the delivery_status or per_delivered values to be set to complete for any reason, you’re right that skip_delivery_note won’t do that.

The OP was asking about the status field showing Overdue when it should show Complete, however. For that, it should work (or at least it does for me).