Compare commits

...

64 Commits

Author SHA1 Message Date
Deepesh Garg
4f8b13ac57 chore: Loan Interest Accrual post topup 2023-12-15 12:46:59 +05:30
mergify[bot]
1b78dd17c9 fix: consider the Valuation Method while picking incorrect SLE (backport #38592) (#38596)
* fix: incorrect SLE for `Moving Average` valuation method

(cherry picked from commit 8beec58670)

* feat: add `Valuation Method` column in `Stock Ledger Variance` report

(cherry picked from commit 16c297c2ec)

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-12-05 18:14:19 +05:30
mergify[bot]
77e01ebacf feat: Company filter in Stock Ledger Variance report (backport #38553) (#38575)
feat: `Company` filter in `Stock Ledger Variance` report

(cherry picked from commit fb3421fcce)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-12-05 12:32:57 +05:30
mergify[bot]
bd371e697c fix: make create button translatable (backport #38165) (#38412)
fix: make create button translatable (#38165)

* fix: make all create buttons translatable

* style: use double quotes

---------

Co-authored-by: PatrickDenis-stack <77415730+PatrickDenis-stack@users.noreply.github.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
(cherry picked from commit 8e4b591ea2)

Co-authored-by: Patrick Eissler <77415730+PatrickDEissler@users.noreply.github.com>
2023-12-01 11:33:35 +05:30
Raffael Meyer
2c4cee025b ci: pin semgrep to old version (#38426)
* ci: pin semgrep to old version

current version has problem with PRs originating from fork

* ci: unpin semgrep

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
Co-authored-by: Ankush Menat <ankushmenat@gmail.com>
2023-11-29 22:06:31 +05:30
Deepesh Garg
303becf1e3 Merge pull request #38390 from GursheenK/payment-reco-currency-symbol
fix: currency symbol in payment rec allocations
2023-11-29 17:22:12 +05:30
Gursheen Anand
6f44a1630f fix: show difference amount in default currency 2023-11-28 16:31:40 +05:30
Gursheen Anand
b7944a7c07 chore: hide currency column 2023-11-28 15:51:33 +05:30
Gursheen Anand
3dfc1450a1 fix: update voucher currency for allocated entry 2023-11-28 15:43:36 +05:30
Gursheen Anand
835c85a087 feat: add currency column for allocated entries 2023-11-28 15:42:30 +05:30
Gursheen Kaur Anand
190f77abff fix: precision for invoice outstandings (#38323)
fix: precision while setting inv outstanding
2023-11-26 18:56:34 +05:30
mergify[bot]
095d99dbd2 fix: patch - Duplicate entry quality inspection parameter (backport #38262) (#38265)
fix: patch - Duplicate entry quality inspection parameter (#38262)

(cherry picked from commit 0ca7527f7a)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-11-22 14:09:00 +05:30
mergify[bot]
a9429e160d fix(Timesheet): warn user if billing hours > actual hours instead of resetting (backport #38239) (#38242)
fix(Timesheet): warn user if billing hours > actual hours instead of resetting  (#38239)

* revert: "fix(Timesheet): reset billing hours equal to hours if they exceed actual hours"

This reverts commit 0ec8034507.

* fix: warn user if billing hours > actual hours

(cherry picked from commit ac91030b31)

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2023-11-21 13:42:36 +05:30
Sagar Vora
5342cd0dfa Merge pull request #38170 from frappe/mergify/bp/version-13-hotfix/pr-38167
fix: pass check permission in `render_address` (backport #38167)
2023-11-18 17:43:40 +05:30
Sagar Vora
3bf84e1464 chore: fix conflicts 2023-11-18 17:42:06 +05:30
Vishakh Desai
65ae8d9c05 fix: pass check permission in render_address
(cherry picked from commit 45299fe4b3)

# Conflicts:
#	erpnext/accounts/party.py
2023-11-18 12:09:18 +00:00
Rucha Mahabal
35717124cd Merge pull request #38158 from ruchamahabal/perf-leave-balance-report-v13
perf: faster Employee Leave Balance report
2023-11-17 22:57:49 +05:30
Rucha Mahabal
89c107ea8b chore: remove states from json 2023-11-17 22:56:42 +05:30
Rucha Mahabal
958db77cda fix: backward compatible query without pluck 2023-11-17 21:11:42 +05:30
Rucha Mahabal
bc1da4678a refactor: retain backward compatible type hints 2023-11-17 20:45:34 +05:30
Rucha Mahabal
6cb8a40339 fix: get_previous_allocation return value 2023-11-17 20:19:39 +05:30
Rucha Mahabal
9139c14639 refactor(Leave Balance Summary report): remove unused department approver queries 2023-11-17 20:19:10 +05:30
Rucha Mahabal
461eb7a50d refactor: rewrite queries with frappe.qb + refactor type hints 2023-11-17 20:17:31 +05:30
Rucha Mahabal
635c3d54f5 perf: limit rows to 1 for cf leave expiry query 2023-11-17 20:17:11 +05:30
Rucha Mahabal
1bd3f4eeef refactor: remove unnecessary approver queries 2023-11-17 20:16:58 +05:30
Rucha Mahabal
4b8ed0f6ae fix: use get_all instead of get_list
- query report already filters records based on permissions for link fields

- allow employees access to leave balance report by default
2023-11-17 20:16:43 +05:30
Rucha Mahabal
eea7bbcea7 perf: index most queried fields in Leave Ledger Entry 2023-11-17 20:16:24 +05:30
mergify[bot]
1e436052e2 fix(Timesheet): reset billing hours equal to hours if they exceed actual hours (backport #38134) (#38154)
fix(Timesheet): reset billing hours equal to hours if they exceed actual hours (#38134)

(cherry picked from commit 20c6e9fca2)

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2023-11-17 18:32:35 +05:30
mergify[bot]
5be5fde276 fix: close Credit Limit Crossed dialog (backport #38052) (#38057)
fix: close `Credit Limit Crossed` dialog (#38052)

(cherry picked from commit 94faa44697)

Co-authored-by: Arjun <arjun99c@gmail.com>
2023-11-12 18:07:50 +05:30
mergify[bot]
8cb0f690d5 fix: remove voucher type and no for Item and Warehouse based reposting (backport #37849) (backport #37850) (#37938)
* fix: remove voucher type and no for Item and Warehouse based reposting (backport #37849) (#37850)

* fix: remove voucher type and no for Item and Warehouse based reposting

(cherry picked from commit 0104897d69)

* chore: fix test cases

---------

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
(cherry picked from commit e19cade12d)

# Conflicts:
#	erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
#	erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
#	erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json

* chore: fix conflicts

* chore: fix conflicts

* chore: fix conflicts

* fix: conflicts

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-11-07 16:07:17 +05:30
mergify[bot]
4789ecacea fix: Quality Inspection Parameter migration - DuplicateEntryError due to case sensitivity (backport #37499) (#37918)
fix: Quality Inspection Parameter migration - DuplicateEntryError due to case sensitivity (#37499)

* fix: account for case-insensitive database primary key for parameter names

* chore: linting

(cherry picked from commit b099590b2c)

Co-authored-by: Richard Case <110036763+casesolved-co-uk@users.noreply.github.com>
2023-11-05 06:22:42 +00:00
Anand Baburajan
d00257ffd7 Merge pull request #37891 from frappe/mergify/bp/version-13-hotfix/pr-37889
fix: add missing disbursement account in update_old_loans patch (backport #37889)
2023-11-03 21:48:57 +05:30
anandbaburajan
37b1a0e778 fix: add missing disbursement account in update_old_loans patch
(cherry picked from commit ad64065ec6)
2023-11-03 16:17:29 +00:00
mergify[bot]
6952f0f082 fix: permission error while creating Supplier Quotation from Portal (backport #37864) (#37872)
* fix: permission error while creating Supplier Quotation from Portal

(cherry picked from commit e019d43d0b)

# Conflicts:
#	erpnext/controllers/buying_controller.py

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-11-03 16:26:45 +05:30
Dany Robert
8f4ded6ad1 fix: e-invoice jwt verification error (#37818) 2023-11-01 13:27:24 +05:30
Ankush Menat
13d5eec194 fix: bump pygithub requirement to handle conflict (#37800)
build: bump pygithub requirement

This conflicts with pyjwt
2023-10-31 19:55:56 +05:30
Raffael Meyer
335b6c84db Merge pull request #37666 from frappe/mergify/bp/version-13-hotfix/pr-37658
fix: wrong german translation (backport #37658)
2023-10-28 23:07:59 +02:00
barredterra
00dff0a219 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-37658 2023-10-28 22:42:43 +02:00
mergify[bot]
b55b428114 chore: fixed test case non_internal_transfer_delivery_note (backport #37671) (#37677)
chore: fixed test case non_internal_transfer_delivery_note (#37671)

(cherry picked from commit 2bcff4c7f2)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-25 13:55:17 +05:30
Raffael Meyer
5669a89afe fix: wrong german translation (#37658)
(cherry picked from commit fa5780ca81)
2023-10-25 03:44:15 +00:00
mergify[bot]
b5b51879ee fix: remove from or target warehouse for non internal transfer entries (backport #37612) (#37629)
* fix: remove from or target warehouse for non internal transfer entries (#37612)

(cherry picked from commit 5136fe196b)

* chore: removed test cases

* chore: removed test case

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-24 08:41:41 +05:30
mergify[bot]
4fc45b035a fix: incorrect cost center in the purchase invoice (backport #37591) (#37610)
* fix: incorrect cost center in the purchase invoice (#37591)

(cherry picked from commit 14b009b093)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-20 17:03:25 +05:30
mergify[bot]
bc907b22d4 fix: Issues related to RFQ and Supplier Quotation on Portal (backport #37565) (backport #37577) (#37588)
* fix: Issues related to RFQ and Supplier Quotation on Portal (backport #37565) (#37577)

* fix: Issues related to RFQ and Supplier Quotation on Portal (#37565)

fix: RFQ and Supplier Quotation for Portal
(cherry picked from commit 2851a41310)

* chore: removed backport changes

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
(cherry picked from commit e1504efd40)

# Conflicts:
#	erpnext/templates/includes/rfq.js

* chore: fix conflicts

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-19 16:39:38 +05:30
mergify[bot]
7e26449b9f chore: rewrite query using query builder (backport #37310) (#37585)
* chore: rewrite query using query builder

(cherry picked from commit 25718f5cc7)

* chore: fix shopping cart tests

(cherry picked from commit fb51cae88b)

* chore: fix test case

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-19 15:58:45 +05:30
mergify[bot]
49d0ab5867 fix: e-commerce permissions for address (backport #37554) (#37560)
* fix: E-commerce permissions

(cherry picked from commit f4d74990fe)

# Conflicts:
#	erpnext/accounts/party.py
#	erpnext/controllers/selling_controller.py

* chore: conflicts

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-10-18 18:46:10 +05:30
Deepesh Garg
ddec91202a Merge branch 'version-13' into version-13-hotfix 2023-10-17 17:59:36 +05:30
Ankush Menat
fe681acaad Merge pull request #37534 from frappe/mergify/bp/version-13-hotfix/pr-37532
fix: keep customer/supplier website role by default (backport #37532)
2023-10-16 17:36:59 +05:30
Ankush Menat
52aff1f703 fix: keep customer/supplier website role by default
(cherry picked from commit d2096cfdb7)
2023-10-16 17:35:44 +05:30
ruthra kumar
1cfc6cfe5d Merge pull request #37509 from frappe/mergify/bp/version-13-hotfix/pr-37435
fix(gp): wrong `allocated_amount` when grouped by Sales Person (backport #37435)
2023-10-15 10:30:28 +05:30
Dany Robert
7805c3acf6 fix(gp): wrong allocated_amount on multi sales person invoice
(cherry picked from commit bda82bf1e9)
2023-10-15 08:14:26 +05:30
mergify[bot]
8b4e69235a fix: use flt to ignore TypeError (backport #37481) (#37489)
fix: use `flt` to ignore TypeError (#37481)

(cherry picked from commit d2b22db500)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-13 10:25:28 +05:30
Frappe PR Bot
35a62e2e8d chore(release): Bumped to Version 13.54.4
## [13.54.4](https://github.com/frappe/erpnext/compare/v13.54.3...v13.54.4) (2023-10-11)

### Bug Fixes

* negative valuation rate in PR return (backport [#37424](https://github.com/frappe/erpnext/issues/37424)) (backport [#37462](https://github.com/frappe/erpnext/issues/37462)) ([#37463](https://github.com/frappe/erpnext/issues/37463)) ([f6b3532](f6b35324ef))
2023-10-11 14:24:45 +00:00
mergify[bot]
f6b35324ef fix: negative valuation rate in PR return (backport #37424) (backport #37462) (#37463)
fix: negative valuation rate in PR return (backport #37424) (#37462)

* fix: negative valuation rate in PR return (#37424)

* fix: negative valuation rate in PR return

* test: add test case for PR return

(cherry picked from commit 26ad688584)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
(cherry picked from commit 66ad823417)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-10-11 19:53:10 +05:30
mergify[bot]
66ad823417 fix: negative valuation rate in PR return (backport #37424) (#37462)
* fix: negative valuation rate in PR return (#37424)

* fix: negative valuation rate in PR return

* test: add test case for PR return

(cherry picked from commit 26ad688584)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-11 19:17:02 +05:30
ruthra kumar
f674635ecf Merge pull request #37440 from ruthra-kumar/patch_for_exchange_rate_service_provider
chore: patch to set default exchange rate service providers
2023-10-10 21:02:10 +05:30
Frappe PR Bot
0000c38563 chore(release): Bumped to Version 13.54.3
## [13.54.3](https://github.com/frappe/erpnext/compare/v13.54.2...v13.54.3) (2023-10-10)

### Bug Fixes

* change currency exchange API to api.frankfurter.app ([76919c4](76919c4af2))
2023-10-10 14:47:51 +00:00
Deepesh Garg
23f12f6463 Merge pull request #37432 from frappe/version-13-hotfix
chore: release v13
2023-10-10 20:16:20 +05:30
ruthra kumar
6f6db432b5 chore: patch to set default exchange rate service provider 2023-10-10 17:37:04 +05:30
ruthra kumar
fd8543ebd3 Merge pull request #37385 from ruthra-kumar/multiple_currency_exchange_provider
refactor: multiple service providers for Currency Exchange rates
2023-10-07 15:34:05 +05:30
ruthra kumar
22fb65621c refactor: access_key field for service_provider 2023-10-07 14:28:32 +05:30
ruthra kumar
55dfd8e995 refactor: selectable exchange rate service providers 2023-10-06 15:05:57 +05:30
ruthra kumar
c86cd99395 Merge pull request #37355 from dsnetprofitxl/fix-exchange-rate
fix: change currency exchange API to api.frankfurter.app
2023-10-04 21:58:13 +05:30
ruthra kumar
a3fd4db450 refactor(test): update exchange rate based on 'frankfurter' provider 2023-10-04 21:33:11 +05:30
dsnetprofitxl
76919c4af2 fix: change currency exchange API to api.frankfurter.app 2023-10-04 16:01:22 +05:30
47 changed files with 589 additions and 424 deletions

View File

@@ -11,10 +11,10 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
- name: Set up Python 3.10
uses: actions/setup-python@v2
with:
python-version: 3.8
python-version: '3.10'
- name: Install and Run Pre-commit
uses: pre-commit/action@v2.0.3
@@ -22,10 +22,8 @@ jobs:
- name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- uses: returntocorp/semgrep-action@v1
env:
SEMGREP_TIMEOUT: 120
with:
config: >-
r/python.lang.correctness
./frappe-semgrep-rules/rules
- name: Download semgrep
run: pip install semgrep
- name: Run Semgrep rules
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness

View File

@@ -4,7 +4,7 @@ import frappe
from erpnext.hooks import regional_overrides
__version__ = "13.54.1"
__version__ = "13.54.4"
def get_default_company(user=None):

View File

@@ -358,6 +358,7 @@ def update_outstanding_amt(
if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]:
ref_doc = frappe.get_doc(against_voucher_type, against_voucher)
bal = flt(bal, frappe.get_precision(against_voucher_type, "outstanding_amount"))
# Didn't use db_set for optimization purpose
ref_doc.outstanding_amount = bal
frappe.db.set_value(against_voucher_type, against_voucher, "outstanding_amount", bal)

View File

@@ -295,6 +295,7 @@ class PaymentReconciliation(Document):
"amount": pay.get("amount"),
"allocated_amount": allocated_amount,
"difference_amount": pay.get("difference_amount"),
"currency": inv.get("currency"),
}
)

View File

@@ -20,7 +20,8 @@
"section_break_5",
"difference_amount",
"column_break_7",
"difference_account"
"difference_account",
"currency"
],
"fields": [
{
@@ -37,7 +38,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Allocated Amount",
"options": "Currency",
"options": "currency",
"reqd": 1
},
{
@@ -112,7 +113,7 @@
"fieldtype": "Currency",
"hidden": 1,
"label": "Unreconciled Amount",
"options": "Currency",
"options": "currency",
"read_only": 1
},
{
@@ -120,7 +121,7 @@
"fieldtype": "Currency",
"hidden": 1,
"label": "Amount",
"options": "Currency",
"options": "currency",
"read_only": 1
},
{
@@ -129,11 +130,18 @@
"hidden": 1,
"label": "Reference Row",
"read_only": 1
},
{
"fieldname": "currency",
"fieldtype": "Link",
"hidden": 1,
"label": "Currency",
"options": "Currency"
}
],
"istable": 1,
"links": [],
"modified": "2021-10-06 11:48:59.616562",
"modified": "2023-11-28 16:30:43.344612",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation Allocation",

View File

@@ -1626,6 +1626,30 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
0,
)
def test_default_cost_center_for_purchase(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
for c_center in ["_Test Cost Center Selling", "_Test Cost Center Buying"]:
create_cost_center(cost_center_name=c_center)
item = create_item(
"_Test Cost Center Item For Purchase",
is_stock_item=1,
buying_cost_center="_Test Cost Center Buying - _TC",
selling_cost_center="_Test Cost Center Selling - _TC",
)
pi = make_purchase_invoice(
item=item.name, qty=1, rate=1000, update_stock=True, do_not_submit=True, cost_center=""
)
pi.items[0].cost_center = ""
pi.set_missing_values()
pi.calculate_taxes_and_totals()
pi.save()
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql(

View File

@@ -2472,36 +2472,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(target_doc.company, "_Test Company 1")
self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
def test_sle_for_target_warehouse(self):
se = make_stock_entry(
item_code="138-CMS Shoe",
target="Finished Goods - _TC",
company="_Test Company",
qty=1,
basic_rate=500,
)
si = frappe.copy_doc(test_records[0])
si.update_stock = 1
si.set_warehouse = "Finished Goods - _TC"
si.set_target_warehouse = "Stores - _TC"
si.get("items")[0].warehouse = "Finished Goods - _TC"
si.get("items")[0].target_warehouse = "Stores - _TC"
si.insert()
si.submit()
sles = frappe.get_all(
"Stock Ledger Entry", filters={"voucher_no": si.name}, fields=["name", "actual_qty"]
)
# check if both SLEs are created
self.assertEqual(len(sles), 2)
self.assertEqual(sum(d.actual_qty for d in sles), 0.0)
# tear down
si.cancel()
se.cancel()
def test_internal_transfer_gl_entry(self):
si = create_sales_invoice(
company="_Test Company with perpetual inventory",

View File

@@ -4,12 +4,7 @@
import frappe
from frappe import _, msgprint, scrub
from frappe.contacts.doctype.address.address import (
get_address_display,
get_company_address,
get_default_address,
)
from frappe.contacts.doctype.contact.contact import get_contact_details
from frappe.contacts.doctype.address.address import get_company_address, get_default_address
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
from frappe.model.utils import get_fetch_values
from frappe.utils import (
@@ -120,6 +115,7 @@ def _get_party_details(
party_address,
company_address,
shipping_address,
ignore_permissions=ignore_permissions,
)
set_contact_details(party_details, party, party_type)
set_other_values(party_details, party, party_type)
@@ -183,6 +179,8 @@ def set_address_details(
party_address=None,
company_address=None,
shipping_address=None,
*,
ignore_permissions=False
):
billing_address_field = (
"customer_address" if party_type == "Lead" else party_type.lower() + "_address"
@@ -195,13 +193,17 @@ def set_address_details(
get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])
)
# address display
party_details.address_display = get_address_display(party_details[billing_address_field])
party_details.address_display = render_address(
party_details[billing_address_field], check_permissions=not ignore_permissions
)
# shipping address
if party_type in ["Customer", "Lead"]:
party_details.shipping_address_name = shipping_address or get_party_shipping_address(
party_type, party.name
)
party_details.shipping_address = get_address_display(party_details["shipping_address_name"])
party_details.shipping_address = render_address(
party_details["shipping_address_name"], check_permissions=not ignore_permissions
)
if doctype:
party_details.update(
get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name)
@@ -224,7 +226,9 @@ def set_address_details(
party_details.update(
{
"shipping_address": shipping_address,
"shipping_address_display": get_address_display(shipping_address),
"shipping_address_display": render_address(
shipping_address, check_permissions=not ignore_permissions
),
**get_fetch_values(doctype, "shipping_address", shipping_address),
}
)
@@ -235,7 +239,8 @@ def set_address_details(
{
"billing_address": party_details.company_address,
"billing_address_display": (
party_details.company_address_display or get_address_display(party_details.company_address)
party_details.company_address_display
or render_address(party_details.company_address, check_permissions=True)
),
**get_fetch_values(doctype, "billing_address", party_details.company_address),
}
@@ -277,7 +282,34 @@ def set_contact_details(party_details, party, party_type):
}
)
else:
party_details.update(get_contact_details(party_details.contact_person))
fields = [
"name as contact_person",
"salutation",
"first_name",
"last_name",
"email_id as contact_email",
"mobile_no as contact_mobile",
"phone as contact_phone",
"designation as contact_designation",
"department as contact_department",
]
contact_details = frappe.db.get_value(
"Contact", party_details.contact_person, fields, as_dict=True
)
contact_details.contact_display = " ".join(
filter(
None,
[
contact_details.get("salutation"),
contact_details.get("first_name"),
contact_details.get("last_name"),
],
)
)
party_details.update(contact_details)
def set_other_values(party_details, party, party_type):
@@ -938,3 +970,13 @@ def add_party_account(party_type, party, company, account):
doc.append("accounts", accounts)
doc.save()
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)

View File

@@ -457,6 +457,8 @@ class GrossProfitGenerator(object):
new_row.qty += flt(row.qty)
new_row.buying_amount += flt(row.buying_amount, self.currency_precision)
new_row.base_amount += flt(row.base_amount, self.currency_precision)
if self.filters.get("group_by") == "Sales Person":
new_row.allocated_amount += flt(row.allocated_amount, self.currency_precision)
new_row = self.set_average_rate(new_row)
self.grouped_data.append(new_row)
else:

View File

@@ -4,7 +4,7 @@
import frappe
from frappe import ValidationError, _, msgprint
from frappe.contacts.doctype.address.address import get_address_display
from frappe.contacts.doctype.address.address import render_address
from frappe.utils import cint, cstr, flt, getdate
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
@@ -14,7 +14,8 @@ from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
from erpnext.controllers.stock_controller import StockController
from erpnext.controllers.subcontracting import Subcontracting
from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.stock.utils import get_incoming_rate
from erpnext.stock.stock_ledger import get_previous_sle
from erpnext.stock.utils import get_incoming_rate, get_valuation_method
class QtyMismatchError(ValidationError):
@@ -186,7 +187,9 @@ class BuyingController(StockController, Subcontracting):
for address_field, address_display_field in address_dict.items():
if self.get(address_field):
self.set(address_display_field, get_address_display(self.get(address_field)))
self.set(
address_display_field, render_address(self.get(address_field), check_permissions=False)
)
def set_total_in_words(self):
from frappe.utils import money_in_words
@@ -504,9 +507,20 @@ class BuyingController(StockController, Subcontracting):
)
if self.is_return:
outgoing_rate = get_rate_for_return(
self.doctype, self.name, d.item_code, self.return_against, item_row=d
)
if get_valuation_method(d.item_code) == "Moving Average":
previous_sle = get_previous_sle(
{
"item_code": d.item_code,
"warehouse": d.warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
}
)
outgoing_rate = flt(previous_sle.get("valuation_rate"))
else:
outgoing_rate = get_rate_for_return(
self.doctype, self.name, d.item_code, self.return_against, item_row=d
)
sle.update({"outgoing_rate": outgoing_rate, "recalculate_rate": 1})
if d.from_warehouse:

View File

@@ -4,9 +4,9 @@
import frappe
from frappe import _, bold, throw
from frappe.contacts.doctype.address.address import get_address_display
from frappe.utils import cint, cstr, flt, get_link_to_form, nowtime
from erpnext.accounts.party import render_address
from erpnext.controllers.accounts_controller import get_taxes_and_charges
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
from erpnext.controllers.stock_controller import StockController
@@ -583,7 +583,9 @@ class SellingController(StockController):
for address_field, address_display_field in address_dict.items():
if self.get(address_field):
self.set(address_display_field, get_address_display(self.get(address_field)))
self.set(
address_display_field, render_address(self.get(address_field), check_permissions=False)
)
def validate_for_duplicate_items(self):
check_list, chk_dupl_itm = [], []

View File

@@ -582,13 +582,21 @@ class StockController(AccountsController):
d.stock_uom_rate = d.rate / (d.conversion_factor or 1)
def validate_internal_transfer(self):
if (
self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt")
and self.is_internal_transfer()
):
self.validate_in_transit_warehouses()
self.validate_multi_currency()
self.validate_packed_items()
if self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt"):
if self.is_internal_transfer():
self.validate_in_transit_warehouses()
self.validate_multi_currency()
self.validate_packed_items()
else:
self.validate_internal_transfer_warehouse()
def validate_internal_transfer_warehouse(self):
for row in self.items:
if row.get("target_warehouse"):
row.target_warehouse = None
if row.get("from_warehouse"):
row.from_warehouse = None
def validate_in_transit_warehouses(self):
if (
@@ -906,8 +914,6 @@ def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=Fa
repost_entry = frappe.new_doc("Repost Item Valuation")
repost_entry.based_on = "Item and Warehouse"
repost_entry.voucher_type = voucher_type
repost_entry.voucher_no = voucher_no
repost_entry.item_code = sle.item_code
repost_entry.warehouse = sle.warehouse

View File

@@ -4,6 +4,7 @@
import frappe
from frappe import _
from frappe.query_builder.functions import Concat_ws, Date
def execute(filters=None):
@@ -69,53 +70,41 @@ def get_columns():
def get_data(filters):
return frappe.db.sql(
"""
SELECT
`tabLead`.name,
`tabLead`.lead_name,
`tabLead`.status,
`tabLead`.lead_owner,
`tabLead`.territory,
`tabLead`.source,
`tabLead`.email_id,
`tabLead`.mobile_no,
`tabLead`.phone,
`tabLead`.owner,
`tabLead`.company,
concat_ws(', ',
trim(',' from `tabAddress`.address_line1),
trim(',' from tabAddress.address_line2)
) AS address,
`tabAddress`.state,
`tabAddress`.pincode,
`tabAddress`.country
FROM
`tabLead` left join `tabDynamic Link` on (
`tabLead`.name = `tabDynamic Link`.link_name and
`tabDynamic Link`.parenttype = 'Address')
left join `tabAddress` on (
`tabAddress`.name=`tabDynamic Link`.parent)
WHERE
company = %(company)s
AND DATE(`tabLead`.creation) BETWEEN %(from_date)s AND %(to_date)s
{conditions}
ORDER BY
`tabLead`.creation asc """.format(
conditions=get_conditions(filters)
),
filters,
as_dict=1,
lead = frappe.qb.DocType("Lead")
address = frappe.qb.DocType("Address")
dynamic_link = frappe.qb.DocType("Dynamic Link")
query = (
frappe.qb.from_(lead)
.left_join(dynamic_link)
.on((lead.name == dynamic_link.link_name) & (dynamic_link.parenttype == "Address"))
.left_join(address)
.on(address.name == dynamic_link.parent)
.select(
lead.name,
lead.lead_name,
lead.status,
lead.lead_owner,
lead.territory,
lead.source,
lead.email_id,
lead.mobile_no,
lead.phone,
lead.owner,
lead.company,
(Concat_ws(", ", address.address_line1, address.address_line2)).as_("address"),
address.state,
address.pincode,
address.country,
)
.where(lead.company == filters.company)
.where(Date(lead.creation).between(filters.from_date, filters.to_date))
)
def get_conditions(filters):
conditions = []
if filters.get("territory"):
conditions.append(" and `tabLead`.territory=%(territory)s")
query = query.where(lead.territory == filters.get("territory"))
if filters.get("status"):
conditions.append(" and `tabLead`.status=%(status)s")
query = query.where(lead.status == filters.get("status"))
return " ".join(conditions) if conditions else ""
return query.run(as_dict=1)

View File

@@ -17,7 +17,6 @@ from erpnext.e_commerce.shopping_cart.cart import (
request_for_quotation,
update_cart,
)
from erpnext.tests.utils import create_test_contact_and_address
class TestShoppingCart(unittest.TestCase):
@@ -28,7 +27,6 @@ class TestShoppingCart(unittest.TestCase):
def setUp(self):
frappe.set_user("Administrator")
create_test_contact_and_address()
self.enable_shopping_cart()
if not frappe.db.exists("Website Item", {"item_code": "_Test Item"}):
make_website_item(frappe.get_cached_doc("Item", "_Test Item"))
@@ -46,48 +44,57 @@ class TestShoppingCart(unittest.TestCase):
frappe.db.sql("delete from `tabTax Rule`")
def test_get_cart_new_user(self):
self.login_as_new_user()
self.login_as_customer(
"test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer"
)
create_address_and_contact(
address_title="_Test Address for Customer 2",
first_name="_Test Contact for Customer 2",
email="test_contact_two_customer@example.com",
customer="_Test Customer 2",
)
# test if lead is created and quotation with new lead is fetched
quotation = _get_cart_quotation()
customer = frappe.get_doc("Customer", "_Test Customer 2")
quotation = _get_cart_quotation(party=customer)
self.assertEqual(quotation.quotation_to, "Customer")
self.assertEqual(
quotation.contact_person,
frappe.db.get_value("Contact", dict(email_id="test_cart_user@example.com")),
frappe.db.get_value("Contact", dict(email_id="test_contact_two_customer@example.com")),
)
self.assertEqual(quotation.contact_email, frappe.session.user)
return quotation
def test_get_cart_customer(self):
def validate_quotation():
def test_get_cart_customer(self, customer="_Test Customer 2"):
def validate_quotation(customer_name):
# test if quotation with customer is fetched
quotation = _get_cart_quotation()
party = frappe.get_doc("Customer", customer_name)
quotation = _get_cart_quotation(party=party)
self.assertEqual(quotation.quotation_to, "Customer")
self.assertEqual(quotation.party_name, "_Test Customer")
self.assertEqual(quotation.party_name, customer_name)
self.assertEqual(quotation.contact_email, frappe.session.user)
return quotation
self.login_as_customer(
"test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer"
)
validate_quotation()
self.login_as_customer()
quotation = validate_quotation()
quotation = validate_quotation(customer)
return quotation
def test_add_to_cart(self):
self.login_as_customer()
self.login_as_customer(
"test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer"
)
create_address_and_contact(
address_title="_Test Address for Customer 2",
first_name="_Test Contact for Customer 2",
email="test_contact_two_customer@example.com",
customer="_Test Customer 2",
)
# clear existing quotations
self.clear_existing_quotations()
# add first item
update_cart("_Test Item", 1)
quotation = self.test_get_cart_customer()
quotation = self.test_get_cart_customer("_Test Customer 2")
self.assertEqual(quotation.get("items")[0].item_code, "_Test Item")
self.assertEqual(quotation.get("items")[0].qty, 1)
@@ -95,7 +102,7 @@ class TestShoppingCart(unittest.TestCase):
# add second item
update_cart("_Test Item 2", 1)
quotation = self.test_get_cart_customer()
quotation = self.test_get_cart_customer("_Test Customer 2")
self.assertEqual(quotation.get("items")[1].item_code, "_Test Item 2")
self.assertEqual(quotation.get("items")[1].qty, 1)
self.assertEqual(quotation.get("items")[1].amount, 20)
@@ -108,7 +115,7 @@ class TestShoppingCart(unittest.TestCase):
# update first item
update_cart("_Test Item", 5)
quotation = self.test_get_cart_customer()
quotation = self.test_get_cart_customer("_Test Customer 2")
self.assertEqual(quotation.get("items")[0].item_code, "_Test Item")
self.assertEqual(quotation.get("items")[0].qty, 5)
self.assertEqual(quotation.get("items")[0].amount, 50)
@@ -121,7 +128,7 @@ class TestShoppingCart(unittest.TestCase):
# remove first item
update_cart("_Test Item", 0)
quotation = self.test_get_cart_customer()
quotation = self.test_get_cart_customer("_Test Customer 2")
self.assertEqual(quotation.get("items")[0].item_code, "_Test Item 2")
self.assertEqual(quotation.get("items")[0].qty, 1)
@@ -129,9 +136,20 @@ class TestShoppingCart(unittest.TestCase):
self.assertEqual(quotation.net_total, 20)
self.assertEqual(len(quotation.get("items")), 1)
@unittest.skip("Flaky in CI")
def test_tax_rule(self):
self.create_tax_rule()
self.login_as_customer()
self.login_as_customer(
"test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer"
)
create_address_and_contact(
address_title="_Test Address for Customer 2",
first_name="_Test Contact for Customer 2",
email="test_contact_two_customer@example.com",
customer="_Test Customer 2",
)
quotation = self.create_quotation()
from erpnext.accounts.party import set_taxes
@@ -319,7 +337,7 @@ class TestShoppingCart(unittest.TestCase):
if frappe.db.exists("User", email):
return
frappe.get_doc(
user = frappe.get_doc(
{
"doctype": "User",
"user_type": "Website User",
@@ -329,6 +347,40 @@ class TestShoppingCart(unittest.TestCase):
}
).insert(ignore_permissions=True)
user.add_roles("Customer")
def create_address_and_contact(**kwargs):
if not frappe.db.get_value("Address", {"address_title": kwargs.get("address_title")}):
frappe.get_doc(
{
"doctype": "Address",
"address_title": kwargs.get("address_title"),
"address_type": kwargs.get("address_type") or "Office",
"address_line1": kwargs.get("address_line1") or "Station Road",
"city": kwargs.get("city") or "_Test City",
"state": kwargs.get("state") or "Test State",
"country": kwargs.get("country") or "India",
"links": [
{"link_doctype": "Customer", "link_name": kwargs.get("customer") or "_Test Customer"}
],
}
).insert()
if not frappe.db.get_value("Contact", {"first_name": kwargs.get("first_name")}):
contact = frappe.get_doc(
{
"doctype": "Contact",
"first_name": kwargs.get("first_name"),
"links": [
{"link_doctype": "Customer", "link_name": kwargs.get("customer") or "_Test Customer"}
],
}
)
contact.add_email(kwargs.get("email") or "test_contact_customer@example.com", is_primary=True)
contact.add_phone(kwargs.get("phone") or "+91 0000000000", is_primary_phone=True)
contact.insert()
test_dependencies = [
"Sales Taxes and Charges Template",

View File

@@ -296,18 +296,27 @@ class LeaveAllocation(Document):
def get_previous_allocation(from_date, leave_type, employee):
"""Returns document properties of previous allocation"""
return frappe.db.get_value(
"Leave Allocation",
filters={
"to_date": ("<", from_date),
"leave_type": leave_type,
"employee": employee,
"docstatus": 1,
},
order_by="to_date DESC",
fieldname=["name", "from_date", "to_date", "employee", "leave_type"],
as_dict=1,
)
Allocation = frappe.qb.DocType("Leave Allocation")
allocations = (
frappe.qb.from_(Allocation)
.select(
Allocation.name,
Allocation.from_date,
Allocation.to_date,
Allocation.employee,
Allocation.leave_type,
)
.where(
(Allocation.employee == employee)
& (Allocation.leave_type == leave_type)
& (Allocation.to_date < from_date)
& (Allocation.docstatus == 1)
)
.orderby(Allocation.to_date, order=frappe.qb.desc)
.limit(1)
).run(as_dict=True)
return allocations[0] if allocations else None
def get_leave_allocation_for_period(

View File

@@ -706,19 +706,22 @@ def get_allocation_expiry_for_cf_leaves(
employee: str, leave_type: str, to_date: str, from_date: str
) -> str:
"""Returns expiry of carry forward allocation in leave ledger entry"""
expiry = frappe.get_all(
"Leave Ledger Entry",
filters={
"employee": employee,
"leave_type": leave_type,
"is_carry_forward": 1,
"transaction_type": "Leave Allocation",
"to_date": ["between", (from_date, to_date)],
"docstatus": 1,
},
fields=["to_date"],
)
return expiry[0]["to_date"] if expiry else ""
Ledger = frappe.qb.DocType("Leave Ledger Entry")
expiry = (
frappe.qb.from_(Ledger)
.select(Ledger.to_date)
.where(
(Ledger.employee == employee)
& (Ledger.leave_type == leave_type)
& (Ledger.is_carry_forward == 1)
& (Ledger.transaction_type == "Leave Allocation")
& (Ledger.to_date.between(from_date, to_date))
& (Ledger.docstatus == 1)
)
.limit(1)
).run()
return expiry[0][0] if expiry else ""
@frappe.whitelist()
@@ -1017,7 +1020,7 @@ def get_leaves_for_period(
if leave_entry.leaves % 1:
half_day = 1
half_day_date = frappe.db.get_value(
"Leave Application", {"name": leave_entry.transaction_name}, ["half_day_date"]
"Leave Application", leave_entry.transaction_name, "half_day_date"
)
leave_days += (

View File

@@ -27,7 +27,8 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Employee",
"options": "Employee"
"options": "Employee",
"search_index": 1
},
{
"fetch_from": "employee.employee_name",
@@ -57,13 +58,15 @@
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Transaction Type",
"options": "DocType"
"options": "DocType",
"search_index": 1
},
{
"fieldname": "transaction_name",
"fieldtype": "Dynamic Link",
"label": "Transaction Name",
"options": "transaction_type"
"options": "transaction_type",
"search_index": 1
},
{
"fieldname": "leaves",
@@ -123,7 +126,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-01-04 18:47:45.146652",
"modified": "2023-11-17 12:36:36.963697",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Ledger Entry",

View File

@@ -225,3 +225,7 @@ def expire_carried_forward_allocation(allocation):
to_date=allocation.to_date,
)
create_leave_ledger_entry(allocation, args)
def on_doctype_update():
frappe.db.add_index("Leave Ledger Entry", ["transaction_type", "transaction_name"])

View File

@@ -1,26 +1,32 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2013-02-22 15:29:34",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2017-02-24 20:18:04.317397",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Leave Balance",
"owner": "Administrator",
"ref_doctype": "Employee",
"report_name": "Employee Leave Balance",
"report_type": "Script Report",
"add_total_row": 0,
"columns": [],
"creation": "2013-02-22 15:29:34",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 3,
"is_standard": "Yes",
"letterhead": null,
"modified": "2023-11-17 13:28:40.669200",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Leave Balance",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Employee",
"report_name": "Employee Leave Balance",
"report_type": "Script Report",
"roles": [
{
"role": "HR User"
},
},
{
"role": "HR Manager"
},
{
"role": "Employee"
}
]
}

View File

@@ -85,19 +85,10 @@ def get_columns() -> List[Dict]:
def get_data(filters: Filters) -> List:
leave_types = frappe.db.get_list("Leave Type", pluck="name", order_by="name")
conditions = get_conditions(filters)
leave_types = get_leave_types()
active_employees = get_employees(filters)
user = frappe.session.user
department_approver_map = get_department_leave_approver_map(filters.department)
active_employees = frappe.get_list(
"Employee",
filters=conditions,
fields=["name", "employee_name", "department", "user_id", "leave_approver"],
)
precision = cint(frappe.db.get_single_value("System Settings", "float_precision", cache=True))
precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
consolidate_leave_types = len(active_employees) > 1 and filters.consolidate_leave_types
row = None
@@ -110,10 +101,6 @@ def get_data(filters: Filters) -> List:
row = frappe._dict({"leave_type": leave_type})
for employee in active_employees:
leave_approvers = department_approver_map.get(employee.department_name, []).append(
employee.leave_approver
)
if consolidate_leave_types:
row = frappe._dict()
else:
@@ -144,6 +131,35 @@ def get_data(filters: Filters) -> List:
return data
def get_leave_types() -> List[str]:
LeaveType = frappe.qb.DocType("Leave Type")
leave_types = (frappe.qb.from_(LeaveType).select(LeaveType.name).orderby(LeaveType.name)).run(
as_dict=True
)
return [leave_type.name for leave_type in leave_types]
def get_employees(filters: Filters) -> List[Dict]:
Employee = frappe.qb.DocType("Employee")
query = frappe.qb.from_(Employee).select(
Employee.name,
Employee.employee_name,
Employee.department,
)
for field in ["company", "department"]:
if filters.get(field):
query = query.where((getattr(Employee, field) == filters.get(field)))
if filters.get("employee"):
query = query.where(Employee.name == filters.get("employee"))
if filters.get("employee_status"):
query = query.where(Employee.status == filters.get("employee_status"))
return query.run(as_dict=True)
def get_opening_balance(
employee: str, leave_type: str, filters: Filters, carry_forwarded_leaves: float
) -> float:
@@ -168,48 +184,6 @@ def get_opening_balance(
return opening_balance
def get_conditions(filters: Filters) -> Dict:
conditions = {}
if filters.employee:
conditions["name"] = filters.employee
if filters.company:
conditions["company"] = filters.company
if filters.department:
conditions["department"] = filters.department
if filters.employee_status:
conditions["status"] = filters.employee_status
return conditions
def get_department_leave_approver_map(department: Optional[str] = None):
# get current department and all its child
department_list = frappe.get_list(
"Department",
filters={"disabled": 0},
or_filters={"name": department, "parent_department": department},
pluck="name",
)
# retrieve approvers list from current department and from its subsequent child departments
approver_list = frappe.get_all(
"Department Approver",
filters={"parentfield": "leave_approvers", "parent": ("in", department_list)},
fields=["parent", "approver"],
as_list=True,
)
approvers = {}
for k, v in approver_list:
approvers.setdefault(k, []).append(v)
return approvers
def get_allocated_and_expired_leaves(
from_date: str, to_date: str, employee: str, leave_type: str
) -> Tuple[float, float, float]:
@@ -244,7 +218,7 @@ def get_leave_ledger_entries(
from_date: str, to_date: str, employee: str, leave_type: str
) -> List[Dict]:
ledger = frappe.qb.DocType("Leave Ledger Entry")
records = (
return (
frappe.qb.from_(ledger)
.select(
ledger.employee,
@@ -270,8 +244,6 @@ def get_leave_ledger_entries(
)
).run(as_dict=True)
return records
def get_chart_data(data: List, filters: Filters) -> Dict:
labels = []

View File

@@ -6,9 +6,6 @@ import frappe
from frappe import _
from erpnext.hr.doctype.leave_application.leave_application import get_leave_details
from erpnext.hr.report.employee_leave_balance.employee_leave_balance import (
get_department_leave_approver_map,
)
def execute(filters=None):
@@ -54,17 +51,11 @@ def get_data(filters, leave_types):
active_employees = frappe.get_all(
"Employee",
filters=conditions,
fields=["name", "employee_name", "department", "user_id", "leave_approver"],
fields=["name", "employee_name", "department", "user_id"],
)
department_approver_map = get_department_leave_approver_map(filters.get("department"))
data = []
for employee in active_employees:
leave_approvers = department_approver_map.get(employee.department_name, [])
if employee.leave_approver:
leave_approvers.append(employee.leave_approver)
row = [employee.name, employee.employee_name, employee.department]
available_leave = get_leave_details(employee.name, filters.date)
for leave_type in leave_types:

View File

@@ -306,7 +306,7 @@ def get_last_accrual_date(loan, posting_date):
def get_last_disbursement_date(loan, posting_date):
last_disbursement_date = frappe.db.get_value(
"Loan Disbursement",
{"docstatus": 1, "against_loan": loan, "posting_date": ("<", posting_date)},
{"docstatus": 1, "against_loan": loan, "posting_date": ("<=", posting_date)},
"MAX(posting_date)",
)

View File

@@ -378,3 +378,4 @@ erpnext.patches.v13_0.update_schedule_type_in_loans
erpnext.patches.v13_0.update_asset_value_for_manual_depr_entries
erpnext.patches.v13_0.update_docs_link
erpnext.patches.v13_0.correct_asset_value_if_je_with_workflow
execute:frappe.db.set_value("Accounts Settings", "Accounts Settings", "service_provider", "frankfurter.app")

View File

@@ -3,23 +3,27 @@ import frappe
def execute():
frappe.reload_doc("stock", "doctype", "quality_inspection_parameter")
params = set()
# get all distinct parameters from QI readigs table
reading_params = frappe.db.get_all(
"Quality Inspection Reading", fields=["distinct specification"]
)
reading_params = [d.specification for d in reading_params]
# get all parameters from QI readings table
for (p,) in frappe.db.get_all(
"Quality Inspection Reading", fields=["specification"], as_list=True
):
params.add(p.strip())
# get all distinct parameters from QI Template as some may be unused in QI
template_params = frappe.db.get_all(
"Item Quality Inspection Parameter", fields=["distinct specification"]
)
template_params = [d.specification for d in template_params]
# get all parameters from QI Template as some may be unused in QI
for (p,) in frappe.db.get_all(
"Item Quality Inspection Parameter", fields=["specification"], as_list=True
):
params.add(p.strip())
params = list(set(reading_params + template_params))
# because db primary keys are case insensitive, so duplicates will cause an exception
params = set({x.casefold(): x for x in params}.values())
for parameter in params:
if not frappe.db.exists("Quality Inspection Parameter", parameter):
frappe.get_doc(
{"doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter}
).insert(ignore_permissions=True)
if frappe.db.exists("Quality Inspection Parameter", parameter):
continue
frappe.get_doc(
{"doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter}
).insert(ignore_permissions=True)

View File

@@ -125,6 +125,7 @@ def execute():
loan_type_doc.company = loan.company
loan_type_doc.mode_of_payment = loan.mode_of_payment
loan_type_doc.payment_account = loan.payment_account
loan_type_doc.disbursement_account = loan.payment_account
loan_type_doc.loan_account = loan.loan_account
loan_type_doc.interest_income_account = loan.interest_income_account
loan_type_doc.penalty_income_account = penalty_account

View File

@@ -71,6 +71,12 @@ class Timesheet(Document):
if args.is_billable:
if flt(args.billing_hours) == 0.0:
args.billing_hours = args.hours
elif flt(args.billing_hours) > flt(args.hours):
frappe.msgprint(
_("Warning - Row {0}: Billing Hours are more than Actual Hours").format(args.idx),
indicator="orange",
alert=True,
)
else:
args.billing_hours = 0

View File

@@ -13,7 +13,7 @@ frappe.ui.form.on("Communication", {
frappe.confirm(__(confirm_msg, [__("Issue")]), () => {
frm.trigger('make_issue_from_communication');
})
}, "Create");
}, __("Create"));
}
if(!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) {

View File

@@ -1378,7 +1378,7 @@ class GSPConnector:
def set_einvoice_data(self, res):
enc_signed_invoice = res.get("SignedInvoice")
dec_signed_invoice = jwt.decode(enc_signed_invoice, verify=False)["data"]
dec_signed_invoice = jwt.decode(enc_signed_invoice, options={"verify_signature": False})["data"]
self.invoice.irn = res.get("Irn")
self.invoice.ewaybill = res.get("EwbNo")

View File

@@ -532,6 +532,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
primary_action={
"label": "Send Email",
"server_action": "erpnext.selling.doctype.customer.customer.send_emails",
"hide_on_success": True,
"args": {
"customer": customer,
"customer_outstanding": customer_outstanding,

View File

@@ -33,6 +33,7 @@ def after_install():
add_standard_navbar_items()
add_app_name()
add_non_standard_user_types()
update_roles()
frappe.db.commit()
@@ -237,6 +238,12 @@ def create_custom_role(data):
).insert(ignore_permissions=True)
def update_roles():
website_user_roles = ("Customer", "Supplier")
for role in website_user_roles:
frappe.db.set_value("Role", role, "desk_access", 0)
def create_user_type(user_type, data):
if frappe.db.exists("User Type", user_type):
doc = frappe.get_cached_doc("User Type", user_type)

View File

@@ -1184,6 +1184,21 @@ class TestDeliveryNote(FrappeTestCase):
frappe.db.rollback()
frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0)
def test_non_internal_transfer_delivery_note(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
dn = create_delivery_note(do_not_submit=True)
warehouse = create_warehouse("Internal Transfer Warehouse", company=dn.company)
dn.items[0].db_set("target_warehouse", warehouse)
dn.reload()
self.assertEqual(dn.items[0].target_warehouse, warehouse)
dn.save()
dn.reload()
self.assertFalse(dn.items[0].target_warehouse)
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")

View File

@@ -742,6 +742,8 @@ def create_item(
opening_stock=0,
is_fixed_asset=0,
asset_category=None,
buying_cost_center=None,
selling_cost_center=None,
company="_Test Company",
):
if not frappe.db.exists("Item", item_code):
@@ -759,7 +761,15 @@ def create_item(
item.is_purchase_item = is_purchase_item
item.is_customer_provided_item = is_customer_provided_item
item.customer = customer or ""
item.append("item_defaults", {"default_warehouse": warehouse, "company": company})
item.append(
"item_defaults",
{
"default_warehouse": warehouse,
"company": company,
"selling_cost_center": selling_cost_center,
"buying_cost_center": buying_cost_center,
},
)
item.save()
else:
item = frappe.get_doc("Item", item_code)

View File

@@ -1069,88 +1069,6 @@ class TestPurchaseReceipt(FrappeTestCase):
pr1.reload()
pr1.cancel()
def test_stock_transfer_from_purchase_receipt(self):
pr1 = make_purchase_receipt(
warehouse="Work In Progress - TCP1", company="_Test Company with perpetual inventory"
)
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", do_not_save=1
)
pr.supplier_warehouse = ""
pr.items[0].from_warehouse = "Work In Progress - TCP1"
pr.submit()
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
sl_entries = get_sl_entries("Purchase Receipt", pr.name)
self.assertFalse(gl_entries)
expected_sle = {"Work In Progress - TCP1": -5, "Stores - TCP1": 5}
for sle in sl_entries:
self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty)
pr.cancel()
pr1.cancel()
def test_stock_transfer_from_purchase_receipt_with_valuation(self):
create_warehouse(
"_Test Warehouse for Valuation",
company="_Test Company with perpetual inventory",
properties={"account": "_Test Account Stock In Hand - TCP1"},
)
pr1 = make_purchase_receipt(
warehouse="_Test Warehouse for Valuation - TCP1",
company="_Test Company with perpetual inventory",
)
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", do_not_save=1
)
pr.items[0].from_warehouse = "_Test Warehouse for Valuation - TCP1"
pr.supplier_warehouse = ""
pr.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": "_Test Account Shipping Charges - TCP1",
"category": "Valuation and Total",
"cost_center": "Main - TCP1",
"description": "Test",
"rate": 9,
},
)
pr.submit()
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
sl_entries = get_sl_entries("Purchase Receipt", pr.name)
expected_gle = [
["Stock In Hand - TCP1", 272.5, 0.0],
["_Test Account Stock In Hand - TCP1", 0.0, 250.0],
["_Test Account Shipping Charges - TCP1", 0.0, 22.5],
]
expected_sle = {"_Test Warehouse for Valuation - TCP1": -5, "Stores - TCP1": 5}
for sle in sl_entries:
self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty)
for i, gle in enumerate(gl_entries):
self.assertEqual(gle.account, expected_gle[i][0])
self.assertEqual(gle.debit, expected_gle[i][1])
self.assertEqual(gle.credit, expected_gle[i][2])
pr.cancel()
pr1.cancel()
def test_subcontracted_pr_for_multi_transfer_batches(self):
from erpnext.buying.doctype.purchase_order.purchase_order import (
make_purchase_receipt,
@@ -1996,6 +1914,75 @@ class TestPurchaseReceipt(FrappeTestCase):
ste7.reload()
self.assertEqual(ste7.items[0].valuation_rate, valuation_rate)
def test_valuation_rate_in_return_purchase_receipt_for_moving_average(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.stock.stock_ledger import get_previous_sle
# Step - 1: Create an Item (Valuation Method = Moving Average)
item_code = make_item(properties={"is_stock_item": 1, "valuation_method": "Moving Average"}).name
# Step - 2: Create a Purchase Receipt (Qty = 10, Rate = 100)
pr = make_purchase_receipt(qty=10, rate=100, item_code=item_code)
# Step - 3: Create a Material Receipt Stock Entry (Qty = 100, Basic Rate = 10)
warehouse = "_Test Warehouse - _TC"
make_stock_entry(
purpose="Material Receipt",
item_code=item_code,
to_warehouse=warehouse,
qty=100,
rate=10,
)
# Step - 4: Create a Material Issue Stock Entry (Qty = 100, Basic Rate = 18.18 [Auto Fetched])
make_stock_entry(
purpose="Material Issue", item_code=item_code, from_warehouse=warehouse, qty=100
)
# Step - 5: Create a Return Purchase Return (Qty = -8, Rate = 100 [Auto fetched])
return_pr = make_purchase_receipt(
is_return=1,
return_against=pr.name,
item_code=item_code,
qty=-8,
)
sle = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_no": return_pr.name, "voucher_detail_no": return_pr.items[0].name},
["posting_date", "posting_time", "outgoing_rate", "valuation_rate"],
as_dict=1,
)
previous_sle_valuation_rate = get_previous_sle(
{
"item_code": item_code,
"warehouse": warehouse,
"posting_date": sle.posting_date,
"posting_time": sle.posting_time,
}
).get("valuation_rate")
# Test - 1: Valuation Rate should be equal to Outgoing Rate
self.assertEqual(flt(sle.outgoing_rate, 2), flt(sle.valuation_rate, 2))
# Test - 2: Valuation Rate should be equal to Previous SLE Valuation Rate
self.assertEqual(flt(sle.valuation_rate, 2), flt(previous_sle_valuation_rate, 2))
def non_internal_transfer_purchase_receipt(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
pr_doc = make_purchase_receipt(do_not_submit=True)
warehouse = create_warehouse("Internal Transfer Warehouse", pr_doc.company)
pr_doc.items[0].db_set("target_warehouse", "warehouse")
pr_doc.reload()
self.assertEqual(pr_doc.items[0].from_warehouse, warehouse.name)
pr_doc.save()
pr_doc.reload()
self.assertFalse(pr_doc.items[0].from_warehouse)
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier

View File

@@ -5,7 +5,7 @@
from unittest.mock import MagicMock, call
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import nowdate
from frappe.utils.data import add_to_date, today
@@ -173,6 +173,7 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
riv.set_status("Skipped")
@change_settings("Stock Reposting Settings", {"item_based_reposting": 0})
def test_prevention_of_cancelled_transaction_riv(self):
frappe.flags.dont_execute_stock_reposts = True
@@ -295,6 +296,7 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
accounts_settings.acc_frozen_upto = ""
accounts_settings.save()
@change_settings("Stock Reposting Settings", {"item_based_reposting": 0})
def test_create_repost_entry_for_cancelled_document(self):
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",

View File

@@ -1414,6 +1414,7 @@ class TestStockEntry(FrappeTestCase):
self.assertEqual(se.items[0].item_name, item.item_name)
self.assertEqual(se.items[0].stock_uom, item.stock_uom)
@change_settings("Stock Reposting Settings", {"item_based_reposting": 0})
def test_reposting_for_depedent_warehouse(self):
from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import repost_sl_entries
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse

View File

@@ -48,7 +48,7 @@
"label": "Limit timeslot for Stock Reposting"
},
{
"default": "0",
"default": "1",
"fieldname": "item_based_reposting",
"fieldtype": "Check",
"label": "Use Item based reposting"
@@ -57,7 +57,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-11-02 01:22:45.155841",
"modified": "2023-11-01 16:14:29.080697",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reposting Settings",
@@ -77,4 +77,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View File

@@ -751,6 +751,12 @@ def get_default_cost_center(args, item=None, item_group=None, brand=None, compan
data = frappe.get_attr(path)(args.get("item_code"), company)
if data and (data.selling_cost_center or data.buying_cost_center):
if args.get("customer") and data.selling_cost_center:
return data.selling_cost_center
elif args.get("supplier") and data.buying_cost_center:
return data.buying_cost_center
return data.selling_cost_center or data.buying_cost_center
if not cost_center and args.get("cost_center"):

View File

@@ -13,10 +13,18 @@ const DIFFERENCE_FIELD_NAMES = [
frappe.query_reports["Stock Ledger Variance"] = {
"filters": [
{
"fieldname": "company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"reqd": 1,
"default": frappe.defaults.get_user_default("Company")
},
{
"fieldname": "item_code",
"fieldtype": "Link",
"label": "Item",
"label": __("Item"),
"options": "Item",
get_query: function() {
return {
@@ -27,7 +35,7 @@ frappe.query_reports["Stock Ledger Variance"] = {
{
"fieldname": "warehouse",
"fieldtype": "Link",
"label": "Warehouse",
"label": __("Warehouse"),
"options": "Warehouse",
get_query: function() {
return {
@@ -38,7 +46,7 @@ frappe.query_reports["Stock Ledger Variance"] = {
{
"fieldname": "difference_in",
"fieldtype": "Select",
"label": "Difference In",
"label": __("Difference In"),
"options": [
"",
"Qty",
@@ -49,7 +57,7 @@ frappe.query_reports["Stock Ledger Variance"] = {
{
"fieldname": "include_disabled",
"fieldtype": "Check",
"label": "Include Disabled",
"label": __("Include Disabled"),
}
],

View File

@@ -55,6 +55,11 @@ def get_columns():
"label": _("Warehouse"),
"options": "Warehouse",
},
{
"fieldname": "valuation_method",
"fieldtype": "Data",
"label": _("Valuation Method"),
},
{
"fieldname": "voucher_type",
"fieldtype": "Link",
@@ -194,6 +199,7 @@ def get_columns():
def get_data(filters=None):
filters = frappe._dict(filters or {})
item_warehouse_map = get_item_warehouse_combinations(filters)
valuation_method = frappe.db.get_single_value("Stock Settings", "valuation_method")
data = []
if item_warehouse_map:
@@ -206,8 +212,17 @@ def get_data(filters=None):
continue
for row in report_data:
if has_difference(row, precision, filters.difference_in):
data.append(add_item_warehouse_details(row, item_warehouse))
if has_difference(
row, precision, filters.difference_in, item_warehouse.valuation_method or valuation_method
):
row.update(
{
"item_code": item_warehouse.item_code,
"warehouse": item_warehouse.warehouse,
"valuation_method": item_warehouse.valuation_method or valuation_method,
}
)
data.append(row)
break
return data
@@ -229,8 +244,14 @@ def get_item_warehouse_combinations(filters: dict = None) -> dict:
.select(
bin.item_code,
bin.warehouse,
item.valuation_method,
)
.where(
(item.is_stock_item == 1)
& (item.has_serial_no == 0)
& (warehouse.is_group == 0)
& (warehouse.company == filters.company)
)
.where((item.is_stock_item == 1) & (item.has_serial_no == 0) & (warehouse.is_group == 0))
)
if filters.item_code:
@@ -243,37 +264,27 @@ def get_item_warehouse_combinations(filters: dict = None) -> dict:
return query.run(as_dict=1)
def has_difference(row, precision, difference_in):
has_qty_difference = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision)
has_value_difference = (
flt(row.diff_value_diff, precision)
or flt(row.fifo_value_diff, precision)
or flt(row.fifo_difference_diff, precision)
)
has_valuation_difference = flt(row.valuation_diff, precision) or flt(
row.fifo_valuation_diff, precision
)
def has_difference(row, precision, difference_in, valuation_method):
if valuation_method == "Moving Average":
qty_diff = flt(row.difference_in_qty, precision)
value_diff = flt(row.diff_value_diff, precision)
valuation_diff = flt(row.valuation_diff, precision)
else:
qty_diff = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision)
value_diff = (
flt(row.diff_value_diff, precision)
or flt(row.fifo_value_diff, precision)
or flt(row.fifo_difference_diff, precision)
)
valuation_diff = flt(row.valuation_diff, precision) or flt(row.fifo_valuation_diff, precision)
if difference_in == "Qty" and has_qty_difference:
if difference_in == "Qty" and qty_diff:
return True
elif difference_in == "Value" and has_value_difference:
elif difference_in == "Value" and value_diff:
return True
elif difference_in == "Valuation" and has_valuation_difference:
elif difference_in == "Valuation" and valuation_diff:
return True
elif difference_in not in ["Qty", "Value", "Valuation"] and (
has_qty_difference or has_value_difference or has_valuation_difference
qty_diff or value_diff or valuation_diff
):
return True
return False
def add_item_warehouse_details(row, item_warehouse):
row.update(
{
"item_code": item_warehouse.item_code,
"warehouse": item_warehouse.warehouse,
}
)
return row

View File

@@ -658,14 +658,16 @@ class update_entries_after(object):
get_rate_for_return, # don't move this import to top
)
rate = get_rate_for_return(
sle.voucher_type,
sle.voucher_no,
sle.item_code,
voucher_detail_no=sle.voucher_detail_no,
sle=sle,
)
if self.valuation_method == "Moving Average":
rate = flt(self.data[self.args.warehouse].previous_sle.valuation_rate)
else:
rate = get_rate_for_return(
sle.voucher_type,
sle.voucher_no,
sle.item_code,
voucher_detail_no=sle.voucher_detail_no,
sle=sle,
)
elif (
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
and sle.voucher_detail_no

View File

@@ -7,7 +7,7 @@
{% if d.thumbnail or d.image %}
{{ product_image(d.thumbnail or d.image, no_border=True) }}
{% else %}
<div class="no-image-cart-item" style="min-height: 100px;">
<div class="no-image-cart-item" style="min-height: 50px;">
{{ frappe.utils.get_abbr(d.item_name) or "NA" }}
</div>
{% endif %}

View File

@@ -81,7 +81,7 @@ rfq = Class.extend({
doc: doc
},
btn: this,
callback: function(r){
callback: function(r) {
frappe.unfreeze();
if(r.message){
$('.btn-sm').hide()

View File

@@ -1,19 +1,25 @@
{% from "erpnext/templates/includes/macros.html" import product_image_square, product_image %}
{% macro item_name_and_description(d, doc) %}
<div class="row">
<div class="col-3">
{{ product_image(d.image) }}
</div>
<div class="col-9">
{{ d.item_code }}
<p class="text-muted small">{{ d.description }}</p>
<div class="row">
<div class="col-3">
{% if d.image %}
{{ product_image(d.image) }}
{% else %}
<div class="website-image h-100 w-100" style="background-color:var(--gray-100);text-align: center;line-height: 3.6;">
{{ frappe.utils.get_abbr(d.item_name)}}
</div>
{% endif %}
</div>
<div class="col-9">
{{ d.item_code }}
<p class="text-muted small">{{ d.description }}</p>
{% set supplier_part_no = frappe.db.get_value("Item Supplier", {'parent': d.item_code, 'supplier': doc.supplier}, "supplier_part_no") %}
<p class="text-muted small supplier-part-no">
{% if supplier_part_no %}
{{_("Supplier Part No") + ": "+ supplier_part_no}}
{% endif %}
</p>
</div>
</div>
</div>
</div>
{% endmacro %}

View File

@@ -153,7 +153,6 @@
</div>
{% endif %}
{% if attachments %}
<div class="order-item-table">
<div class="row order-items order-item-header text-muted">
@@ -181,6 +180,7 @@
{% endif %}
{% endblock %}
{% block script %}
<script> {% include "templates/pages/order.js" %} </script>
<script>

View File

@@ -1,7 +1,7 @@
{% extends "templates/web.html" %}
{% block header %}
<h1>{{ doc.name }}</h1>
<h1 style="margin-top: 10px;">{{ doc.name }}</h1>
{% endblock %}
{% block script %}
@@ -16,7 +16,7 @@
{% if doc.items %}
<button class="btn btn-primary btn-sm"
type="button">
{{ _("Submit") }}</button>
{{ _("Make Quotation") }}</button>
{% endif %}
{% endblock %}

View File

@@ -369,7 +369,7 @@ Base,Basis,
Base URL,Basis-URL,
Based On,Basiert auf,
Based On Payment Terms,Basierend auf Zahlungsbedingungen,
Basic,Grundeinkommen,
Basic,Basic,
Batch,Charge,
Batch Entries,Batch-Einträge,
Batch ID is mandatory,Batch-ID ist obligatorisch,
Can't render this file because it is too large.

View File

@@ -4,7 +4,7 @@ googlemaps # used in ERPNext, but dependency is defined in Frappe
pandas>=1.1.5,<2.0.0
plaid-python~=7.2.1
pycountry~=20.7.3
PyGithub~=1.54.1
PyGithub~=2.1.1
python-stdnum~=1.16
python-youtube~=0.8.0
taxjar~=1.9.2