Compare commits

...

43 Commits

Author SHA1 Message Date
Frappe PR Bot
7a2b07a2a3 chore(release): Bumped to Version 14.78.1
## [14.78.1](https://github.com/frappe/erpnext/compare/v14.78.0...v14.78.1) (2024-12-05)

### Bug Fixes

* required by date in the reorder material request (backport [#44497](https://github.com/frappe/erpnext/issues/44497)) ([#44508](https://github.com/frappe/erpnext/issues/44508)) ([e0b8c27](e0b8c2788b))
2024-12-05 08:18:33 +00:00
rohitwaghchaure
86ff1545ce Merge pull request #44535 from frappe/mergify/bp/version-14/pr-44508
fix: required by date in the reorder material request (backport #44497) (backport #44508)
2024-12-05 13:47:02 +05:30
mergify[bot]
e0b8c2788b fix: required by date in the reorder material request (backport #44497) (#44508)
* fix: required by date in the reorder material request (#44497)

(cherry picked from commit 4001166ecc)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/test_stock_entry.py

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
(cherry picked from commit 15e3663633)
2024-12-05 07:24:59 +00:00
Frappe PR Bot
c1a1346e2a chore(release): Bumped to Version 14.78.0
# [14.78.0](https://github.com/frappe/erpnext/compare/v14.77.3...v14.78.0) (2024-12-04)

### Bug Fixes

* Add filter for `outstanding_amount` to fetch open PRs ([7e847f2](7e847f27dd))
* Add translation for showing mandatory fields in error msg ([de5b253](de5b253215))
* added fieldname to avoid fieldname to translate ([d7caa8d](d7caa8d51b))
* Added translation for `Account` column ([2061c7c](2061c7ca46))
* adjusted incoming rate for zero rated item in purchase receipt ([d7583e3](d7583e3993))
* correct buying amount for product bundel ([91f6393](91f6393104))
* Dashboard for `Payment Request` ([c911a70](c911a70a84))
* Data Should be Computed in Backend to Maintain Consistent Behaviour ([#44195](https://github.com/frappe/erpnext/issues/44195)) ([8529eb4](8529eb4573))
* handle multi currency in common party journal entry ([bed6dab](bed6dabecb))
* incorrect Gross Margin on project ([#44461](https://github.com/frappe/erpnext/issues/44461)) ([5533592](5533592b2b))
* remove queries ([7d724d7](7d724d7647))
* show "Send SMS" only when enabled (backport [#43941](https://github.com/frappe/erpnext/issues/43941)) ([#43969](https://github.com/frappe/erpnext/issues/43969)) ([9ae04df](9ae04dfed3))
* source warehouse not set in required items of WO (backport [#44426](https://github.com/frappe/erpnext/issues/44426)) ([#44433](https://github.com/frappe/erpnext/issues/44433)) ([cbce4e7](cbce4e72d9))
* Translate `Party Account` column label ([c4103d2](c4103d26be))
* typeerror on transaction.js ([147eb04](147eb047aa))

### Features

* add Company Contact Person in selling transactions (backport [#44362](https://github.com/frappe/erpnext/issues/44362)) ([#44397](https://github.com/frappe/erpnext/issues/44397)) ([78ab44c](78ab44ce1a))

### Performance Improvements

* cache product bundle items at document level ([#44440](https://github.com/frappe/erpnext/issues/44440)) ([df0dac5](df0dac5610))
* reduce queries during transaction save ([f884cf8](f884cf8a5e))

### Reverts

* remove default `Payment Request` indicator color ([9baaaca](9baaaca924))
2024-12-04 04:36:03 +00:00
ruthra kumar
1e02d06424 Merge pull request #44482 from frappe/version-14-hotfix
chore: release v14
2024-12-04 10:04:44 +05:30
ruthra kumar
2812eae589 Merge pull request #44484 from frappe/mergify/bp/version-14-hotfix/pr-44467
fix: Multiple Fixes in Gross Profit Report (backport #44467)
2024-12-03 19:15:34 +05:30
ruthra kumar
a94e3cd433 chore: fix typo
(cherry picked from commit fc0122ce76)
2024-12-03 12:00:36 +00:00
ljain112
91f6393104 fix: correct buying amount for product bundel
(cherry picked from commit 4e6a5893e7)
2024-12-03 12:00:36 +00:00
ljain112
7d724d7647 fix: remove queries
(cherry picked from commit a86b223aed)
2024-12-03 12:00:35 +00:00
ruthra kumar
e4b811be23 Merge pull request #44469 from frappe/mergify/bp/version-14-hotfix/pr-44461
fix: incorrect Gross Margin on project (backport #44461)
2024-12-03 15:32:04 +05:30
rohitwaghchaure
ccd7992e99 chore: fix conflicts 2024-12-03 15:08:38 +05:30
rohitwaghchaure
86ff3e0c10 chore: fix conflicts 2024-12-03 15:06:16 +05:30
ruthra kumar
96fe3f4a2c Merge pull request #44479 from frappe/mergify/bp/version-14-hotfix/pr-44415
fix: adjusted incoming rate for zero rated item in purchase receipt (backport #44415)
2024-12-03 14:48:24 +05:30
ljain112
d7583e3993 fix: adjusted incoming rate for zero rated item in purchase receipt
(cherry picked from commit 3182c6981c)
2024-12-03 08:56:06 +00:00
rohitwaghchaure
5533592b2b fix: incorrect Gross Margin on project (#44461)
(cherry picked from commit 7de9c14a2c)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.py
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2024-12-02 10:10:31 +00:00
ruthra kumar
ba19e06e64 Merge pull request #44464 from frappe/mergify/bp/version-14-hotfix/pr-44412
fix: handle multi currency in common party journal entry (backport #44412)
2024-12-02 14:03:21 +05:30
ruthra kumar
1988c23539 chore: resolve conflict 2024-12-02 13:42:27 +05:30
ruthra kumar
70cac3186b Merge pull request #44462 from frappe/mergify/bp/version-14-hotfix/pr-44437
fix: Added translation for `Account` column (backport #44437)
2024-12-02 13:39:59 +05:30
ljain112
bed6dabecb fix: handle multi currency in common party journal entry
(cherry picked from commit e371f68d66)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2024-12-02 07:10:05 +00:00
Abdeali Chharchhoda
c4103d26be fix: Translate Party Account column label
(cherry picked from commit a4f8315602)
2024-12-02 06:53:37 +00:00
Abdeali Chharchhoda
2061c7ca46 fix: Added translation for Account column
(cherry picked from commit de6cbd382f)
2024-12-02 06:53:37 +00:00
Sagar Vora
84bd0c8c50 Merge pull request #44446 from frappe/mergify/bp/version-14-hotfix/pr-44443
perf: reduce queries during transaction save (backport #44443)
2024-11-30 00:47:53 +05:30
Sagar Vora
f884cf8a5e perf: reduce queries during transaction save
(cherry picked from commit b6b8a06fda)
2024-11-29 19:17:26 +00:00
Sagar Vora
54782d41be Merge pull request #44444 from frappe/mergify/bp/version-14-hotfix/pr-44439
fix: added fieldname to avoid fieldname to translate (backport #44439)
2024-11-30 00:29:47 +05:30
Abdeali Chharchhoda
d7caa8d51b fix: added fieldname to avoid fieldname to translate
(cherry picked from commit b80022133c)
2024-11-29 18:56:31 +00:00
Sagar Vora
9e8150554f Merge pull request #44441 from frappe/mergify/bp/version-14-hotfix/pr-44440
perf: cache product bundle items at document level (backport #44440)
2024-11-29 23:04:01 +05:30
Sagar Vora
df0dac5610 perf: cache product bundle items at document level (#44440)
(cherry picked from commit 6de7320ef4)
2024-11-29 17:22:51 +00:00
mergify[bot]
cbce4e72d9 fix: source warehouse not set in required items of WO (backport #44426) (#44433)
* fix: source warehouse not set in required items of WO (#44426)

fix: source warehouse not set in required items of WO on data import
(cherry picked from commit 4050ea07eb)

# Conflicts:
#	erpnext/manufacturing/doctype/work_order/work_order.py

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-11-29 18:13:25 +05:30
ruthra kumar
2c2a204a06 Merge pull request #44390 from frappe/mergify/bp/version-14-hotfix/pr-44386
fix: Add translation for showing mandatory fields in error msg (backport #44386)
2024-11-29 15:38:52 +05:30
ruthra kumar
da5b57e4af Merge pull request #44424 from frappe/mergify/bp/version-14-hotfix/pr-44302
fix: Minor Updates in `Payment Request` and `Payment Entry`  (backport #44302)
2024-11-29 15:38:11 +05:30
Abdeali Chharchhoda
de5b253215 fix: Add translation for showing mandatory fields in error msg
(cherry picked from commit f42ec6a124)
2024-11-29 14:57:21 +05:30
ruthra kumar
a8ef90b40c chore: resolve conflict 2024-11-29 14:51:46 +05:30
Abdeali Chharchhoda
7e847f27dd fix: Add filter for outstanding_amount to fetch open PRs
(cherry picked from commit 214dfab269)
2024-11-29 09:11:07 +00:00
Abdeali Chharchhoda
eb809847f1 refactor: Move PR link filters to client side
(cherry picked from commit 2db2c8bce1)
2024-11-29 09:11:06 +00:00
Abdeali Chharchhoda
9baaaca924 revert: remove default Payment Request indicator color
(cherry picked from commit 37ceb09955)

# Conflicts:
#	erpnext/accounts/doctype/payment_request/payment_request_list.js
2024-11-29 09:11:06 +00:00
Abdeali Chharchhoda
c911a70a84 fix: Dashboard for Payment Request
(cherry picked from commit 91955e27c3)
2024-11-29 09:11:06 +00:00
Abdeali Chharchhoda
e2728db5c2 refactor: Used object to get payment request status indicator
(cherry picked from commit e1c4d6e1e6)

# Conflicts:
#	erpnext/accounts/doctype/payment_request/payment_request_list.js
2024-11-29 09:11:06 +00:00
Smit Vora
f6f1052a80 Merge pull request #44409 from frappe/mergify/bp/version-14-hotfix/pr-44195
fix: Data Should be Computed in Backend to Maintain Consistent Behaviour (backport #44195)
2024-11-28 16:27:16 +05:30
Ninad Parikh
8529eb4573 fix: Data Should be Computed in Backend to Maintain Consistent Behaviour (#44195)
(cherry picked from commit 69bd90b038)
2024-11-28 10:31:28 +00:00
ruthra kumar
a73fedbaca Merge pull request #44406 from frappe/mergify/bp/version-14-hotfix/pr-44405
fix: typeerror on transaction.js (backport #44405)
2024-11-28 15:13:27 +05:30
ruthra kumar
147eb047aa fix: typeerror on transaction.js
(cherry picked from commit 46ce8780f2)
2024-11-28 15:02:35 +05:30
mergify[bot]
9ae04dfed3 fix: show "Send SMS" only when enabled (backport #43941) (#43969)
fix: show "Send SMS" only when enabled (#43941)

(cherry picked from commit 65088cbb1b)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2024-11-27 23:52:12 +01:00
mergify[bot]
78ab44ce1a feat: add Company Contact Person in selling transactions (backport #44362) (#44397)
* feat: add Company Contact Person in selling transactions (#44362)

(cherry picked from commit f6776c7d6b)

* chore: resolve conflicts

---------

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2024-11-27 17:09:09 +01:00
32 changed files with 506 additions and 134 deletions

View File

@@ -3,7 +3,7 @@ import inspect
import frappe
__version__ = "14.77.3"
__version__ = "14.78.1"
def get_default_company(user=None):

View File

@@ -165,6 +165,10 @@ frappe.ui.form.on('Payment Entry', {
filters: {
reference_doctype: row.reference_doctype,
reference_name: row.reference_name,
company: doc.company,
status: ["!=", "Paid"],
outstanding_amount: [">", 0], // for compatibility with old data
docstatus: 1,
},
};
});

View File

@@ -2605,6 +2605,7 @@ def get_open_payment_requests_for_references(references=None):
.where(Tuple(PR.reference_doctype, PR.reference_name).isin(list(refs)))
.where(PR.status != "Paid")
.where(PR.docstatus == 1)
.where(PR.outstanding_amount > 0) # to avoid old PRs with 0 outstanding amount
.orderby(Coalesce(PR.transaction_date, PR.creation), order=frappe.qb.asc)
).run(as_dict=True)

View File

@@ -848,12 +848,7 @@ def get_open_payment_requests_query(doctype, txt, searchfield, start, page_len,
open_payment_requests = frappe.get_list(
"Payment Request",
filters={
**filters,
"status": ["!=", "Paid"],
"outstanding_amount": ["!=", 0], # for compatibility with old data
"docstatus": 1,
},
filters=filters,
fields=["name", "grand_total", "outstanding_amount"],
order_by="transaction_date ASC,creation ASC",
)

View File

@@ -0,0 +1,14 @@
from frappe import _
def get_data():
return {
"fieldname": "payment_request",
"internal_links": {
"Payment Entry": ["references", "payment_request"],
"Payment Order": ["references", "payment_order"],
},
"transactions": [
{"label": _("Payment"), "items": ["Payment Entry", "Payment Order"]},
],
}

View File

@@ -1,19 +1,18 @@
const INDICATORS = {
"Partially Paid": "orange",
Cancelled: "red",
Draft: "gray",
Failed: "red",
Initiated: "green",
Paid: "blue",
Requested: "green",
};
frappe.listview_settings["Payment Request"] = {
add_fields: ["status"],
get_indicator: function (doc) {
if (doc.status == "Draft") {
return [__("Draft"), "gray", "status,=,Draft"];
}
if (doc.status == "Requested") {
return [__("Requested"), "green", "status,=,Requested"];
} else if (doc.status == "Initiated") {
return [__("Initiated"), "green", "status,=,Initiated"];
} else if (doc.status == "Partially Paid") {
return [__("Partially Paid"), "orange", "status,=,Partially Paid"];
} else if (doc.status == "Paid") {
return [__("Paid"), "blue", "status,=,Paid"];
} else if (doc.status == "Cancelled") {
return [__("Cancelled"), "red", "status,=,Cancelled"];
}
if (!doc.status || !INDICATORS[doc.status]) return;
return [__(doc.status), INDICATORS[doc.status], `status,=,${doc.status}`];
},
};

View File

@@ -48,6 +48,7 @@
"shipping_address",
"company_address",
"company_address_display",
"company_contact_person",
"currency_and_price_list",
"currency",
"conversion_rate",
@@ -1557,12 +1558,19 @@
"fieldname": "update_billed_amount_in_delivery_note",
"fieldtype": "Check",
"label": "Update Billed Amount in Delivery Note"
},
{
"fieldname": "company_contact_person",
"fieldtype": "Link",
"label": "Company Contact Person",
"options": "Contact",
"print_hide": 1
}
],
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2024-03-20 16:00:34.268756",
"modified": "2024-11-26 13:10:50.309570",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",

View File

@@ -1643,6 +1643,30 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1)
# Cost of Item is zero in Purchase Receipt
pr = make_purchase_receipt(qty=1, rate=0)
stock_value_difference = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"stock_value_difference",
)
self.assertEqual(stock_value_difference, 0)
pi = create_purchase_invoice_from_receipt(pr.name)
for row in pi.items:
row.rate = 150
pi.save()
pi.submit()
stock_value_difference = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
"stock_value_difference",
)
self.assertEqual(stock_value_difference, 150)
# Increase the cost of the item
pr = make_purchase_receipt(qty=1, rate=100)

View File

@@ -160,8 +160,9 @@
"dispatch_address",
"company_address_section",
"company_address",
"company_addr_col_break",
"company_address_display",
"company_addr_col_break",
"company_contact_person",
"terms_tab",
"payment_schedule_section",
"ignore_default_payment_terms_template",
@@ -2171,6 +2172,13 @@
"label": "Update Outstanding for Self",
"no_copy": 1,
"print_hide": 1
},
{
"fieldname": "company_contact_person",
"fieldtype": "Link",
"label": "Company Contact Person",
"options": "Contact",
"print_hide": 1
}
],
"icon": "fa fa-file-text",
@@ -2183,7 +2191,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2024-07-18 15:30:39.428519",
"modified": "2024-11-26 12:34:09.110690",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -1556,8 +1556,12 @@ class SalesInvoice(SellingController):
)
def update_project(self):
if self.project:
project = frappe.get_doc("Project", self.project)
unique_projects = list(set([d.project for d in self.get("items") if d.project]))
if self.project and self.project not in unique_projects:
unique_projects.append(self.project)
for p in unique_projects:
project = frappe.get_doc("Project", p)
project.update_billed_amount()
project.db_update()

View File

@@ -3760,6 +3760,102 @@ class TestSalesInvoice(FrappeTestCase):
self.assertTrue(jv)
self.assertEqual(jv[0], si.grand_total)
@change_settings("Accounts Settings", {"enable_common_party_accounting": True})
def test_common_party_with_different_currency_in_debtor_and_creditor(self):
from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
make_customer,
)
from erpnext.accounts.doctype.party_link.party_link import create_party_link
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
from erpnext.setup.utils import get_exchange_rate
creditors = create_account(
account_name="Creditors INR",
parent_account="Accounts Payable - _TC",
company="_Test Company",
account_currency="INR",
account_type="Payable",
)
debtors = create_account(
account_name="Debtors USD",
parent_account="Accounts Receivable - _TC",
company="_Test Company",
account_currency="USD",
account_type="Receivable",
)
# create a customer
customer = make_customer(customer="_Test Common Party USD")
cust_doc = frappe.get_doc("Customer", customer)
cust_doc.default_currency = "USD"
test_account_details = {
"company": "_Test Company",
"account": debtors,
}
cust_doc.append("accounts", test_account_details)
cust_doc.save()
# create a supplier
supplier = create_supplier(supplier_name="_Test Common Party INR").name
supp_doc = frappe.get_doc("Supplier", supplier)
supp_doc.default_currency = "INR"
test_account_details = {
"company": "_Test Company",
"account": creditors,
}
supp_doc.append("accounts", test_account_details)
supp_doc.save()
# create a party link between customer & supplier
create_party_link("Supplier", supplier, customer)
# create a sales invoice
si = create_sales_invoice(
customer=customer,
currency="USD",
conversion_rate=get_exchange_rate("USD", "INR"),
debit_to=debtors,
do_not_save=1,
)
si.party_account_currency = "USD"
si.save()
si.submit()
# check outstanding of sales invoice
si.reload()
self.assertEqual(si.status, "Paid")
self.assertEqual(flt(si.outstanding_amount), 0.0)
# check creation of journal entry
jv = frappe.get_all(
"Journal Entry Account",
{
"account": si.debit_to,
"party_type": "Customer",
"party": si.customer,
"reference_type": si.doctype,
"reference_name": si.name,
},
pluck="credit_in_account_currency",
)
self.assertTrue(jv)
self.assertEqual(jv[0], si.grand_total)
def test_total_billed_amount(self):
si = create_sales_invoice(do_not_submit=True)
project = frappe.new_doc("Project")
project.project_name = "Test Total Billed Amount"
project.save()
si.project = project.name
si.save()
si.submit()
doc = frappe.get_doc("Project", project.name)
self.assertEqual(doc.total_billed_amount, si.grand_total)
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql(

View File

@@ -29,6 +29,12 @@ from erpnext.accounts.utils import get_fiscal_year
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
from erpnext.utilities.regional import temporary_flag
try:
from frappe.contacts.doctype.address.address import render_address as _render_address
except ImportError:
# Older frappe versions where this function is not available
from frappe.contacts.doctype.address.address import get_address_display as _render_address
PURCHASE_TRANSACTION_TYPES = {
"Supplier Quotation",
"Purchase Order",
@@ -985,10 +991,4 @@ def add_party_account(party_type, party, company, account):
def render_address(address, check_permissions=True):
try:
from frappe.contacts.doctype.address.address import render_address as _render
except ImportError:
# Older frappe versions where this function is not available
from frappe.contacts.doctype.address.address import get_address_display as _render
return frappe.call(_render, address, check_permissions=check_permissions)
return frappe.call(_render_address, address, check_permissions=check_permissions)

View File

@@ -1004,7 +1004,7 @@ class ReceivablePayableReport:
def get_columns(self):
self.columns = []
self.add_column(_("Posting Date"), fieldtype="Date")
self.add_column(_("Posting Date"), fieldname="posting_date", fieldtype="Date")
self.add_column(
label=_("Party Type"),
fieldname="party_type",
@@ -1018,8 +1018,15 @@ class ReceivablePayableReport:
options="party_type",
width=180,
)
if self.account_type == "Receivable":
label = _("Receivable Account")
elif self.account_type == "Payable":
label = _("Payable Account")
else:
label = _("Party Account")
self.add_column(
label=self.account_type + " Account",
label=label,
fieldname="party_account",
fieldtype="Link",
options="Account",
@@ -1057,7 +1064,7 @@ class ReceivablePayableReport:
width=180,
)
self.add_column(label=_("Due Date"), fieldtype="Date")
self.add_column(label=_("Due Date"), fieldname="due_date", fieldtype="Date")
if self.account_type == "Payable":
self.add_column(label=_("Bill No"), fieldname="bill_no", fieldtype="Data")

View File

@@ -7,6 +7,7 @@ from frappe import _
from frappe.utils import cint, flt
from erpnext.accounts.report.financial_statements import (
compute_growth_view_data,
get_columns,
get_data,
get_filtered_list_for_consolidated_report,
@@ -101,6 +102,9 @@ def execute(filters=None):
period_list, asset, liability, equity, provisional_profit_loss, currency, filters
)
if filters.get("selected_view") == "Growth":
compute_growth_view_data(data, period_list)
return columns, data, message, chart, report_summary, primitive_summary

View File

@@ -2,6 +2,7 @@
# License: GNU General Public License v3. See license.txt
import copy
import functools
import math
import re
@@ -653,3 +654,67 @@ def get_filtered_list_for_consolidated_report(filters, period_list):
filtered_summary_list.append(period)
return filtered_summary_list
def compute_growth_view_data(data, columns):
data_copy = copy.deepcopy(data)
for row_idx in range(len(data_copy)):
for column_idx in range(1, len(columns)):
previous_period_key = columns[column_idx - 1].get("key")
current_period_key = columns[column_idx].get("key")
current_period_value = data_copy[row_idx].get(current_period_key)
previous_period_value = data_copy[row_idx].get(previous_period_key)
annual_growth = 0
if current_period_value is None:
data[row_idx][current_period_key] = None
continue
if previous_period_value == 0 and current_period_value > 0:
annual_growth = 1
elif previous_period_value > 0:
annual_growth = (current_period_value - previous_period_value) / previous_period_value
growth_percent = round(annual_growth * 100, 2)
data[row_idx][current_period_key] = growth_percent
def compute_margin_view_data(data, columns, accumulated_values):
if not columns:
return
if not accumulated_values:
columns.append({"key": "total"})
data_copy = copy.deepcopy(data)
base_row = None
for row in data_copy:
if row.get("account_name") == _("Income"):
base_row = row
break
if not base_row:
return
for row_idx in range(len(data_copy)):
# Taking the total income from each column (for all the financial years) as the base (100%)
row = data_copy[row_idx]
if not row:
continue
for column in columns:
curr_period = column.get("key")
base_value = base_row[curr_period]
curr_value = row[curr_period]
if curr_value is None or base_value <= 0:
data[row_idx][curr_period] = None
continue
margin_percent = round((curr_value / base_value) * 100, 2)
data[row_idx][curr_period] = margin_percent

View File

@@ -402,10 +402,10 @@ class GrossProfitGenerator:
self.load_invoice_items()
self.get_delivery_notes()
self.load_product_bundle()
if filters.group_by == "Invoice":
self.group_items_by_invoice()
self.load_product_bundle()
self.load_non_stock_items()
self.get_returned_invoice_items()
self.process()
@@ -617,6 +617,7 @@ class GrossProfitGenerator:
if packed_item.get("parent_detail_docname") == row.item_row:
packed_item_row = row.copy()
packed_item_row.warehouse = packed_item.warehouse
packed_item_row.qty = packed_item.total_qty * -1
buying_amount += self.get_buying_amount(packed_item_row, packed_item.item_code)
return flt(buying_amount, self.currency_precision)
@@ -649,7 +650,9 @@ class GrossProfitGenerator:
else:
my_sle = self.get_stock_ledger_entries(item_code, row.warehouse)
if (row.update_stock or row.dn_detail) and my_sle:
parenttype, parent = row.parenttype, row.parent
parenttype = row.parenttype
parent = row.invoice or row.parent
if row.dn_detail:
parenttype, parent = "Delivery Note", row.delivery_note
@@ -797,6 +800,7 @@ class GrossProfitGenerator:
`tabSales Invoice`.project, `tabSales Invoice`.update_stock,
`tabSales Invoice`.customer, `tabSales Invoice`.customer_group,
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
`tabSales Invoice`.base_net_total as "invoice_base_net_total",
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group,
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.so_detail,
@@ -857,6 +861,7 @@ class GrossProfitGenerator:
"""
grouped = OrderedDict()
product_bundles = self.product_bundles.get("Sales Invoice", {})
for row in self.si_list:
# initialize list with a header row for each new parent
@@ -867,8 +872,7 @@ class GrossProfitGenerator:
)
# if item is a bundle, add it's components as seperate rows
if frappe.db.exists("Product Bundle", row.item_code):
bundled_items = self.get_bundle_items(row)
if bundled_items := product_bundles.get(row.parent, {}).get(row.item_code):
for x in bundled_items:
bundle_item = self.get_bundle_item_row(row, x)
grouped.get(row.parent).append(bundle_item)
@@ -904,47 +908,40 @@ class GrossProfitGenerator:
"item_row": None,
"is_return": row.is_return,
"cost_center": row.cost_center,
"base_net_amount": frappe.db.get_value("Sales Invoice", row.parent, "base_net_total"),
"base_net_amount": row.invoice_base_net_total,
}
)
def get_bundle_items(self, product_bundle):
return frappe.get_all(
"Product Bundle Item", filters={"parent": product_bundle.item_code}, fields=["item_code", "qty"]
)
def get_bundle_item_row(self, product_bundle, item):
item_name, description, item_group, brand = self.get_bundle_item_details(item.item_code)
def get_bundle_item_row(self, row, item):
return frappe._dict(
{
"parent_invoice": product_bundle.item_code,
"indent": product_bundle.indent + 1,
"parent_invoice": row.item_code,
"parenttype": row.parenttype,
"indent": row.indent + 1,
"parent": None,
"invoice_or_item": item.item_code,
"posting_date": product_bundle.posting_date,
"posting_time": product_bundle.posting_time,
"project": product_bundle.project,
"customer": product_bundle.customer,
"customer_group": product_bundle.customer_group,
"posting_date": row.posting_date,
"posting_time": row.posting_time,
"project": row.project,
"customer": row.customer,
"customer_group": row.customer_group,
"item_code": item.item_code,
"item_name": item_name,
"description": description,
"warehouse": product_bundle.warehouse,
"item_group": item_group,
"brand": brand,
"dn_detail": product_bundle.dn_detail,
"delivery_note": product_bundle.delivery_note,
"qty": (flt(product_bundle.qty) * flt(item.qty)),
"item_row": None,
"is_return": product_bundle.is_return,
"cost_center": product_bundle.cost_center,
"item_name": item.item_name,
"description": item.description,
"warehouse": item.warehouse or row.warehouse,
"update_stock": row.update_stock,
"item_group": "",
"brand": "",
"dn_detail": row.dn_detail,
"delivery_note": row.delivery_note,
"qty": item.total_qty * -1,
"item_row": row.item_row,
"is_return": row.is_return,
"cost_center": row.cost_center,
"invoice": row.parent,
}
)
def get_bundle_item_details(self, item_code):
return frappe.db.get_value("Item", item_code, ["item_name", "description", "item_group", "brand"])
def get_stock_ledger_entries(self, item_code, warehouse):
if item_code and warehouse:
if (item_code, warehouse) not in self.sle:

View File

@@ -7,6 +7,8 @@ from frappe import _
from frappe.utils import flt
from erpnext.accounts.report.financial_statements import (
compute_growth_view_data,
compute_margin_view_data,
get_columns,
get_data,
get_filtered_list_for_consolidated_report,
@@ -68,6 +70,12 @@ def execute(filters=None):
period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters
)
if filters.get("selected_view") == "Growth":
compute_growth_view_data(data, period_list)
if filters.get("selected_view") == "Margin":
compute_margin_view_data(data, period_list, filters.accumulated_values)
return columns, data, None, chart, report_summary, primitive_summary

View File

@@ -2313,6 +2313,12 @@ class AccountsController(TransactionBase):
secondary_account = get_party_account(secondary_party_type, secondary_party, self.company)
primary_account_currency = get_account_currency(primary_account)
secondary_account_currency = get_account_currency(secondary_account)
default_currency = erpnext.get_company_currency(self.company)
# Determine if multi-currency journal entry is needed
multi_currency = (
primary_account_currency != default_currency or secondary_account_currency != default_currency
)
jv = frappe.new_doc("Journal Entry")
jv.voucher_type = "Journal Entry"
@@ -2337,7 +2343,7 @@ class AccountsController(TransactionBase):
advance_entry.cost_center = self.cost_center or erpnext.get_default_cost_center(self.company)
advance_entry.is_advance = "Yes"
# update dimesions
# Update dimensions
dimensions_dict = frappe._dict()
active_dimensions = get_dimensions()[0]
for dim in active_dimensions:
@@ -2346,17 +2352,58 @@ class AccountsController(TransactionBase):
reconcilation_entry.update(dimensions_dict)
advance_entry.update(dimensions_dict)
if self.doctype == "Sales Invoice":
reconcilation_entry.credit_in_account_currency = self.outstanding_amount
advance_entry.debit_in_account_currency = self.outstanding_amount
# Calculate exchange rates if necessary
if multi_currency:
# Exchange rates for primary and secondary accounts
exc_rate_primary_to_default = (
1
if primary_account_currency == default_currency
else get_exchange_rate(primary_account_currency, default_currency, self.posting_date)
)
exc_rate_secondary_to_default = (
1
if secondary_account_currency == default_currency
else get_exchange_rate(secondary_account_currency, default_currency, self.posting_date)
)
exc_rate_secondary_to_primary = (
1
if secondary_account_currency == primary_account_currency
else get_exchange_rate(
secondary_account_currency, primary_account_currency, self.posting_date
)
)
# Convert outstanding amount from secondary to primary account currency, if needed
os_in_default_currency = self.outstanding_amount * exc_rate_secondary_to_default
os_in_primary_currency = self.outstanding_amount * exc_rate_secondary_to_primary
if self.doctype == "Sales Invoice":
# Calculate credit and debit values for reconciliation and advance entries
reconcilation_entry.credit_in_account_currency = self.outstanding_amount
reconcilation_entry.credit = os_in_default_currency
advance_entry.debit_in_account_currency = os_in_primary_currency
advance_entry.debit = os_in_default_currency
else:
advance_entry.credit_in_account_currency = os_in_primary_currency
advance_entry.credit = os_in_default_currency
reconcilation_entry.debit_in_account_currency = self.outstanding_amount
reconcilation_entry.debit = os_in_default_currency
# Set exchange rates for entries
reconcilation_entry.exchange_rate = exc_rate_secondary_to_default
advance_entry.exchange_rate = exc_rate_primary_to_default
else:
advance_entry.credit_in_account_currency = self.outstanding_amount
reconcilation_entry.debit_in_account_currency = self.outstanding_amount
default_currency = erpnext.get_company_currency(self.company)
if primary_account_currency != default_currency or secondary_account_currency != default_currency:
jv.multi_currency = 1
if self.doctype == "Sales Invoice":
reconcilation_entry.credit_in_account_currency = self.outstanding_amount
advance_entry.debit_in_account_currency = self.outstanding_amount
else:
advance_entry.credit_in_account_currency = self.outstanding_amount
reconcilation_entry.debit_in_account_currency = self.outstanding_amount
jv.multi_currency = multi_currency
jv.append("accounts", reconcilation_entry)
jv.append("accounts", advance_entry)

View File

@@ -68,19 +68,13 @@ class SellingController(StockController):
if customer:
from erpnext.accounts.party import _get_party_details
fetch_payment_terms_template = False
if self.get("__islocal") or self.company != frappe.db.get_value(
self.doctype, self.name, "company"
):
fetch_payment_terms_template = True
party_details = _get_party_details(
customer,
ignore_permissions=self.flags.ignore_permissions,
doctype=self.doctype,
company=self.company,
posting_date=self.get("posting_date"),
fetch_payment_terms_template=fetch_payment_terms_template,
fetch_payment_terms_template=self.has_value_changed("company"),
party_address=self.customer_address,
shipping_address=self.shipping_address_name,
company_address=self.get("company_address"),
@@ -365,12 +359,32 @@ class SellingController(StockController):
return il
def has_product_bundle(self, item_code):
product_bundle = frappe.qb.DocType("Product Bundle")
return (
frappe.qb.from_(product_bundle)
.select(product_bundle.name)
.where((product_bundle.new_item_code == item_code) & (product_bundle.disabled == 0))
).run()
product_bundle_items = getattr(self, "_product_bundle_items", None)
if product_bundle_items is None:
self._product_bundle_items = product_bundle_items = {}
if item_code not in product_bundle_items:
self._fetch_product_bundle_items(item_code)
return product_bundle_items[item_code]
def _fetch_product_bundle_items(self, item_code):
product_bundle_items = self._product_bundle_items
items_to_fetch = {row.item_code for row in self.items if row.item_code not in product_bundle_items}
# fetch for requisite item_code even if it is not in items
items_to_fetch.add(item_code)
items_with_product_bundle = {
row.new_item_code
for row in frappe.get_all(
"Product Bundle",
filters={"new_item_code": ("in", items_to_fetch), "disabled": 0},
fields="new_item_code",
)
}
for item_code in items_to_fetch:
product_bundle_items[item_code] = item_code in items_with_product_bundle
def get_already_delivered_qty(self, current_docname, so, so_detail):
delivered_via_dn = frappe.db.sql(

View File

@@ -89,10 +89,18 @@ class WorkOrder(Document):
self.status = self.get_status()
self.validate_workstation_type()
if self.source_warehouse:
self.set_warehouses()
validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"])
self.set_required_items(reset_only_qty=len(self.get("required_items")))
def set_warehouses(self):
for row in self.required_items:
if not row.source_warehouse:
row.source_warehouse = self.source_warehouse
def validate_workstation_type(self):
for row in self.operations:
if not row.workstation and not row.workstation_type:

View File

@@ -421,7 +421,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
setup_sms() {
var me = this;
let blacklist = ['Purchase Invoice', 'BOM'];
if(this.frm.doc.docstatus===1 && !["Lost", "Stopped", "Closed"].includes(this.frm.doc.status)
if(frappe.boot.sms_gateway_enabled && this.frm.doc.docstatus===1 && !["Lost", "Stopped", "Closed"].includes(this.frm.doc.status)
&& !blacklist.includes(this.frm.doctype)) {
this.frm.page.add_menu_item(__('Send SMS'), function() { me.send_sms(); });
}
@@ -990,7 +990,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
apply_discount_on_item(doc, cdt, cdn, field) {
var item = frappe.get_doc(cdt, cdn);
if(!item.price_list_rate) {
if(item && !item.price_list_rate) {
item[field] = 0.0;
} else {
this.price_list_rate(doc, cdt, cdn);

View File

@@ -9,40 +9,29 @@ erpnext.financial_statements = {
data &&
column.colIndex >= 3
) {
//Assuming that the first three columns are s.no, account name and the very first year of the accounting values, to calculate the relative percentage values of the successive columns.
const lastAnnualValue = row[column.colIndex - 1].content;
const currentAnnualvalue = data[column.fieldname];
if (currentAnnualvalue == undefined) return "NA"; //making this not applicable for undefined/null values
let annualGrowth = 0;
if (lastAnnualValue == 0 && currentAnnualvalue > 0) {
//If the previous year value is 0 and the current value is greater than 0
annualGrowth = 1;
} else if (lastAnnualValue > 0) {
annualGrowth = (currentAnnualvalue - lastAnnualValue) / lastAnnualValue;
}
const growthPercent = data[column.fieldname];
const growthPercent = Math.round(annualGrowth * 10000) / 100; //calculating the rounded off percentage
if (growthPercent == undefined) return "NA"; //making this not applicable for undefined/null values
value = $(`<span>${(growthPercent >= 0 ? "+" : "") + growthPercent + "%"}</span>`);
if (growthPercent < 0) {
value = $(value).addClass("text-danger");
if (column.fieldname === "total") {
value = $(`<span>${growthPercent}</span>`);
} else {
value = $(value).addClass("text-success");
value = $(`<span>${(growthPercent >= 0 ? "+" : "") + growthPercent + "%"}</span>`);
if (growthPercent < 0) {
value = $(value).addClass("text-danger");
} else {
value = $(value).addClass("text-success");
}
}
value = $(value).wrap("<p></p>").parent().html();
return value;
} else if (frappe.query_report.get_filter_value("selected_view") == "Margin" && data) {
if (column.fieldname == "account" && data.account_name == __("Income")) {
//Taking the total income from each column (for all the financial years) as the base (100%)
this.baseData = row;
}
if (column.colIndex >= 2) {
//Assuming that the first two columns are s.no and account name, to calculate the relative percentage values of the successive columns.
const currentAnnualvalue = data[column.fieldname];
const baseValue = this.baseData[column.colIndex].content;
if (currentAnnualvalue == undefined || baseValue <= 0) return "NA";
const marginPercent = Math.round((currentAnnualvalue / baseValue) * 10000) / 100;
const marginPercent = data[column.fieldname];
if (marginPercent == undefined) return "NA"; //making this not applicable for undefined/null values
value = $(`<span>${marginPercent + "%"}</span>`);
if (marginPercent < 0) value = $(value).addClass("text-danger");

View File

@@ -64,6 +64,17 @@ $.extend(erpnext.queries, {
}
},
company_contact_query: function (doc) {
if (!doc.company) {
frappe.throw(__("Please set {0}", [__(frappe.meta.get_label(doc.doctype, "company", doc.name))]));
}
return {
query: "frappe.contacts.doctype.contact.contact.contact_query",
filters: { link_doctype: "Company", link_name: doc.company },
};
},
address_query: function (doc) {
if (frappe.dynamic_link) {
if (!doc[frappe.dynamic_link.fieldname]) {

View File

@@ -95,8 +95,9 @@
"shipping_address",
"company_address_section",
"company_address",
"column_break_87",
"company_address_display",
"column_break_87",
"company_contact_person",
"terms_tab",
"payment_schedule_section",
"payment_terms_template",
@@ -1066,13 +1067,20 @@
"fieldname": "named_place",
"fieldtype": "Data",
"label": "Named Place"
},
{
"fieldname": "company_contact_person",
"fieldtype": "Link",
"label": "Company Contact Person",
"options": "Contact",
"print_hide": 1
}
],
"icon": "fa fa-shopping-cart",
"idx": 82,
"is_submittable": 1,
"links": [],
"modified": "2024-03-20 16:04:21.567847",
"modified": "2024-11-26 12:43:29.293637",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",

View File

@@ -455,7 +455,9 @@ def _make_customer(source_name, ignore_permissions=False):
raise
except frappe.MandatoryError as e:
mandatory_fields = e.args[0].split(":")[1].split(",")
mandatory_fields = [customer.meta.get_label(field.strip()) for field in mandatory_fields]
mandatory_fields = [
_(customer.meta.get_label(field.strip())) for field in mandatory_fields
]
frappe.local.message_log = []
lead_link = frappe.utils.get_link_to_form("Lead", lead_name)

View File

@@ -112,8 +112,9 @@
"dispatch_address",
"col_break46",
"company_address",
"column_break_92",
"company_address_display",
"column_break_92",
"company_contact_person",
"payment_schedule_section",
"payment_terms_section",
"payment_terms_template",
@@ -1626,13 +1627,20 @@
"fieldname": "named_place",
"fieldtype": "Data",
"label": "Named Place"
},
{
"fieldname": "company_contact_person",
"fieldtype": "Link",
"label": "Company Contact Person",
"options": "Contact",
"print_hide": 1
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2024-05-23 16:35:54.905804",
"modified": "2024-11-26 12:42:06.872527",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
@@ -1711,4 +1719,4 @@
"title_field": "customer_name",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -41,6 +41,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
me.frm.set_query('shipping_address_name', erpnext.queries.address_query);
me.frm.set_query('dispatch_address_name', erpnext.queries.dispatch_address_query);
me.frm.set_query('company_address', erpnext.queries.company_address_query);
me.frm.set_query('company_contact_person', erpnext.queries.company_contact_query);
erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype);

View File

@@ -109,8 +109,9 @@
"dispatch_address",
"company_address_section",
"company_address",
"column_break_101",
"company_address_display",
"column_break_101",
"company_contact_person",
"terms_tab",
"tc_name",
"terms",
@@ -1395,13 +1396,20 @@
"fieldname": "named_place",
"fieldtype": "Data",
"label": "Named Place"
},
{
"fieldname": "company_contact_person",
"fieldtype": "Link",
"label": "Company Contact Person",
"options": "Contact",
"print_hide": 1
}
],
"icon": "fa fa-truck",
"idx": 146,
"is_submittable": 1,
"links": [],
"modified": "2024-03-20 16:05:02.854990",
"modified": "2024-11-26 12:44:28.258215",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",

View File

@@ -907,7 +907,7 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
if adjust_incoming_rate:
adjusted_amt = 0.0
if item.billed_amt and item.amount:
if item.billed_amt is not None and item.amount is not None:
adjusted_amt = flt(item.billed_amt) - flt(item.amount)
item.db_set("rate_difference_with_purchase_invoice", adjusted_amt, update_modified=False)

View File

@@ -1727,6 +1727,46 @@ class TestStockEntry(FrappeTestCase):
mr.cancel()
mr.delete()
def test_auto_reorder_level_with_lead_time_days(self):
from erpnext.stock.reorder_item import reorder_item
item_doc = make_item(
"Test Auto Reorder Item - 002",
properties={"stock_uom": "Kg", "purchase_uom": "Nos", "is_stock_item": 1, "lead_time_days": 2},
uoms=[{"uom": "Nos", "conversion_factor": 5}],
)
if not frappe.db.exists("Item Reorder", {"parent": item_doc.name}):
item_doc.append(
"reorder_levels",
{
"warehouse_reorder_level": 0,
"warehouse_reorder_qty": 10,
"warehouse": "_Test Warehouse - _TC",
"material_request_type": "Purchase",
},
)
item_doc.save(ignore_permissions=True)
frappe.db.set_single_value("Stock Settings", "auto_indent", 1)
mr_list = reorder_item()
frappe.db.set_single_value("Stock Settings", "auto_indent", 0)
mrs = frappe.get_all(
"Material Request Item",
fields=["schedule_date"],
filters={"item_code": item_doc.name, "uom": "Nos"},
)
for mri in mrs:
self.assertEqual(getdate(mri.schedule_date), getdate(add_days(today(), 2)))
for mr in mr_list:
mr.cancel()
mr.delete()
def test_stock_entry_for_same_posting_date_and_time(self):
warehouse = "_Test Warehouse - _TC"
item_code = "Test Stock Entry For Same Posting Datetime 1"

View File

@@ -98,6 +98,7 @@ def _reorder_item():
"description": d.description,
"stock_uom": d.stock_uom,
"purchase_uom": d.purchase_uom,
"lead_time_days": d.lead_time_days,
}
),
)
@@ -129,6 +130,7 @@ def get_items_for_reorder() -> dict[str, list]:
item_table.brand,
item_table.variant_of,
item_table.has_variants,
item_table.lead_time_days,
)
.where(
(item_table.disabled == 0)

View File

@@ -194,11 +194,11 @@ def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None):
if isinstance(qty_fields, str):
qty_fields = [qty_fields]
distinct_uoms = list(set(d.get(uom_field) for d in doc.get_all_children()))
integer_uoms = list(
filter(
lambda uom: frappe.db.get_value("UOM", uom, "must_be_whole_number", cache=True) or None,
distinct_uoms,
distinct_uoms = tuple(set(uom for uom in (d.get(uom_field) for d in doc.get_all_children()) if uom))
integer_uoms = set(
d[0]
for d in frappe.db.get_values(
"UOM", (("name", "in", distinct_uoms), ("must_be_whole_number", "=", 1)), cache=True
)
)