Compare commits

..

103 Commits

Author SHA1 Message Date
Deepesh Garg
c2dec3389b Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2022-04-13 14:11:23 +05:30
Deepesh Garg
512b480f6a chore: version bump 2022-04-12 23:31:47 +05:30
Deepesh Garg
d5d6b34380 Merge pull request #30695 from deepeshgarg007/v13_26_0
chore: change log and version bump
2022-04-12 23:29:44 +05:30
Deepesh Garg
2c2d5ccb8f Merge pull request #30694 from frappe/version-13-pre-release
chore: Release for v13.26.0
2022-04-12 23:28:24 +05:30
Deepesh Garg
147499bc9c chore: change log and version bump 2022-04-12 23:01:09 +05:30
Deepesh Garg
08e2b9110c Merge pull request #30693 from frappe/version-13-hotfix
chore: Pre release for v13.26.0
2022-04-12 16:56:52 +05:30
Deepesh Garg
4a48a6aae3 Merge pull request #30690 from frappe/mergify/bp/version-13-hotfix/pr-30686
feat: Ignore permlevel for specific fields (backport #30686)
2022-04-12 16:10:51 +05:30
Nabin Hait
e650d99cdd feat: Ignore permlevel for specific fields
(cherry picked from commit 993c6c0de9)
2022-04-12 10:26:17 +00:00
Deepesh Garg
23ccadbcae Merge pull request #30683 from frappe/mergify/bp/version-13-hotfix/pr-30651
fix: Download JSON for GSTR-1 report (backport #30651)
2022-04-12 14:00:55 +05:30
Deepesh Garg
bfd8ab6d72 Merge pull request #30687 from frappe/mergify/bp/version-13-hotfix/pr-30626
feat(india): e-invoicing for intra-state union territory transactions (backport #30626)
2022-04-12 14:00:38 +05:30
Saqib Ansari
2ab431ad36 feat(india): e-invoicing for intra-state union territory transactions
(cherry picked from commit 45fca6bed7)
2022-04-12 08:03:23 +00:00
mergify[bot]
21066a48b6 fix: ignore item-less maintenance visit for sr no (#30684) (#30685)
(cherry picked from commit 60fb71bd2a)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-12 11:16:59 +05:30
Deepesh Garg
9efc0d1043 fix: Download JSON for GSTR-1 report
(cherry picked from commit b532ade383)
2022-04-11 18:37:17 +00:00
Deepesh Garg
54ea9319a1 Merge pull request #30680 from frappe/mergify/bp/version-13-hotfix/pr-30602
fix: Deferred Revenue/Expense Account validation (backport #30602)
2022-04-11 20:54:52 +05:30
Marica
f54f9771e4 Merge pull request #30681 from frappe/mergify/bp/version-13-hotfix/pr-30674
fix: Handle multiple item transfer in separate SEs against WO (backport #30674)
2022-04-11 17:46:12 +05:30
marination
e322e7654a test: Multiple RM transfer in separate Stock Entries
- Added test and acceptance of 0 as For Quantity in test helper

(cherry picked from commit 5aa60bb651)
2022-04-11 11:54:48 +00:00
marination
c674180f1a style: Missing Semicolon
(cherry picked from commit be2e5ce966)
2022-04-11 11:54:48 +00:00
marination
d079d8771b fix: Handle multiple item transfer in separate SEs against WO
- Check for pending qty in child items to show/hide "Start" button
- If no qty needed to transfer (FG qty is fulfilled), but RM qty pending: map pending in SE with For Quantity = 0

(cherry picked from commit dfff4beaf4)
2022-04-11 11:54:48 +00:00
Deepesh Garg
4296fc36cc test: Add test
(cherry picked from commit 553178bfe7)
2022-04-11 11:33:33 +00:00
Deepesh Garg
f6c9f052d2 fix: Deferred Revenue/Expense Account validation
(cherry picked from commit 9bf5f76ac8)
2022-04-11 11:33:33 +00:00
Ankush Menat
c9af4e8ce5 chore: broken link 2022-04-11 16:55:22 +05:30
mergify[bot]
e91dea62b8 fix: update translation (#30654) (#30676)
(cherry picked from commit 03c631d723)

Co-authored-by: HENRY Florian <florian.henry@open-concept.pro>
2022-04-11 14:48:17 +05:30
Deepesh Garg
cc3c9c7042 Merge pull request #30662 from deepeshgarg007/implicit_ignore_prcing_rule_check_on_returns
fix: Implicit ignore pricing rule check on returns
2022-04-09 20:15:57 +05:30
Deepesh Garg
8e742d6612 Merge pull request #30663 from frappe/mergify/bp/version-13-hotfix/pr-30542
fix: Ignore disabled tax categories (backport #30542)
2022-04-09 20:15:30 +05:30
Deepesh Garg
37dc11e59a fix: Ignore disabled tax categories
(cherry picked from commit 9a6a181f14)
2022-04-09 14:22:53 +00:00
Deepesh Garg
1b25a7fe76 fix: Implicit ignore pricing rule check on returns 2022-04-09 19:20:51 +05:30
mergify[bot]
e69849d333 fix(pos): cannot change paid amount in pos payments (#30661) 2022-04-09 16:28:25 +05:30
Saqib Ansari
e4bb81d830 fix(pos): cannot change paid amount in pos payments (#30657) 2022-04-09 16:13:29 +05:30
Jannat Patel
d66979ff52 Merge pull request #30650 from frappe/mergify/bp/version-13-hotfix/pr-30596 2022-04-08 16:50:38 +05:30
Jannat Patel
2ccf58d6ad fix: removed unused courses template
(cherry picked from commit bce1c2a028)
2022-04-08 10:58:18 +00:00
Ankush Menat
9b6c5f2e54 test: prevent cancelling RIV of cancelled voucher
(cherry picked from commit d74181630a)
2022-04-08 16:14:01 +05:30
Ankush Menat
8cac70a63c fix: prevent deleting repost queue for cancelled transactions
(cherry picked from commit a281998bcb)
2022-04-08 16:14:01 +05:30
Deepesh Garg
8d4d12c3b0 Merge pull request #30633 from frappe/mergify/bp/version-13-hotfix/pr-30606
fix: Exchange gain and loss button in Payment Entry (backport #30606)
2022-04-08 13:05:23 +05:30
mergify[bot]
341e8dffd3 fix: remove bad defaults from BOM operation (#30644) (#30645)
[skip ci]

(cherry picked from commit 49560d20bc)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-08 11:27:17 +05:30
Deepesh Garg
13f9207b41 Merge pull request #30620 from nabinhait/party-account-in-ar-ap-report-v13
feat: Receivable/Payable Account column and filter in AR/AP report
2022-04-07 21:24:56 +05:30
Deepesh Garg
a4ea9fa801 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-30606 2022-04-07 21:24:24 +05:30
mergify[bot]
c0cf18d2d6 Merge pull request #30624 from frappe/mergify/bp/version-13-hotfix/pr-30499
feat: 'customer' column and more filter to Payment terms status report (backport #30499)
2022-04-07 13:47:58 +00:00
Ankush Menat
d62928ed65 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-30499 2022-04-07 19:02:47 +05:30
mergify[bot]
4a7ae5c4a7 fix: dont reassign mutable (list) to a different field (backport #30634) (#30635)
This is an automatic backport of pull request #30634 done by [Mergify](https://mergify.com).
Conflicts fixed.
2022-04-07 13:30:24 +00:00
Deepesh Garg
be438c08db fix: Exchange gain and loss button in Payment Entry
(cherry picked from commit 8feb4f08c5)
2022-04-07 11:19:25 +00:00
Nabin Hait
709fcd5051 feat: Receivable/Payable Account column and filter in AR/AP report 2022-04-07 15:56:42 +05:30
mergify[bot]
560c55935f fix: warehouse naming when suffix is present (#30621) (#30629)
(cherry picked from commit be04eaf723)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-07 15:37:33 +05:30
Chillar Anand
30a86951c3 test: Fix failing tests for healthcare service unit (#30625) 2022-04-07 14:58:38 +05:30
mergify[bot]
4b77a4de28 fix: update translation (backport #30474) (#30593)
* fix: update translation (#30474)

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

* fix: update translation

(cherry picked from commit 4895761d89)

* chore: fix translation csv file

Co-authored-by: HENRY Florian <florian.henry@open-concept.pro>
Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-07 13:24:48 +05:30
Rucha Mahabal
8932d7040f fix: conflicts 2022-04-07 13:21:06 +05:30
Rucha Mahabal
07ff2cb1d3 fix: conflicts 2022-04-07 13:21:06 +05:30
Rucha Mahabal
8393d113d4 fix: show allocation history for earned leaves allocated via scheduler
(cherry picked from commit ec65af5f38)
2022-04-07 13:21:06 +05:30
Rucha Mahabal
c68a38eb5b fix: make New Leaves Allocated read-only if policy assignment is linked to the allocation and leave type is earned leave
(cherry picked from commit 6203ffc8fa)
2022-04-07 13:21:06 +05:30
Rucha Mahabal
0c163eef23 fix: enable Track Changes in Leave Allocation
(cherry picked from commit f8f1c3d8b5)

# Conflicts:
#	erpnext/hr/doctype/leave_allocation/leave_allocation.json
2022-04-07 13:21:06 +05:30
mergify[bot]
af039be03e fix: fallback to item_name if description is not found (backport #30619) (#30622)
* fix: strip html tags before checking for empty description (#30619)

(cherry picked from commit e4c6d6a1a6)

# Conflicts:
#	erpnext/stock/doctype/item/test_item.py

* fix: resolve conflicts

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-07 13:20:36 +05:30
ruthra kumar
39f8ee2ea6 test: added test cases for group filters
(cherry picked from commit 16bfb930f8)
2022-04-07 07:32:04 +00:00
ruthra kumar
a9d5000eab refactor: use group fields from Sales Order and Sales Order Items
(cherry picked from commit b2ed9fd3fe)
2022-04-07 07:32:03 +00:00
ruthra kumar
00acb000dc refactor: item filters are linked with group filters
(cherry picked from commit e324d668d3)
2022-04-07 07:32:02 +00:00
ruthra kumar
9336a3413f refactor: adding new filters and column to test cases
(cherry picked from commit 7558f1b078)
2022-04-07 07:32:02 +00:00
ruthra kumar
c6f946e3e8 feat: additional filters in payment terms status report
(cherry picked from commit eaeadbc422)
2022-04-07 07:32:01 +00:00
Saqib Ansari
d720d15e3d fix(pos): reload doc before set value (#30610) 2022-04-06 22:32:23 +05:30
Saqib Ansari
2e29cf38bb Merge pull request #30611 from nextchamp-saqib/fix-pos-patch-v13
fix(pos): reload doc before set value
2022-04-06 22:28:49 +05:30
Saqib Ansari
f94afc1fff fix(pos): reload doc before set value 2022-04-06 22:14:37 +05:30
Ankush Menat
f89c1b2c0c fix: dont trigger closed WO check on new Job card 2022-04-06 18:28:54 +05:30
mergify[bot]
52aa3561e3 fix: hide pending qty only if original item is assigned (#30599) (#30600)
(cherry picked from commit 8b090a9f7d)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-06 16:38:25 +05:30
mergify[bot]
14f46a3e26 fix: check null values in is_cancelled patch (#30594) (#30595)
(cherry picked from commit bb875fe217)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2022-04-06 15:14:37 +05:30
Deepesh Garg
bffd7feadb fix: Remove docstatus check during budget validate 2022-03-11 16:59:28 +05:30
Deepesh Garg
1fe608ea23 Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2022-03-11 16:55:03 +05:30
Deepesh Garg
9464b3c3e6 fix: Make abbreviation limit to 10 2022-02-23 20:43:48 +05:30
Deepesh Garg
8da929635f fix: Handle stringified jsons 2022-02-09 12:29:09 +05:30
Deepesh Garg
1f6e0b871b fix: Incorrect tax template in Sales Invocie via data import 2022-02-09 11:59:10 +05:30
Deepesh Garg
18ac08413b feat: API method to created consolidated purchase invoice from receipts 2022-02-09 11:58:01 +05:30
Deepesh Garg
0f83ba221f fix: first depreciation amount in asset 2022-01-21 12:43:09 +05:30
Deepesh Garg
e5898a37dc chore: Add test case 2022-01-19 16:43:38 +05:30
Deepesh Garg
f4b3e208b7 feat: Splitting group assets 2022-01-19 16:43:36 +05:30
Deepesh Garg
eeb3d72dc0 Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2022-01-14 11:43:58 +05:30
Deepesh Garg
29ca1950a2 Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-12-29 18:25:52 +05:30
Deepesh Garg
8a4de134ac Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-12-14 11:38:46 +05:30
Deepesh Garg
69c9856bee Merge branch 'enterprise-hotfix' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-12-14 11:37:21 +05:30
Saqib
4343bdd8db fix: cannot load company form (#28536) 2021-11-24 11:04:15 +05:30
Saqib
2bf30db3d1 fix: Add indexes in stock queries (#27888) 2021-10-11 10:27:23 +05:30
Deepesh Garg
8b1be1db4c fix: Incorrect actual qty updation 2021-10-11 09:54:35 +05:30
Deepesh Garg
13d501c0b9 fix: Remove print statements 2021-10-11 09:54:33 +05:30
Deepesh Garg
59ae7e09cb fix: Remove unwanted comments 2021-10-11 09:54:14 +05:30
Deepesh Garg
4425442bbb fix: Optimization on Bin updation 2021-10-11 09:53:55 +05:30
Deepesh Garg
ed98125807 fix: Add indexes in stock queries 2021-10-11 09:53:44 +05:30
Deepesh Garg
5aae91e6cb Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-10-11 09:49:47 +05:30
mergify[bot]
c6725d43cb fix: cannot set custom label for 'total' field in print format (#27666) 2021-09-27 14:29:54 +05:30
Subin Tom
f36ed34907 fix: Tax Breakup table headers fix 2021-09-20 16:16:35 +05:30
Deepesh Garg
ccf1472c56 fix: Update modified timestamp in selling settings 2021-09-20 14:14:59 +05:30
Subin Tom
bb95287357 fix: tax breakup test fix, eway bill hsn fix 2021-09-20 13:01:50 +05:30
Subin Tom
35d6c6ade4 fix: added gst fields,warehouse validation to pos inv,patch 2021-09-20 13:01:49 +05:30
Subin Tom
f9c46e766b fix: Tax breakup based on items 2021-09-20 13:00:33 +05:30
Deepesh Garg
1c183c16d4 Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-09-20 12:57:46 +05:30
Saqib
a4140a1487 fix: fieldname 2021-09-14 13:50:51 +05:30
Deepesh Garg
3c20553104 fix: Linting Issues 2021-09-14 13:18:42 +05:30
Deepesh Garg
b169558885 feat: Merge POS invoices based on customer group 2021-09-14 13:18:24 +05:30
Deepesh Garg
21d2efbbdc fix: Add missing imports 2021-09-09 11:52:30 +05:30
Deepesh Garg
591b57a02a fix: Add missing imports 2021-09-09 11:22:04 +05:30
Deepesh Garg
be55fb31e4 fix: Autoname for customer and supplier 2021-09-09 10:27:06 +05:30
Deepesh Garg
0419ba1d78 Merge branch 'enterprise-hotfix' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-09-07 18:15:01 +05:30
Deepesh Garg
945214771a Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-09-07 18:14:01 +05:30
Deepesh Garg
0e10f69c6e Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-08-23 23:00:54 +05:30
Deepesh Garg
3d039d0fac fix: Store records to delete in a separate list 2021-08-12 18:19:21 +05:30
Deepesh Garg
b74db6f2b8 fix: Cascade deletion for Company 2021-08-12 17:53:27 +05:30
Deepesh Garg
efc76f4245 Merge branch 'version-13' of https://github.com/frappe/erpnext into enterprise-hotfix 2021-08-12 17:46:44 +05:30
Deepesh Garg
653e1455ed fix: Use update flag for company dependant fixtures 2021-07-13 20:02:38 +05:30
Deepesh Garg
ac5e7f2b74 fix: Error on creation of company for India 2021-07-13 20:01:36 +05:30
54 changed files with 1130 additions and 337 deletions

View File

@@ -4,7 +4,7 @@ import frappe
from erpnext.hooks import regional_overrides
__version__ = '13.25.2'
__version__ = "13.26.0"
def get_default_company(user=None):
"""Get default company for user"""

View File

@@ -386,7 +386,6 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
doc,
credit_account,
debit_account,
against,
amount,
base_amount,
end_date,
@@ -570,7 +569,6 @@ def book_revenue_via_journal_entry(
doc,
credit_account,
debit_account,
against,
amount,
base_amount,
posting_date,
@@ -591,6 +589,7 @@ def book_revenue_via_journal_entry(
journal_entry.voucher_type = (
"Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
)
journal_entry.process_deferred_accounting = deferred_process
debit_entry = {
"account": credit_account,

View File

@@ -10,6 +10,7 @@
"sgst_account",
"igst_account",
"cess_account",
"utgst_account",
"is_reverse_charge_account"
],
"fields": [
@@ -64,12 +65,18 @@
"fieldtype": "Check",
"in_list_view": 1,
"label": "Is Reverse Charge Account"
},
{
"fieldname": "utgst_account",
"fieldtype": "Link",
"label": "UTGST Account",
"options": "Account"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-04-09 12:30:25.889993",
"modified": "2022-04-07 12:59:14.039768",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GST Account",
@@ -78,5 +85,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -3,7 +3,7 @@
"allow_auto_repeat": 1,
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-03-25 10:53:52",
"creation": "2022-01-25 10:29:58.717206",
"doctype": "DocType",
"document_type": "Document",
"engine": "InnoDB",
@@ -13,6 +13,7 @@
"voucher_type",
"naming_series",
"finance_book",
"process_deferred_accounting",
"reversal_of",
"tax_withholding_category",
"column_break1",
@@ -524,13 +525,20 @@
"label": "Reversal Of",
"options": "Journal Entry",
"read_only": 1
},
{
"fieldname": "process_deferred_accounting",
"fieldtype": "Link",
"label": "Process Deferred Accounting",
"options": "Process Deferred Accounting",
"read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 176,
"is_submittable": 1,
"links": [],
"modified": "2022-01-04 13:39:36.485954",
"modified": "2022-04-06 17:18:46.865259",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",
@@ -578,6 +586,7 @@
"search_fields": "voucher_type,posting_date, due_date, cheque_no",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "title",
"track_changes": 1
}

View File

@@ -226,10 +226,7 @@ frappe.ui.form.on('Payment Entry', {
(frm.doc.total_allocated_amount > party_amount)));
frm.toggle_display("set_exchange_gain_loss",
(frm.doc.paid_amount && frm.doc.received_amount && frm.doc.difference_amount &&
((frm.doc.paid_from_account_currency != company_currency ||
frm.doc.paid_to_account_currency != company_currency) &&
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency)));
frm.doc.paid_amount && frm.doc.received_amount && frm.doc.difference_amount);
frm.refresh_fields();
},

View File

@@ -11,7 +11,7 @@ from erpnext.accounts.deferred_revenue import (
convert_deferred_expense_to_expense,
convert_deferred_revenue_to_income,
)
from erpnext.accounts.general_ledger import make_reverse_gl_entries
from erpnext.accounts.general_ledger import make_gl_entries
class ProcessDeferredAccounting(Document):
@@ -34,4 +34,4 @@ class ProcessDeferredAccounting(Document):
filters={"against_voucher_type": self.doctype, "against_voucher": self.name},
)
make_reverse_gl_entries(gl_entries=gl_entries)
make_gl_entries(gl_entries=gl_entries, cancel=1)

View File

@@ -2270,6 +2270,14 @@ class TestSalesInvoice(unittest.TestCase):
check_gl_entries(self, si.name, expected_gle, "2019-01-30")
def test_deferred_revenue_missing_account(self):
si = create_sales_invoice(posting_date="2019-01-10", do_not_submit=True)
si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-10"
si.items[0].service_end_date = "2019-03-15"
self.assertRaises(frappe.ValidationError, si.save)
def test_fixed_deferred_revenue(self):
deferred_account = create_account(
account_name="Deferred Revenue",
@@ -3042,7 +3050,7 @@ class TestSalesInvoice(unittest.TestCase):
acc_settings = frappe.get_single("Accounts Settings")
acc_settings.book_deferred_entries_via_journal_entry = 0
acc_settings.submit_journal_entriessubmit_journal_entries = 0
acc_settings.submit_journal_entries = 0
acc_settings.save()
frappe.db.set_value("Accounts Settings", None, "acc_frozen_upto", None)

View File

@@ -53,6 +53,22 @@ frappe.query_reports["Accounts Payable"] = {
}
}
},
{
"fieldname": "party_account",
"label": __("Payable Account"),
"fieldtype": "Link",
"options": "Account",
get_query: () => {
var company = frappe.query_report.get_filter_value('company');
return {
filters: {
'company': company,
'account_type': 'Payable',
'is_group': 0
}
};
}
},
{
"fieldname": "ageing_based_on",
"label": __("Ageing Based On"),

View File

@@ -66,6 +66,22 @@ frappe.query_reports["Accounts Receivable"] = {
}
}
},
{
"fieldname": "party_account",
"label": __("Receivable Account"),
"fieldtype": "Link",
"options": "Account",
get_query: () => {
var company = frappe.query_report.get_filter_value('company');
return {
filters: {
'company': company,
'account_type': 'Receivable',
'is_group': 0
}
};
}
},
{
"fieldname": "ageing_based_on",
"label": __("Ageing Based On"),

View File

@@ -111,6 +111,7 @@ class ReceivablePayableReport(object):
voucher_type=gle.voucher_type,
voucher_no=gle.voucher_no,
party=gle.party,
party_account=gle.account,
posting_date=gle.posting_date,
account_currency=gle.account_currency,
remarks=gle.remarks if self.filters.get("show_remarks") else None,
@@ -777,18 +778,21 @@ class ReceivablePayableReport(object):
conditions.append("party=%s")
values.append(self.filters.get(party_type_field))
# get GL with "receivable" or "payable" account_type
account_type = "Receivable" if self.party_type == "Customer" else "Payable"
accounts = [
d.name
for d in frappe.get_all(
"Account", filters={"account_type": account_type, "company": self.filters.company}
)
]
if accounts:
conditions.append("account in (%s)" % ",".join(["%s"] * len(accounts)))
values += accounts
if self.filters.party_account:
conditions.append("account =%s")
values.append(self.filters.party_account)
else:
# get GL with "receivable" or "payable" account_type
account_type = "Receivable" if self.party_type == "Customer" else "Payable"
accounts = [
d.name
for d in frappe.get_all(
"Account", filters={"account_type": account_type, "company": self.filters.company}
)
]
if accounts:
conditions.append("account in (%s)" % ",".join(["%s"] * len(accounts)))
values += accounts
def add_customer_filters(self, conditions, values):
if self.filters.get("customer_group"):
@@ -889,6 +893,14 @@ class ReceivablePayableReport(object):
width=180,
)
self.add_column(
label="Receivable Account" if self.party_type == "Customer" else "Payable Account",
fieldname="party_account",
fieldtype="Link",
options="Account",
width=180,
)
if self.party_naming_by == "Naming Series":
self.add_column(
_("{0} Name").format(self.party_type),

View File

@@ -50,12 +50,19 @@ class TestAccountsReceivable(unittest.TestCase):
make_credit_note(name)
report = execute(filters)
expected_data_after_credit_note = [100, 0, 0, 40, -40]
expected_data_after_credit_note = [100, 0, 0, 40, -40, "Debtors - _TC2"]
row = report[1][0]
self.assertEqual(
expected_data_after_credit_note,
[row.invoice_grand_total, row.invoiced, row.paid, row.credit_note, row.outstanding],
[
row.invoice_grand_total,
row.invoiced,
row.paid,
row.credit_note,
row.outstanding,
row.party_account,
],
)

View File

@@ -124,11 +124,10 @@ def get_columns(invoice_list, additional_table_columns):
_("Purchase Receipt") + ":Link/Purchase Receipt:100",
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
]
expense_accounts = (
tax_accounts
) = (
expense_columns
) = tax_columns = unrealized_profit_loss_accounts = unrealized_profit_loss_account_columns = []
expense_accounts = []
tax_accounts = []
unrealized_profit_loss_accounts = []
if invoice_list:
expense_accounts = frappe.db.sql_list(
@@ -163,10 +162,11 @@ def get_columns(invoice_list, additional_table_columns):
unrealized_profit_loss_account_columns = [
(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts
]
for account in tax_accounts:
if account not in expense_accounts:
tax_columns.append(account + ":Currency/currency:120")
tax_columns = [
(account + ":Currency/currency:120")
for account in tax_accounts
if account not in expense_accounts
]
columns = (
columns

View File

@@ -108,6 +108,10 @@ frappe.ui.form.on('Asset', {
frm.trigger("create_asset_repair");
}, __("Manage"));
frm.add_custom_button(__("Split Asset"), function() {
frm.trigger("split_asset");
}, __("Manage"));
if (frm.doc.status != 'Fully Depreciated') {
frm.add_custom_button(__("Adjust Asset Value"), function() {
frm.trigger("create_asset_value_adjustment");
@@ -322,6 +326,43 @@ frappe.ui.form.on('Asset', {
});
},
split_asset: function(frm) {
const title = __('Split Asset');
const fields = [
{
fieldname: 'split_qty',
fieldtype: 'Int',
label: __('Split Qty'),
reqd: 1
}
];
let dialog = new frappe.ui.Dialog({
title: title,
fields: fields
});
dialog.set_primary_action(__('Split'), function() {
const dialog_data = dialog.get_values();
frappe.call({
args: {
"asset_name": frm.doc.name,
"split_qty": cint(dialog_data.split_qty)
},
method: "erpnext.assets.doctype.asset.asset.split_asset",
callback: function(r) {
let doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
}
});
dialog.hide();
});
dialog.show();
},
create_asset_value_adjustment: function(frm) {
frappe.call({
args: {

View File

@@ -3,7 +3,7 @@
"allow_import": 1,
"allow_rename": 1,
"autoname": "naming_series:",
"creation": "2016-03-01 17:01:27.920130",
"creation": "2022-01-18 02:26:55.975005",
"doctype": "DocType",
"document_type": "Document",
"engine": "InnoDB",
@@ -23,6 +23,7 @@
"asset_name",
"asset_category",
"location",
"split_from",
"custodian",
"department",
"disposal_date",
@@ -35,6 +36,7 @@
"available_for_use_date",
"column_break_23",
"gross_purchase_amount",
"asset_quantity",
"purchase_date",
"section_break_23",
"calculate_depreciation",
@@ -481,6 +483,18 @@
"fieldname": "section_break_36",
"fieldtype": "Section Break",
"label": "Finance Books"
},
{
"fieldname": "split_from",
"fieldtype": "Link",
"label": "Split From",
"options": "Asset",
"read_only": 1
},
{
"fieldname": "asset_quantity",
"fieldtype": "Int",
"label": "Asset Quantity"
}
],
"idx": 72,
@@ -507,6 +521,7 @@
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -543,6 +558,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "asset_name",
"track_changes": 1
}

View File

@@ -39,7 +39,8 @@ class Asset(AccountsController):
self.validate_item()
self.validate_cost_center()
self.set_missing_values()
self.prepare_depreciation_data()
if not self.split_from:
self.prepare_depreciation_data()
self.validate_gross_and_purchase_amount()
if self.get("schedules"):
self.validate_expected_value_after_useful_life()
@@ -235,175 +236,180 @@ class Asset(AccountsController):
start = self.clear_depreciation_schedule()
for finance_book in self.get("finance_books"):
self.validate_asset_finance_books(finance_book)
self._make_depreciation_schedule(finance_book, start, date_of_sale)
# value_after_depreciation - current Asset value
if self.docstatus == 1 and finance_book.value_after_depreciation:
value_after_depreciation = flt(finance_book.value_after_depreciation)
else:
value_after_depreciation = flt(self.gross_purchase_amount) - flt(
self.opening_accumulated_depreciation
def _make_depreciation_schedule(self, finance_book, start, date_of_sale):
self.validate_asset_finance_books(finance_book)
value_after_depreciation = self._get_value_after_depreciation(finance_book)
finance_book.value_after_depreciation = value_after_depreciation
number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - cint(
self.number_of_depreciations_booked
)
has_pro_rata = self.check_is_pro_rata(finance_book)
if has_pro_rata:
number_of_pending_depreciations += 1
skip_row = False
for n in range(start[finance_book.idx - 1], number_of_pending_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row:
continue
depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book)
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
schedule_date = add_months(
finance_book.depreciation_start_date, n * cint(finance_book.frequency_of_depreciation)
)
finance_book.value_after_depreciation = value_after_depreciation
# schedule date will be a year later from start date
# so monthly schedule date is calculated by removing 11 months from it
monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1)
number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - cint(
self.number_of_depreciations_booked
)
has_pro_rata = self.check_is_pro_rata(finance_book)
if has_pro_rata:
number_of_pending_depreciations += 1
skip_row = False
for n in range(start[finance_book.idx - 1], number_of_pending_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row:
continue
depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book)
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
schedule_date = add_months(
finance_book.depreciation_start_date, n * cint(finance_book.frequency_of_depreciation)
)
# schedule date will be a year later from start date
# so monthly schedule date is calculated by removing 11 months from it
monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1)
# if asset is being sold
if date_of_sale:
from_date = self.get_from_date(finance_book.finance_book)
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, from_date, date_of_sale
)
if depreciation_amount > 0:
self.append(
"schedules",
{
"schedule_date": date_of_sale,
"depreciation_amount": depreciation_amount,
"depreciation_method": finance_book.depreciation_method,
"finance_book": finance_book.finance_book,
"finance_book_id": finance_book.idx,
},
)
break
# For first row
if has_pro_rata and not self.opening_accumulated_depreciation and n == 0:
from_date = add_days(
self.available_for_use_date, -1
) # needed to calc depr amount for available_for_use_date too
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, from_date, finance_book.depreciation_start_date
)
# For first depr schedule date will be the start date
# so monthly schedule date is calculated by removing month difference between use date and start date
monthly_schedule_date = add_months(finance_book.depreciation_start_date, -months + 1)
# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
if not self.flags.increase_in_asset_life:
# In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
self.to_date = add_months(
self.available_for_use_date,
(n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation),
)
depreciation_amount_without_pro_rata = depreciation_amount
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, schedule_date, self.to_date
)
depreciation_amount = self.get_adjusted_depreciation_amount(
depreciation_amount_without_pro_rata, depreciation_amount, finance_book.finance_book
)
monthly_schedule_date = add_months(schedule_date, 1)
schedule_date = add_days(schedule_date, days)
last_schedule_date = schedule_date
if not depreciation_amount:
continue
value_after_depreciation -= flt(depreciation_amount, self.precision("gross_purchase_amount"))
# Adjust depreciation amount in the last period based on the expected value after useful life
if finance_book.expected_value_after_useful_life and (
(
n == cint(number_of_pending_depreciations) - 1
and value_after_depreciation != finance_book.expected_value_after_useful_life
)
or value_after_depreciation < finance_book.expected_value_after_useful_life
):
depreciation_amount += (
value_after_depreciation - finance_book.expected_value_after_useful_life
)
skip_row = True
# if asset is being sold
if date_of_sale:
from_date = self.get_from_date(finance_book.finance_book)
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, from_date, date_of_sale
)
if depreciation_amount > 0:
# With monthly depreciation, each depreciation is divided by months remaining until next date
if self.allow_monthly_depreciation:
# month range is 1 to 12
# In pro rata case, for first and last depreciation, month range would be different
month_range = (
months
if (has_pro_rata and n == 0)
or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1)
else finance_book.frequency_of_depreciation
)
self._add_depreciation_row(
date_of_sale,
depreciation_amount,
finance_book.depreciation_method,
finance_book.finance_book,
finance_book.idx,
)
for r in range(month_range):
if has_pro_rata and n == 0:
# For first entry of monthly depr
if r == 0:
days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date)
per_day_amt = depreciation_amount / days
depreciation_amount_for_current_month = per_day_amt * days_until_first_depr
depreciation_amount -= depreciation_amount_for_current_month
date = monthly_schedule_date
amount = depreciation_amount_for_current_month
else:
date = add_months(monthly_schedule_date, r)
amount = depreciation_amount / (month_range - 1)
elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(
month_range
) - 1:
# For last entry of monthly depr
date = last_schedule_date
amount = depreciation_amount / month_range
break
# For first row
if has_pro_rata and not self.opening_accumulated_depreciation and n == 0:
from_date = add_days(
self.available_for_use_date, -1
) # needed to calc depr amount for available_for_use_date too
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, from_date, finance_book.depreciation_start_date
)
# For first depr schedule date will be the start date
# so monthly schedule date is calculated by removing month difference between use date and start date
monthly_schedule_date = add_months(finance_book.depreciation_start_date, -months + 1)
# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
if not self.flags.increase_in_asset_life:
# In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
self.to_date = add_months(
self.available_for_use_date,
(n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation),
)
depreciation_amount_without_pro_rata = depreciation_amount
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, schedule_date, self.to_date
)
depreciation_amount = self.get_adjusted_depreciation_amount(
depreciation_amount_without_pro_rata, depreciation_amount, finance_book.finance_book
)
monthly_schedule_date = add_months(schedule_date, 1)
schedule_date = add_days(schedule_date, days)
last_schedule_date = schedule_date
if not depreciation_amount:
continue
value_after_depreciation -= flt(depreciation_amount, self.precision("gross_purchase_amount"))
# Adjust depreciation amount in the last period based on the expected value after useful life
if finance_book.expected_value_after_useful_life and (
(
n == cint(number_of_pending_depreciations) - 1
and value_after_depreciation != finance_book.expected_value_after_useful_life
)
or value_after_depreciation < finance_book.expected_value_after_useful_life
):
depreciation_amount += value_after_depreciation - finance_book.expected_value_after_useful_life
skip_row = True
if depreciation_amount > 0:
# With monthly depreciation, each depreciation is divided by months remaining until next date
if self.allow_monthly_depreciation:
# month range is 1 to 12
# In pro rata case, for first and last depreciation, month range would be different
month_range = (
months
if (has_pro_rata and n == 0)
or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1)
else finance_book.frequency_of_depreciation
)
for r in range(month_range):
if has_pro_rata and n == 0:
# For first entry of monthly depr
if r == 0:
days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date)
per_day_amt = depreciation_amount / days
depreciation_amount_for_current_month = per_day_amt * days_until_first_depr
depreciation_amount -= depreciation_amount_for_current_month
date = monthly_schedule_date
amount = depreciation_amount_for_current_month
else:
date = add_months(monthly_schedule_date, r)
amount = depreciation_amount / month_range
amount = depreciation_amount / (month_range - 1)
elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(
month_range
) - 1:
# For last entry of monthly depr
date = last_schedule_date
amount = depreciation_amount / month_range
else:
date = add_months(monthly_schedule_date, r)
amount = depreciation_amount / month_range
self.append(
"schedules",
{
"schedule_date": date,
"depreciation_amount": amount,
"depreciation_method": finance_book.depreciation_method,
"finance_book": finance_book.finance_book,
"finance_book_id": finance_book.idx,
},
)
else:
self.append(
"schedules",
{
"schedule_date": schedule_date,
"depreciation_amount": depreciation_amount,
"depreciation_method": finance_book.depreciation_method,
"finance_book": finance_book.finance_book,
"finance_book_id": finance_book.idx,
},
self._add_depreciation_row(
date, amount, finance_book.depreciation_method, finance_book.finance_book, finance_book.idx
)
else:
self._add_depreciation_row(
schedule_date,
depreciation_amount,
finance_book.depreciation_method,
finance_book.finance_book,
finance_book.idx,
)
def _add_depreciation_row(
self, schedule_date, depreciation_amount, depreciation_method, finance_book, finance_book_id
):
self.append(
"schedules",
{
"schedule_date": schedule_date,
"depreciation_amount": depreciation_amount,
"depreciation_method": depreciation_method,
"finance_book": finance_book,
"finance_book_id": finance_book_id,
},
)
def _get_value_after_depreciation(self, finance_book):
# value_after_depreciation - current Asset value
if self.docstatus == 1 and finance_book.value_after_depreciation:
value_after_depreciation = flt(finance_book.value_after_depreciation)
else:
value_after_depreciation = flt(self.gross_purchase_amount) - flt(
self.opening_accumulated_depreciation
)
return value_after_depreciation
# depreciation schedules need to be cleared before modification due to increase in asset life/asset sales
# JE: Journal Entry, FB: Finance Book
@@ -413,7 +419,6 @@ class Asset(AccountsController):
depr_schedule = []
for schedule in self.get("schedules"):
# to update start when there are JEs linked with all the schedule rows corresponding to an FB
if len(start) == (int(schedule.finance_book_id) - 2):
start.append(num_of_depreciations_completed)
@@ -1124,3 +1129,150 @@ def get_depreciation_amount(asset, depreciable_value, row):
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
return depreciation_amount
@frappe.whitelist()
def split_asset(asset_name, split_qty):
asset = frappe.get_doc("Asset", asset_name)
split_qty = cint(split_qty)
if split_qty >= asset.asset_quantity:
frappe.throw(_("Split qty cannot be grater than or equal to asset qty"))
remaining_qty = asset.asset_quantity - split_qty
new_asset = create_new_asset_after_split(asset, split_qty)
update_existing_asset(asset, remaining_qty)
return new_asset
def update_existing_asset(asset, remaining_qty):
remaining_gross_purchase_amount = flt(
(asset.gross_purchase_amount * remaining_qty) / asset.asset_quantity
)
opening_accumulated_depreciation = flt(
(asset.opening_accumulated_depreciation * remaining_qty) / asset.asset_quantity
)
frappe.db.set_value(
"Asset",
asset.name,
{
"opening_accumulated_depreciation": opening_accumulated_depreciation,
"gross_purchase_amount": remaining_gross_purchase_amount,
"asset_quantity": remaining_qty,
},
)
for finance_book in asset.get("finance_books"):
value_after_depreciation = flt(
(finance_book.value_after_depreciation * remaining_qty) / asset.asset_quantity
)
expected_value_after_useful_life = flt(
(finance_book.expected_value_after_useful_life * remaining_qty) / asset.asset_quantity
)
frappe.db.set_value(
"Asset Finance Book", finance_book.name, "value_after_depreciation", value_after_depreciation
)
frappe.db.set_value(
"Asset Finance Book",
finance_book.name,
"expected_value_after_useful_life",
expected_value_after_useful_life,
)
accumulated_depreciation = 0
for term in asset.get("schedules"):
depreciation_amount = flt((term.depreciation_amount * remaining_qty) / asset.asset_quantity)
frappe.db.set_value(
"Depreciation Schedule", term.name, "depreciation_amount", depreciation_amount
)
accumulated_depreciation += depreciation_amount
frappe.db.set_value(
"Depreciation Schedule", term.name, "accumulated_depreciation_amount", accumulated_depreciation
)
def create_new_asset_after_split(asset, split_qty):
new_asset = frappe.copy_doc(asset)
new_gross_purchase_amount = flt((asset.gross_purchase_amount * split_qty) / asset.asset_quantity)
opening_accumulated_depreciation = flt(
(asset.opening_accumulated_depreciation * split_qty) / asset.asset_quantity
)
new_asset.gross_purchase_amount = new_gross_purchase_amount
new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation
new_asset.asset_quantity = split_qty
new_asset.split_from = asset.name
accumulated_depreciation = 0
for finance_book in new_asset.get("finance_books"):
finance_book.value_after_depreciation = flt(
(finance_book.value_after_depreciation * split_qty) / asset.asset_quantity
)
finance_book.expected_value_after_useful_life = flt(
(finance_book.expected_value_after_useful_life * split_qty) / asset.asset_quantity
)
for term in new_asset.get("schedules"):
depreciation_amount = flt((term.depreciation_amount * split_qty) / asset.asset_quantity)
term.depreciation_amount = depreciation_amount
accumulated_depreciation += depreciation_amount
term.accumulated_depreciation_amount = accumulated_depreciation
new_asset.submit()
new_asset.set_status()
for term in new_asset.get("schedules"):
# Update references in JV
if term.journal_entry:
add_reference_in_jv_on_split(
term.journal_entry, new_asset.name, asset.name, term.depreciation_amount
)
return new_asset
def add_reference_in_jv_on_split(entry_name, new_asset_name, old_asset_name, depreciation_amount):
journal_entry = frappe.get_doc("Journal Entry", entry_name)
entries_to_add = []
idx = len(journal_entry.get("accounts")) + 1
for account in journal_entry.get("accounts"):
if account.reference_name == old_asset_name:
entries_to_add.append(frappe.copy_doc(account).as_dict())
if account.credit:
account.credit = account.credit - depreciation_amount
account.credit_in_account_currency = (
account.credit_in_account_currency - account.exchange_rate * depreciation_amount
)
elif account.debit:
account.debit = account.debit - depreciation_amount
account.debit_in_account_currency = (
account.debit_in_account_currency - account.exchange_rate * depreciation_amount
)
for entry in entries_to_add:
entry.reference_name = new_asset_name
if entry.credit:
entry.credit = depreciation_amount
entry.credit_in_account_currency = entry.exchange_rate * depreciation_amount
elif entry.debit:
entry.debit = depreciation_amount
entry.debit_in_account_currency = entry.exchange_rate * depreciation_amount
entry.idx = idx
idx += 1
journal_entry.append("accounts", entry)
journal_entry.flags.ignore_validate_update_after_submit = True
journal_entry.save()
# Repost GL Entries
journal_entry.docstatus = 2
journal_entry.make_gl_entries(1)
journal_entry.docstatus = 1
journal_entry.make_gl_entries()

View File

@@ -7,7 +7,7 @@ import frappe
from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.assets.doctype.asset.asset import make_sales_invoice
from erpnext.assets.doctype.asset.asset import make_sales_invoice, split_asset
from erpnext.assets.doctype.asset.depreciation import (
post_depreciation_entries,
restore_asset,
@@ -232,6 +232,57 @@ class TestAsset(AssetSetup):
si.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
def test_asset_splitting(self):
asset = create_asset(
calculate_depreciation=1,
asset_quantity=10,
available_for_use_date="2020-01-01",
purchase_date="2020-01-01",
expected_value_after_useful_life=0,
total_number_of_depreciations=6,
number_of_depreciations_booked=1,
frequency_of_depreciation=10,
depreciation_start_date="2021-01-01",
opening_accumulated_depreciation=20000,
gross_purchase_amount=120000,
submit=1,
)
post_depreciation_entries(date="2021-01-01")
self.assertEqual(asset.asset_quantity, 10)
self.assertEqual(asset.gross_purchase_amount, 120000)
self.assertEqual(asset.opening_accumulated_depreciation, 20000)
new_asset = split_asset(asset.name, 2)
asset.load_from_db()
self.assertEqual(new_asset.asset_quantity, 2)
self.assertEqual(new_asset.gross_purchase_amount, 24000)
self.assertEqual(new_asset.opening_accumulated_depreciation, 4000)
self.assertEqual(new_asset.split_from, asset.name)
self.assertEqual(new_asset.schedules[0].depreciation_amount, 4000)
self.assertEqual(new_asset.schedules[1].depreciation_amount, 4000)
self.assertEqual(asset.asset_quantity, 8)
self.assertEqual(asset.gross_purchase_amount, 96000)
self.assertEqual(asset.opening_accumulated_depreciation, 16000)
self.assertEqual(asset.schedules[0].depreciation_amount, 16000)
self.assertEqual(asset.schedules[1].depreciation_amount, 16000)
journal_entry = asset.schedules[0].journal_entry
jv = frappe.get_doc("Journal Entry", journal_entry)
self.assertEqual(jv.accounts[0].credit_in_account_currency, 16000)
self.assertEqual(jv.accounts[1].debit_in_account_currency, 16000)
self.assertEqual(jv.accounts[2].credit_in_account_currency, 4000)
self.assertEqual(jv.accounts[3].debit_in_account_currency, 4000)
self.assertEqual(jv.accounts[0].reference_name, asset.name)
self.assertEqual(jv.accounts[1].reference_name, asset.name)
self.assertEqual(jv.accounts[2].reference_name, new_asset.name)
self.assertEqual(jv.accounts[3].reference_name, new_asset.name)
def test_expense_head(self):
pr = make_purchase_receipt(
item_code="Macbook Pro", qty=2, rate=200000.0, location="Test Location"
@@ -1291,6 +1342,7 @@ def create_asset(**args):
"location": args.location or "Test Location",
"asset_owner": args.asset_owner or "Company",
"is_existing_asset": args.is_existing_asset or 1,
"asset_quantity": args.get("asset_quantity") or 1,
}
)

View File

@@ -0,0 +1,31 @@
## Version 13.26.0 Release Notes
### Features & Enhancements
- feat: Receivable/Payable Account column and filter in AR/AP report ([#30620](https://github.com/frappe/erpnext/pull/30620))
- feat(india): e-invoicing for intra-state union territory transactions ([#30626](https://github.com/frappe/erpnext/pull/30626))
- feat: Ignore permlevel for specific fields ([#30686](https://github.com/frappe/erpnext/pull/30686))
- feat: 'customer' column and more filter to Payment terms status report ([#30499](https://github.com/frappe/erpnext/pull/30499))
### Fixes
- fix: fallback to item_name if description is not found ([#30619](https://github.com/frappe/erpnext/pull/30619))
- fix: Deferred Revenue/Expense Account validation ([#30602](https://github.com/frappe/erpnext/pull/30602))
- fix(pos): reload doc before set value ([#30610](https://github.com/frappe/erpnext/pull/30610))
- fix: Exchange gain and loss button in Payment Entry ([#30606](https://github.com/frappe/erpnext/pull/30606))
- fix(pos): cannot change paid amount in pos payments ([#30657](https://github.com/frappe/erpnext/pull/30657))
- fix: hide pending qty only if original item is assigned ([#30599](https://github.com/frappe/erpnext/pull/30599))
- fix(pos): reload doc before set value ([#30611](https://github.com/frappe/erpnext/pull/30611))
- fix: Handle multiple item transfer in separate SEs against WO ([#30674](https://github.com/frappe/erpnext/pull/30674))
- fix(patch): check null values in is_cancelled patch ([#30594](https://github.com/frappe/erpnext/pull/30594))
- fix: Download JSON for GSTR-1 report ([#30651](https://github.com/frappe/erpnext/pull/30651))
- fix: remove bad defaults from BOM operation ([#30644](https://github.com/frappe/erpnext/pull/30644))
- fix: update translation ([#30474](https://github.com/frappe/erpnext/pull/30474))
- fix: dont reassign mutable (list) to a different field ([#30634](https://github.com/frappe/erpnext/pull/30634))
- fix: update translation ([#30654](https://github.com/frappe/erpnext/pull/30654))
- fix: ignore item-less maintenance visit for sr no ([#30684](https://github.com/frappe/erpnext/pull/30684))
- fix: removed unused courses template ([#30596](https://github.com/frappe/erpnext/pull/30596))
- fix: Implicit ignore pricing rule check on returns ([#30662](https://github.com/frappe/erpnext/pull/30662))
- fix: warehouse naming when suffix is present ([#30621](https://github.com/frappe/erpnext/pull/30621))
- fix: Ignore disabled tax categories ([#30542](https://github.com/frappe/erpnext/pull/30542))

View File

@@ -181,6 +181,7 @@ class AccountsController(TransactionBase):
else:
self.validate_deferred_start_and_end_date()
self.validate_deferred_income_expense_account()
self.set_inter_company_account()
if self.doctype == "Purchase Invoice":
@@ -209,6 +210,27 @@ class AccountsController(TransactionBase):
(self.doctype, self.name),
)
def validate_deferred_income_expense_account(self):
field_map = {
"Sales Invoice": "deferred_revenue_account",
"Purchase Invoice": "deferred_expense_account",
}
for item in self.get("items"):
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
if not item.get(field_map.get(self.doctype)):
default_deferred_account = frappe.db.get_value(
"Company", self.company, "default_" + field_map.get(self.doctype)
)
if not default_deferred_account:
frappe.throw(
_(
"Row #{0}: Please update deferred revenue/expense account in item row or default account in company master"
).format(item.idx)
)
else:
item.set(field_map.get(self.doctype), default_deferred_account)
def validate_deferred_start_and_end_date(self):
for d in self.items:
if d.get("enable_deferred_revenue") or d.get("enable_deferred_expense"):

View File

@@ -22,6 +22,9 @@ class QtyMismatchError(ValidationError):
class BuyingController(StockController, Subcontracting):
def __setup__(self):
self.flags.ignore_permlevel_for_fields = ["buying_price_list", "price_list_currency"]
def get_feed(self):
if self.get("supplier_name"):
return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total)
@@ -624,7 +627,7 @@ class BuyingController(StockController, Subcontracting):
}
)
validate_expense_against_budget(args)
validate_expense_against_budget(args)
def process_fixed_asset(self):
if self.doctype == "Purchase Invoice" and not self.update_stock:

View File

@@ -330,7 +330,6 @@ def make_return_doc(doctype, source_name, target_doc=None):
doc = frappe.get_doc(target)
doc.is_return = 1
doc.return_against = source.name
doc.ignore_pricing_rule = 1
doc.set_warehouse = ""
if doctype == "Sales Invoice" or doctype == "POS Invoice":
doc.is_pos = source.is_pos

View File

@@ -16,6 +16,9 @@ from erpnext.stock.utils import get_incoming_rate
class SellingController(StockController):
def __setup__(self):
self.flags.ignore_permlevel_for_fields = ["selling_price_list", "price_list_currency"]
def get_feed(self):
return _("To {0} | {1} {2}").format(self.customer_name, self.currency, self.grand_total)

View File

@@ -37,7 +37,7 @@ frappe.ui.form.on("Shopify Settings", "refresh", function(frm){
}
let app_link = "<a href='https://frappecloud.com/marketplace/apps/ecommerce-integrations' target='_blank'>Ecommerce Integrations</a>"
let app_link = "<a href='https://frappecloud.com/marketplace/apps/ecommerce_integrations' target='_blank'>Ecommerce Integrations</a>"
frm.dashboard.add_comment(__("Shopify Integration will be removed from ERPNext in Version 14. Please install {0} app to continue using it.", [app_link]), "yellow", true);
})

View File

@@ -2,7 +2,6 @@
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:healthcare_service_unit_name",
"beta": 1,
"creation": "2016-09-21 13:48:14.731437",
"description": "Healthcare Service Unit",
@@ -207,7 +206,7 @@
],
"is_tree": 1,
"links": [],
"modified": "2021-08-19 14:09:11.643464",
"modified": "2022-04-07 03:11:36.023277",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare Service Unit",

View File

@@ -34,6 +34,15 @@ frappe.ui.form.on("Leave Allocation", {
});
}
}
// make new leaves allocated field read only if allocation is created via leave policy assignment
// and leave type is earned leave, since these leaves would be allocated via the scheduler
if (frm.doc.leave_policy_assignment) {
frappe.db.get_value("Leave Type", frm.doc.leave_type, "is_earned_leave", (r) => {
if (r && cint(r.is_earned_leave))
frm.set_df_property("new_leaves_allocated", "read_only", 1);
});
}
},
expire_allocation: function(frm) {

View File

@@ -237,7 +237,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-10-01 15:28:26.335104",
"modified": "2022-04-07 09:50:33.145825",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Allocation",
@@ -278,5 +278,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"timeline_field": "employee"
"timeline_field": "employee",
"title_field": "employee_name",
"track_changes": 1
}

View File

@@ -489,6 +489,17 @@ def update_previous_leave_allocation(
allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
if e_leave_type.based_on_date_of_joining:
text = _("allocated {0} leave(s) via scheduler on {1} based on the date of joining").format(
frappe.bold(earned_leaves), frappe.bold(formatdate(today_date))
)
else:
text = _("allocated {0} leave(s) via scheduler on {1}").format(
frappe.bold(earned_leaves), frappe.bold(formatdate(today_date))
)
allocation.add_comment(comment_type="Info", text=text)
def get_monthly_earned_leave(annual_leaves, frequency, rounding):
earned_leaves = 0.0

View File

@@ -12,6 +12,9 @@ frappe.ui.form.on('Maintenance Visit', {
// filters for serial no based on item code
if (frm.doc.maintenance_type === "Scheduled") {
let item_code = frm.doc.purposes[0].item_code;
if (!item_code) {
return;
}
frappe.call({
method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.get_serial_nos_from_schedule",
args: {

View File

@@ -100,7 +100,6 @@
"read_only": 1
},
{
"default": "5",
"depends_on": "eval:parent.doctype == 'BOM'",
"fieldname": "base_operating_cost",
"fieldtype": "Currency",
@@ -178,7 +177,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-03-10 06:19:08.462027",
"modified": "2022-04-08 01:18:33.547481",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Operation",

View File

@@ -28,12 +28,12 @@ frappe.ui.form.on('Job Card', {
frappe.flags.resume_job = 0;
let has_items = frm.doc.items && frm.doc.items.length;
if (frm.doc.__onload.work_order_stopped) {
if (!frm.is_new() && frm.doc.__onload.work_order_stopped) {
frm.disable_save();
return;
}
if (!frm.doc.__islocal && has_items && frm.doc.docstatus < 2) {
if (!frm.is_new() && has_items && frm.doc.docstatus < 2) {
let to_request = frm.doc.for_quantity > frm.doc.transferred_qty;
let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer;

View File

@@ -1100,6 +1100,56 @@ class TestWorkOrder(FrappeTestCase):
for index, row in enumerate(ste_manu.get("items"), start=1):
self.assertEqual(index, row.idx)
@change_settings(
"Manufacturing Settings",
{"backflush_raw_materials_based_on": "Material Transferred for Manufacture"},
)
def test_work_order_multiple_material_transfer(self):
"""
Test transferring multiple RMs in separate Stock Entries.
"""
work_order = make_wo_order_test_record(planned_start_date=now(), qty=1)
test_stock_entry.make_stock_entry( # stock up RM
item_code="_Test Item",
target="_Test Warehouse - _TC",
qty=1,
basic_rate=5000.0,
)
test_stock_entry.make_stock_entry( # stock up RM
item_code="_Test Item Home Desktop 100",
target="_Test Warehouse - _TC",
qty=2,
basic_rate=1000.0,
)
transfer_entry = frappe.get_doc(
make_stock_entry(work_order.name, "Material Transfer for Manufacture", 1)
)
del transfer_entry.get("items")[0] # transfer only one RM
transfer_entry.submit()
# WO's "Material Transferred for Mfg" shows all is transferred, one RM is pending
work_order.reload()
self.assertEqual(work_order.material_transferred_for_manufacturing, 1)
self.assertEqual(work_order.required_items[0].transferred_qty, 0)
self.assertEqual(work_order.required_items[1].transferred_qty, 2)
final_transfer_entry = frappe.get_doc( # transfer last RM with For Quantity = 0
make_stock_entry(work_order.name, "Material Transfer for Manufacture", 0)
)
final_transfer_entry.save()
self.assertEqual(final_transfer_entry.fg_completed_qty, 0.0)
self.assertEqual(final_transfer_entry.items[0].qty, 1)
final_transfer_entry.submit()
work_order.reload()
# WO's "Material Transferred for Mfg" shows all is transferred, no RM is pending
self.assertEqual(work_order.material_transferred_for_manufacturing, 1)
self.assertEqual(work_order.required_items[0].transferred_qty, 1)
self.assertEqual(work_order.required_items[1].transferred_qty, 2)
def update_job_card(job_card, jc_qty=None):
employee = frappe.db.get_value("Employee", {"status": "Active"}, "name")

View File

@@ -540,8 +540,10 @@ erpnext.work_order = {
|| frm.doc.transfer_material_against == 'Job Card') ? 0 : 1;
if (show_start_btn) {
if ((flt(doc.material_transferred_for_manufacturing) < flt(doc.qty))
&& frm.doc.status != 'Stopped') {
let pending_to_transfer = frm.doc.required_items.some(
item => flt(item.transferred_qty) < flt(item.required_qty)
);
if (pending_to_transfer && frm.doc.status != 'Stopped') {
frm.has_start_btn = true;
frm.add_custom_button(__('Create Pick List'), function() {
erpnext.work_order.create_pick_list(frm);

View File

@@ -1174,7 +1174,11 @@ def make_stock_entry(work_order_id, purpose, qty=None):
stock_entry.from_bom = 1
stock_entry.bom_no = work_order.bom_no
stock_entry.use_multi_level_bom = work_order.use_multi_level_bom
stock_entry.fg_completed_qty = qty or (flt(work_order.qty) - flt(work_order.produced_qty))
# accept 0 qty as well
stock_entry.fg_completed_qty = (
qty if qty is not None else (flt(work_order.qty) - flt(work_order.produced_qty))
)
if work_order.bom_no:
stock_entry.inspection_required = frappe.db.get_value(
"BOM", work_order.bom_no, "inspection_required"

View File

@@ -20,7 +20,7 @@ def execute():
"""
UPDATE `tab{doctype}`
SET is_cancelled = 0
where is_cancelled in ('', NULL, 'No')""".format(
where is_cancelled in ('', 'No') or is_cancelled is NULL""".format(
doctype=doctype
)
)

View File

@@ -79,11 +79,11 @@ erpnext.setup.slides_settings = [
slide.get_input("company_name").on("change", function () {
var parts = slide.get_input("company_name").val().split(" ");
var abbr = $.map(parts, function (p) { return p ? p.substr(0, 1) : null }).join("");
slide.get_field("company_abbr").set_value(abbr.slice(0, 5).toUpperCase());
slide.get_field("company_abbr").set_value(abbr.slice(0, 10).toUpperCase());
}).val(frappe.boot.sysdefaults.company_name || "").trigger("change");
slide.get_input("company_abbr").on("change", function () {
if (slide.get_input("company_abbr").val().length > 5) {
if (slide.get_input("company_abbr").val().length > 10) {
frappe.msgprint(__("Company Abbreviation cannot have more than 5 characters"));
slide.get_field("company_abbr").set_value("");
}
@@ -97,7 +97,7 @@ erpnext.setup.slides_settings = [
if (!this.values.company_abbr) {
return false;
}
if (this.values.company_abbr.length > 5) {
if (this.values.company_abbr.length > 10) {
return false;
}
return true;

View File

@@ -315,10 +315,14 @@ def update_item_taxes(invoice, item):
item.cess_rate += item_tax_rate
item.cess_amount += abs(item_tax_amount_after_discount)
for tax_type in ["igst", "cgst", "sgst"]:
for tax_type in ["igst", "cgst", "sgst", "utgst"]:
if t.account_head in gst_accounts[f"{tax_type}_account"]:
item.tax_rate += item_tax_rate
item[f"{tax_type}_amount"] += abs(item_tax_amount)
if tax_type == "utgst":
# utgst taxes are reported same as sgst tax
item["sgst_amount"] += abs(item_tax_amount)
else:
item[f"{tax_type}_amount"] += abs(item_tax_amount)
else:
# TODO: other charges per item
pass
@@ -360,11 +364,15 @@ def update_invoice_taxes(invoice, invoice_value_details):
# using after discount amt since item also uses after discount amt for cess calc
invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount)
for tax_type in ["igst", "cgst", "sgst"]:
for tax_type in ["igst", "cgst", "sgst", "utgst"]:
if t.account_head in gst_accounts[f"{tax_type}_account"]:
if tax_type == "utgst":
invoice_value_details["total_sgst_amt"] += abs(tax_amount)
else:
invoice_value_details[f"total_{tax_type}_amt"] += abs(tax_amount)
invoice_value_details[f"total_{tax_type}_amt"] += abs(tax_amount)
update_other_charges(t, invoice_value_details, gst_accounts_list, invoice, considered_rows)
else:
invoice_value_details.total_other_charges += abs(tax_amount)

View File

@@ -341,7 +341,7 @@ def get_tax_template(master_doctype, company, is_inter_state, state_code):
tax_categories = frappe.get_all(
"Tax Category",
fields=["name", "is_inter_state", "gst_state"],
filters={"is_inter_state": is_inter_state, "is_reverse_charge": 0},
filters={"is_inter_state": is_inter_state, "is_reverse_charge": 0, "disabled": 0},
)
default_tax = ""
@@ -825,7 +825,7 @@ def get_gst_accounts(
gst_settings_accounts = frappe.get_all(
"GST Account",
filters=filters,
fields=["cgst_account", "sgst_account", "igst_account", "cess_account"],
fields=["cgst_account", "sgst_account", "igst_account", "cess_account", "utgst_account"],
)
if not gst_settings_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate:

View File

@@ -78,8 +78,9 @@ frappe.query_reports["GSTR-1"] = {
}
});
report.page.add_inner_button(__("Download as JSON"), function () {
let filters = report.get_values();
frappe.call({
method: 'erpnext.regional.report.gstr_1.gstr_1.get_json',
args: {

View File

@@ -100,7 +100,8 @@ class Customer(TransactionBase):
@frappe.whitelist()
def get_customer_group_details(self):
doc = frappe.get_doc("Customer Group", self.customer_group)
self.accounts = self.credit_limits = []
self.accounts = []
self.credit_limits = []
self.payment_terms = self.default_price_list = ""
tables = [["accounts", "account"], ["credit_limits", "credit_limit"]]

View File

@@ -47,7 +47,8 @@ class TestCustomer(FrappeTestCase):
c_doc.customer_name = "Testing Customer"
c_doc.customer_group = "_Testing Customer Group"
c_doc.payment_terms = c_doc.default_price_list = ""
c_doc.accounts = c_doc.credit_limits = []
c_doc.accounts = []
c_doc.credit_limits = []
c_doc.insert()
c_doc.get_customer_group_details()
self.assertEqual(c_doc.payment_terms, "_Test Payment Term Template 3")

View File

@@ -27,28 +27,55 @@ function get_filters() {
"default": frappe.datetime.get_today()
},
{
"fieldname":"sales_order",
"label": __("Sales Order"),
"fieldtype": "MultiSelectList",
"fieldname":"customer_group",
"label": __("Customer Group"),
"fieldtype": "Link",
"width": 100,
"options": "Sales Order",
"get_data": function(txt) {
return frappe.db.get_link_options("Sales Order", txt, this.filters());
},
"filters": () => {
return {
docstatus: 1,
payment_terms_template: ['not in', ['']],
company: frappe.query_report.get_filter_value("company"),
transaction_date: ['between', [frappe.query_report.get_filter_value("period_start_date"), frappe.query_report.get_filter_value("period_end_date")]]
"options": "Customer Group",
},
{
"fieldname":"customer",
"label": __("Customer"),
"fieldtype": "Link",
"width": 100,
"options": "Customer",
"get_query": () => {
var customer_group = frappe.query_report.get_filter_value('customer_group');
return{
"query": "erpnext.selling.report.payment_terms_status_for_sales_order.payment_terms_status_for_sales_order.get_customers_or_items",
"filters": [
['Customer', 'disabled', '=', '0'],
['Customer Group','name', '=', customer_group]
]
}
}
},
{
"fieldname":"item_group",
"label": __("Item Group"),
"fieldtype": "Link",
"width": 100,
"options": "Item Group",
},
{
"fieldname":"item",
"label": __("Item"),
"fieldtype": "Link",
"width": 100,
"options": "Item",
"get_query": () => {
var item_group = frappe.query_report.get_filter_value('item_group');
return{
"query": "erpnext.selling.report.payment_terms_status_for_sales_order.payment_terms_status_for_sales_order.get_customers_or_items",
"filters": [
['Item', 'disabled', '=', '0'],
['Item Group','name', '=', item_group]
]
}
},
on_change: function(){
frappe.query_report.refresh();
}
}
]
return filters;
}

View File

@@ -3,7 +3,7 @@
import frappe
from frappe import _, qb, query_builder
from frappe.query_builder import functions
from frappe.query_builder import Criterion, functions
def get_columns():
@@ -14,6 +14,12 @@ def get_columns():
"fieldtype": "Link",
"options": "Sales Order",
},
{
"label": _("Customer"),
"fieldname": "customer",
"fieldtype": "Link",
"options": "Customer",
},
{
"label": _("Posting Date"),
"fieldname": "submitted",
@@ -67,6 +73,55 @@ def get_columns():
return columns
def get_descendants_of(doctype, group_name):
group_doc = qb.DocType(doctype)
# get lft and rgt of group node
lft, rgt = (
qb.from_(group_doc).select(group_doc.lft, group_doc.rgt).where(group_doc.name == group_name)
).run()[0]
# get all children of group node
query = (
qb.from_(group_doc).select(group_doc.name).where((group_doc.lft >= lft) & (group_doc.rgt <= rgt))
)
child_nodes = []
for x in query.run():
child_nodes.append(x[0])
return child_nodes
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_customers_or_items(doctype, txt, searchfield, start, page_len, filters):
filter_list = []
if isinstance(filters, list):
for item in filters:
if item[0] == doctype:
filter_list.append(item)
elif item[0] == "Customer Group":
if item[3] != "":
filter_list.append(
[doctype, "customer_group", "in", get_descendants_of("Customer Group", item[3])]
)
elif item[0] == "Item Group":
if item[3] != "":
filter_list.append([doctype, "item_group", "in", get_descendants_of("Item Group", item[3])])
if searchfield and txt:
filter_list.append([doctype, searchfield, "like", "%%%s%%" % txt])
return frappe.desk.reportview.execute(
doctype,
filters=filter_list,
fields=["name", "customer_group"] if doctype == "Customer" else ["name", "item_group"],
limit_start=start,
limit_page_length=page_len,
as_list=True,
)
def get_conditions(filters):
"""
Convert filter options to conditions used in query
@@ -79,11 +134,37 @@ def get_conditions(filters):
conditions.start_date = filters.period_start_date or frappe.utils.add_months(
conditions.end_date, -1
)
conditions.sales_order = filters.sales_order or []
return conditions
def build_filter_criterions(filters):
filters = frappe._dict(filters) if filters else frappe._dict({})
qb_criterions = []
if filters.customer_group:
qb_criterions.append(
qb.DocType("Sales Order").customer_group.isin(
get_descendants_of("Customer Group", filters.customer_group)
)
)
if filters.customer:
qb_criterions.append(qb.DocType("Sales Order").customer == filters.customer)
if filters.item_group:
qb_criterions.append(
qb.DocType("Sales Order Item").item_group.isin(
get_descendants_of("Item Group", filters.item_group)
)
)
if filters.item:
qb_criterions.append(qb.DocType("Sales Order Item").item_code == filters.item)
return qb_criterions
def get_so_with_invoices(filters):
"""
Get Sales Order with payment terms template with their associated Invoices
@@ -92,16 +173,23 @@ def get_so_with_invoices(filters):
so = qb.DocType("Sales Order")
ps = qb.DocType("Payment Schedule")
soi = qb.DocType("Sales Order Item")
conditions = get_conditions(filters)
filter_criterions = build_filter_criterions(filters)
datediff = query_builder.CustomFunction("DATEDIFF", ["cur_date", "due_date"])
ifelse = query_builder.CustomFunction("IF", ["condition", "then", "else"])
conditions = get_conditions(filters)
query_so = (
qb.from_(so)
.join(soi)
.on(soi.parent == so.name)
.join(ps)
.on(ps.parent == so.name)
.select(
so.name,
so.customer,
so.transaction_date.as_("submitted"),
ifelse(datediff(ps.due_date, functions.CurDate()) < 0, "Overdue", "Unpaid").as_("status"),
ps.payment_term,
@@ -117,12 +205,10 @@ def get_so_with_invoices(filters):
& (so.company == conditions.company)
& (so.transaction_date[conditions.start_date : conditions.end_date])
)
.where(Criterion.all(filter_criterions))
.orderby(so.name, so.transaction_date, ps.due_date)
)
if conditions.sales_order != []:
query_so = query_so.where(so.name.isin(conditions.sales_order))
sorders = query_so.run(as_dict=True)
invoices = []

View File

@@ -11,10 +11,13 @@ from erpnext.selling.report.payment_terms_status_for_sales_order.payment_terms_s
)
from erpnext.stock.doctype.item.test_item import create_item
test_dependencies = ["Sales Order", "Item", "Sales Invoice", "Payment Terms Template"]
test_dependencies = ["Sales Order", "Item", "Sales Invoice", "Payment Terms Template", "Customer"]
class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
def tearDown(self):
frappe.db.rollback()
def create_payment_terms_template(self):
# create template for 50-50 payments
template = None
@@ -48,9 +51,9 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
template.insert()
self.template = template
def test_payment_terms_status(self):
def test_01_payment_terms_status(self):
self.create_payment_terms_template()
item = create_item(item_code="_Test Excavator", is_stock_item=0)
item = create_item(item_code="_Test Excavator 1", is_stock_item=0)
so = make_sales_order(
transaction_date="2021-06-15",
delivery_date=add_days("2021-06-15", -30),
@@ -78,13 +81,14 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
"company": "_Test Company",
"period_start_date": "2021-06-01",
"period_end_date": "2021-06-30",
"sales_order": [so.name],
"item": item.item_code,
}
)
expected_value = [
{
"name": so.name,
"customer": so.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Completed",
"payment_term": None,
@@ -98,6 +102,7 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
},
{
"name": so.name,
"customer": so.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Partly Paid",
"payment_term": None,
@@ -132,11 +137,11 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
)
doc.insert()
def test_alternate_currency(self):
def test_02_alternate_currency(self):
transaction_date = "2021-06-15"
self.create_payment_terms_template()
self.create_exchange_rate(transaction_date)
item = create_item(item_code="_Test Excavator", is_stock_item=0)
item = create_item(item_code="_Test Excavator 2", is_stock_item=0)
so = make_sales_order(
transaction_date=transaction_date,
currency="USD",
@@ -166,7 +171,7 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
"company": "_Test Company",
"period_start_date": "2021-06-01",
"period_end_date": "2021-06-30",
"sales_order": [so.name],
"item": item.item_code,
}
)
@@ -174,6 +179,7 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
expected_value = [
{
"name": so.name,
"customer": so.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Completed",
"payment_term": None,
@@ -187,6 +193,7 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
},
{
"name": so.name,
"customer": so.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Partly Paid",
"payment_term": None,
@@ -200,3 +207,134 @@ class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
},
]
self.assertEqual(data, expected_value)
def test_03_group_filters(self):
transaction_date = "2021-06-15"
self.create_payment_terms_template()
item1 = create_item(item_code="_Test Excavator 1", is_stock_item=0)
item1.item_group = "Products"
item1.save()
so1 = make_sales_order(
transaction_date=transaction_date,
delivery_date=add_days(transaction_date, -30),
item=item1.item_code,
qty=1,
rate=1000000,
do_not_save=True,
)
so1.po_no = ""
so1.taxes_and_charges = ""
so1.taxes = ""
so1.payment_terms_template = self.template.name
so1.save()
so1.submit()
item2 = create_item(item_code="_Test Steel", is_stock_item=0)
item2.item_group = "Raw Material"
item2.save()
so2 = make_sales_order(
customer="_Test Customer 1",
transaction_date=transaction_date,
delivery_date=add_days(transaction_date, -30),
item=item2.item_code,
qty=100,
rate=1000,
do_not_save=True,
)
so2.po_no = ""
so2.taxes_and_charges = ""
so2.taxes = ""
so2.payment_terms_template = self.template.name
so2.save()
so2.submit()
base_filters = {
"company": "_Test Company",
"period_start_date": "2021-06-01",
"period_end_date": "2021-06-30",
}
expected_value_so1 = [
{
"name": so1.name,
"customer": so1.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Overdue",
"payment_term": None,
"description": "_Test 50-50",
"due_date": datetime.date(2021, 6, 30),
"invoice_portion": 50.0,
"currency": "INR",
"base_payment_amount": 500000.0,
"paid_amount": 0.0,
"invoices": "",
},
{
"name": so1.name,
"customer": so1.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Overdue",
"payment_term": None,
"description": "_Test 50-50",
"due_date": datetime.date(2021, 7, 15),
"invoice_portion": 50.0,
"currency": "INR",
"base_payment_amount": 500000.0,
"paid_amount": 0.0,
"invoices": "",
},
]
expected_value_so2 = [
{
"name": so2.name,
"customer": so2.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Overdue",
"payment_term": None,
"description": "_Test 50-50",
"due_date": datetime.date(2021, 6, 30),
"invoice_portion": 50.0,
"currency": "INR",
"base_payment_amount": 50000.0,
"paid_amount": 0.0,
"invoices": "",
},
{
"name": so2.name,
"customer": so2.customer,
"submitted": datetime.date(2021, 6, 15),
"status": "Overdue",
"payment_term": None,
"description": "_Test 50-50",
"due_date": datetime.date(2021, 7, 15),
"invoice_portion": 50.0,
"currency": "INR",
"base_payment_amount": 50000.0,
"paid_amount": 0.0,
"invoices": "",
},
]
group_filters = [
{"customer_group": "All Customer Groups"},
{"item_group": "All Item Groups"},
{"item_group": "Products"},
{"item_group": "Raw Material"},
]
expected_values_for_group_filters = [
expected_value_so1 + expected_value_so2,
expected_value_so1 + expected_value_so2,
expected_value_so1,
expected_value_so2,
]
for idx, g in enumerate(group_filters, 0):
# build filter
filters = frappe._dict({}).update(base_filters).update(g)
with self.subTest(filters=filters):
columns, data, message, chart = execute(filters)
self.assertEqual(data, expected_values_for_group_filters[idx])

View File

@@ -659,7 +659,6 @@
},
{
"collapsible": 1,
"default": "eval:!doc.is_fixed_asset",
"fieldname": "sales_details",
"fieldtype": "Section Break",
"label": "Sales Details",
@@ -1026,4 +1025,4 @@
"sort_order": "DESC",
"title_field": "item_name",
"track_changes": 1
}
}

View File

@@ -18,6 +18,7 @@ from frappe.utils import (
now_datetime,
nowtime,
strip,
strip_html,
)
from frappe.utils.html_utils import clean_html
@@ -69,10 +70,6 @@ class Item(Document):
self.item_code = strip(self.item_code)
self.name = self.item_code
def before_insert(self):
if not self.description:
self.description = self.item_name
def after_insert(self):
"""set opening stock and item price"""
if self.standard_rate:
@@ -86,7 +83,7 @@ class Item(Document):
if not self.item_name:
self.item_name = self.item_code
if not self.description:
if not strip_html(cstr(self.description)).strip():
self.description = self.item_name
self.validate_uom()

View File

@@ -683,6 +683,13 @@ class TestItem(FrappeTestCase):
self.assertEqual(item.sample_quantity, None)
item.delete()
def test_empty_description(self):
item = make_item(properties={"description": "<p></p>"})
self.assertEqual(item.description, item.item_name)
item.description = ""
item.save()
self.assertEqual(item.description, item.item_name)
def set_item_variant_settings(fields):
doc = frappe.get_doc("Item Variant Settings")

View File

@@ -61,6 +61,22 @@ class RepostItemValuation(Document):
repost(self)
def before_cancel(self):
self.check_pending_repost_against_cancelled_transaction()
def check_pending_repost_against_cancelled_transaction(self):
if self.status not in ("Queued", "In Progress"):
return
if not (self.voucher_no and self.voucher_no):
return
transaction_status = frappe.db.get_value(self.voucher_type, self.voucher_no, "docstatus")
if transaction_status == 2:
msg = _("Cannot cancel as processing of cancelled documents is pending.")
msg += "<br>" + _("Please try again in an hour.")
frappe.throw(msg, title=_("Pending processing"))
@frappe.whitelist()
def restart_reposting(self):
self.set_status("Queued", write=False)

View File

@@ -1,20 +1,25 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import nowdate
from erpnext.controllers.stock_controller import create_item_wise_repost_entries
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import (
in_configured_timeslot,
)
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.utils import PendingRepostingError
class TestRepostItemValuation(unittest.TestCase):
class TestRepostItemValuation(FrappeTestCase):
def tearDown(self):
frappe.flags.dont_execute_stock_reposts = False
def test_repost_time_slot(self):
repost_settings = frappe.get_doc("Stock Reposting Settings")
@@ -162,3 +167,22 @@ class TestRepostItemValuation(unittest.TestCase):
self.assertRaises(PendingRepostingError, stock_settings.save)
riv.set_status("Skipped")
def test_prevention_of_cancelled_transaction_riv(self):
frappe.flags.dont_execute_stock_reposts = True
item = make_item()
warehouse = "_Test Warehouse - _TC"
old = make_stock_entry(item_code=item.name, to_warehouse=warehouse, qty=2, rate=5)
_new = make_stock_entry(item_code=item.name, to_warehouse=warehouse, qty=5, rate=10)
old.cancel()
riv = frappe.get_last_doc(
"Repost Item Valuation", {"voucher_type": old.doctype, "voucher_no": old.name}
)
self.assertRaises(frappe.ValidationError, riv.cancel)
riv.db_set("status", "Skipped")
riv.reload()
riv.cancel() # it should cancel now

View File

@@ -1802,7 +1802,9 @@ class StockEntry(StockController):
or (desire_to_transfer > 0 and backflush_based_on == "Material Transferred for Manufacture")
or allow_overproduction
):
item_dict[item]["qty"] = desire_to_transfer
# "No need for transfer but qty still pending to transfer" case can occur
# when transferring multiple RM in different Stock Entries
item_dict[item]["qty"] = desire_to_transfer if (desire_to_transfer > 0) else pending_to_issue
elif pending_to_issue > 0:
item_dict[item]["qty"] = pending_to_issue
else:

View File

@@ -38,6 +38,16 @@ class TestWarehouse(FrappeTestCase):
self.assertEqual(p_warehouse.name, child_warehouse.parent_warehouse)
self.assertEqual(child_warehouse.is_group, 0)
def test_naming(self):
company = "Wind Power LLC"
warehouse_name = "Named Warehouse - WP"
wh = frappe.get_doc(doctype="Warehouse", warehouse_name=warehouse_name, company=company).insert()
self.assertEqual(wh.name, warehouse_name)
warehouse_name = "Unnamed Warehouse"
wh = frappe.get_doc(doctype="Warehouse", warehouse_name=warehouse_name, company=company).insert()
self.assertIn(warehouse_name, wh.name)
def test_unlinking_warehouse_from_item_defaults(self):
company = "_Test Company"

View File

@@ -21,8 +21,9 @@ class Warehouse(NestedSet):
suffix = " - " + frappe.get_cached_value("Company", self.company, "abbr")
if not self.warehouse_name.endswith(suffix):
self.name = self.warehouse_name + suffix
else:
self.name = self.warehouse_name
return
self.name = self.warehouse_name
def onload(self):
"""load account name for General Ledger Report"""

View File

@@ -175,9 +175,9 @@ def validate_cancellation(args):
)
if repost_entry.status == "Queued":
doc = frappe.get_doc("Repost Item Valuation", repost_entry.name)
doc.status = "Skipped"
doc.flags.ignore_permissions = True
doc.cancel()
doc.delete()
def set_as_cancel(voucher_type, voucher_no):

View File

@@ -1,11 +0,0 @@
{% extends "templates/web.html" %}
{% block header %}
<h1> About </h1>
{% endblock %}
{% block page_content %}
<p class="post-description"> {{ intro }} </p>
{% endblock %}

View File

@@ -1,18 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
def get_context(context):
course = frappe.get_doc("Course", frappe.form_dict.course)
sidebar_title = course.name
context.no_cache = 1
context.show_sidebar = True
course = frappe.get_doc("Course", frappe.form_dict.course)
course.has_permission("read")
context.doc = course
context.sidebar_title = sidebar_title
context.intro = course.course_intro

View File

@@ -285,7 +285,7 @@ Asset scrapped via Journal Entry {0},Actif mis au rebut via Écriture de Journal
"Asset {0} cannot be scrapped, as it is already {1}","L'actif {0} ne peut pas être mis au rebut, car il est déjà {1}",
Asset {0} does not belong to company {1},L'actif {0} ne fait pas partie à la société {1},
Asset {0} must be submitted,L'actif {0} doit être soumis,
Assets,Les atouts,
Assets,Actifs - Immo.,
Assign,Assigner,
Assign Salary Structure,Affecter la structure salariale,
Assign To,Attribuer À,
@@ -951,14 +951,14 @@ End time cannot be before start time,L'heure de fin ne peut pas être avant l'he
Ends On date cannot be before Next Contact Date.,La date de fin ne peut pas être avant la prochaine date de contact,
Energy,Énergie,
Engineer,Ingénieur,
Enough Parts to Build,Pièces Suffisantes pour Construire,
Enough Parts to Build,Pièces Suffisantes pour Construire
Enroll,Inscrire,
Enrolling student,Inscrire un étudiant,
Enrolling students,Inscription des étudiants,
Enter depreciation details,Veuillez entrer les détails de l'amortissement,
Enter the Bank Guarantee Number before submittting.,Entrez le numéro de garantie bancaire avant de soumettre.,
Enter the name of the Beneficiary before submittting.,Entrez le nom du bénéficiaire avant de soumettre.,
Enter the name of the bank or lending institution before submittting.,Entrez le nom de la banque ou de l'institution de prêt avant de soumettre.,
Enter the Bank Guarantee Number before submittting.,Entrez le numéro de garantie bancaire avant de valider.
Enter the name of the Beneficiary before submittting.,Entrez le nom du bénéficiaire avant de valider.
Enter the name of the bank or lending institution before submittting.,Entrez le nom de la banque ou de l'institution de prêt avant de valider.,
Enter value betweeen {0} and {1},Entrez une valeur entre {0} et {1},
Entertainment & Leisure,Divertissement et Loisir,
Entertainment Expenses,Charges de Représentation,
@@ -1068,7 +1068,7 @@ For Employee,Employé,
For Quantity (Manufactured Qty) is mandatory,Pour Quantité (Qté Produite) est obligatoire,
For Supplier,Pour Fournisseur,
For Warehouse,Pour lEntrepôt,
For Warehouse is required before Submit,Pour lEntrepôt est requis avant de Soumettre,
For Warehouse is required before Submit,Pour lEntrepôt est requis avant de Valider,
"For an item {0}, quantity must be negative number","Pour l'article {0}, la quantité doit être un nombre négatif",
"For an item {0}, quantity must be positive number","Pour un article {0}, la quantité doit être un nombre positif",
"For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry","Pour la carte de travail {0}, vous pouvez uniquement saisir une entrée de stock de type &quot;Transfert d'article pour fabrication&quot;.",
@@ -1211,7 +1211,7 @@ Hello,Bonjour,
Help Results for,Aide Résultats pour,
High,Haut,
High Sensitivity,Haute sensibilité,
Hold,Tenir,
Hold,Mettre en attente,
Hold Invoice,Facture en attente,
Holiday,Vacances,
Holiday List,Liste de vacances,
@@ -1693,7 +1693,7 @@ No Items with Bill of Materials to Manufacture,Aucun Article avec une Liste de M
No Items with Bill of Materials.,Aucun article avec nomenclature.,
No Permission,Aucune autorisation,
No Remarks,Aucune Remarque,
No Result to submit,Aucun résultat à soumettre,
No Result to submit,Aucun résultat à valider,
No Salary Structure assigned for Employee {0} on given date {1},Aucune structure de salaire attribuée à l&#39;employé {0} à la date donnée {1},
No Staffing Plans found for this Designation,Aucun plan de dotation trouvé pour cette désignation,
No Student Groups created.,Aucun Groupe d'Étudiants créé.,
@@ -2847,12 +2847,12 @@ Sub Type,Sous type,
Sub-contracting,Sous-traitant,
Subcontract,Sous-traiter,
Subject,Sujet,
Submit,Soumettre,
Submit Proof,Soumettre une preuve,
Submit Salary Slip,Soumettre la Fiche de Paie,
Submit this Work Order for further processing.,Soumettre cet ordre de travail pour continuer son traitement.,
Submit this to create the Employee record,Soumettre pour créer la fiche employé,
Submitting Salary Slips...,Soumission des bulletins de salaire ...,
Submit,Valider,
Submit Proof,Valider une preuve,
Submit Salary Slip,Valider la Fiche de Paie,
Submit this Work Order for further processing.,Valider cet ordre de travail pour continuer son traitement.,
Submit this to create the Employee record,Valider pour créer la fiche employé,
Submitting Salary Slips...,Validation des bulletins de salaire ...,
Subscription,Abonnement,
Subscription Management,Gestion des abonnements,
Subscriptions,Abonnements,
@@ -2954,7 +2954,7 @@ The Term End Date cannot be earlier than the Term Start Date. Please correct the
The Term End Date cannot be later than the Year End Date of the Academic Year to which the term is linked (Academic Year {}). Please correct the dates and try again.,La Date de Fin de Terme ne peut pas être postérieure à la Date de Fin de l'Année Académique à laquelle le terme est lié (Année Académique {}). Veuillez corriger les dates et essayer à nouveau.,
The Term Start Date cannot be earlier than the Year Start Date of the Academic Year to which the term is linked (Academic Year {}). Please correct the dates and try again.,La Date de Début de Terme ne peut pas être antérieure à la Date de Début de l'Année Académique à laquelle le terme est lié (Année Académique {}). Veuillez corriger les dates et essayer à nouveau.,
The Year End Date cannot be earlier than the Year Start Date. Please correct the dates and try again.,La Date de Fin d'Année ne peut pas être antérieure à la Date de Début dAnnée. Veuillez corriger les dates et essayer à nouveau.,
The amount of {0} set in this payment request is different from the calculated amount of all payment plans: {1}. Make sure this is correct before submitting the document.,Le montant {0} défini dans cette requête de paiement est différent du montant calculé de tous les plans de paiement: {1}.\nVeuillez vérifier que c'est correct avant de soumettre le document.,
The amount of {0} set in this payment request is different from the calculated amount of all payment plans: {1}. Make sure this is correct before submitting the document.,Le montant {0} défini dans cette requête de paiement est différent du montant calculé de tous les plans de paiement: {1}.\nVeuillez vérifier que c'est correct avant de valider le document.,
The day(s) on which you are applying for leave are holidays. You need not apply for leave.,Le(s) jour(s) pour le(s)quel(s) vous demandez un congé sont des jour(s) férié(s). Vous navez pas besoin deffectuer de demande.,
The field From Shareholder cannot be blank,Le champ 'De l'actionnaire' ne peut pas être vide,
The field To Shareholder cannot be blank,Le champ 'A l'actionnaire' ne peut pas être vide,
@@ -3011,7 +3011,7 @@ This is based on transactions against this Healthcare Practitioner.,Ce graphique
This is based on transactions against this Patient. See timeline below for details,Ceci est basé sur les transactions de ce patient. Voir la chronologie ci-dessous pour plus de détails,
This is based on transactions against this Sales Person. See timeline below for details,Ceci est basé sur les transactions contre ce vendeur. Voir la chronologie ci-dessous pour plus de détails,
This is based on transactions against this Supplier. See timeline below for details,Basé sur les transactions avec ce fournisseur. Voir la chronologie ci-dessous pour plus de détails,
This will submit Salary Slips and create accrual Journal Entry. Do you want to proceed?,Cela permettra de soumettre des bulletins de salaire et de créer une écriture de journal d&#39;accumulation. Voulez-vous poursuivre?,
This will submit Salary Slips and create accrual Journal Entry. Do you want to proceed?,Cela permettra de valider des bulletins de salaire et de créer une écriture de journal d&#39;accumulation. Voulez-vous poursuivre?,
This {0} conflicts with {1} for {2} {3},Ce {0} est en conflit avec {1} pour {2} {3},
Time Sheet for manufacturing.,Feuille de Temps pour la production.,
Time Tracking,Suivi du temps,
@@ -3312,7 +3312,7 @@ Work Order {0} must be cancelled before cancelling this Sales Order,L'ordre de t
Work Order {0} must be submitted,L'ordre de travail {0} doit être soumis,
Work Orders Created: {0},Ordres de travail créés: {0},
Work Summary for {0},Résumé de travail de {0},
Work-in-Progress Warehouse is required before Submit,L'entrepôt des Travaux en Cours est nécessaire avant de Soumettre,
Work-in-Progress Warehouse is required before Submit,L'entrepôt des Travaux en Cours est nécessaire avant de Valider,
Workflow,Flux de Travail,
Working,Travail en cours,
Working Hours,Heures de travail,
@@ -3331,7 +3331,7 @@ You can only have Plans with the same billing cycle in a Subscription,Vous ne po
You can only redeem max {0} points in this order.,Vous pouvez uniquement échanger un maximum de {0} points dans cet commande.,
You can only renew if your membership expires within 30 days,Vous ne pouvez renouveler que si votre abonnement expire dans les 30 jours,
You can only select a maximum of one option from the list of check boxes.,Vous pouvez sélectionner au maximum une option dans la liste des cases à cocher.,
You can only submit Leave Encashment for a valid encashment amount,Vous pouvez uniquement soumettre un encaissement de congé pour un montant d'encaissement valide,
You can only submit Leave Encashment for a valid encashment amount,Vous pouvez uniquement valider un encaissement de congé pour un montant d'encaissement valide,
You can't redeem Loyalty Points having more value than the Grand Total.,Vous ne pouvez pas échanger des points de fidélité ayant plus de valeur que le total général.,
You cannot credit and debit same account at the same time,Vous ne pouvez pas créditer et débiter le même compte simultanément,
You cannot delete Fiscal Year {0}. Fiscal Year {0} is set as default in Global Settings,Vous ne pouvez pas supprimer l'exercice fiscal {0}. L'exercice fiscal {0} est défini par défaut dans les Paramètres Globaux,
@@ -3684,8 +3684,8 @@ Create Quality Inspection for Item {0},Créer un contrôle qualité pour l'artic
Creating Accounts...,Création de comptes ...,
Creating bank entries...,Création d'entrées bancaires ...,
Credit limit is already defined for the Company {0},La limite de crédit est déjà définie pour la société {0}.,
Ctrl + Enter to submit,Ctrl + Entrée pour soumettre,
Ctrl+Enter to submit,Ctrl + Entrée pour soumettre,
Ctrl + Enter to submit,Ctrl + Entrée pour valider,
Ctrl+Enter to submit,Ctrl + Entrée pour valider,
Currency,Devise,
Current Status,Statut Actuel,
Customer PO,Bon de commande client,
@@ -3709,7 +3709,7 @@ Dimension Filter,Filtre de dimension,
Disabled,Desactivé,
Disbursement and Repayment,Décaissement et remboursement,
Distance cannot be greater than 4000 kms,La distance ne peut pas dépasser 4000 km,
Do you want to submit the material request,Voulez-vous soumettre la demande de matériel,
Do you want to submit the material request,Voulez-vous valider la demande de matériel,
Doctype,Doctype,
Document {0} successfully uncleared,Document {0} non effacé avec succès,
Download Template,Télécharger le Modèle,
@@ -4240,7 +4240,7 @@ For Default Supplier (Optional),Pour le fournisseur par défaut (facultatif),
From date cannot be greater than To date,La Date Initiale ne peut pas être postérieure à la Date Finale,
Group by,Grouper Par,
In stock,En stock,
Item name,Nom de l'article,
Item name,Libellé de l'article,
Loan amount is mandatory,Le montant du prêt est obligatoire,
Minimum Qty,Quantité minimum,
More details,Plus de détails,
@@ -4309,7 +4309,7 @@ Requested,Demandé,
Partially Paid,Partiellement payé,
Invalid Account Currency,Devise de compte non valide,
"Row {0}: The item {1}, quantity must be positive number","Ligne {0}: l&#39;article {1}, la quantité doit être un nombre positif",
"Please set {0} for Batched Item {1}, which is used to set {2} on Submit.","Veuillez définir {0} pour l&#39;article par lots {1}, qui est utilisé pour définir {2} sur Soumettre.",
"Please set {0} for Batched Item {1}, which is used to set {2} on Submit.","Veuillez définir {0} pour l&#39;article par lots {1}, qui est utilisé pour définir {2} sur Valider.",
Expiry Date Mandatory,Date d&#39;expiration obligatoire,
Variant Item,Élément de variante,
BOM 1 {0} and BOM 2 {1} should not be same,La nomenclature 1 {0} et la nomenclature 2 {1} ne doivent pas être identiques,
@@ -4589,7 +4589,7 @@ Bank Transaction Entries,Ecritures de transactions bancaires,
New Transactions,Nouvelles transactions,
Match Transaction to Invoices,Faire correspondre la transaction aux factures,
Create New Payment/Journal Entry,Créer un nouveau paiement / écriture de journal,
Submit/Reconcile Payments,Soumettre / rapprocher les paiements,
Submit/Reconcile Payments,Valider / rapprocher les paiements,
Matching Invoices,Factures correspondantes,
Payment Invoice Items,Articles de la facture de paiement,
Reconciled Transactions,Transactions rapprochées,
@@ -5473,7 +5473,7 @@ Percentage you are allowed to transfer more against the quantity ordered. For ex
PUR-ORD-.YYYY.-,PUR-ORD-.YYYY.-,
Get Items from Open Material Requests,Obtenir des Articles de Demandes Matérielles Ouvertes,
Fetch items based on Default Supplier.,Récupérez les articles en fonction du fournisseur par défaut.,
Required By,Requis Par,
Required By,Requis pour le,
Order Confirmation No,No de confirmation de commande,
Order Confirmation Date,Date de confirmation de la commande,
Customer Mobile No,N° de Portable du Client,
@@ -6208,7 +6208,7 @@ Collect Fee for Patient Registration,Collecter les honoraires pour l'inscription
Checking this will create new Patients with a Disabled status by default and will only be enabled after invoicing the Registration Fee.,Cochez cette case pour créer de nouveaux patients avec un statut Désactivé par défaut et ne seront activés qu&#39;après facturation des frais d&#39;inscription.,
Registration Fee,Frais d'Inscription,
Automate Appointment Invoicing,Automatiser la facturation des rendez-vous,
Manage Appointment Invoice submit and cancel automatically for Patient Encounter,Gérer les factures de rendez-vous soumettre et annuler automatiquement pour la consultation des patients,
Manage Appointment Invoice submit and cancel automatically for Patient Encounter,Gérer les factures de rendez-vous valider et annuler automatiquement pour la consultation des patients,
Enable Free Follow-ups,Activer les suivis gratuits,
Number of Patient Encounters in Valid Days,Nombre de rencontres de patients en jours valides,
The number of free follow ups (Patient Encounters in valid days) allowed,Le nombre de suivis gratuits (rencontres de patients en jours valides) autorisés,
@@ -7223,8 +7223,8 @@ Basic Rate (Company Currency),Taux de Base (Devise de la Société ),
Scrap %,% de Rebut,
Original Item,Article original,
BOM Operation,Opération LDM,
Operation Time ,Moment de l&#39;opération,
In minutes,En quelques minutes,
Operation Time ,Durée de l&#39;opération,
In minutes,En minutes,
Batch Size,Taille du lot,
Base Hour Rate(Company Currency),Taux Horaire de Base (Devise de la Société),
Operating Cost(Company Currency),Coût d'Exploitation (Devise Société),
@@ -8679,7 +8679,7 @@ Book Deferred Entries Based On,Enregistrer les entrées différées en fonction
Days,Journées,
Months,Mois,
Book Deferred Entries Via Journal Entry,Enregistrer les écritures différées via l&#39;écriture au journal,
Submit Journal Entries,Soumettre les entrées de journal,
Submit Journal Entries,Valider les entrées de journal,
If this is unchecked Journal Entries will be saved in a Draft state and will have to be submitted manually,"Si cette case n&#39;est pas cochée, les entrées de journal seront enregistrées dans un état Brouillon et devront être soumises manuellement",
Enable Distributed Cost Center,Activer le centre de coûts distribués,
Distributed Cost Center,Centre de coûts distribués,
@@ -9065,7 +9065,7 @@ Rented To Date,Loué à ce jour,
Monthly Eligible Amount,Montant mensuel admissible,
Total Eligible HRA Exemption,Exemption HRA totale éligible,
Validating Employee Attendance...,Validation de la présence des employés ...,
Submitting Salary Slips and creating Journal Entry...,Soumettre des fiches de salaire et créer une écriture au journal ...,
Submitting Salary Slips and creating Journal Entry...,Validation des fiches de salaire et créer une écriture au journal ...,
Calculate Payroll Working Days Based On,Calculer les jours ouvrables de paie en fonction de,
Consider Unmarked Attendance As,Considérez la participation non marquée comme,
Fraction of Daily Salary for Half Day,Fraction du salaire journalier pour une demi-journée,
@@ -9166,8 +9166,8 @@ Enter customer's phone number,Entrez le numéro de téléphone du client,
Customer contact updated successfully.,Contact client mis à jour avec succès.,
Item will be removed since no serial / batch no selected.,L&#39;article sera supprimé car aucun numéro de série / lot sélectionné.,
Discount (%),Remise (%),
You cannot submit the order without payment.,Vous ne pouvez pas soumettre la commande sans paiement.,
You cannot submit empty order.,Vous ne pouvez pas soumettre de commande vide.,
You cannot submit the order without payment.,Vous ne pouvez pas valider la commande sans paiement.,
You cannot submit empty order.,Vous ne pouvez pas valider de commande vide.,
To Be Paid,Être payé,
Create POS Opening Entry,Créer une entrée d&#39;ouverture de PDV,
Please add Mode of payments and opening balance details.,Veuillez ajouter le mode de paiement et les détails du solde d&#39;ouverture.,
@@ -9267,7 +9267,7 @@ Sales Order Analysis,Analyse des commandes clients,
Amount Delivered,Montant livré,
Delay (in Days),Retard (en jours),
Group by Sales Order,Regrouper par commande client,
Sales Value,La valeur des ventes,
Sales Value,La valeur des ventes,
Stock Qty vs Serial No Count,Quantité de stock vs numéro de série,
Serial No Count,Numéro de série,
Work Order Summary,Résumé de l&#39;ordre de travail,
@@ -9305,7 +9305,7 @@ Courses updated,Cours mis à jour,
{0} {1} has been added to all the selected topics successfully.,{0} {1} a bien été ajouté à tous les sujets sélectionnés.,
Topics updated,Sujets mis à jour,
Academic Term and Program,Terme académique et programme,
Please remove this item and try to submit again or update the posting time.,Veuillez supprimer cet élément et réessayer de le soumettre ou mettre à jour l&#39;heure de publication.,
Please remove this item and try to submit again or update the posting time.,Veuillez supprimer cet élément et réessayer de le valider ou mettre à jour l&#39;heure de publication.,
Failed to Authenticate the API key.,Échec de l&#39;authentification de la clé API.,
Invalid Credentials,Les informations d&#39;identification invalides,
URL can only be a string,L&#39;URL ne peut être qu&#39;une chaîne,
@@ -9416,7 +9416,7 @@ Import Italian Supplier Invoice.,Importer la facture du fournisseur italien.,
"Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}.",Le taux de valorisation de l&#39;article {0} est requis pour effectuer des écritures comptables pour {1} {2}.,
Here are the options to proceed:,Voici les options pour continuer:,
"If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table.","Si l&#39;article est traité comme un article à taux de valorisation nul dans cette entrée, veuillez activer &quot;Autoriser le taux de valorisation nul&quot; dans le {0} tableau des articles.",
"If not, you can Cancel / Submit this entry ","Sinon, vous pouvez annuler / soumettre cette entrée",
"If not, you can Cancel / Submit this entry ","Sinon, vous pouvez annuler / valider cette entrée",
performing either one below:,effectuer l&#39;un ou l&#39;autre ci-dessous:,
Create an incoming stock transaction for the Item.,Créez une transaction de stock entrante pour l&#39;article.,
Mention Valuation Rate in the Item master.,Mentionnez le taux de valorisation dans la fiche article.,
@@ -9573,7 +9573,7 @@ Accounting entries are frozen up to this date. Nobody can create or modify entri
Role Allowed to Set Frozen Accounts and Edit Frozen Entries,Rôle autorisé à définir des comptes gelés et à modifier les entrées gelées,
Address used to determine Tax Category in transactions,Adresse utilisée pour déterminer la catégorie de taxe dans les transactions,
"The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ","Le pourcentage que vous êtes autorisé à facturer davantage par rapport au montant commandé. Par exemple, si la valeur de la commande est de 100 USD pour un article et que la tolérance est définie sur 10%, vous êtes autorisé à facturer jusqu&#39;à 110 USD.",
This role is allowed to submit transactions that exceed credit limits,Ce rôle est autorisé à soumettre des transactions qui dépassent les limites de crédit,
This role is allowed to submit transactions that exceed credit limits,Ce rôle est autorisé à valider des transactions qui dépassent les limites de crédit,
"If ""Months"" is selected, a fixed amount will be booked as deferred revenue or expense for each month irrespective of the number of days in a month. It will be prorated if deferred revenue or expense is not booked for an entire month","Si «Mois» est sélectionné, un montant fixe sera comptabilisé en tant que revenus ou dépenses différés pour chaque mois, quel que soit le nombre de jours dans un mois. Il sera calculé au prorata si les revenus ou les dépenses différés ne sont pas comptabilisés pour un mois entier",
"If this is unchecked, direct GL entries will be created to book deferred revenue or expense","Si cette case n&#39;est pas cochée, des entrées GL directes seront créées pour enregistrer les revenus ou les dépenses différés",
Show Inclusive Tax in Print,Afficher la taxe incluse en version imprimée,
@@ -9647,7 +9647,7 @@ Allow Multiple Sales Orders Against a Customer's Purchase Order,Autoriser plusie
Validate Selling Price for Item Against Purchase Rate or Valuation Rate,Valider le prix de vente de l&#39;article par rapport au taux d&#39;achat ou au taux de valorisation,
Hide Customer's Tax ID from Sales Transactions,Masquer le numéro d&#39;identification fiscale du client dans les transactions de vente,
"The percentage you are allowed to receive or deliver more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed to receive 110 units.","Le pourcentage que vous êtes autorisé à recevoir ou à livrer plus par rapport à la quantité commandée. Par exemple, si vous avez commandé 100 unités et que votre allocation est de 10%, vous êtes autorisé à recevoir 110 unités.",
Action If Quality Inspection Is Not Submitted,Action si l&#39;inspection de la qualité n&#39;est pas soumise,
Action If Quality Inspection Is Not Submitted,Action si l&#39;inspection qualité n&#39;est pas soumise,
Auto Insert Price List Rate If Missing,Taux de liste de prix d&#39;insertion automatique s&#39;il est manquant,
Automatically Set Serial Nos Based on FIFO,Définir automatiquement les numéros de série en fonction de FIFO,
Set Qty in Transactions Based on Serial No Input,Définir la quantité dans les transactions en fonction du numéro de série,
@@ -9744,7 +9744,7 @@ Print Receipt,Imprimer le reçu,
Edit Receipt,Modifier le reçu,
Focus on search input,Focus sur l&#39;entrée de recherche,
Focus on Item Group filter,Focus sur le filtre de groupe d&#39;articles,
Checkout Order / Submit Order / New Order,Commander la commande / Soumettre la commande / Nouvelle commande,
Checkout Order / Submit Order / New Order,Commander la commande / Valider la commande / Nouvelle commande,
Add Order Discount,Ajouter une remise de commande,
Item Code: {0} is not available under warehouse {1}.,Code d&#39;article: {0} n&#39;est pas disponible dans l&#39;entrepôt {1}.,
Serial numbers unavailable for Item {0} under warehouse {1}. Please try changing warehouse.,Numéros de série non disponibles pour l&#39;article {0} sous l&#39;entrepôt {1}. Veuillez essayer de changer dentrepôt.,
@@ -9787,11 +9787,11 @@ because expense is booked against this account in Purchase Receipt {},car les d
as no Purchase Receipt is created against Item {}. ,car aucun reçu d&#39;achat n&#39;est créé pour l&#39;article {}.,
This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice,Ceci est fait pour gérer la comptabilité des cas où le reçu d&#39;achat est créé après la facture d&#39;achat,
Purchase Order Required for item {},Bon de commande requis pour l&#39;article {},
To submit the invoice without purchase order please set {} ,"Pour soumettre la facture sans bon de commande, veuillez définir {}",
To submit the invoice without purchase order please set {} ,"Pour valider la facture sans bon de commande, veuillez définir {}",
as {} in {},un péché {},
Mandatory Purchase Order,Bon de commande obligatoire,
Purchase Receipt Required for item {},Reçu d&#39;achat requis pour l&#39;article {},
To submit the invoice without purchase receipt please set {} ,"Pour soumettre la facture sans reçu d&#39;achat, veuillez définir {}",
To submit the invoice without purchase receipt please set {} ,"Pour valider la facture sans reçu d&#39;achat, veuillez définir {}",
Mandatory Purchase Receipt,Reçu d&#39;achat obligatoire,
POS Profile {} does not belongs to company {},Le profil PDV {} n&#39;appartient pas à l&#39;entreprise {},
User {} is disabled. Please select valid user/cashier,L&#39;utilisateur {} est désactivé. Veuillez sélectionner un utilisateur / caissier valide,
@@ -9838,3 +9838,37 @@ Enable European Access,Activer l&#39;accès européen,
Creating Purchase Order ...,Création d&#39;une commande d&#39;achat ...,
"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Sélectionnez un fournisseur parmi les fournisseurs par défaut des articles ci-dessous. Lors de la sélection, un bon de commande sera effectué contre des articles appartenant uniquement au fournisseur sélectionné.",
Row #{}: You must select {} serial numbers for item {}.,Ligne n ° {}: vous devez sélectionner {} numéros de série pour l&#39;article {}.,
Update Rate as per Last Purchase,Mettre à jour avec les derniers prix d'achats
Company Shipping Address,Adresse d&#39;expédition
Shipping Address Details,Détail d&#39;adresse d&#39;expédition
Company Billing Address,Adresse de la société de facturation
Supplier Address Details,
Bank Reconciliation Tool,Outil de réconcialiation d&#39;écritures bancaires
Supplier Contact,Contact fournisseur
Subcontracting,Sous traitance
Order Status,Statut de la commande
Build,Personnalisations avancées
Dispatch Address Name,Adresse de livraison intermédiaire
Amount Eligible for Commission,Montant éligible à comission
Grant Commission,Eligible aux commissions
Stock Transactions Settings, Paramétre des transactions
Role Allowed to Over Deliver/Receive, Rôle autorisé à dépasser cette limite
Users with this role are allowed to over deliver/receive against orders above the allowance percentage,Rôle Utilisateur qui sont autorisé à livrée/commandé au-delà de la limite
Over Transfer Allowance,Autorisation de limite de transfert
"The percentage you are allowed to transfer more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed transfer 110 units","Le pourcentage de quantité que vous pourrez receptionné en plus de la quantité commandée"
Quality Inspection Settings,Paramétre de l&#39;inspection qualité
Action If Quality Inspection Is Rejected,Action si l'inspection qualité est rejetée
Disable Serial No And Batch Selector,Désactiver le sélecteur de numéro de lot/série
Is Rate Adjustment Entry (Debit Note),Est un justement du prix de la note de débit
Issue a debit note with 0 qty against an existing Sales Invoice,Creer une note de débit avec une quatité à O pour la facture
Control Historical Stock Transactions,Controle de l&#39;historique des stransaction de stock
No stock transactions can be created or modified before this date.,Aucune transaction ne peux être créée ou modifié avant cette date.
Stock transactions that are older than the mentioned days cannot be modified.,Les transactions de stock plus ancienne que le nombre de jours ci-dessus ne peuvent être modifiées
Role Allowed to Create/Edit Back-dated Transactions,Rôle autorisé à créer et modifier des transactions anti-datée
"If mentioned, the system will allow only the users with this Role to create or modify any stock transaction earlier than the latest stock transaction for a specific item and warehouse. If set as blank, it allows all users to create/edit back-dated transactions.","Les utilisateur de ce role pourront creer et modifier des transactions dans le passé. Si vide tout les utilisateurs pourrons le faire"
Auto Insert Item Price If Missing,Création du prix de l'article dans les listes de prix si abscent
Update Existing Price List Rate,Mise a jour automatique du prix dans les listes de prix
Show Barcode Field in Stock Transactions,Afficher le champ Code Barre dans les transactions de stock
Convert Item Description to Clean HTML in Transactions,Convertir les descriptions d'articles en HTML valide lors des transactions
Have Default Naming Series for Batch ID?,Nom de série par défaut pour les Lots ou Séries
"The percentage you are allowed to transfer more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed transfer 110 units","Le pourcentage de quantité que vous pourrez réceptionner en plus de la quantité commandée. Par exemple, vous avez commandé 100 unités, votre pourcentage de dépassement est de 10%, vous pourrez réceptionner 110 unités"
Can't render this file because it is too large.