Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Total Ordered cost on Project #41308

Open
wants to merge 20 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
66bf037
feat: add Total ordered cost on Project
FHenry May 2, 2024
dd41e17
feat: add Total ordered cost on Project
FHenry May 2, 2024
aa7e842
feat: add Total ordered cost on Project
FHenry May 2, 2024
8efd6da
feat: add Total ordered cost on Project
FHenry May 2, 2024
43644fd
feat: add Total ordered cost on Project
FHenry May 2, 2024
3dc10ed
feat: add Total ordered cost on Project
FHenry May 2, 2024
25bdcd7
feat: add Total ordered cost on Project
FHenry May 6, 2024
5b3cb15
Merge branch 'develop' of https://github.com/frappe/erpnext into dev_…
FHenry May 6, 2024
7aebc77
feat: add Total ordered cost on Project
FHenry May 6, 2024
850dd6d
Merge branch 'develop' of https://github.com/frappe/erpnext into dev_…
FHenry May 22, 2024
e110e86
Merge branch 'develop' of https://github.com/frappe/erpnext into dev_…
FHenry Jun 24, 2024
862b98c
Merge branch 'develop' of https://github.com/frappe/erpnext into dev_…
FHenry Jul 11, 2024
0f75085
Merge branch 'develop' of https://github.com/frappe/erpnext into dev_…
FHenry Jul 29, 2024
ed1a3bb
Merge branch 'develop' of https://github.com/frappe/erpnext into dev_…
FHenry Aug 14, 2024
31e823f
Merge branch 'develop' of https://github.com/frappe/erpnext into dev_…
FHenry Sep 3, 2024
b796807
Merge branch 'develop' of https://github.com/frappe/erpnext into dev_…
FHenry Sep 9, 2024
c6652f1
Merge branch 'develop' of https://github.com/frappe/erpnext into dev_…
FHenry Oct 3, 2024
63660a3
Merge branch 'develop' of https://github.com/frappe/erpnext into dev_…
FHenry Oct 29, 2024
46f2ead
Merge branch 'develop' of https://github.com/frappe/erpnext into dev_…
FHenry Nov 16, 2024
45c288f
chore: Rename function name and change return type in project.py purc…
FHenry Nov 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions erpnext/buying/doctype/purchase_order/purchase_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,9 @@ def on_submit(self):

self.update_blanket_order()

if frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction":
self.update_project()

update_linked_doc(self.doctype, self.name, self.inter_company_order_reference)

self.auto_create_subcontracting_order()
Expand All @@ -506,6 +509,9 @@ def on_cancel(self):
self.update_reserved_qty_for_subcontract()
self.check_on_hold_or_closed_status()

if frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction":
self.update_project()

self.db_set("status", "Cancelled")

self.update_prevdoc_status()
Expand Down Expand Up @@ -654,6 +660,21 @@ def update_subcontracting_order_status(self):
if sco:
update_sco_status(sco, "Closed" if self.status == "Closed" else None)

def update_project(self):
projects = frappe._dict()
for d in self.items:
if d.project:
if self.docstatus == 1:
projects[d.project] = projects.get(d.project, 0) + d.base_net_amount
elif self.docstatus == 2:
projects[d.project] = projects.get(d.project, 0) - d.base_net_amount

pj = frappe.qb.DocType("Project")
for proj, value in projects.items():
res = frappe.qb.from_(pj).select(pj.total_ordered_cost).where(pj.name == proj).for_update().run()
current_ordered_cost = res and res[0][0] or 0
frappe.db.set_value("Project", proj, "total_ordered_cost", current_ordered_cost + value)

def set_missing_values(self, for_validate=False):
tds_category = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
if tds_category and not for_validate:
Expand Down
29 changes: 29 additions & 0 deletions erpnext/buying/doctype/purchase_order/test_purchase_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
)
from erpnext.controllers.accounts_controller import InvalidQtyError, update_child_qty_rate
from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
from erpnext.projects.doctype.project.test_project import make_project
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
from erpnext.stock.doctype.material_request.test_material_request import make_material_request
Expand Down Expand Up @@ -1172,6 +1173,33 @@ def test_po_billed_amount_against_return_entry(self):
po.reload()
self.assertEqual(po.per_billed, 100)

def test_total_ordered_cost_for_project(self):
if not frappe.db.exists("Project", {"project_name": "_Test Project for Ordered"}):
project = make_project({"project_name": "_Test Project for Ordered"})
else:
project = frappe.get_doc("Project", "_Test Project for Ordered")

pi = create_purchase_order(qty=1, rate=500, project=project.name)
self.assertEqual(
frappe.db.get_value("Project", project.name, "total_ordered_cost"),
500.0,
)

pi1 = create_purchase_order(qty=10, rate=500, project=project.name)
self.assertEqual(
frappe.db.get_value("Project", project.name, "total_ordered_cost"),
5500.0,
)

pi1.cancel()
self.assertEqual(
frappe.db.get_value("Project", project.name, "total_ordered_cost"),
500.0,
)

pi.cancel()
self.assertEqual(frappe.db.get_value("Project", project.name, "total_ordered_cost"), 0)


