Skip to content

Commit

Permalink
Merge pull request #36 from navariltd/deploy
Browse files Browse the repository at this point in the history
Deploy
  • Loading branch information
muruthigitau authored Jan 8, 2025
2 parents 1bd9f51 + bf0f4df commit 0dc7414
Show file tree
Hide file tree
Showing 19 changed files with 178 additions and 106 deletions.
136 changes: 97 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,65 +1,124 @@
# <center>Utility Billing</center>
# **Utility Billing**

## <center> Utility Billing System Integration for ERPNext</center>
## **Utility Billing System Integration for ERPNext**

### Overview
### **Overview**

The Utility Billing application provides a robust solution for managing utility billing processes within the [ERPNext](https://erpnext.com) framework. This integration streamlines the billing lifecycle for utilities such as water, electricity, and sanitation, ensuring accurate billing.
The Utility Billing application provides a robust solution for managing utility billing processes within the [ERPNext](https://erpnext.com) framework. This integration streamlines the billing lifecycle for utilities such as water, electricity, and sanitation, ensuring accurate billing.
![Utility Service Request Process Flow Screenshot](./utility_billing/docs/images/service-flow.png)

### Summary of Main Features:

1. **Service Request Management:**

- Streamline handling of customer service requests related to utility issues, ensuring prompt responses and efficient resolutions.
---

2. **Meter Reading:**
### **Summary of Main Features**

- Facilitate accurate meter reading processes for the collection and recording of consumption data, supporting billing accuracy and efficiency.
1. **Service Request Management**
- Streamline handling of customer service requests related to utility issues, ensuring prompt responses and efficient resolutions.

3. **Flexible Tariff Structures:**
2. **Meter Reading**
- Facilitate accurate meter reading processes for the collection and recording of consumption data, supporting billing accuracy and efficiency.

- Define and manage various tariff plans, allowing customization for different customer types and consumption levels to optimize billing accuracy.
3. **Flexible Tariff Structures**
- Define and manage various tariff plans, allowing customization for different customer types and consumption levels to optimize billing accuracy.

4. **Bulk Billing:**
- Enable bulk billing capabilities for processing multiple invoices at once, improving efficiency and reducing manual workload in the billing cycle.
4. **Bulk Billing**
- Enable bulk billing capabilities for processing multiple invoices at once, improving efficiency and reducing manual workload in the billing cycle.

## Key Features
---

### <a id="setup"></a> 1. Setup
## **Key Features**

I. **Utility Settings**
Go to the **Utility Settings** doctype to configure your utility provider details and settings.
### 1. **Setup**

![Utility Settings Screenshot](./utility_billing/docs/images/utility-settings.png)
#### **1.1 Utility Billing Settings**
Go to the **Utility Settings** doctype to configure your utility provider details and settings.

II. **Customer Groups**:
Use **Customer Groups** to categorize customers, enabling specific tariff setups for each group.
![Utility Settings Screenshot](./utility_billing/docs/images/utility-settings.png)

III. **Price List and Item Prices**
This section allows the configuration of operational preferences to streamline utility billing. Below are the key details and their functionalities:
- **Sales Order Creation State**
- Defines the default state for new sales orders created through the utility billing module. The state can be set to "Draft" or "Submitted." Setting this to "Draft" allows for reviewing and verifying sales orders before final submission. Choosing "Submitted" directly generates submitted sales orders without additional review steps.
- **Sales Invoice Creation State**
- Determines whether sales invoices generated are set to "Draft" or "Submitted" by default. Select "Draft" for an additional review process or "Submitted" for immediate invoicing.
- **Stock Entry Creation State**
- Specifies the default state for stock entries created during utility billing-related transactions. Configurable to "Draft" or "Submitted."
- **Create Single Invoice for Multiple Sales Orders per Customer**
- Consolidates multiple sales orders for a single customer into one invoice.

- **Define Price List**: Create a price list associated with the relevant **Customer Group**, specifying the pricing strategy for different utility services.
#### **1.2 Customer Groups**
- Categorize customers into groups like residential, commercial, or industrial. This categorization helps in applying tailored tariffs and services to different customer segments. To learn more about Customer Groups, visit [Customer Groups](https://docs.erpnext.com/docs/user/manual/en/customer-group).

![Item Prices Screenshot](./utility_billing/docs/images/item-prices.png)
#### **1.3 Price List and Item Prices**
- **Price List**: Create price lists specific to each customer group, detailing the rates for services like water usage, electricity consumption, or sewer charges. To learn more about Price Lists, visit [Price Lists](https://docs.erpnext.com/docs/user/manual/en/price-lists).
- **Item Prices**: Use the "Item Prices" doctype to input detailed pricing, including block rates or fixed charges, for accurate billing. [Learn more about Item Prices here](https://docs.erpnext.com/docs/user/manual/en/item-price).

- **Item Prices**: Within the **Item Prices** doctype, configure the tariffs captured in the tariffs table. This ensures that each **Customer Group** has tailored pricing based on consumption patterns and service types.
![Item Prices Screenshot](./utility_billing/docs/images/item-prices.png)

### <a id="billing_management"></a> 2. Billing Management
---

#### Meter Reading
### 2. **Billing Management**

#### **2.1 Meter Reading**
![Meter Reading Screenshot](./utility_billing/docs/images/meter-reading.png)

Meter Numbers are maintained as serial Numbers in ERPNext. To assign Meter Numbers to each customer, create a Warranty Claim. To learn more about Warranty Claim, [visit Warranty Claim here](https://docs.erpnext.com/docs/user/manual/en/warranty-claim).

The Meter Reading doctype is used to log periodic meter readings for customers. To create a new Meter Reading:
1. Go to Meter Reading List.
2. Click "Add Meter Reading."
3. Select the Customer.
4. In the Items table, select the Utility Items and set the Current Readings.
- The previous reading will be pulled from the Customer’s previous Sales Invoice, and the Consumption is calculated using the formula:
**Consumption = (Current Reading - Previous Reading)**
- The Meter Number is fetched from the Customer’s associated Warranty Claim.
5. Save and Submit.

On saving, the consumption rates are auto-calculated based on the associated Item Price.
On submitting, the system automatically creates a sales order for that customer, either in "Draft" or "Submitted" status, depending on the Utility Billing Settings.

#### **2.2 Utility Service Request**
![Utility Service Request Screenshot](./utility_billing/docs/images/utility-service-request.png)

Manage initial customer service requests efficiently. To create a new Utility Service Request:
1. Go to Utility Service Request List.
2. Click "Add Utility Service Request."
3. Fill in the Customer (Requester) details.
4. Select the Utility Request Type, e.g., New Connection.
5. Tag the appropriate Cost Centers and Projects, if applicable.
6. In the Items table, select the Utility Services requested.
7. New Meter Numbers can also be assigned to the Customer at this stage by selecting a Metered Item and selecting the Meter Number.
8. Save.
9. Click on the "Create" button and select "Site Survey."
- A related issue to conduct a site survey is initialized here. The issue captures the findings of the site visit, ensuring all necessary conditions are met for the utility service. It must be in “Closed” or “Resolved” status to proceed.
10. Click on the "Create" button and select "BOM."
- Generate a BOM for any required materials or equipment. Ensure all components needed for the request (e.g., meters, pipes) are listed. Submit the BOM to finalize resource allocation.
11. Submit and create a Sales Order.
- On creating a Sales Order from the Utility Service Request, the Customer will be automatically created with their associated details, including Meter Number assignment, if any exists.

#### **2.3 Mass Billing**
![Mass Billing Screenshot](./utility_billing/docs/images/mass-billing.png)

Streamlines the billing process by generating a single invoice from multiple customers’ sales orders.
1. Go to Sales Order List.
2. Select the specific Sales Orders.
3. Click on the "Menu," then click on "Sales Invoice."
4. A background job will run to generate the invoices.

![Meter Reading Screenshot](./utility_billing/docs/images/meter-reading.png)
---

Utilize the **Meter Reading** doctype to record periodic meter readings for each customer.
### 3. **NOTE**

#### Mass Billing
#### **3.1 Item Configuration**
- Ensure that the "Is Utility Item" checkbox is ticked to clearly identify items that pertain to utility services.
![Is Utility Screenshot](./utility_billing/docs/images/is_utility_item.png)

![Mass Billing Screenshot](./utility_billing/docs/images/mass-billing.png)
#### **3.2 Meter Numbers**
- Maintain meter numbers as serial numbers in ERPNext. Assign them via Warranty Claims for seamless integration.
![Warranty Claim Screenshot](./utility_billing/docs/images/meter-number.png)

Process bulk invoices efficiently using **Sales Orders**. Generate a single invoice for each unique customer, improving efficiency and reducing manual workload in the billing cycle.
---

## <a id="doctypes"></a> Doctypes
## **Doctypes**

The Utility Billing application includes several key Doctypes essential for managing the utility billing process:

### Core Doctypes

Expand Down Expand Up @@ -121,10 +180,9 @@ The Utility Billing application includes several key Doctypes essential for mana
![Item Screenshot](./utility_billing/docs/images/is_utility_item.png)

### **3. Item Price**
- Tariff table - This table allows defining blocks with upper and lower limits and corresponding rates. It helps distribute rates based on consumption levels, ensuring accurate billing according to predefined tariff structures.

- Is Fixed Meter Charge - When checked, this field indicates that the item has a fixed rate, and the rate applies as a static charge regardless of the meter readings.

![Item Screenshot](./utility_billing/docs/images/is_fixed_meter_charge.png)
![Item Screenshot](./utility_billing/docs/images/item-prices.png)

### Manual/Self-Hosted Installation

Expand All @@ -147,7 +205,7 @@ Replace `{branch-name}` with the desired branch name from the repository. Ensure

```sh

$ bench --site {sitename} install-app utility-billing
$ bench --site {sitename} install-app utility_billing

```

Expand Down
Binary file modified utility_billing/docs/images/is_utility_item.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified utility_billing/docs/images/item-prices.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified utility_billing/docs/images/mass-billing.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added utility_billing/docs/images/meter-number.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified utility_billing/docs/images/meter-reading.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added utility_billing/docs/images/service-flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified utility_billing/docs/images/utility-service-request.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified utility_billing/docs/images/utility-settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 6 additions & 8 deletions utility_billing/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,18 +173,16 @@
# Hook on document methods and events

doc_events = {
# "*": {
# "on_update": "method",
# "on_cancel": "method",
# "on_trash": "method"
# }

"Sales Invoice": {
# "*": {
# "on_update": "method",
# "on_cancel": "method",
# "on_trash": "method"
# }
"Sales Invoice": {
"before_validate": [
"utility_billing.utility_billing.overrides.server.sales_invoice.before_validate"
],
},

}

# Scheduled Tasks
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# Copyright (c) 2024, Navari and contributors
# For license information, please see license.txt
import frappe
from erpnext.controllers.accounts_controller import AccountsController
from frappe.model.document import Document
from frappe.query_builder import DocType
from frappe.utils import nowdate
from erpnext.controllers.accounts_controller import AccountsController
from frappe.query_builder import DocType
from pypika import Order

from ...utils.create_meter_reading_rates import create_meter_reading_rates
Expand All @@ -20,9 +19,7 @@ def validate(self):
def on_submit(self):
settings = frappe.get_single("Utility Billing Settings")
if not self.rates or len(self.rates) == 0:
frappe.throw(
frappe._("Cannot submit Meter Reading. No rates available.")
)
frappe.throw(frappe._("Cannot submit Meter Reading. No rates available."))
existing_sales_order = frappe.db.exists(
{
"doctype": "Sales Order Meter Reading",
Expand All @@ -41,15 +38,13 @@ def validate_item_readings(self, item):
"""Validate readings for each item."""
if item.current_reading is None:
frappe.throw(
frappe._(
f"Current reading is required for item: {item.item_code}"
)
frappe._(f"Current reading is required for item: {item.item_code}")
)

previous_reading = get_previous_invoice_reading(
item_code=item.item_code,
customer=self.customer,
meter_number=item.meter_number
meter_number=item.meter_number,
)
item.previous_reading = previous_reading

Expand All @@ -75,18 +70,21 @@ def create_sales_order(meter_reading):
"selling_price_list": meter_reading.price_list,
}
)
utility_property = frappe.get_value("Customer", meter_reading.customer, "utility_property")
utility_property = frappe.get_value(
"Customer", meter_reading.customer, "utility_property"
)
if utility_property:
sales_order.utility_property = utility_property

for rate in meter_reading.rates:
rate_dict = rate.as_dict()
rate_dict["delivery_date"] = nowdate()
sales_order.append("items", rate_dict)


for i in meter_reading.items:
prev_reading = get_previous_invoice_reading(i.item_code, meter_reading.customer, i.meter_number)
prev_reading = get_previous_invoice_reading(
i.item_code, meter_reading.customer, i.meter_number
)
sales_order.append(
"meter_readings",
{
Expand All @@ -100,8 +98,8 @@ def create_sales_order(meter_reading):
"consumption": i.consumption,
},
)
sales_order.insert()

sales_order.insert()
AccountsController.append_taxes_from_item_tax_template(sales_order)
sales_order.save()

Expand All @@ -111,24 +109,25 @@ def create_sales_order(meter_reading):
@frappe.whitelist()
def get_previous_invoice_reading(item_code, customer, meter_number=None):
"""Fetch the latest reading for the specified customer, item, and optional meter number."""

SalesInvoiceMeterReading = DocType("Sales Invoice Meter Reading")
SalesInvoice = DocType("Sales Invoice")

query = (
frappe.qb.from_(SalesInvoiceMeterReading)
.join(SalesInvoice).on(SalesInvoice.name == SalesInvoiceMeterReading.parent)
.join(SalesInvoice)
.on(SalesInvoice.name == SalesInvoiceMeterReading.parent)
.select(SalesInvoiceMeterReading.current_reading)
.where(SalesInvoice.customer == customer)
.where(SalesInvoiceMeterReading.item_code == item_code)
.where(SalesInvoice.docstatus == 1)
)

if meter_number:
query = query.where(SalesInvoiceMeterReading.meter_number == meter_number)
else:
query = query.where(SalesInvoiceMeterReading.meter_number.isnull())

query = query.orderby(SalesInvoiceMeterReading.creation, order=Order.desc)
result = query.limit(1).run()

Expand Down Expand Up @@ -167,4 +166,4 @@ def get_serial_numbers_from_warranty_claims(customer):
if claim.get("serial_no"):
serial_list.extend(claim["serial_no"].split("\n"))

return list(set(serial_list))
return list(set(serial_list))
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@


class TestUtilityCategory(FrappeTestCase):
pass
pass
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@


class UtilityCategory(Document):
pass
pass
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@


class TestUtilityProperty(FrappeTestCase):
pass
pass
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Copyright (c) 2024, Navari and contributors
# For license information, please see license.txt

from frappe.contacts.address_and_contact import load_address_and_contact
# import frappe
from frappe.utils.nestedset import NestedSet
from frappe.contacts.address_and_contact import load_address_and_contact


class UtilityProperty(NestedSet):
def onload(self):
load_address_and_contact(self)
load_address_and_contact(self)
Loading

0 comments on commit 0dc7414

Please sign in to comment.