def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
Expand Down Expand Up @@ -1275,6 +1303,7 @@ def create_purchase_order(**args):
"against_blanket": args.against_blanket,
"material_request": args.material_request,
"material_request_item": args.material_request_item,
"project": args.project,
},
)

Expand Down
23 changes: 23 additions & 0 deletions erpnext/projects/doctype/project/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ frappe.ui.form.on("Project", {
__("Actions")
);

frm.add_custom_button(
__("Update Total Ordered Cost"),
() => {
frm.events.update_total_ordered_cost(frm);
},
__("Actions")
);

frm.add_custom_button(
__("Update Total Purchase Cost"),
() => {
Expand Down Expand Up @@ -144,6 +152,21 @@ frappe.ui.form.on("Project", {
});
},

update_total_ordered_cost: function (frm) {
frappe.call({
method: "erpnext.projects.doctype.project.project.recalculate_project_total_ordered_cost",
args: { project: frm.doc.name },
freeze: true,
freeze_message: __("Recalculating Ordered Cost against this Project..."),
callback: function (r) {
if (r && !r.exc) {
frappe.msgprint(__("Total Ordered Cost has been updated"));
frm.refresh();
}
},
});
},

set_project_status_button: function (frm) {
frm.add_custom_button(
__("Set Project Status"),
Expand Down
9 changes: 8 additions & 1 deletion erpnext/projects/doctype/project/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"project_details",
"estimated_costing",
"total_costing_amount",
"total_ordered_cost",
"total_purchase_cost",
"company",
"column_break_28",
Expand Down Expand Up @@ -447,14 +448,20 @@
"print_hide": 1,
"reqd": 1,
"set_only_once": 1
},
{
"fieldname": "total_ordered_cost",
"fieldtype": "Currency",
"label": "Total Ordered Cost (via Purchase Order)",
"read_only": 1
}
],
"icon": "fa fa-puzzle-piece",
"idx": 29,
"index_web_pages_for_search": 1,
"links": [],
"max_attachments": 4,
"modified": "2024-04-24 10:56:16.001032",
"modified": "2024-05-02 22:34:24.991034",
"modified_by": "Administrator",
"module": "Projects",
"name": "Project",
Expand Down
40 changes: 36 additions & 4 deletions erpnext/projects/doctype/project/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class Project(Document):
total_billed_amount: DF.Currency
total_consumed_material_cost: DF.Currency
total_costing_amount: DF.Currency
total_ordered_cost: DF.Currency
total_purchase_cost: DF.Currency
total_sales_amount: DF.Currency
users: DF.Table[ProjectUser]
Expand Down Expand Up @@ -295,6 +296,7 @@ def update_costing(self):
self.actual_time = from_time_sheet.time

self.update_purchase_costing()
self.update_ordered_costing()
self.update_sales_amount()
self.update_billed_amount()
self.calculate_gross_margin()
Expand All @@ -311,9 +313,13 @@ def calculate_gross_margin(self):
self.per_gross_margin = (self.gross_margin / flt(self.total_billed_amount)) * 100

def update_purchase_costing(self):
total_purchase_cost = calculate_total_purchase_cost(self.name)
total_purchase_cost = get_total_purchase_cost(self.name)
self.total_purchase_cost = total_purchase_cost and total_purchase_cost[0][0] or 0

def update_ordered_costing(self):
total_ordered_cost = get_total_ordered_cost(self.name)
self.total_ordered_cost = total_ordered_cost and total_ordered_cost[0][0] or 0

def update_sales_amount(self):
total_sales_amount = frappe.db.sql(
"""select sum(base_net_total)
Expand Down Expand Up @@ -729,7 +735,7 @@ def get_users_email(doc):
return [d.email for d in doc.users if frappe.db.get_value("User", d.user, "enabled")]


def calculate_total_purchase_cost(project: str | None = None):
def get_total_purchase_cost(project: str | None = None):
if project:
pitem = qb.DocType("Purchase Invoice Item")
total_purchase_cost = (
Expand All @@ -739,16 +745,42 @@ def calculate_total_purchase_cost(project: str | None = None):
.run(as_list=True)
)
return total_purchase_cost
return None
return 0.0


@frappe.whitelist()
def recalculate_project_total_purchase_cost(project: str | None = None):
if project:
total_purchase_cost = calculate_total_purchase_cost(project)
total_purchase_cost = get_total_purchase_cost(project)
frappe.db.set_value(
"Project",
project,
"total_purchase_cost",
(total_purchase_cost and total_purchase_cost[0][0] or 0),
)


def get_total_ordered_cost(project: str | None = None):
if project:
pitem = qb.DocType("Purchase Order Item")
frappe.qb.DocType("Purchase Order Item")
total_ordered_cost = (
qb.from_(pitem)
.select(Sum(pitem.base_net_amount))
.where((pitem.project == project) & (pitem.docstatus == 1))
.run(as_list=True)
)
return total_ordered_cost
FHenry marked this conversation as resolved.
Show resolved Hide resolved
return 0.0


@frappe.whitelist()
def recalculate_project_total_ordered_cost(project: str | None = None):
if project:
total_ordered_cost = get_total_ordered_cost(project)
frappe.db.set_value(
"Project",
project,
"total_ordered_cost",
(total_ordered_cost and total_ordered_cost[0][0] or 0),
)
Loading