Merge branch 'version-13-hotfix' into e-commerce-refactor
This commit is contained in:
3
.flake8
3
.flake8
@@ -29,4 +29,5 @@ ignore =
|
||||
B950,
|
||||
W191,
|
||||
|
||||
max-line-length = 200
|
||||
max-line-length = 200
|
||||
exclude=.github/helper/semgrep_rules
|
||||
|
||||
@@ -4,25 +4,61 @@ from frappe import _, flt
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
# ruleid: frappe-modifying-but-not-comitting
|
||||
def on_submit(self):
|
||||
if self.value_of_goods == 0:
|
||||
frappe.throw(_('Value of goods cannot be 0'))
|
||||
# ruleid: frappe-modifying-after-submit
|
||||
self.status = 'Submitted'
|
||||
|
||||
|
||||
# ok: frappe-modifying-but-not-comitting
|
||||
def on_submit(self):
|
||||
if flt(self.per_billed) < 100:
|
||||
self.update_billing_status()
|
||||
else:
|
||||
# todook: frappe-modifying-after-submit
|
||||
self.status = "Completed"
|
||||
self.db_set("status", "Completed")
|
||||
if self.value_of_goods == 0:
|
||||
frappe.throw(_('Value of goods cannot be 0'))
|
||||
self.status = 'Submitted'
|
||||
self.db_set('status', 'Submitted')
|
||||
|
||||
class TestDoc(Document):
|
||||
pass
|
||||
# ok: frappe-modifying-but-not-comitting
|
||||
def on_submit(self):
|
||||
if self.value_of_goods == 0:
|
||||
frappe.throw(_('Value of goods cannot be 0'))
|
||||
x = "y"
|
||||
self.status = x
|
||||
self.db_set('status', x)
|
||||
|
||||
def validate(self):
|
||||
#ruleid: frappe-modifying-child-tables-while-iterating
|
||||
for item in self.child_table:
|
||||
if item.value < 0:
|
||||
self.remove(item)
|
||||
|
||||
# ok: frappe-modifying-but-not-comitting
|
||||
def on_submit(self):
|
||||
x = "y"
|
||||
self.status = x
|
||||
self.save()
|
||||
|
||||
# ruleid: frappe-modifying-but-not-comitting-other-method
|
||||
class DoctypeClass(Document):
|
||||
def on_submit(self):
|
||||
self.good_method()
|
||||
self.tainted_method()
|
||||
|
||||
def tainted_method(self):
|
||||
self.status = "uptate"
|
||||
|
||||
|
||||
# ok: frappe-modifying-but-not-comitting-other-method
|
||||
class DoctypeClass(Document):
|
||||
def on_submit(self):
|
||||
self.good_method()
|
||||
self.tainted_method()
|
||||
|
||||
def tainted_method(self):
|
||||
self.status = "update"
|
||||
self.db_set("status", "update")
|
||||
|
||||
# ok: frappe-modifying-but-not-comitting-other-method
|
||||
class DoctypeClass(Document):
|
||||
def on_submit(self):
|
||||
self.good_method()
|
||||
self.tainted_method()
|
||||
self.save()
|
||||
|
||||
def tainted_method(self):
|
||||
self.status = "uptate"
|
||||
|
||||
@@ -1,32 +1,93 @@
|
||||
# This file specifies rules for correctness according to how frappe doctype data model works.
|
||||
|
||||
rules:
|
||||
- id: frappe-modifying-after-submit
|
||||
- id: frappe-modifying-but-not-comitting
|
||||
patterns:
|
||||
- pattern: self.$ATTR = ...
|
||||
- pattern-inside: |
|
||||
def on_submit(self, ...):
|
||||
- pattern: |
|
||||
def $METHOD(self, ...):
|
||||
...
|
||||
self.$ATTR = ...
|
||||
- pattern-not: |
|
||||
def $METHOD(self, ...):
|
||||
...
|
||||
self.$ATTR = ...
|
||||
...
|
||||
self.db_set(..., self.$ATTR, ...)
|
||||
- pattern-not: |
|
||||
def $METHOD(self, ...):
|
||||
...
|
||||
self.$ATTR = $SOME_VAR
|
||||
...
|
||||
self.db_set(..., $SOME_VAR, ...)
|
||||
- pattern-not: |
|
||||
def $METHOD(self, ...):
|
||||
...
|
||||
self.$ATTR = $SOME_VAR
|
||||
...
|
||||
self.save()
|
||||
- metavariable-regex:
|
||||
metavariable: '$ATTR'
|
||||
# this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me)
|
||||
regex: '^(?!status_updater)(.*)$'
|
||||
regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
|
||||
- metavariable-regex:
|
||||
metavariable: "$METHOD"
|
||||
regex: "(on_submit|on_cancel)"
|
||||
message: |
|
||||
Doctype modified after submission. Please check if modification of self.$ATTR is commited to database.
|
||||
DocType modified in self.$METHOD. Please check if modification of self.$ATTR is commited to database.
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-modifying-after-cancel
|
||||
- id: frappe-modifying-but-not-comitting-other-method
|
||||
patterns:
|
||||
- pattern: self.$ATTR = ...
|
||||
- pattern-inside: |
|
||||
def on_cancel(self, ...):
|
||||
- pattern: |
|
||||
class $DOCTYPE(...):
|
||||
def $METHOD(self, ...):
|
||||
...
|
||||
- metavariable-regex:
|
||||
metavariable: '$ATTR'
|
||||
regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
|
||||
self.$ANOTHER_METHOD()
|
||||
...
|
||||
|
||||
def $ANOTHER_METHOD(self, ...):
|
||||
...
|
||||
self.$ATTR = ...
|
||||
- pattern-not: |
|
||||
class $DOCTYPE(...):
|
||||
def $METHOD(self, ...):
|
||||
...
|
||||
self.$ANOTHER_METHOD()
|
||||
...
|
||||
|
||||
def $ANOTHER_METHOD(self, ...):
|
||||
...
|
||||
self.$ATTR = ...
|
||||
...
|
||||
self.db_set(..., self.$ATTR, ...)
|
||||
- pattern-not: |
|
||||
class $DOCTYPE(...):
|
||||
def $METHOD(self, ...):
|
||||
...
|
||||
self.$ANOTHER_METHOD()
|
||||
...
|
||||
|
||||
def $ANOTHER_METHOD(self, ...):
|
||||
...
|
||||
self.$ATTR = $SOME_VAR
|
||||
...
|
||||
self.db_set(..., $SOME_VAR, ...)
|
||||
- pattern-not: |
|
||||
class $DOCTYPE(...):
|
||||
def $METHOD(self, ...):
|
||||
...
|
||||
self.$ANOTHER_METHOD()
|
||||
...
|
||||
self.save()
|
||||
def $ANOTHER_METHOD(self, ...):
|
||||
...
|
||||
self.$ATTR = ...
|
||||
- metavariable-regex:
|
||||
metavariable: "$METHOD"
|
||||
regex: "(on_submit|on_cancel)"
|
||||
message: |
|
||||
Doctype modified after cancellation. Please check if modification of self.$ATTR is commited to database.
|
||||
self.$ANOTHER_METHOD is called from self.$METHOD, check if changes to self.$ATTR are commited to database.
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
|
||||
7
.github/helper/semgrep_rules/translate.js
vendored
7
.github/helper/semgrep_rules/translate.js
vendored
@@ -35,3 +35,10 @@ __('You have' + 'subscribers in your mailing list.')
|
||||
// ruleid: frappe-translation-js-splitting
|
||||
__('You have {0} subscribers' +
|
||||
'in your mailing list', [subscribers.length])
|
||||
|
||||
// ok: frappe-translation-js-splitting
|
||||
__("Ctrl+Enter to add comment")
|
||||
|
||||
// ruleid: frappe-translation-js-splitting
|
||||
__('You have {0} subscribers \
|
||||
in your mailing list', [subscribers.length])
|
||||
|
||||
9
.github/helper/semgrep_rules/translate.yml
vendored
9
.github/helper/semgrep_rules/translate.yml
vendored
@@ -42,9 +42,10 @@ rules:
|
||||
|
||||
- id: frappe-translation-python-splitting
|
||||
pattern-either:
|
||||
- pattern: _(...) + ... + _(...)
|
||||
- pattern: _(...) + _(...)
|
||||
- pattern: _("..." + "...")
|
||||
- pattern-regex: '_\([^\)]*\\\s*'
|
||||
- pattern-regex: '_\([^\)]*\\\s*' # lines broken by `\`
|
||||
- pattern-regex: '_\(\s*\n' # line breaks allowed by python for using ( )
|
||||
message: |
|
||||
Do not split strings inside translate function. Do not concatenate using translate functions.
|
||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||
@@ -53,8 +54,8 @@ rules:
|
||||
|
||||
- id: frappe-translation-js-splitting
|
||||
pattern-either:
|
||||
- pattern-regex: '__\([^\)]*[\+\\]\s*'
|
||||
- pattern: __('...' + '...')
|
||||
- pattern-regex: '__\([^\)]*[\\]\s+'
|
||||
- pattern: __('...' + '...', ...)
|
||||
- pattern: __('...') + __('...')
|
||||
message: |
|
||||
Do not split strings inside translate function. Do not concatenate using translate functions.
|
||||
|
||||
23
.github/workflows/ci-tests.yml
vendored
23
.github/workflows/ci-tests.yml
vendored
@@ -80,14 +80,29 @@ jobs:
|
||||
env:
|
||||
TYPE: ${{ matrix.TYPE }}
|
||||
|
||||
- name: Coverage
|
||||
if: matrix.TYPE == 'server'
|
||||
- name: Coverage - Pull Request
|
||||
if: matrix.TYPE == 'server' && github.event_name == 'pull_request'
|
||||
run: |
|
||||
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
|
||||
cd ${GITHUB_WORKSPACE}
|
||||
pip install coveralls==3.0.1
|
||||
pip install coverage==5.5
|
||||
pip install coveralls==2.2.0
|
||||
pip install coverage==4.5.4
|
||||
coveralls --service=github
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
|
||||
COVERALLS_SERVICE_NAME: github
|
||||
|
||||
- name: Coverage - Push
|
||||
if: matrix.TYPE == 'server' && github.event_name == 'push'
|
||||
run: |
|
||||
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
|
||||
cd ${GITHUB_WORKSPACE}
|
||||
pip install coveralls==2.2.0
|
||||
pip install coverage==4.5.4
|
||||
coveralls --service=github-actions
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
|
||||
COVERALLS_SERVICE_NAME: github-actions
|
||||
|
||||
|
||||
12
.github/workflows/semgrep.yml
vendored
12
.github/workflows/semgrep.yml
vendored
@@ -4,6 +4,8 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
- version-13-hotfix
|
||||
- version-13-pre-release
|
||||
jobs:
|
||||
semgrep:
|
||||
name: Frappe Linter
|
||||
@@ -14,11 +16,19 @@ jobs:
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Run semgrep
|
||||
|
||||
- name: Setup semgrep
|
||||
run: |
|
||||
python -m pip install -q semgrep
|
||||
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
|
||||
|
||||
- name: Semgrep errors
|
||||
run: |
|
||||
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
||||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files
|
||||
semgrep --config="r/python.lang.correctness" --quiet --error $files
|
||||
|
||||
- name: Semgrep warnings
|
||||
run: |
|
||||
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
||||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files
|
||||
|
||||
@@ -39,6 +39,10 @@ ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a
|
||||
|
||||
---
|
||||
|
||||
### Containerized Installation
|
||||
|
||||
Use docker to deploy ERPNext in production or for development of [Frappe](https://github.com/frappe/frappe) apps. See https://github.com/frappe/frappe_docker for more details.
|
||||
|
||||
### Full Install
|
||||
|
||||
The Easy Way: our install script for bench will install all dependencies (e.g. MariaDB). See https://github.com/frappe/bench for more details.
|
||||
|
||||
@@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '13.0.0-dev'
|
||||
__version__ = '13.2.0'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
||||
@@ -13,7 +13,7 @@ class BalanceMismatchError(frappe.ValidationError): pass
|
||||
class Account(NestedSet):
|
||||
nsm_parent_field = 'parent_account'
|
||||
def on_update(self):
|
||||
if frappe.local.flags.ignore_on_update:
|
||||
if frappe.local.flags.ignore_update_nsm:
|
||||
return
|
||||
else:
|
||||
super(Account, self).on_update()
|
||||
|
||||
@@ -57,10 +57,10 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch
|
||||
|
||||
# Rebuild NestedSet HSM tree for Account Doctype
|
||||
# after all accounts are already inserted.
|
||||
frappe.local.flags.ignore_on_update = True
|
||||
frappe.local.flags.ignore_update_nsm = True
|
||||
_import_accounts(chart, None, None, root_account=True)
|
||||
rebuild_tree("Account", "parent_account")
|
||||
frappe.local.flags.ignore_on_update = False
|
||||
frappe.local.flags.ignore_update_nsm = False
|
||||
|
||||
def add_suffix_if_duplicate(account_name, account_number, accounts):
|
||||
if account_number:
|
||||
|
||||
@@ -7,26 +7,30 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"auto_accounting_for_stock",
|
||||
"acc_frozen_upto",
|
||||
"frozen_accounts_modifier",
|
||||
"determine_address_tax_category_from",
|
||||
"accounts_transactions_settings_section",
|
||||
"over_billing_allowance",
|
||||
"role_allowed_to_over_bill",
|
||||
"column_break_4",
|
||||
"credit_controller",
|
||||
"check_supplier_invoice_uniqueness",
|
||||
"make_payment_via_journal_entry",
|
||||
"column_break_11",
|
||||
"check_supplier_invoice_uniqueness",
|
||||
"unlink_payment_on_cancellation_of_invoice",
|
||||
"unlink_advance_payment_on_cancelation_of_order",
|
||||
"book_asset_depreciation_entry_automatically",
|
||||
"add_taxes_from_item_tax_template",
|
||||
"automatically_fetch_payment_terms",
|
||||
"delete_linked_ledger_entries",
|
||||
"book_asset_depreciation_entry_automatically",
|
||||
"unlink_advance_payment_on_cancelation_of_order",
|
||||
"tax_settings_section",
|
||||
"determine_address_tax_category_from",
|
||||
"column_break_19",
|
||||
"add_taxes_from_item_tax_template",
|
||||
"period_closing_settings_section",
|
||||
"acc_frozen_upto",
|
||||
"frozen_accounts_modifier",
|
||||
"column_break_4",
|
||||
"credit_controller",
|
||||
"deferred_accounting_settings_section",
|
||||
"automatically_process_deferred_accounting_entry",
|
||||
"book_deferred_entries_based_on",
|
||||
"column_break_18",
|
||||
"automatically_process_deferred_accounting_entry",
|
||||
"book_deferred_entries_via_journal_entry",
|
||||
"submit_journal_entries",
|
||||
"print_settings",
|
||||
@@ -40,15 +44,6 @@
|
||||
"use_custom_cash_flow"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "1",
|
||||
"description": "If enabled, the system will post accounting entries for inventory automatically",
|
||||
"fieldname": "auto_accounting_for_stock",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Make Accounting Entry For Every Stock Movement"
|
||||
},
|
||||
{
|
||||
"description": "Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below",
|
||||
"fieldname": "acc_frozen_upto",
|
||||
@@ -94,6 +89,7 @@
|
||||
"default": "0",
|
||||
"fieldname": "make_payment_via_journal_entry",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Make Payment via Journal Entry"
|
||||
},
|
||||
{
|
||||
@@ -234,6 +230,29 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Role Allowed to Over Bill ",
|
||||
"options": "Role"
|
||||
},
|
||||
{
|
||||
"fieldname": "period_closing_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Period Closing Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounts_transactions_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Transactions Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Tax Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_19",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@@ -241,7 +260,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-11 18:52:05.601996",
|
||||
"modified": "2021-04-30 15:25:10.381008",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -30,5 +30,5 @@ class AccountsSettings(Document):
|
||||
def enable_payment_schedule_in_print(self):
|
||||
show_in_print = cint(self.show_payment_schedule_in_print)
|
||||
for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"):
|
||||
make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check")
|
||||
make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check")
|
||||
make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False)
|
||||
make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check", validate_fields_for_doctype=False)
|
||||
|
||||
@@ -78,8 +78,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
|
||||
if (
|
||||
frm.doc.bank_account &&
|
||||
frm.doc.bank_statement_from_date &&
|
||||
frm.doc.bank_statement_to_date &&
|
||||
frm.doc.bank_statement_closing_balance
|
||||
frm.doc.bank_statement_to_date
|
||||
) {
|
||||
frm.trigger("render_chart");
|
||||
frm.trigger("render");
|
||||
|
||||
@@ -39,13 +39,13 @@
|
||||
"depends_on": "eval: doc.bank_account",
|
||||
"fieldname": "bank_statement_from_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Bank Statement From Date"
|
||||
"label": "From Date"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.bank_statement_from_date",
|
||||
"fieldname": "bank_statement_to_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Bank Statement To Date"
|
||||
"label": "To Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
@@ -63,11 +63,10 @@
|
||||
"depends_on": "eval: doc.bank_statement_to_date",
|
||||
"fieldname": "bank_statement_closing_balance",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Bank Statement Closing Balance",
|
||||
"label": "Closing Balance",
|
||||
"options": "Currency"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.bank_statement_closing_balance",
|
||||
"fieldname": "section_break_1",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Reconcile"
|
||||
@@ -90,7 +89,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-02 01:35:53.043578",
|
||||
"modified": "2021-04-21 11:13:49.831769",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Reconciliation Tool",
|
||||
|
||||
@@ -42,9 +42,9 @@ class TestDunning(unittest.TestCase):
|
||||
['Sales - _TC', 0.0, 20.44]
|
||||
])
|
||||
for gle in gl_entries:
|
||||
self.assertEquals(expected_values[gle.account][0], gle.account)
|
||||
self.assertEquals(expected_values[gle.account][1], gle.debit)
|
||||
self.assertEquals(expected_values[gle.account][2], gle.credit)
|
||||
self.assertEqual(expected_values[gle.account][0], gle.account)
|
||||
self.assertEqual(expected_values[gle.account][1], gle.debit)
|
||||
self.assertEqual(expected_values[gle.account][2], gle.credit)
|
||||
|
||||
def test_payment_entry(self):
|
||||
dunning = create_dunning()
|
||||
|
||||
@@ -21,21 +21,17 @@ frappe.ui.form.on('Exchange Rate Revaluation', {
|
||||
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.docstatus==1) {
|
||||
frappe.db.get_value("Journal Entry Account", {
|
||||
'reference_type': 'Exchange Rate Revaluation',
|
||||
'reference_name': frm.doc.name,
|
||||
'docstatus': 1
|
||||
}, "sum(debit) as sum", (r) =>{
|
||||
let total_amt = 0;
|
||||
frm.doc.accounts.forEach(d=> {
|
||||
total_amt = total_amt + d['new_balance_in_base_currency'];
|
||||
});
|
||||
if(total_amt !== r.sum) {
|
||||
frm.add_custom_button(__('Journal Entry'), function() {
|
||||
return frm.events.make_jv(frm);
|
||||
}, __('Create'));
|
||||
frappe.call({
|
||||
method: 'check_journal_entry_condition',
|
||||
doc: frm.doc,
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.add_custom_button(__('Journal Entry'), function() {
|
||||
return frm.events.make_jv(frm);
|
||||
}, __('Create'));
|
||||
}
|
||||
}
|
||||
}, 'Journal Entry');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -27,6 +27,23 @@ class ExchangeRateRevaluation(Document):
|
||||
if not (self.company and self.posting_date):
|
||||
frappe.throw(_("Please select Company and Posting Date to getting entries"))
|
||||
|
||||
@frappe.whitelist()
|
||||
def check_journal_entry_condition(self):
|
||||
total_debit = frappe.db.get_value("Journal Entry Account", {
|
||||
'reference_type': 'Exchange Rate Revaluation',
|
||||
'reference_name': self.name,
|
||||
'docstatus': 1
|
||||
}, "sum(debit) as sum")
|
||||
|
||||
total_amt = 0
|
||||
for d in self.accounts:
|
||||
total_amt = total_amt + d.new_balance_in_base_currency
|
||||
|
||||
if total_amt != total_debit:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_accounts_data(self, account=None):
|
||||
accounts = []
|
||||
|
||||
@@ -54,4 +54,4 @@ class TestGLEntry(unittest.TestCase):
|
||||
self.assertTrue(all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries)))
|
||||
|
||||
new_naming_series_current_value = frappe.db.sql("SELECT current from tabSeries where name = %s", naming_series)[0][0]
|
||||
self.assertEquals(old_naming_series_current_value + 2, new_naming_series_current_value)
|
||||
self.assertEqual(old_naming_series_current_value + 2, new_naming_series_current_value)
|
||||
|
||||
@@ -592,6 +592,7 @@ class JournalEntry(AccountsController):
|
||||
|
||||
self.validate_total_debit_and_credit()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_outstanding_invoices(self):
|
||||
self.set('accounts', [])
|
||||
total = 0
|
||||
|
||||
@@ -1,87 +1,39 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2014-08-29 16:02:39.740505",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"actions": [],
|
||||
"creation": "2014-08-29 16:02:39.740505",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"field_order": [
|
||||
"company",
|
||||
"account"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Account",
|
||||
"options": "Account"
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-07-11 03:28:03.348246",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Party Account",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-07 18:13:08.833822",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Party Account",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
@@ -561,7 +561,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate));
|
||||
|
||||
if(frm.doc.payment_type == "Pay")
|
||||
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount);
|
||||
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount, 1);
|
||||
else
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
|
||||
@@ -582,7 +582,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
|
||||
if(frm.doc.payment_type == "Receive")
|
||||
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount);
|
||||
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount, 1);
|
||||
else
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
},
|
||||
@@ -606,9 +606,9 @@ frappe.ui.form.on('Payment Entry', {
|
||||
{fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"},
|
||||
{fieldtype:"Section Break"},
|
||||
{fieldtype:"Link", label:__("Cost Center"), fieldname:"cost_center", options:"Cost Center",
|
||||
"get_query": function() {
|
||||
return {
|
||||
"filters": {"company": frm.doc.company}
|
||||
"get_query": function() {
|
||||
return {
|
||||
"filters": {"company": frm.doc.company}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -743,7 +743,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
});
|
||||
},
|
||||
|
||||
allocate_party_amount_against_ref_docs: function(frm, paid_amount) {
|
||||
allocate_party_amount_against_ref_docs: function(frm, paid_amount, paid_amount_change) {
|
||||
var total_positive_outstanding_including_order = 0;
|
||||
var total_negative_outstanding = 0;
|
||||
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
|
||||
@@ -800,22 +800,15 @@ frappe.ui.form.on('Payment Entry', {
|
||||
//If allocate payment amount checkbox is unchecked, set zero to allocate amount
|
||||
row.allocated_amount = 0;
|
||||
|
||||
} else if (frappe.flags.allocate_payment_amount != 0 && !row.allocated_amount) {
|
||||
if (row.outstanding_amount > 0 && allocated_positive_outstanding > 0) {
|
||||
if (row.outstanding_amount >= allocated_positive_outstanding) {
|
||||
row.allocated_amount = allocated_positive_outstanding;
|
||||
} else {
|
||||
row.allocated_amount = row.outstanding_amount;
|
||||
}
|
||||
|
||||
} else if (frappe.flags.allocate_payment_amount != 0 && (!row.allocated_amount || paid_amount_change)) {
|
||||
if (row.outstanding_amount > 0 && allocated_positive_outstanding >= 0) {
|
||||
row.allocated_amount = (row.outstanding_amount >= allocated_positive_outstanding) ?
|
||||
allocated_positive_outstanding : row.outstanding_amount;
|
||||
allocated_positive_outstanding -= flt(row.allocated_amount);
|
||||
} else if (row.outstanding_amount < 0 && allocated_negative_outstanding) {
|
||||
if (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) {
|
||||
row.allocated_amount = -1*allocated_negative_outstanding;
|
||||
} else {
|
||||
row.allocated_amount = row.outstanding_amount;
|
||||
};
|
||||
|
||||
} else if (row.outstanding_amount < 0 && allocated_negative_outstanding) {
|
||||
row.allocated_amount = (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) ?
|
||||
-1*allocated_negative_outstanding : row.outstanding_amount;
|
||||
allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@ class TestPaymentOrder(unittest.TestCase):
|
||||
|
||||
doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry")
|
||||
reference_doc = doc.get("references")[0]
|
||||
self.assertEquals(reference_doc.reference_name, payment_entry.name)
|
||||
self.assertEquals(reference_doc.reference_doctype, "Payment Entry")
|
||||
self.assertEquals(reference_doc.supplier, "_Test Supplier")
|
||||
self.assertEquals(reference_doc.amount, 250)
|
||||
self.assertEqual(reference_doc.reference_name, payment_entry.name)
|
||||
self.assertEqual(reference_doc.reference_doctype, "Payment Entry")
|
||||
self.assertEqual(reference_doc.supplier, "_Test Supplier")
|
||||
self.assertEqual(reference_doc.amount, 250)
|
||||
|
||||
def create_payment_order_against_payment_entry(ref_doc, order_type):
|
||||
payment_order = frappe.get_doc(dict(
|
||||
|
||||
@@ -234,7 +234,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
||||
});
|
||||
|
||||
if (invoices) {
|
||||
this.frm.fields_dict.payment.grid.update_docfield_property(
|
||||
this.frm.fields_dict.payments.grid.update_docfield_property(
|
||||
'invoice_number', 'options', "\n" + invoices.join("\n")
|
||||
);
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ class PaymentReconciliation(Document):
|
||||
'party_type': self.party_type,
|
||||
'voucher_type': voucher_type,
|
||||
'account': self.receivable_payable_account
|
||||
}, as_dict=1, debug=1)
|
||||
}, as_dict=1)
|
||||
|
||||
def add_payment_entries(self, entries):
|
||||
self.set('payments', [])
|
||||
|
||||
@@ -20,10 +20,11 @@
|
||||
"discount",
|
||||
"section_break_9",
|
||||
"payment_amount",
|
||||
"outstanding",
|
||||
"paid_amount",
|
||||
"discounted_amount",
|
||||
"column_break_3",
|
||||
"outstanding",
|
||||
"paid_amount"
|
||||
"base_payment_amount"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -78,7 +79,8 @@
|
||||
"depends_on": "paid_amount",
|
||||
"fieldname": "paid_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Paid Amount"
|
||||
"label": "Paid Amount",
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
@@ -97,6 +99,7 @@
|
||||
"fieldname": "outstanding",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Outstanding",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -145,12 +148,18 @@
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "base_payment_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Payment Amount (Company Currency)",
|
||||
"options": "Company:company:default_currency"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-15 21:03:12.540546",
|
||||
"modified": "2021-04-28 05:41:35.084233",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Schedule",
|
||||
|
||||
@@ -22,7 +22,43 @@ frappe.ui.form.on('POS Closing Entry', {
|
||||
});
|
||||
|
||||
if (frm.doc.docstatus === 0 && !frm.doc.amended_from) frm.set_value("period_end_date", frappe.datetime.now_datetime());
|
||||
if (frm.doc.docstatus === 1) set_html_data(frm);
|
||||
|
||||
frappe.realtime.on('closing_process_complete', async function(data) {
|
||||
await frm.reload_doc();
|
||||
if (frm.doc.status == 'Failed' && frm.doc.error_message && data.user == frappe.session.user) {
|
||||
frappe.msgprint({
|
||||
title: __('POS Closing Failed'),
|
||||
message: frm.doc.error_message,
|
||||
indicator: 'orange',
|
||||
clear: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
set_html_data(frm);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.docstatus == 1 && frm.doc.status == 'Failed') {
|
||||
const issue = '<a id="jump_to_error" style="text-decoration: underline;">issue</a>';
|
||||
frm.dashboard.set_headline(
|
||||
__('POS Closing failed while running in a background process. You can resolve the {0} and retry the process again.', [issue]));
|
||||
|
||||
$('#jump_to_error').on('click', (e) => {
|
||||
e.preventDefault();
|
||||
frappe.utils.scroll_to(
|
||||
cur_frm.get_field("error_message").$wrapper,
|
||||
true,
|
||||
30
|
||||
);
|
||||
});
|
||||
|
||||
frm.add_custom_button(__('Retry'), function () {
|
||||
frm.call('retry', {}, () => {
|
||||
frm.reload_doc();
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
pos_opening_entry(frm) {
|
||||
@@ -61,44 +97,24 @@ frappe.ui.form.on('POS Closing Entry', {
|
||||
refresh_fields(frm);
|
||||
set_html_data(frm);
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
before_save: function(frm) {
|
||||
for (let row of frm.doc.pos_transactions) {
|
||||
frappe.db.get_doc("POS Invoice", row.pos_invoice).then(doc => {
|
||||
cur_frm.doc.grand_total -= flt(doc.grand_total);
|
||||
cur_frm.doc.net_total -= flt(doc.net_total);
|
||||
cur_frm.doc.total_quantity -= flt(doc.total_qty);
|
||||
refresh_payments(doc, cur_frm, 1);
|
||||
refresh_taxes(doc, cur_frm, 1);
|
||||
refresh_fields(cur_frm);
|
||||
set_html_data(cur_frm);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cur_frm.cscript.before_pos_transactions_remove = function(doc, cdt, cdn) {
|
||||
const removed_row = locals[cdt][cdn];
|
||||
|
||||
if (!removed_row.pos_invoice) return;
|
||||
|
||||
frappe.db.get_doc("POS Invoice", removed_row.pos_invoice).then(doc => {
|
||||
cur_frm.doc.grand_total -= flt(doc.grand_total);
|
||||
cur_frm.doc.net_total -= flt(doc.net_total);
|
||||
cur_frm.doc.total_quantity -= flt(doc.total_qty);
|
||||
refresh_payments(doc, cur_frm, 1);
|
||||
refresh_taxes(doc, cur_frm, 1);
|
||||
refresh_fields(cur_frm);
|
||||
set_html_data(cur_frm);
|
||||
});
|
||||
}
|
||||
|
||||
frappe.ui.form.on('POS Invoice Reference', {
|
||||
pos_invoice(frm, cdt, cdn) {
|
||||
const added_row = locals[cdt][cdn];
|
||||
|
||||
if (!added_row.pos_invoice) return;
|
||||
|
||||
frappe.db.get_doc("POS Invoice", added_row.pos_invoice).then(doc => {
|
||||
frm.doc.grand_total += flt(doc.grand_total);
|
||||
frm.doc.net_total += flt(doc.net_total);
|
||||
frm.doc.total_quantity += flt(doc.total_qty);
|
||||
refresh_payments(doc, frm);
|
||||
refresh_taxes(doc, frm);
|
||||
refresh_fields(frm);
|
||||
set_html_data(frm);
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
frappe.ui.form.on('POS Closing Entry Detail', {
|
||||
closing_amount: (frm, cdt, cdn) => {
|
||||
const row = locals[cdt][cdn];
|
||||
@@ -177,11 +193,13 @@ function refresh_fields(frm) {
|
||||
}
|
||||
|
||||
function set_html_data(frm) {
|
||||
frappe.call({
|
||||
method: "get_payment_reconciliation_details",
|
||||
doc: frm.doc,
|
||||
callback: (r) => {
|
||||
frm.get_field("payment_reconciliation_details").$wrapper.html(r.message);
|
||||
}
|
||||
})
|
||||
if (frm.doc.docstatus === 1 && frm.doc.status == 'Submitted') {
|
||||
frappe.call({
|
||||
method: "get_payment_reconciliation_details",
|
||||
doc: frm.doc,
|
||||
callback: (r) => {
|
||||
frm.get_field("payment_reconciliation_details").$wrapper.html(r.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
"total_quantity",
|
||||
"column_break_16",
|
||||
"taxes",
|
||||
"failure_description_section",
|
||||
"error_message",
|
||||
"section_break_14",
|
||||
"amended_from"
|
||||
],
|
||||
@@ -195,7 +197,7 @@
|
||||
"fieldtype": "Select",
|
||||
"hidden": 1,
|
||||
"label": "Status",
|
||||
"options": "Draft\nSubmitted\nQueued\nCancelled",
|
||||
"options": "Draft\nSubmitted\nQueued\nFailed\nCancelled",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -203,6 +205,21 @@
|
||||
"fieldname": "period_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Period Details"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "error_message",
|
||||
"depends_on": "error_message",
|
||||
"fieldname": "failure_description_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Failure Description"
|
||||
},
|
||||
{
|
||||
"depends_on": "error_message",
|
||||
"fieldname": "error_message",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Error",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
@@ -212,7 +229,7 @@
|
||||
"link_fieldname": "pos_closing_entry"
|
||||
}
|
||||
],
|
||||
"modified": "2021-02-01 13:47:20.722104",
|
||||
"modified": "2021-05-05 16:59:49.723261",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Closing Entry",
|
||||
|
||||
@@ -16,28 +16,8 @@ class POSClosingEntry(StatusUpdater):
|
||||
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
||||
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
||||
|
||||
self.validate_pos_closing()
|
||||
self.validate_pos_invoices()
|
||||
|
||||
def validate_pos_closing(self):
|
||||
user = frappe.db.sql("""
|
||||
SELECT name FROM `tabPOS Closing Entry`
|
||||
WHERE
|
||||
user = %(user)s AND docstatus = 1 AND pos_profile = %(profile)s AND
|
||||
(period_start_date between %(start)s and %(end)s OR period_end_date between %(start)s and %(end)s)
|
||||
""", {
|
||||
'user': self.user,
|
||||
'profile': self.pos_profile,
|
||||
'start': self.period_start_date,
|
||||
'end': self.period_end_date
|
||||
})
|
||||
|
||||
if user:
|
||||
bold_already_exists = frappe.bold(_("already exists"))
|
||||
bold_user = frappe.bold(self.user)
|
||||
frappe.throw(_("POS Closing Entry {} against {} between selected period")
|
||||
.format(bold_already_exists, bold_user), title=_("Invalid Period"))
|
||||
|
||||
def validate_pos_invoices(self):
|
||||
invalid_rows = []
|
||||
for d in self.pos_transactions:
|
||||
@@ -80,6 +60,10 @@ class POSClosingEntry(StatusUpdater):
|
||||
def on_cancel(self):
|
||||
unconsolidate_pos_invoices(closing_entry=self)
|
||||
|
||||
@frappe.whitelist()
|
||||
def retry(self):
|
||||
consolidate_pos_invoices(closing_entry=self)
|
||||
|
||||
def update_opening_entry(self, for_cancel=False):
|
||||
opening_entry = frappe.get_doc("POS Opening Entry", self.pos_opening_entry)
|
||||
opening_entry.pos_closing_entry = self.name if not for_cancel else None
|
||||
|
||||
@@ -8,6 +8,7 @@ frappe.listview_settings['POS Closing Entry'] = {
|
||||
"Draft": "red",
|
||||
"Submitted": "blue",
|
||||
"Queued": "orange",
|
||||
"Failed": "red",
|
||||
"Cancelled": "red"
|
||||
|
||||
};
|
||||
|
||||
@@ -96,30 +96,45 @@ class POSInvoice(SalesInvoice):
|
||||
if paid_amt and pay.amount != paid_amt:
|
||||
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
|
||||
|
||||
def validate_pos_reserved_serial_nos(self, item):
|
||||
serial_nos = get_serial_nos(item.serial_no)
|
||||
filters = {"item_code": item.item_code, "warehouse": item.warehouse}
|
||||
if item.batch_no:
|
||||
filters["batch_no"] = item.batch_no
|
||||
|
||||
reserved_serial_nos = get_pos_reserved_serial_nos(filters)
|
||||
invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos]
|
||||
|
||||
bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos))
|
||||
if len(invalid_serial_nos) == 1:
|
||||
frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||
.format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable"))
|
||||
elif invalid_serial_nos:
|
||||
frappe.throw(_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||
.format(item.idx, bold_invalid_serial_nos), title=_("Item Unavailable"))
|
||||
|
||||
def validate_delivered_serial_nos(self, item):
|
||||
serial_nos = get_serial_nos(item.serial_no)
|
||||
delivered_serial_nos = frappe.db.get_list('Serial No', {
|
||||
'item_code': item.item_code,
|
||||
'name': ['in', serial_nos],
|
||||
'sales_invoice': ['is', 'set']
|
||||
}, pluck='name')
|
||||
|
||||
if delivered_serial_nos:
|
||||
bold_delivered_serial_nos = frappe.bold(', '.join(delivered_serial_nos))
|
||||
frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another Sales Invoice. Please select valid serial no.")
|
||||
.format(item.idx, bold_delivered_serial_nos), title=_("Item Unavailable"))
|
||||
|
||||
def validate_stock_availablility(self):
|
||||
if self.is_return:
|
||||
return
|
||||
|
||||
allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
|
||||
error_msg = []
|
||||
allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
|
||||
for d in self.get('items'):
|
||||
msg = ""
|
||||
if d.serial_no:
|
||||
filters = { "item_code": d.item_code, "warehouse": d.warehouse }
|
||||
if d.batch_no:
|
||||
filters["batch_no"] = d.batch_no
|
||||
reserved_serial_nos = get_pos_reserved_serial_nos(filters)
|
||||
serial_nos = get_serial_nos(d.serial_no)
|
||||
invalid_serial_nos = [s for s in serial_nos if s in reserved_serial_nos]
|
||||
|
||||
bold_invalid_serial_nos = frappe.bold(', '.join(invalid_serial_nos))
|
||||
if len(invalid_serial_nos) == 1:
|
||||
msg = (_("Row #{}: Serial No. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||
.format(d.idx, bold_invalid_serial_nos))
|
||||
elif invalid_serial_nos:
|
||||
msg = (_("Row #{}: Serial Nos. {} has already been transacted into another POS Invoice. Please select valid serial no.")
|
||||
.format(d.idx, bold_invalid_serial_nos))
|
||||
|
||||
self.validate_pos_reserved_serial_nos(d)
|
||||
self.validate_delivered_serial_nos(d)
|
||||
else:
|
||||
if allow_negative_stock:
|
||||
return
|
||||
@@ -127,15 +142,11 @@ class POSInvoice(SalesInvoice):
|
||||
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
||||
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
|
||||
if flt(available_stock) <= 0:
|
||||
msg = (_('Row #{}: Item Code: {} is not available under warehouse {}.').format(d.idx, item_code, warehouse))
|
||||
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.')
|
||||
.format(d.idx, item_code, warehouse), title=_("Item Unavailable"))
|
||||
elif flt(available_stock) < flt(d.qty):
|
||||
msg = (_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.')
|
||||
.format(d.idx, item_code, warehouse, qty))
|
||||
if msg:
|
||||
error_msg.append(msg)
|
||||
|
||||
if error_msg:
|
||||
frappe.throw(error_msg, title=_("Item Unavailable"), as_list=True)
|
||||
frappe.throw(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}.')
|
||||
.format(d.idx, item_code, warehouse, available_stock), title=_("Item Unavailable"))
|
||||
|
||||
def validate_serialised_or_batched_item(self):
|
||||
error_msg = []
|
||||
@@ -202,9 +213,8 @@ class POSInvoice(SalesInvoice):
|
||||
for d in self.get("items"):
|
||||
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
|
||||
if not is_stock_item:
|
||||
frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ").format(
|
||||
d.idx, frappe.bold(d.item_code)
|
||||
), title=_("Invalid Item"))
|
||||
frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ")
|
||||
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
||||
|
||||
def validate_mode_of_payment(self):
|
||||
if len(self.payments) == 0:
|
||||
@@ -451,7 +461,17 @@ def get_stock_availability(item_code, warehouse):
|
||||
order by posting_date desc, posting_time desc
|
||||
limit 1""", (item_code, warehouse), as_dict=1)
|
||||
|
||||
pos_sales_qty = frappe.db.sql("""select sum(p_item.qty) as qty
|
||||
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
|
||||
|
||||
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
|
||||
|
||||
if sle_qty and pos_sales_qty:
|
||||
return sle_qty - pos_sales_qty
|
||||
else:
|
||||
return sle_qty
|
||||
|
||||
def get_pos_reserved_qty(item_code, warehouse):
|
||||
reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty
|
||||
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
|
||||
where p.name = p_item.parent
|
||||
and p.consolidated_invoice is NULL
|
||||
@@ -460,14 +480,8 @@ def get_stock_availability(item_code, warehouse):
|
||||
and p_item.item_code = %s
|
||||
and p_item.warehouse = %s
|
||||
""", (item_code, warehouse), as_dict=1)
|
||||
|
||||
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
|
||||
pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0
|
||||
|
||||
if sle_qty and pos_sales_qty:
|
||||
return sle_qty - pos_sales_qty
|
||||
else:
|
||||
return sle_qty
|
||||
|
||||
return reserved_qty[0].qty or 0 if reserved_qty else 0
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_return(source_name, target_doc=None):
|
||||
|
||||
@@ -10,10 +10,12 @@ from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
|
||||
class TestPOSInvoice(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item", qty=800, basic_rate=100)
|
||||
frappe.db.sql("delete from `tabTax Rule`")
|
||||
|
||||
def tearDown(self):
|
||||
@@ -320,6 +322,34 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pos2.insert)
|
||||
|
||||
def test_delivered_serialized_item_transaction(self):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
se = make_serialized_item(company='_Test Company',
|
||||
target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC')
|
||||
|
||||
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
|
||||
|
||||
si = create_sales_invoice(company='_Test Company', debit_to='Debtors - _TC',
|
||||
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
|
||||
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
|
||||
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
||||
|
||||
si.get("items")[0].serial_no = serial_nos[0]
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
pos2 = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
|
||||
account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
|
||||
expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
|
||||
item=se.get("items")[0].item_code, rate=1000, do_not_save=1)
|
||||
|
||||
pos2.get("items")[0].serial_no = serial_nos[0]
|
||||
pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000})
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pos2.insert)
|
||||
|
||||
def test_loyalty_points(self):
|
||||
from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import create_records
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import get_loyalty_program_details_with_points
|
||||
|
||||
@@ -13,8 +13,7 @@ from frappe.model.mapper import map_doc, map_child_doc
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||
import json
|
||||
|
||||
from six import iteritems
|
||||
import six
|
||||
|
||||
class POSInvoiceMergeLog(Document):
|
||||
def validate(self):
|
||||
@@ -235,11 +234,11 @@ def get_invoice_customer_map(pos_invoices):
|
||||
|
||||
return pos_invoice_customer_map
|
||||
|
||||
def consolidate_pos_invoices(pos_invoices=[], closing_entry={}):
|
||||
invoices = pos_invoices or closing_entry.get('pos_transactions') or get_all_unconsolidated_invoices()
|
||||
def consolidate_pos_invoices(pos_invoices=None, closing_entry=None):
|
||||
invoices = pos_invoices or (closing_entry and closing_entry.get('pos_transactions')) or get_all_unconsolidated_invoices()
|
||||
invoice_by_customer = get_invoice_customer_map(invoices)
|
||||
|
||||
if len(invoices) >= 5 and closing_entry:
|
||||
if len(invoices) >= 10 and closing_entry:
|
||||
closing_entry.set_status(update=True, status='Queued')
|
||||
enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry)
|
||||
else:
|
||||
@@ -252,51 +251,83 @@ def unconsolidate_pos_invoices(closing_entry):
|
||||
pluck='name'
|
||||
)
|
||||
|
||||
if len(merge_logs) >= 5:
|
||||
if len(merge_logs) >= 10:
|
||||
closing_entry.set_status(update=True, status='Queued')
|
||||
enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry)
|
||||
else:
|
||||
cancel_merge_logs(merge_logs, closing_entry)
|
||||
|
||||
def create_merge_logs(invoice_by_customer, closing_entry={}):
|
||||
for customer, invoices in iteritems(invoice_by_customer):
|
||||
merge_log = frappe.new_doc('POS Invoice Merge Log')
|
||||
merge_log.posting_date = getdate(closing_entry.get('posting_date'))
|
||||
merge_log.customer = customer
|
||||
merge_log.pos_closing_entry = closing_entry.get('name', None)
|
||||
def create_merge_logs(invoice_by_customer, closing_entry=None):
|
||||
try:
|
||||
for customer, invoices in six.iteritems(invoice_by_customer):
|
||||
merge_log = frappe.new_doc('POS Invoice Merge Log')
|
||||
merge_log.posting_date = getdate(closing_entry.get('posting_date')) if closing_entry else nowdate()
|
||||
merge_log.customer = customer
|
||||
merge_log.pos_closing_entry = closing_entry.get('name') if closing_entry else None
|
||||
|
||||
merge_log.set('pos_invoices', invoices)
|
||||
merge_log.save(ignore_permissions=True)
|
||||
merge_log.submit()
|
||||
merge_log.set('pos_invoices', invoices)
|
||||
merge_log.save(ignore_permissions=True)
|
||||
merge_log.submit()
|
||||
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status='Submitted')
|
||||
closing_entry.update_opening_entry()
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status='Submitted')
|
||||
closing_entry.db_set('error_message', '')
|
||||
closing_entry.update_opening_entry()
|
||||
|
||||
def cancel_merge_logs(merge_logs, closing_entry={}):
|
||||
for log in merge_logs:
|
||||
merge_log = frappe.get_doc('POS Invoice Merge Log', log)
|
||||
merge_log.flags.ignore_permissions = True
|
||||
merge_log.cancel()
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
message_log = frappe.message_log.pop()
|
||||
error_message = safe_load_json(message_log)
|
||||
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status='Cancelled')
|
||||
closing_entry.update_opening_entry(for_cancel=True)
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status='Failed')
|
||||
closing_entry.db_set('error_message', error_message)
|
||||
raise
|
||||
|
||||
def enqueue_job(job, merge_logs=None, invoice_by_customer=None, closing_entry=None):
|
||||
finally:
|
||||
frappe.db.commit()
|
||||
frappe.publish_realtime('closing_process_complete', {'user': frappe.session.user})
|
||||
|
||||
def cancel_merge_logs(merge_logs, closing_entry=None):
|
||||
try:
|
||||
for log in merge_logs:
|
||||
merge_log = frappe.get_doc('POS Invoice Merge Log', log)
|
||||
merge_log.flags.ignore_permissions = True
|
||||
merge_log.cancel()
|
||||
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status='Cancelled')
|
||||
closing_entry.db_set('error_message', '')
|
||||
closing_entry.update_opening_entry(for_cancel=True)
|
||||
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
message_log = frappe.message_log.pop()
|
||||
error_message = safe_load_json(message_log)
|
||||
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status='Submitted')
|
||||
closing_entry.db_set('error_message', error_message)
|
||||
raise
|
||||
|
||||
finally:
|
||||
frappe.db.commit()
|
||||
frappe.publish_realtime('closing_process_complete', {'user': frappe.session.user})
|
||||
|
||||
def enqueue_job(job, **kwargs):
|
||||
check_scheduler_status()
|
||||
|
||||
closing_entry = kwargs.get('closing_entry') or {}
|
||||
|
||||
job_name = closing_entry.get("name")
|
||||
if not job_already_enqueued(job_name):
|
||||
enqueue(
|
||||
job,
|
||||
**kwargs,
|
||||
queue="long",
|
||||
timeout=10000,
|
||||
event="processing_merge_logs",
|
||||
job_name=job_name,
|
||||
closing_entry=closing_entry,
|
||||
invoice_by_customer=invoice_by_customer,
|
||||
merge_logs=merge_logs,
|
||||
now=frappe.conf.developer_mode or frappe.flags.in_test
|
||||
)
|
||||
|
||||
@@ -314,4 +345,14 @@ def check_scheduler_status():
|
||||
def job_already_enqueued(job_name):
|
||||
enqueued_jobs = [d.get("job_name") for d in get_info()]
|
||||
if job_name in enqueued_jobs:
|
||||
return True
|
||||
return True
|
||||
|
||||
def safe_load_json(message):
|
||||
JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError
|
||||
|
||||
try:
|
||||
json_message = json.loads(message).get('message')
|
||||
except JSONDecodeError:
|
||||
json_message = message
|
||||
|
||||
return json_message
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-04-19 14:56:06.652327",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"field",
|
||||
"fieldname"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Fieldname"
|
||||
},
|
||||
{
|
||||
"fieldname": "field",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Field"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-21 11:12:54.632093",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Search Fields",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class POSSearchFields(Document):
|
||||
pass
|
||||
@@ -1,9 +1,17 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
let search_fields_datatypes = ['Data', 'Link', 'Dynamic Link', 'Long Text', 'Select', 'Small Text', 'Text', 'Text Editor'];
|
||||
let do_not_include_fields = ["naming_series", "item_code", "item_name", "stock_uom", "hub_sync_id", "asset_naming_series",
|
||||
"default_material_request_type", "valuation_method", "warranty_period", "weight_uom", "batch_number_series",
|
||||
"serial_no_series", "purchase_uom", "customs_tariff_number", "sales_uom", "deferred_revenue_account",
|
||||
"deferred_expense_account", "quality_inspection_template", "route", "slideshow", "website_image_alt", "thumbnail",
|
||||
"web_long_description", "hub_sync_id"]
|
||||
|
||||
frappe.ui.form.on('POS Settings', {
|
||||
onload: function(frm) {
|
||||
frm.trigger("get_invoice_fields");
|
||||
frm.trigger("add_search_options");
|
||||
},
|
||||
|
||||
get_invoice_fields: function(frm) {
|
||||
@@ -21,6 +29,38 @@ frappe.ui.form.on('POS Settings', {
|
||||
);
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
add_search_options: function(frm) {
|
||||
frappe.model.with_doctype("Item", () => {
|
||||
var fields = $.map(frappe.get_doc("DocType", "Item").fields, function(d) {
|
||||
if (search_fields_datatypes.includes(d.fieldtype) && !(do_not_include_fields.includes(d.fieldname))) {
|
||||
return [d.label];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
fields.unshift('');
|
||||
frm.fields_dict.pos_search_fields.grid.update_docfield_property('field', 'options', fields);
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on("POS Search Fields", {
|
||||
field: function(frm, doctype, name) {
|
||||
var doc = frappe.get_doc(doctype, name);
|
||||
var df = $.map(frappe.get_doc("DocType", "Item").fields, function(d) {
|
||||
if (doc.field == d.label && search_fields_datatypes.includes(d.fieldtype)) {
|
||||
return d;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})[0];
|
||||
|
||||
doc.fieldname = df.fieldname;
|
||||
frm.refresh_field("fields");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"invoice_fields"
|
||||
"invoice_fields",
|
||||
"pos_search_fields"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -13,11 +14,17 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "POS Field",
|
||||
"options": "POS Field"
|
||||
},
|
||||
{
|
||||
"fieldname": "pos_search_fields",
|
||||
"fieldtype": "Table",
|
||||
"label": "POS Search Fields",
|
||||
"options": "POS Search Fields"
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-01 15:46:41.478928",
|
||||
"modified": "2021-04-19 14:56:24.465218",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Settings",
|
||||
|
||||
@@ -99,7 +99,7 @@ class TestPricingRule(unittest.TestCase):
|
||||
|
||||
args.item_code = "_Test Item 2"
|
||||
details = get_item_details(args)
|
||||
self.assertEquals(details.get("discount_percentage"), 15)
|
||||
self.assertEqual(details.get("discount_percentage"), 15)
|
||||
|
||||
def test_pricing_rule_for_margin(self):
|
||||
from erpnext.stock.get_item_details import get_item_details
|
||||
@@ -145,8 +145,8 @@ class TestPricingRule(unittest.TestCase):
|
||||
"name": None
|
||||
})
|
||||
details = get_item_details(args)
|
||||
self.assertEquals(details.get("margin_type"), "Percentage")
|
||||
self.assertEquals(details.get("margin_rate_or_amount"), 10)
|
||||
self.assertEqual(details.get("margin_type"), "Percentage")
|
||||
self.assertEqual(details.get("margin_rate_or_amount"), 10)
|
||||
|
||||
def test_mixed_conditions_for_item_group(self):
|
||||
for item in ["Mixed Cond Item 1", "Mixed Cond Item 2"]:
|
||||
@@ -192,7 +192,7 @@ class TestPricingRule(unittest.TestCase):
|
||||
"name": None
|
||||
})
|
||||
details = get_item_details(args)
|
||||
self.assertEquals(details.get("discount_percentage"), 10)
|
||||
self.assertEqual(details.get("discount_percentage"), 10)
|
||||
|
||||
def test_pricing_rule_for_variants(self):
|
||||
from erpnext.stock.get_item_details import get_item_details
|
||||
@@ -322,11 +322,11 @@ class TestPricingRule(unittest.TestCase):
|
||||
si.insert(ignore_permissions=True)
|
||||
|
||||
item = si.items[0]
|
||||
self.assertEquals(item.margin_rate_or_amount, 10)
|
||||
self.assertEquals(item.rate_with_margin, 1100)
|
||||
self.assertEqual(item.margin_rate_or_amount, 10)
|
||||
self.assertEqual(item.rate_with_margin, 1100)
|
||||
self.assertEqual(item.discount_percentage, 10)
|
||||
self.assertEquals(item.discount_amount, 110)
|
||||
self.assertEquals(item.rate, 990)
|
||||
self.assertEqual(item.discount_amount, 110)
|
||||
self.assertEqual(item.rate, 990)
|
||||
|
||||
def test_pricing_rule_with_margin_and_discount_amount(self):
|
||||
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
|
||||
@@ -338,10 +338,10 @@ class TestPricingRule(unittest.TestCase):
|
||||
si.insert(ignore_permissions=True)
|
||||
|
||||
item = si.items[0]
|
||||
self.assertEquals(item.margin_rate_or_amount, 10)
|
||||
self.assertEquals(item.rate_with_margin, 1100)
|
||||
self.assertEquals(item.discount_amount, 110)
|
||||
self.assertEquals(item.rate, 990)
|
||||
self.assertEqual(item.margin_rate_or_amount, 10)
|
||||
self.assertEqual(item.rate_with_margin, 1100)
|
||||
self.assertEqual(item.discount_amount, 110)
|
||||
self.assertEqual(item.rate, 990)
|
||||
|
||||
def test_pricing_rule_for_product_discount_on_same_item(self):
|
||||
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
|
||||
@@ -458,21 +458,21 @@ class TestPricingRule(unittest.TestCase):
|
||||
si.items[0].price_list_rate = 1000
|
||||
si.submit()
|
||||
item = si.items[0]
|
||||
self.assertEquals(item.rate, 100)
|
||||
self.assertEqual(item.rate, 100)
|
||||
|
||||
# Correct Customer and Incorrect is_return value
|
||||
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1)
|
||||
si.items[0].price_list_rate = 1000
|
||||
si.submit()
|
||||
item = si.items[0]
|
||||
self.assertEquals(item.rate, 100)
|
||||
self.assertEqual(item.rate, 100)
|
||||
|
||||
# Correct Customer and correct is_return value
|
||||
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0)
|
||||
si.items[0].price_list_rate = 1000
|
||||
si.submit()
|
||||
item = si.items[0]
|
||||
self.assertEquals(item.rate, 900)
|
||||
self.assertEqual(item.rate, 900)
|
||||
|
||||
def test_multiple_pricing_rules(self):
|
||||
make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
|
||||
@@ -545,11 +545,11 @@ class TestPricingRule(unittest.TestCase):
|
||||
apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10)
|
||||
|
||||
si = create_sales_invoice(qty=5, do_not_submit=True)
|
||||
self.assertEquals(len(si.items), 2)
|
||||
self.assertEquals(si.items[1].rate, 10)
|
||||
self.assertEqual(len(si.items), 2)
|
||||
self.assertEqual(si.items[1].rate, 10)
|
||||
|
||||
si1 = create_sales_invoice(qty=2, do_not_submit=True)
|
||||
self.assertEquals(len(si1.items), 1)
|
||||
self.assertEqual(len(si1.items), 1)
|
||||
|
||||
for doc in [si, si1]:
|
||||
doc.delete()
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in data %}
|
||||
{% for row in data %}
|
||||
<tr>
|
||||
{% if(row.posting_date) %}
|
||||
<td>{{ frappe.format(row.posting_date, 'Date') }}</td>
|
||||
@@ -60,8 +60,8 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<br><br>
|
||||
{% if aging %}
|
||||
<h3 class="text-center">{{ _("Ageing Report Based On ") }} {{ aging.ageing_based_on }}</h3>
|
||||
{% if ageing %}
|
||||
<h3 class="text-center">{{ _("Ageing Report Based On ") }} {{ ageing.ageing_based_on }}</h3>
|
||||
<h5 class="text-center">
|
||||
{{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
|
||||
</h5>
|
||||
@@ -78,10 +78,10 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ aging.range1 }}</td>
|
||||
<td>{{ aging.range2 }}</td>
|
||||
<td>{{ aging.range3 }}</td>
|
||||
<td>{{ aging.range4 }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa
|
||||
from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing
|
||||
from frappe.core.doctype.communication.email import make
|
||||
from erpnext import get_company_currency
|
||||
from erpnext.accounts.party import get_party_account_currency
|
||||
|
||||
from frappe.utils.print_format import report_to_pdf
|
||||
from frappe.utils.pdf import get_pdf
|
||||
@@ -29,7 +31,7 @@ class ProcessStatementOfAccounts(Document):
|
||||
validate_template(self.body)
|
||||
|
||||
if not self.customers:
|
||||
frappe.throw(frappe._('Customers not selected.'))
|
||||
frappe.throw(_('Customers not selected.'))
|
||||
|
||||
if self.enable_auto_email:
|
||||
self.to_date = self.start_date
|
||||
@@ -38,7 +40,7 @@ class ProcessStatementOfAccounts(Document):
|
||||
|
||||
def get_report_pdf(doc, consolidated=True):
|
||||
statement_dict = {}
|
||||
aging = ''
|
||||
ageing = ''
|
||||
base_template_path = "frappe/www/printview.html"
|
||||
template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
|
||||
|
||||
@@ -54,26 +56,30 @@ def get_report_pdf(doc, consolidated=True):
|
||||
'range4': 120,
|
||||
'customer': entry.customer
|
||||
})
|
||||
col1, aging = get_ageing(ageing_filters)
|
||||
aging[0]['ageing_based_on'] = doc.ageing_based_on
|
||||
col1, ageing = get_ageing(ageing_filters)
|
||||
|
||||
if ageing:
|
||||
ageing[0]['ageing_based_on'] = doc.ageing_based_on
|
||||
|
||||
tax_id = frappe.get_doc('Customer', entry.customer).tax_id
|
||||
presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \
|
||||
or doc.currency or get_company_currency(doc.company)
|
||||
|
||||
filters= frappe._dict({
|
||||
'from_date': doc.from_date,
|
||||
'to_date': doc.to_date,
|
||||
'company': doc.company,
|
||||
'finance_book': doc.finance_book if doc.finance_book else None,
|
||||
"account": doc.account if doc.account else None,
|
||||
'account': doc.account if doc.account else None,
|
||||
'party_type': 'Customer',
|
||||
'party': [entry.customer],
|
||||
'presentation_currency': presentation_currency,
|
||||
'group_by': doc.group_by,
|
||||
'currency': doc.currency,
|
||||
'cost_center': [cc.cost_center_name for cc in doc.cost_center],
|
||||
'project': [p.project_name for p in doc.project],
|
||||
'show_opening_entries': 0,
|
||||
'include_default_book_entries': 0,
|
||||
'show_cancelled_entries': 1,
|
||||
'tax_id': tax_id if tax_id else None
|
||||
})
|
||||
col, res = get_soa(filters)
|
||||
@@ -83,11 +89,14 @@ def get_report_pdf(doc, consolidated=True):
|
||||
|
||||
if len(res) == 3:
|
||||
continue
|
||||
|
||||
html = frappe.render_template(template_path, \
|
||||
{"filters": filters, "data": res, "aging": aging[0] if doc.include_ageing else None})
|
||||
{"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None})
|
||||
|
||||
html = frappe.render_template(base_template_path, {"body": html, \
|
||||
"css": get_print_style(), "title": "Statement For " + entry.customer})
|
||||
statement_dict[entry.customer] = html
|
||||
|
||||
if not bool(statement_dict):
|
||||
return False
|
||||
elif consolidated:
|
||||
@@ -167,7 +176,7 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory):
|
||||
if customer_collection == 'Sales Person':
|
||||
customers = get_customers_based_on_sales_person(collection_name)
|
||||
if not bool(customers):
|
||||
frappe.throw('No Customers found with selected options.')
|
||||
frappe.throw(_('No Customers found with selected options.'))
|
||||
else:
|
||||
if customer_collection == 'Sales Partner':
|
||||
customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
|
||||
@@ -199,14 +208,14 @@ def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=Tr
|
||||
|
||||
if len(billing_email) == 0 or (billing_email[0][0] is None):
|
||||
if billing_and_primary:
|
||||
frappe.throw('No billing email found for customer: '+ customer_name)
|
||||
frappe.throw(_("No billing email found for customer: {0}").format(customer_name))
|
||||
else:
|
||||
return ''
|
||||
|
||||
if billing_and_primary:
|
||||
primary_email = frappe.get_value('Customer', customer_name, 'email_id')
|
||||
if primary_email is None and int(primary_mandatory):
|
||||
frappe.throw('No primary email found for customer: '+ customer_name)
|
||||
frappe.throw(_("No primary email found for customer: {0}").format(customer_name))
|
||||
return [primary_email or '', billing_email[0][0]]
|
||||
else:
|
||||
return billing_email[0][0] or ''
|
||||
|
||||
@@ -514,6 +514,28 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.events.add_custom_buttons(frm);
|
||||
},
|
||||
|
||||
add_custom_buttons: function(frm) {
|
||||
if (frm.doc.per_received < 100) {
|
||||
frm.add_custom_button(__('Purchase Receipt'), () => {
|
||||
frm.events.make_purchase_receipt(frm);
|
||||
}, __('Create'));
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus == 1 && frm.doc.per_received > 0) {
|
||||
frm.add_custom_button(__('Purchase Receipt'), () => {
|
||||
frappe.route_options = {
|
||||
'purchase_invoice': frm.doc.name
|
||||
}
|
||||
|
||||
frappe.set_route("List", "Purchase Receipt", "List")
|
||||
}, __('View'));
|
||||
}
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
if(frm.doc.__onload && frm.is_new()) {
|
||||
if(frm.doc.supplier) {
|
||||
@@ -539,5 +561,13 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
update_stock: function(frm) {
|
||||
hide_fields(frm.doc);
|
||||
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false);
|
||||
},
|
||||
|
||||
make_purchase_receipt: function(frm) {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt",
|
||||
frm: frm,
|
||||
freeze_message: __("Creating Purchase Receipt ...")
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -163,7 +163,8 @@
|
||||
"to_date",
|
||||
"column_break_114",
|
||||
"auto_repeat",
|
||||
"update_auto_repeat_reference"
|
||||
"update_auto_repeat_reference",
|
||||
"per_received"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -1364,13 +1365,22 @@
|
||||
"print_hide": 1,
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"fieldname": "per_received",
|
||||
"fieldtype": "Percent",
|
||||
"hidden": 1,
|
||||
"label": "Per Received",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-30 22:45:58.334107",
|
||||
"modified": "2021-04-30 22:45:58.334107",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -1207,3 +1207,41 @@ def make_inter_company_sales_invoice(source_name, target_doc=None):
|
||||
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"])
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_receipt(source_name, target_doc=None):
|
||||
def update_item(obj, target, source_parent):
|
||||
target.qty = flt(obj.qty) - flt(obj.received_qty)
|
||||
target.received_qty = flt(obj.qty) - flt(obj.received_qty)
|
||||
target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor)
|
||||
target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate)
|
||||
target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \
|
||||
flt(obj.rate) * flt(source_parent.conversion_rate)
|
||||
|
||||
doc = get_mapped_doc("Purchase Invoice", source_name, {
|
||||
"Purchase Invoice": {
|
||||
"doctype": "Purchase Receipt",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1],
|
||||
}
|
||||
},
|
||||
"Purchase Invoice Item": {
|
||||
"doctype": "Purchase Receipt Item",
|
||||
"field_map": {
|
||||
"name": "purchase_invoice_item",
|
||||
"parent": "purchase_invoice",
|
||||
"bom": "bom",
|
||||
"purchase_order": "purchase_order",
|
||||
"po_detail": "purchase_order_item",
|
||||
"material_request": "material_request",
|
||||
"material_request_item": "material_request_item"
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
|
||||
},
|
||||
"Purchase Taxes and Charges": {
|
||||
"doctype": "Purchase Taxes and Charges"
|
||||
}
|
||||
}, target_doc)
|
||||
|
||||
return doc
|
||||
|
||||
@@ -397,7 +397,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
|
||||
pi.update({
|
||||
"payment_schedule": get_payment_terms("_Test Payment Term Template",
|
||||
pi.posting_date, pi.grand_total)
|
||||
pi.posting_date, pi.grand_total, pi.base_grand_total)
|
||||
})
|
||||
|
||||
pi.save()
|
||||
|
||||
@@ -607,6 +607,7 @@
|
||||
"oldfieldname": "purchase_order",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Purchase Order",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
@@ -853,7 +854,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-23 00:59:52.614805",
|
||||
"modified": "2021-03-30 09:02:39.256602",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -356,11 +356,11 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
},
|
||||
|
||||
items_on_form_rendered: function() {
|
||||
erpnext.setup_serial_no();
|
||||
erpnext.setup_serial_or_batch_no();
|
||||
},
|
||||
|
||||
packed_items_on_form_rendered: function(doc, grid_row) {
|
||||
erpnext.setup_serial_no();
|
||||
erpnext.setup_serial_or_batch_no();
|
||||
},
|
||||
|
||||
make_sales_return: function() {
|
||||
|
||||
@@ -1111,7 +1111,7 @@ class SalesInvoice(SellingController):
|
||||
if not item.serial_no:
|
||||
continue
|
||||
|
||||
for serial_no in item.serial_no.split("\n"):
|
||||
for serial_no in get_serial_nos(item.serial_no):
|
||||
if serial_no and frappe.db.get_value('Serial No', serial_no, 'item_code') == item.item_code:
|
||||
frappe.db.set_value('Serial No', serial_no, 'sales_invoice', invoice)
|
||||
|
||||
@@ -1755,15 +1755,10 @@ def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, wa
|
||||
item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item))
|
||||
|
||||
def get_delivery_note_details(internal_reference):
|
||||
so_item_map = {}
|
||||
|
||||
si_item_details = frappe.get_all('Delivery Note Item', fields=['name', 'so_detail'],
|
||||
filters={'parent': internal_reference})
|
||||
|
||||
for d in si_item_details:
|
||||
so_item_map.setdefault(d.name, d.so_detail)
|
||||
|
||||
return so_item_map
|
||||
return {d.name: d.so_detail for d in si_item_details if d.so_detail}
|
||||
|
||||
def get_sales_invoice_details(internal_reference):
|
||||
dn_item_map = {}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"additional_discount_percentage",
|
||||
"additional_discount_amount",
|
||||
"sb_3",
|
||||
"submit_invoice",
|
||||
"invoices",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
@@ -45,9 +46,7 @@
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "cb_1",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
@@ -55,97 +54,73 @@
|
||||
"label": "Status",
|
||||
"no_copy": 1,
|
||||
"options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "subscription_period",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Subscription Period",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Subscription Period"
|
||||
},
|
||||
{
|
||||
"fieldname": "cancelation_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Cancelation Date",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "trial_period_start",
|
||||
"fieldtype": "Date",
|
||||
"label": "Trial Period Start Date",
|
||||
"set_only_once": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.trial_period_start",
|
||||
"fieldname": "trial_period_end",
|
||||
"fieldtype": "Date",
|
||||
"label": "Trial Period End Date",
|
||||
"set_only_once": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "current_invoice_start",
|
||||
"fieldtype": "Date",
|
||||
"label": "Current Invoice Start Date",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "current_invoice_end",
|
||||
"fieldtype": "Date",
|
||||
"label": "Current Invoice End Date",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Number of days that the subscriber has to pay invoices generated by this subscription",
|
||||
"fieldname": "days_until_due",
|
||||
"fieldtype": "Int",
|
||||
"label": "Days Until Due",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Days Until Due"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "cancel_at_period_end",
|
||||
"fieldtype": "Check",
|
||||
"label": "Cancel At End Of Period",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Cancel At End Of Period"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "generate_invoice_at_period_start",
|
||||
"fieldtype": "Check",
|
||||
"label": "Generate Invoice At Beginning Of Period",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Generate Invoice At Beginning Of Period"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "sb_4",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Plans",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Plans"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@@ -153,84 +128,62 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "Plans",
|
||||
"options": "Subscription Plan Detail",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:['Customer', 'Supplier'].includes(doc.party_type)",
|
||||
"fieldname": "sb_1",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Taxes",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Taxes"
|
||||
},
|
||||
{
|
||||
"fieldname": "sb_2",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Discounts",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Discounts"
|
||||
},
|
||||
{
|
||||
"fieldname": "apply_additional_discount",
|
||||
"fieldtype": "Select",
|
||||
"label": "Apply Additional Discount On",
|
||||
"options": "\nGrand Total\nNet Total",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "\nGrand Total\nNet Total"
|
||||
},
|
||||
{
|
||||
"fieldname": "cb_2",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "additional_discount_percentage",
|
||||
"fieldtype": "Percent",
|
||||
"label": "Additional DIscount Percentage",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Additional DIscount Percentage"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "additional_discount_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Additional DIscount Amount",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Additional DIscount Amount"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.invoices",
|
||||
"fieldname": "sb_3",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Invoices",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Invoices"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "invoices",
|
||||
"fieldtype": "Table",
|
||||
"label": "Invoices",
|
||||
"options": "Subscription Invoice",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Subscription Invoice"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Accounting Dimensions"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "party_type",
|
||||
@@ -238,9 +191,7 @@
|
||||
"label": "Party Type",
|
||||
"options": "DocType",
|
||||
"reqd": 1,
|
||||
"set_only_once": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "party",
|
||||
@@ -249,27 +200,21 @@
|
||||
"label": "Party",
|
||||
"options": "party_type",
|
||||
"reqd": 1,
|
||||
"set_only_once": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.party_type === 'Customer'",
|
||||
"fieldname": "sales_tax_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Sales Taxes and Charges Template",
|
||||
"options": "Sales Taxes and Charges Template",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Sales Taxes and Charges Template"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.party_type === 'Supplier'",
|
||||
"fieldname": "purchase_tax_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Taxes and Charges Template",
|
||||
"options": "Purchase Taxes and Charges Template",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Purchase Taxes and Charges Template"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -277,55 +222,49 @@
|
||||
"fieldname": "follow_calendar_months",
|
||||
"fieldtype": "Check",
|
||||
"label": "Follow Calendar Months",
|
||||
"set_only_once": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "New invoices will be generated as per schedule even if current invoices are unpaid or past due date",
|
||||
"fieldname": "generate_new_invoices_past_due_date",
|
||||
"fieldtype": "Check",
|
||||
"label": "Generate New Invoices Past Due Date",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Generate New Invoices Past Due Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "end_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Subscription End Date",
|
||||
"set_only_once": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Subscription Start Date",
|
||||
"set_only_once": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "submit_invoice",
|
||||
"fieldtype": "Check",
|
||||
"label": "Submit Invoice Automatically"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-09 15:44:20.024789",
|
||||
"modified": "2021-04-19 15:24:27.550797",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Subscription",
|
||||
|
||||
@@ -276,7 +276,7 @@ class Subscription(Document):
|
||||
frappe.throw(_('Subscription End Date is mandatory to follow calendar months'))
|
||||
|
||||
if billing_info[0]['billing_interval'] != 'Month':
|
||||
frappe.throw('Billing Interval in Subscription Plan must be Month to follow calendar months')
|
||||
frappe.throw(_('Billing Interval in Subscription Plan must be Month to follow calendar months'))
|
||||
|
||||
def after_insert(self):
|
||||
# todo: deal with users who collect prepayments. Maybe a new Subscription Invoice doctype?
|
||||
@@ -383,7 +383,9 @@ class Subscription(Document):
|
||||
|
||||
invoice.flags.ignore_mandatory = True
|
||||
invoice.save()
|
||||
invoice.submit()
|
||||
|
||||
if self.submit_invoice:
|
||||
invoice.submit()
|
||||
|
||||
return invoice
|
||||
|
||||
|
||||
@@ -21,7 +21,10 @@ def get_party_details(inv):
|
||||
else:
|
||||
party_type = 'Supplier'
|
||||
party = inv.supplier
|
||||
|
||||
|
||||
if not party:
|
||||
frappe.throw(_("Please select {0} first").format(party_type))
|
||||
|
||||
return party_type, party
|
||||
|
||||
def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
||||
@@ -324,7 +327,7 @@ def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, post
|
||||
net_total, ldc.certificate_limit
|
||||
):
|
||||
tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
|
||||
|
||||
|
||||
return tds_amount
|
||||
|
||||
def get_debit_note_amount(suppliers, fiscal_year_details, company=None):
|
||||
|
||||
@@ -18,7 +18,8 @@ def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, upd
|
||||
gl_map = process_gl_map(gl_map, merge_entries)
|
||||
if gl_map and len(gl_map) > 1:
|
||||
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
|
||||
else:
|
||||
# Post GL Map proccess there may no be any GL Entries
|
||||
elif gl_map:
|
||||
frappe.throw(_("Incorrect number of General Ledger Entries found. You might have selected a wrong Account in the transaction."))
|
||||
else:
|
||||
make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
|
||||
@@ -170,7 +171,7 @@ def round_off_debit_credit(gl_map):
|
||||
else:
|
||||
allowance = .5
|
||||
|
||||
if abs(debit_credit_diff) >= allowance:
|
||||
if abs(debit_credit_diff) > allowance:
|
||||
frappe.throw(_("Debit and Credit not equal for {0} #{1}. Difference is {2}.")
|
||||
.format(gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff))
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"attach_print": 0,
|
||||
"channel": "Email",
|
||||
"condition": "doc.auto_created",
|
||||
"creation": "2018-04-25 14:19:05.440361",
|
||||
"days_in_advance": 0,
|
||||
|
||||
@@ -364,7 +364,7 @@ class ReceivablePayableReport(object):
|
||||
payment_terms_details = frappe.db.sql("""
|
||||
select
|
||||
si.name, si.party_account_currency, si.currency, si.conversion_rate,
|
||||
ps.due_date, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
|
||||
ps.due_date, ps.payment_term, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
|
||||
from `tab{0}` si, `tabPayment Schedule` ps
|
||||
where
|
||||
si.name = ps.parent and
|
||||
@@ -394,7 +394,7 @@ class ReceivablePayableReport(object):
|
||||
"due_date": d.due_date,
|
||||
"invoiced": invoiced,
|
||||
"invoice_grand_total": row.invoiced,
|
||||
"payment_term": d.description,
|
||||
"payment_term": d.description or d.payment_term,
|
||||
"paid": d.paid_amount + d.discounted_amount,
|
||||
"credit_note": 0.0,
|
||||
"outstanding": invoiced - d.paid_amount - d.discounted_amount
|
||||
|
||||
@@ -5,7 +5,8 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt, cint
|
||||
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
|
||||
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data,
|
||||
get_filtered_list_for_consolidated_report)
|
||||
|
||||
def execute(filters=None):
|
||||
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
|
||||
@@ -132,6 +133,10 @@ def get_report_summary(period_list, asset, liability, equity, provisional_profit
|
||||
if filters.get('accumulated_values'):
|
||||
period_list = [period_list[-1]]
|
||||
|
||||
# from consolidated financial statement
|
||||
if filters.get('accumulated_in_group_company'):
|
||||
period_list = get_filtered_list_for_consolidated_report(period_list)
|
||||
|
||||
for period in period_list:
|
||||
key = period if consolidated else period.key
|
||||
if asset:
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports['Billed Items To Be Received'] = {
|
||||
'filters': [
|
||||
{
|
||||
'label': __('Company'),
|
||||
'fieldname': 'company',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Company',
|
||||
'reqd': 1,
|
||||
'default': frappe.defaults.get_default('Company')
|
||||
},
|
||||
{
|
||||
'label': __('As on Date'),
|
||||
'fieldname': 'posting_date',
|
||||
'fieldtype': 'Date',
|
||||
'reqd': 1,
|
||||
'default': get_today()
|
||||
},
|
||||
{
|
||||
'label': __('Purchase Invoice'),
|
||||
'fieldname': 'purchase_invoice',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Purchase Invoice'
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2021-03-30 09:35:38.683028",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2021-03-31 08:48:30.944429",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Billed Items To Be Received",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"query": "",
|
||||
"ref_doctype": "Purchase Invoice",
|
||||
"report_name": "Billed Items To Be Received",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Auditor"
|
||||
},
|
||||
{
|
||||
"role": "Stock User"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
def execute(filters=None):
|
||||
data = get_data(filters) or []
|
||||
columns = get_columns()
|
||||
|
||||
return columns, data
|
||||
|
||||
def get_data(report_filters):
|
||||
filters = get_report_filters(report_filters)
|
||||
fields = get_report_fields()
|
||||
|
||||
return frappe.get_all('Purchase Invoice',
|
||||
fields= fields, filters=filters)
|
||||
|
||||
def get_report_filters(report_filters):
|
||||
filters = [['Purchase Invoice','company','=',report_filters.get('company')],
|
||||
['Purchase Invoice','posting_date','<=',report_filters.get('posting_date')], ['Purchase Invoice','docstatus','=',1],
|
||||
['Purchase Invoice','per_received','<',100], ['Purchase Invoice','update_stock','=',0]]
|
||||
|
||||
if report_filters.get('purchase_invoice'):
|
||||
filters.append(['Purchase Invoice','per_received','in',[report_filters.get('purchase_invoice')]])
|
||||
|
||||
return filters
|
||||
|
||||
def get_report_fields():
|
||||
fields = []
|
||||
for p_field in ['name', 'supplier', 'company', 'posting_date', 'currency']:
|
||||
fields.append('`tabPurchase Invoice`.`{}`'.format(p_field))
|
||||
|
||||
for c_field in ['item_code', 'item_name', 'uom', 'qty', 'received_qty', 'rate', 'amount']:
|
||||
fields.append('`tabPurchase Invoice Item`.`{}`'.format(c_field))
|
||||
|
||||
return fields
|
||||
|
||||
def get_columns():
|
||||
return [
|
||||
{
|
||||
'label': _('Purchase Invoice'),
|
||||
'fieldname': 'name',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Purchase Invoice',
|
||||
'width': 170
|
||||
},
|
||||
{
|
||||
'label': _('Supplier'),
|
||||
'fieldname': 'supplier',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Supplier',
|
||||
'width': 120
|
||||
},
|
||||
{
|
||||
'label': _('Posting Date'),
|
||||
'fieldname': 'posting_date',
|
||||
'fieldtype': 'Date',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Item Code'),
|
||||
'fieldname': 'item_code',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Item',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Item Name'),
|
||||
'fieldname': 'item_name',
|
||||
'fieldtype': 'Data',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('UOM'),
|
||||
'fieldname': 'uom',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'UOM',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Invoiced Qty'),
|
||||
'fieldname': 'qty',
|
||||
'fieldtype': 'Float',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Received Qty'),
|
||||
'fieldname': 'received_qty',
|
||||
'fieldtype': 'Float',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Rate'),
|
||||
'fieldname': 'rate',
|
||||
'fieldtype': 'Currency',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Amount'),
|
||||
'fieldname': 'amount',
|
||||
'fieldtype': 'Currency',
|
||||
'width': 100
|
||||
}
|
||||
]
|
||||
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, cstr
|
||||
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
|
||||
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data, get_filtered_list_for_consolidated_report)
|
||||
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import get_net_profit_loss
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from six import iteritems
|
||||
@@ -67,9 +67,9 @@ def execute(filters=None):
|
||||
section_data.append(account_data)
|
||||
|
||||
add_total_row_account(data, section_data, cash_flow_account['section_footer'],
|
||||
period_list, company_currency, summary_data)
|
||||
period_list, company_currency, summary_data, filters)
|
||||
|
||||
add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency, summary_data)
|
||||
add_total_row_account(data, data, _("Net Change in Cash"), period_list, company_currency, summary_data, filters)
|
||||
columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company)
|
||||
|
||||
chart = get_chart_data(columns, data)
|
||||
@@ -162,18 +162,26 @@ def get_start_date(period, accumulated_values, company):
|
||||
|
||||
return start_date
|
||||
|
||||
def add_total_row_account(out, data, label, period_list, currency, summary_data, consolidated = False):
|
||||
def add_total_row_account(out, data, label, period_list, currency, summary_data, filters, consolidated=False):
|
||||
total_row = {
|
||||
"account_name": "'" + _("{0}").format(label) + "'",
|
||||
"account": "'" + _("{0}").format(label) + "'",
|
||||
"currency": currency
|
||||
}
|
||||
|
||||
summary_data[label] = 0
|
||||
|
||||
# from consolidated financial statement
|
||||
if filters.get('accumulated_in_group_company'):
|
||||
period_list = get_filtered_list_for_consolidated_report(filters, period_list)
|
||||
|
||||
for row in data:
|
||||
if row.get("parent_account"):
|
||||
for period in period_list:
|
||||
key = period if consolidated else period['key']
|
||||
total_row.setdefault(key, 0.0)
|
||||
total_row[key] += row.get(key, 0.0)
|
||||
summary_data[label] += row.get(key)
|
||||
|
||||
total_row.setdefault("total", 0.0)
|
||||
total_row["total"] += row["total"]
|
||||
@@ -181,7 +189,6 @@ def add_total_row_account(out, data, label, period_list, currency, summary_data,
|
||||
out.append(total_row)
|
||||
out.append({})
|
||||
|
||||
summary_data[label] = total_row["total"]
|
||||
|
||||
def get_report_summary(summary_data, currency):
|
||||
report_summary = []
|
||||
|
||||
@@ -2,118 +2,128 @@
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Consolidated Financial Statement"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"filter_based_on",
|
||||
"label": __("Filter Based On"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Fiscal Year", "Date Range"],
|
||||
"default": ["Fiscal Year"],
|
||||
"reqd": 1,
|
||||
on_change: function() {
|
||||
let filter_based_on = frappe.query_report.get_filter_value('filter_based_on');
|
||||
frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range');
|
||||
frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range');
|
||||
frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year');
|
||||
frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year');
|
||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
frappe.query_reports["Consolidated Financial Statement"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"filter_based_on",
|
||||
"label": __("Filter Based On"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Fiscal Year", "Date Range"],
|
||||
"default": ["Fiscal Year"],
|
||||
"reqd": 1,
|
||||
on_change: function() {
|
||||
let filter_based_on = frappe.query_report.get_filter_value('filter_based_on');
|
||||
frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range');
|
||||
frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range');
|
||||
frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year');
|
||||
frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year');
|
||||
|
||||
frappe.query_report.refresh();
|
||||
frappe.query_report.refresh();
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"period_start_date",
|
||||
"label": __("Start Date"),
|
||||
"fieldtype": "Date",
|
||||
"hidden": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"period_end_date",
|
||||
"label": __("End Date"),
|
||||
"fieldtype": "Date",
|
||||
"hidden": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"from_fiscal_year",
|
||||
"label": __("Start Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"to_fiscal_year",
|
||||
"label": __("End Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"finance_book",
|
||||
"label": __("Finance Book"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname":"report",
|
||||
"label": __("Report"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"],
|
||||
"default": "Balance Sheet",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "presentation_currency",
|
||||
"label": __("Currency"),
|
||||
"fieldtype": "Select",
|
||||
"options": erpnext.get_presentation_currency_list(),
|
||||
"default": frappe.defaults.get_user_default("Currency")
|
||||
},
|
||||
{
|
||||
"fieldname":"accumulated_in_group_company",
|
||||
"label": __("Accumulated Values in Group Company"),
|
||||
"fieldtype": "Check",
|
||||
"default": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
}
|
||||
],
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
if (data && column.fieldname=="account") {
|
||||
value = data.account_name || value;
|
||||
|
||||
column.link_onclick =
|
||||
"erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";
|
||||
column.is_tree = true;
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"period_start_date",
|
||||
"label": __("Start Date"),
|
||||
"fieldtype": "Date",
|
||||
"hidden": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"period_end_date",
|
||||
"label": __("End Date"),
|
||||
"fieldtype": "Date",
|
||||
"hidden": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"from_fiscal_year",
|
||||
"label": __("Start Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"to_fiscal_year",
|
||||
"label": __("End Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"finance_book",
|
||||
"label": __("Finance Book"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname":"report",
|
||||
"label": __("Report"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Profit and Loss Statement", "Balance Sheet", "Cash Flow"],
|
||||
"default": "Balance Sheet",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "presentation_currency",
|
||||
"label": __("Currency"),
|
||||
"fieldtype": "Select",
|
||||
"options": erpnext.get_presentation_currency_list(),
|
||||
"default": frappe.defaults.get_user_default("Currency")
|
||||
},
|
||||
{
|
||||
"fieldname":"accumulated_in_group_company",
|
||||
"label": __("Accumulated Values in Group Company"),
|
||||
"fieldtype": "Check",
|
||||
"default": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
}
|
||||
],
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
if (!data.parent_account) {
|
||||
value = $(`<span>${value}</span>`);
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
var $value = $(value).css("font-weight", "bold");
|
||||
if (!data.parent_account) {
|
||||
value = $(`<span>${value}</span>`);
|
||||
|
||||
value = $value.wrap("<p></p>").parent().html();
|
||||
}
|
||||
return value;
|
||||
},
|
||||
onload: function() {
|
||||
let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
|
||||
var $value = $(value).css("font-weight", "bold");
|
||||
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
frappe.query_report.set_filter_value({
|
||||
period_start_date: fy.year_start_date,
|
||||
period_end_date: fy.year_end_date
|
||||
value = $value.wrap("<p></p>").parent().html();
|
||||
}
|
||||
return value;
|
||||
},
|
||||
onload: function() {
|
||||
let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
|
||||
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
frappe.query_report.set_filter_value({
|
||||
period_start_date: fy.year_start_date,
|
||||
period_end_date: fy.year_end_date
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -94,7 +94,7 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters):
|
||||
|
||||
chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss)
|
||||
|
||||
report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, True)
|
||||
report_summary = get_pl_summary(companies, '', income, expense, net_profit_loss, company_currency, filters, True)
|
||||
|
||||
return data, None, chart, report_summary
|
||||
|
||||
@@ -149,9 +149,9 @@ def get_cash_flow_data(fiscal_year, companies, filters):
|
||||
section_data.append(account_data)
|
||||
|
||||
add_total_row_account(data, section_data, cash_flow_account['section_footer'],
|
||||
companies, company_currency, summary_data, True)
|
||||
companies, company_currency, summary_data, filters, True)
|
||||
|
||||
add_total_row_account(data, data, _("Net Change in Cash"), companies, company_currency, summary_data, True)
|
||||
add_total_row_account(data, data, _("Net Change in Cash"), companies, company_currency, summary_data, filters, True)
|
||||
|
||||
report_summary = get_cash_flow_summary(summary_data, company_currency)
|
||||
|
||||
@@ -329,8 +329,9 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com
|
||||
has_value = False
|
||||
total = 0
|
||||
row = frappe._dict({
|
||||
"account_name": _(d.account_name),
|
||||
"account": _(d.account_name),
|
||||
"account_name": ('%s - %s' %(_(d.account_number), _(d.account_name))
|
||||
if d.account_number else _(d.account_name)),
|
||||
"account": _(d.name),
|
||||
"parent_account": _(d.parent_account),
|
||||
"indent": flt(d.indent),
|
||||
"year_start_date": start_date,
|
||||
|
||||
@@ -119,10 +119,10 @@ def validate_fiscal_year(fiscal_year, from_fiscal_year, to_fiscal_year):
|
||||
|
||||
def validate_dates(from_date, to_date):
|
||||
if not from_date or not to_date:
|
||||
frappe.throw("From Date and To Date are mandatory")
|
||||
frappe.throw(_("From Date and To Date are mandatory"))
|
||||
|
||||
if to_date < from_date:
|
||||
frappe.throw("To Date cannot be less than From Date")
|
||||
frappe.throw(_("To Date cannot be less than From Date"))
|
||||
|
||||
def get_months(start_date, end_date):
|
||||
diff = (12 * end_date.year + end_date.month) - (12 * start_date.year + start_date.month)
|
||||
@@ -522,4 +522,12 @@ def get_columns(periodicity, period_list, accumulated_values=1, company=None):
|
||||
"width": 150
|
||||
})
|
||||
|
||||
return columns
|
||||
return columns
|
||||
|
||||
def get_filtered_list_for_consolidated_report(filters, period_list):
|
||||
filtered_summary_list = []
|
||||
for period in period_list:
|
||||
if period == filters.get('company'):
|
||||
filtered_summary_list.append(period)
|
||||
|
||||
return filtered_summary_list
|
||||
|
||||
@@ -116,22 +116,19 @@ def validate_filters(filters):
|
||||
frappe.throw(_("Can not filter based on Payment Method, if grouped by Payment Method"))
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s".format(
|
||||
company=filters.get("company"),
|
||||
from_date=filters.get("from_date"),
|
||||
to_date=filters.get("to_date"))
|
||||
conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s"
|
||||
|
||||
if filters.get("pos_profile"):
|
||||
conditions += " AND pos_profile = %(pos_profile)s".format(pos_profile=filters.get("pos_profile"))
|
||||
conditions += " AND pos_profile = %(pos_profile)s"
|
||||
|
||||
if filters.get("owner"):
|
||||
conditions += " AND owner = %(owner)s".format(owner=filters.get("owner"))
|
||||
conditions += " AND owner = %(owner)s"
|
||||
|
||||
if filters.get("customer"):
|
||||
conditions += " AND customer = %(customer)s".format(customer=filters.get("customer"))
|
||||
conditions += " AND customer = %(customer)s"
|
||||
|
||||
if filters.get("is_return"):
|
||||
conditions += " AND is_return = %(is_return)s".format(is_return=filters.get("is_return"))
|
||||
conditions += " AND is_return = %(is_return)s"
|
||||
|
||||
if filters.get("mode_of_payment"):
|
||||
conditions += """
|
||||
|
||||
@@ -5,7 +5,8 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
|
||||
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data,
|
||||
get_filtered_list_for_consolidated_report)
|
||||
|
||||
def execute(filters=None):
|
||||
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
|
||||
@@ -33,13 +34,17 @@ def execute(filters=None):
|
||||
chart = get_chart_data(filters, columns, income, expense, net_profit_loss)
|
||||
|
||||
currency = filters.presentation_currency or frappe.get_cached_value('Company', filters.company, "default_currency")
|
||||
report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency)
|
||||
report_summary = get_report_summary(period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters)
|
||||
|
||||
return columns, data, None, chart, report_summary
|
||||
|
||||
def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, consolidated=False):
|
||||
def get_report_summary(period_list, periodicity, income, expense, net_profit_loss, currency, filters, consolidated=False):
|
||||
net_income, net_expense, net_profit = 0.0, 0.0, 0.0
|
||||
|
||||
# from consolidated financial statement
|
||||
if filters.get('accumulated_in_group_company'):
|
||||
period_list = get_filtered_list_for_consolidated_report(filters, period_list)
|
||||
|
||||
for period in period_list:
|
||||
key = period if consolidated else period.key
|
||||
if income:
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"hide_custom": 0,
|
||||
"icon": "accounting",
|
||||
"idx": 0,
|
||||
"is_default": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Accounting",
|
||||
"links": [
|
||||
@@ -625,9 +626,9 @@
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Bank Reconciliation",
|
||||
"link_to": "bank-reconciliation",
|
||||
"link_type": "Page",
|
||||
"label": "Bank Reconciliation Tool",
|
||||
"link_to": "Bank Reconciliation Tool",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
@@ -641,26 +642,6 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Bank Statement Transaction Entry",
|
||||
"link_to": "Bank Statement Transaction Entry",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Bank Statement Settings",
|
||||
"link_to": "Bank Statement Settings",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
@@ -1071,7 +1052,7 @@
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2021-03-04 00:38:35.349024",
|
||||
"modified": "2021-05-12 11:48:01.905144",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting",
|
||||
|
||||
@@ -195,8 +195,7 @@ class Asset(AccountsController):
|
||||
# If depreciation is already completed (for double declining balance)
|
||||
if skip_row: continue
|
||||
|
||||
depreciation_amount = self.get_depreciation_amount(value_after_depreciation,
|
||||
d.total_number_of_depreciations, d)
|
||||
depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d)
|
||||
|
||||
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
|
||||
schedule_date = add_months(d.depreciation_start_date,
|
||||
@@ -208,7 +207,7 @@ class Asset(AccountsController):
|
||||
|
||||
# For first row
|
||||
if has_pro_rata and n==0:
|
||||
depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount,
|
||||
depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount,
|
||||
self.available_for_use_date, d.depreciation_start_date)
|
||||
|
||||
# For first depr schedule date will be the start date
|
||||
@@ -220,7 +219,7 @@ class Asset(AccountsController):
|
||||
to_date = add_months(self.available_for_use_date,
|
||||
n * cint(d.frequency_of_depreciation))
|
||||
|
||||
depreciation_amount, days, months = get_pro_rata_amt(d,
|
||||
depreciation_amount, days, months = self.get_pro_rata_amt(d,
|
||||
depreciation_amount, schedule_date, to_date)
|
||||
|
||||
monthly_schedule_date = add_months(schedule_date, 1)
|
||||
@@ -365,24 +364,6 @@ class Asset(AccountsController):
|
||||
def get_value_after_depreciation(self, idx):
|
||||
return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation)
|
||||
|
||||
def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row):
|
||||
precision = self.precision("gross_purchase_amount")
|
||||
|
||||
if row.depreciation_method in ("Straight Line", "Manual"):
|
||||
depreciation_left = (cint(row.total_number_of_depreciations) - cint(self.number_of_depreciations_booked))
|
||||
|
||||
if not depreciation_left:
|
||||
frappe.msgprint(_("All the depreciations has been booked"))
|
||||
depreciation_amount = flt(row.expected_value_after_useful_life)
|
||||
return depreciation_amount
|
||||
|
||||
depreciation_amount = (flt(row.value_after_depreciation) -
|
||||
flt(row.expected_value_after_useful_life)) / depreciation_left
|
||||
else:
|
||||
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision)
|
||||
|
||||
return depreciation_amount
|
||||
|
||||
def validate_expected_value_after_useful_life(self):
|
||||
for row in self.get('finance_books'):
|
||||
accumulated_depreciation_after_full_schedule = [d.accumulated_depreciation_amount
|
||||
@@ -575,6 +556,13 @@ class Asset(AccountsController):
|
||||
|
||||
return 100 * (1 - flt(depreciation_rate, float_precision))
|
||||
|
||||
def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date):
|
||||
days = date_diff(to_date, from_date)
|
||||
months = month_diff(to_date, from_date)
|
||||
total_days = get_total_days(to_date, row.frequency_of_depreciation)
|
||||
|
||||
return (depreciation_amount * flt(days)) / flt(total_days), days, months
|
||||
|
||||
def update_maintenance_status():
|
||||
assets = frappe.get_all(
|
||||
"Asset", filters={"docstatus": 1, "maintenance_required": 1}
|
||||
@@ -758,15 +746,20 @@ def make_asset_movement(assets, purpose=None):
|
||||
def is_cwip_accounting_enabled(asset_category):
|
||||
return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting"))
|
||||
|
||||
def get_pro_rata_amt(row, depreciation_amount, from_date, to_date):
|
||||
days = date_diff(to_date, from_date)
|
||||
months = month_diff(to_date, from_date)
|
||||
total_days = get_total_days(to_date, row.frequency_of_depreciation)
|
||||
|
||||
return (depreciation_amount * flt(days)) / flt(total_days), days, months
|
||||
|
||||
def get_total_days(date, frequency):
|
||||
period_start_date = add_months(date,
|
||||
cint(frequency) * -1)
|
||||
|
||||
return date_diff(date, period_start_date)
|
||||
|
||||
@erpnext.allow_regional
|
||||
def get_depreciation_amount(asset, depreciable_value, row):
|
||||
depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
|
||||
|
||||
if row.depreciation_method in ("Straight Line", "Manual"):
|
||||
depreciation_amount = (flt(row.value_after_depreciation) -
|
||||
flt(row.expected_value_after_useful_life)) / depreciation_left
|
||||
else:
|
||||
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
|
||||
|
||||
return depreciation_amount
|
||||
@@ -78,7 +78,7 @@ class TestAsset(unittest.TestCase):
|
||||
})
|
||||
|
||||
doc.set_missing_values()
|
||||
self.assertEquals(doc.items[0].is_fixed_asset, 1)
|
||||
self.assertEqual(doc.items[0].is_fixed_asset, 1)
|
||||
|
||||
def test_schedule_for_straight_line_method(self):
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||
@@ -565,7 +565,7 @@ class TestAsset(unittest.TestCase):
|
||||
|
||||
doc = make_invoice(pr.name)
|
||||
|
||||
self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
|
||||
self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account)
|
||||
|
||||
def test_asset_cwip_toggling_cases(self):
|
||||
cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting")
|
||||
@@ -635,6 +635,45 @@ class TestAsset(unittest.TestCase):
|
||||
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc)
|
||||
frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc)
|
||||
|
||||
def test_discounted_wdv_depreciation_rate_for_indian_region(self):
|
||||
# set indian company
|
||||
company_flag = frappe.flags.company
|
||||
frappe.flags.company = "_Test Company"
|
||||
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||
qty=1, rate=8000.0, location="Test Location")
|
||||
|
||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
||||
asset = frappe.get_doc('Asset', asset_name)
|
||||
asset.calculate_depreciation = 1
|
||||
asset.available_for_use_date = '2030-06-12'
|
||||
asset.purchase_date = '2030-01-01'
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 1000,
|
||||
"depreciation_method": "Written Down Value",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 12,
|
||||
"depreciation_start_date": "2030-12-31"
|
||||
})
|
||||
asset.save(ignore_permissions=True)
|
||||
|
||||
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
|
||||
|
||||
expected_schedules = [
|
||||
["2030-12-31", 1106.85, 1106.85],
|
||||
["2031-12-31", 3446.58, 4553.43],
|
||||
["2032-12-31", 1723.29, 6276.72],
|
||||
["2033-06-12", 723.28, 7000.00]
|
||||
]
|
||||
|
||||
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
|
||||
for d in asset.get("schedules")]
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
# reset indian company
|
||||
frappe.flags.company = company_flag
|
||||
|
||||
def create_asset_data():
|
||||
if not frappe.db.exists("Asset Category", "Computers"):
|
||||
create_asset_category()
|
||||
|
||||
@@ -187,7 +187,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
update_child_qty_rate('Purchase Order', trans_item, po.name)
|
||||
|
||||
po.reload()
|
||||
self.assertEquals(len(po.get('items')), 2)
|
||||
self.assertEqual(len(po.get('items')), 2)
|
||||
self.assertEqual(po.status, 'To Receive and Bill')
|
||||
# ordered qty should increase on row addition
|
||||
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7)
|
||||
@@ -234,7 +234,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
update_child_qty_rate('Purchase Order', trans_item, po.name)
|
||||
|
||||
po.reload()
|
||||
self.assertEquals(len(po.get('items')), 1)
|
||||
self.assertEqual(len(po.get('items')), 1)
|
||||
self.assertEqual(po.status, 'To Receive and Bill')
|
||||
|
||||
# ordered qty should decrease (back to initial) on row deletion
|
||||
@@ -435,6 +435,35 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
po.load_from_db()
|
||||
self.assertEqual(po.get("items")[0].received_qty, 5)
|
||||
|
||||
def test_purchase_order_invoice_receipt_workflow(self):
|
||||
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt
|
||||
|
||||
po = create_purchase_order()
|
||||
pi = make_pi_from_po(po.name)
|
||||
|
||||
pi.submit()
|
||||
|
||||
pr = make_purchase_receipt(pi.name)
|
||||
pr.submit()
|
||||
|
||||
pi.load_from_db()
|
||||
|
||||
self.assertEqual(pi.per_received, 100.00)
|
||||
self.assertEqual(pi.items[0].qty, pi.items[0].received_qty)
|
||||
|
||||
po.load_from_db()
|
||||
|
||||
self.assertEqual(po.per_received, 100.00)
|
||||
self.assertEqual(po.per_billed, 100.00)
|
||||
|
||||
pr.cancel()
|
||||
|
||||
pi.load_from_db()
|
||||
pi.cancel()
|
||||
|
||||
po.load_from_db()
|
||||
po.cancel()
|
||||
|
||||
def test_make_purchase_invoice(self):
|
||||
po = create_purchase_order(do_not_submit=True)
|
||||
|
||||
@@ -645,8 +674,8 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname=["reserved_qty_for_sub_contract", "projected_qty"], as_dict=1)
|
||||
|
||||
self.assertEquals(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
|
||||
self.assertEquals(bin2.projected_qty, bin1.projected_qty - 10)
|
||||
self.assertEqual(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
|
||||
self.assertEqual(bin2.projected_qty, bin1.projected_qty - 10)
|
||||
|
||||
# Create stock transfer
|
||||
rm_item = [{"item_code":"_Test FG Item","rm_item_code":"_Test Item","item_name":"_Test Item",
|
||||
@@ -661,7 +690,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract", as_dict=1)
|
||||
|
||||
self.assertEquals(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
|
||||
self.assertEqual(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
|
||||
|
||||
# close PO
|
||||
po.update_status("Closed")
|
||||
@@ -669,7 +698,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract", as_dict=1)
|
||||
|
||||
self.assertEquals(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
|
||||
self.assertEqual(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
|
||||
|
||||
# Re-open PO
|
||||
po.update_status("Submitted")
|
||||
@@ -677,7 +706,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract", as_dict=1)
|
||||
|
||||
self.assertEquals(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
|
||||
self.assertEqual(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
|
||||
|
||||
make_stock_entry(target="_Test Warehouse 1 - _TC", item_code="_Test Item",
|
||||
qty=40, basic_rate=100)
|
||||
@@ -694,7 +723,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract", as_dict=1)
|
||||
|
||||
self.assertEquals(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
|
||||
self.assertEqual(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
|
||||
|
||||
# Cancel PR
|
||||
pr.cancel()
|
||||
@@ -702,7 +731,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract", as_dict=1)
|
||||
|
||||
self.assertEquals(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
|
||||
self.assertEqual(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
|
||||
|
||||
# Make Purchase Invoice
|
||||
pi = make_pi_from_po(po.name)
|
||||
@@ -714,7 +743,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract", as_dict=1)
|
||||
|
||||
self.assertEquals(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
|
||||
self.assertEqual(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
|
||||
|
||||
# Cancel PR
|
||||
pi.cancel()
|
||||
@@ -722,7 +751,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract", as_dict=1)
|
||||
|
||||
self.assertEquals(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
|
||||
self.assertEqual(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
|
||||
|
||||
# Cancel Stock Entry
|
||||
se.cancel()
|
||||
@@ -730,7 +759,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract", as_dict=1)
|
||||
|
||||
self.assertEquals(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
|
||||
self.assertEqual(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
|
||||
|
||||
# Cancel PO
|
||||
po.reload()
|
||||
@@ -739,7 +768,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
|
||||
fieldname="reserved_qty_for_sub_contract", as_dict=1)
|
||||
|
||||
self.assertEquals(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
|
||||
self.assertEqual(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
|
||||
|
||||
def test_exploded_items_in_subcontracted(self):
|
||||
item_code = "_Test Subcontracted FG Item 1"
|
||||
@@ -753,7 +782,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
|
||||
exploded_items = sorted([d.item_code for d in bom.exploded_items if not d.get('sourced_by_supplier')])
|
||||
supplied_items = sorted([d.rm_item_code for d in po.supplied_items])
|
||||
self.assertEquals(exploded_items, supplied_items)
|
||||
self.assertEqual(exploded_items, supplied_items)
|
||||
|
||||
po1 = create_purchase_order(item_code=item_code, qty=1,
|
||||
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=0)
|
||||
@@ -761,7 +790,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
supplied_items1 = sorted([d.rm_item_code for d in po1.supplied_items])
|
||||
bom_items = sorted([d.item_code for d in bom.items if not d.get('sourced_by_supplier')])
|
||||
|
||||
self.assertEquals(supplied_items1, bom_items)
|
||||
self.assertEqual(supplied_items1, bom_items)
|
||||
|
||||
def test_backflush_based_on_stock_entry(self):
|
||||
item_code = "_Test Subcontracted FG Item 1"
|
||||
@@ -811,8 +840,8 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name])
|
||||
issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')])
|
||||
|
||||
self.assertEquals(transferred_items, issued_items)
|
||||
self.assertEquals(pr.get('items')[0].rm_supp_cost, 2000)
|
||||
self.assertEqual(transferred_items, issued_items)
|
||||
self.assertEqual(pr.get('items')[0].rm_supp_cost, 2000)
|
||||
|
||||
|
||||
transferred_rm_map = frappe._dict()
|
||||
|
||||
129
erpnext/change_log/v13/v13_1_0.md
Normal file
129
erpnext/change_log/v13/v13_1_0.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Version 13.1.0 Release Notes
|
||||
|
||||
### Features
|
||||
|
||||
- Recursive pricing rule ([#24922](https://github.com/frappe/erpnext/pull/24922))
|
||||
- Discount configuration on early payments ([#24586](https://github.com/frappe/erpnext/pull/24586))
|
||||
- Bulk e-invoice generation ([#24969](https://github.com/frappe/erpnext/pull/24969))
|
||||
- Employee Self Service ([#24408](https://github.com/frappe/erpnext/pull/24408))
|
||||
- Share doc with employee approvers if they don't have access ([#25190](https://github.com/frappe/erpnext/pull/25190))
|
||||
- Price margin in buying ([#24685](https://github.com/frappe/erpnext/pull/24685))
|
||||
- Allow changing Work Stations in Work Order & Job Card ([#24897](https://github.com/frappe/erpnext/pull/24897))
|
||||
- Add document type field for e-invoicing (Italy) ([#25256](https://github.com/frappe/erpnext/pull/25256))
|
||||
- Add checkbox for disabling leave notification in HR Settings ([#24877](https://github.com/frappe/erpnext/pull/24877))
|
||||
- Enhancements in Material Request Plan Item in Production Plan ([#25025](https://github.com/frappe/erpnext/pull/25025))
|
||||
|
||||
|
||||
### Fixes and Enhancements
|
||||
- Mode of payments disappear on loading draft pos invoice ([#24917](https://github.com/frappe/erpnext/pull/24917))
|
||||
- Sales order not saving due type mismatch in promo scheme (#24748) ([#25222](https://github.com/frappe/erpnext/pull/25222))
|
||||
- Zero amount completed delivery notes being shown in Sales Invoice get items ([#25317](https://github.com/frappe/erpnext/pull/25317))
|
||||
- Incorrect status creating PR from PO after creating PI ([#25109](https://github.com/frappe/erpnext/pull/25109))
|
||||
- Precision and formatted document for stock level in item dashboard. ([#24921](https://github.com/frappe/erpnext/pull/24921))
|
||||
- Precision issues while allocating advance amount ([#25086](https://github.com/frappe/erpnext/pull/25086))
|
||||
- Round off final tax amount instead of current tax amount ([#25188](https://github.com/frappe/erpnext/pull/25188))
|
||||
- Redesign fixes ([#24896](https://github.com/frappe/erpnext/pull/24896))
|
||||
- TDS check getting checked after reload ([#24972](https://github.com/frappe/erpnext/pull/24972))
|
||||
- Github Action not failing when tests fail ([#24867](https://github.com/frappe/erpnext/pull/24867))
|
||||
- Calculate 80g certificate amount on validate for memberships ([#24925](https://github.com/frappe/erpnext/pull/24925))
|
||||
- Purchase from registered composition dealer ([#25040](https://github.com/frappe/erpnext/pull/25040))
|
||||
- Reduce number of queries for checking if future SL entry exists ([#24881](https://github.com/frappe/erpnext/pull/24881))
|
||||
- Remove unwanted parameter in calculate_rate_and_amount ([#24883](https://github.com/frappe/erpnext/pull/24883))
|
||||
- Membership renewal validation ([#24963](https://github.com/frappe/erpnext/pull/24963))
|
||||
- Not able to save material request ([#25112](https://github.com/frappe/erpnext/pull/25112))
|
||||
- POS print receipt ([#25330](https://github.com/frappe/erpnext/pull/25330))
|
||||
- Supplier was not able to Submit RFQ due to insufficient permission ([#24622](https://github.com/frappe/erpnext/pull/24622))
|
||||
- Unequal debit and credit issue on RCM Invoice ([#24836](https://github.com/frappe/erpnext/pull/24836))
|
||||
- Picked Qty conversion from Stock Qty to Qty while creating DN from Pick List ([#25105](https://github.com/frappe/erpnext/pull/25105))
|
||||
- Salary Structure object has no attribute set_totals ([#25113](https://github.com/frappe/erpnext/pull/25113))
|
||||
- Incorrect Nil Exempt and Non GST amount in GSTR3B report ([#24916](https://github.com/frappe/erpnext/pull/24916))
|
||||
- Add method for regional round off account back ([#24893](https://github.com/frappe/erpnext/pull/24893))
|
||||
- Employee profile pic upload access for erpnext user ([#25022](https://github.com/frappe/erpnext/pull/25022))
|
||||
- Make filters for payroll entry ([#25386](https://github.com/frappe/erpnext/pull/25386))
|
||||
- Fix dynamically changing grid properties ([#25310](https://github.com/frappe/erpnext/pull/25310))
|
||||
- Consider paid repayment entries in subsequent loan repayments ([#25271](https://github.com/frappe/erpnext/pull/25271))
|
||||
- Allow duplicate additional salaries ([#24842](https://github.com/frappe/erpnext/pull/24842))
|
||||
- Object referencing the same address issue ([#25159](https://github.com/frappe/erpnext/pull/25159))
|
||||
- Validating party currency with doc currency ([#24318](https://github.com/frappe/erpnext/pull/24318))
|
||||
- Non Profit fixes ([#25060](https://github.com/frappe/erpnext/pull/25060))
|
||||
- Additional Salary component amount not getting set ([#25356](https://github.com/frappe/erpnext/pull/25356))
|
||||
- Allow user to update exchange rate in Multi-currency LCV ([#24912](https://github.com/frappe/erpnext/pull/24912))
|
||||
- Allow creating stock entry based on work order for customer provided items ([#24885](https://github.com/frappe/erpnext/pull/24885))
|
||||
- Create property setters for shorter naming series on setup ([#25128](https://github.com/frappe/erpnext/pull/25128))
|
||||
- Add GST category field in Delivery Note ([#25053](https://github.com/frappe/erpnext/pull/25053))
|
||||
- Ignore Permission for Leave Ledger Entry ([#25172](https://github.com/frappe/erpnext/pull/25172))
|
||||
- Pending shortfall update on processing loan security shortfall ([#24971](https://github.com/frappe/erpnext/pull/24971))
|
||||
- Added flag for dont_fetch_price_list_rate in transaction ([#25041](https://github.com/frappe/erpnext/pull/25041))
|
||||
- Exchange Rate not getting set in Salary Slip ([#25004](https://github.com/frappe/erpnext/pull/25004))
|
||||
- Repost not completed backdated transactions ([#24980](https://github.com/frappe/erpnext/pull/24980))
|
||||
- frappe.whitelist for doc methods ([#25230](https://github.com/frappe/erpnext/pull/25230))
|
||||
- Opportunity-quotation mapping order status ([#25001](https://github.com/frappe/erpnext/pull/25001))
|
||||
- GST on freight charge in e-invoicing ([#25000](https://github.com/frappe/erpnext/pull/25000))
|
||||
- Role to override maintain same rate check in transactions ([#25193](https://github.com/frappe/erpnext/pull/25193))
|
||||
- Added blank option for status in report related to issue ([#25082](https://github.com/frappe/erpnext/pull/25082))
|
||||
- Cashier query in POS Opening/Closing Entry ([#25399](https://github.com/frappe/erpnext/pull/25399))
|
||||
- Lead Source's module ([#24583](https://github.com/frappe/erpnext/pull/24583))
|
||||
- Hide alt tag if item is not shown in website ([#24937](https://github.com/frappe/erpnext/pull/24937))
|
||||
- Ignore Customer Group Perm on All Products page ([#25397](https://github.com/frappe/erpnext/pull/25397))
|
||||
- Give first preference to loan security on repayment ([#25212](https://github.com/frappe/erpnext/pull/25212))
|
||||
- Add shortfall ratio in Loan Security Shortfall ([#25138](https://github.com/frappe/erpnext/pull/25138))
|
||||
- Condition for SLA status banner ([#25261](https://github.com/frappe/erpnext/pull/25261))
|
||||
- Component amount calculation based on formula with abbr not working ([#25117](https://github.com/frappe/erpnext/pull/25117))
|
||||
- Remove gst name validation for purchase Invoice ([#25235](https://github.com/frappe/erpnext/pull/25235))
|
||||
- Do not fetch stopped MR in production plan ([#25063](https://github.com/frappe/erpnext/pull/25063))
|
||||
- Backport missing commits to develop branch ([#25305](https://github.com/frappe/erpnext/pull/25305))
|
||||
- UOM length unit in global setup list is empty ([#24855](https://github.com/frappe/erpnext/pull/24855))
|
||||
- Round total quantity in job card ([#25240](https://github.com/frappe/erpnext/pull/25240))
|
||||
- Default total_estimated_cost to zero ([#24939](https://github.com/frappe/erpnext/pull/24939))
|
||||
- Serial no refresh issue ([#25127](https://github.com/frappe/erpnext/pull/25127))
|
||||
- Correct calculation for discount amount when margin is set ([#25179](https://github.com/frappe/erpnext/pull/25179))
|
||||
- Get correct holiday list when calculating dates; test fixes ([#24901](https://github.com/frappe/erpnext/pull/24901))
|
||||
- POS print receipt ([#24924](https://github.com/frappe/erpnext/pull/24924))
|
||||
- Condition for setting agreement status ([#25255](https://github.com/frappe/erpnext/pull/25255))
|
||||
- Loan Repayment entry cancellation on salary slip cancel ([#24879](https://github.com/frappe/erpnext/pull/24879))
|
||||
- Add company validation for e-invoicing ([#25349](https://github.com/frappe/erpnext/pull/25349))
|
||||
- Query values incorrectly escaped while back updating Quality Inspection ([#25118](https://github.com/frappe/erpnext/pull/25118))
|
||||
- Update Bin via Update Item on Purchase/Sales Order ([#23509](https://github.com/frappe/erpnext/pull/23509))
|
||||
- Declare data before assigning ([#25287](https://github.com/frappe/erpnext/pull/25287))
|
||||
- Do not set standard link in Sales Invoice as custom ([#25096](https://github.com/frappe/erpnext/pull/25096))
|
||||
- Hide serial and batch selector in Stock Entry ([#25107](https://github.com/frappe/erpnext/pull/25107))
|
||||
- Taxable value including Freight and Forwarding charges in GSTR-1 Report ([#25290](https://github.com/frappe/erpnext/pull/25290))
|
||||
- Remove nonexistent method from pick list ([#25279](https://github.com/frappe/erpnext/pull/25279))
|
||||
- Allow zero valuation in stock reconciliation ([#24888](https://github.com/frappe/erpnext/pull/24888))
|
||||
- Place of supply of e-invoicing ([#25148](https://github.com/frappe/erpnext/pull/25148))
|
||||
- Delivery note print error ([#25080](https://github.com/frappe/erpnext/pull/25080))
|
||||
- Fix Payment references from disappearing on adding Cost Center in Payment Entry ([#24831](https://github.com/frappe/erpnext/pull/24831))
|
||||
- Company field in Warehouse ([#25196](https://github.com/frappe/erpnext/pull/25196))
|
||||
- Available employee for selection ([#25378](https://github.com/frappe/erpnext/pull/25378))
|
||||
- Cannot set qty to less than zero ([#25258](https://github.com/frappe/erpnext/pull/25258))
|
||||
- Don't delete mode of payment account details while deleting comp… ([#25217](https://github.com/frappe/erpnext/pull/25217))
|
||||
- Exclude current doc while validation. ([#24914](https://github.com/frappe/erpnext/pull/24914))
|
||||
- POS Opening Entry with empty balance detail rows ([#24876](https://github.com/frappe/erpnext/pull/24876))
|
||||
- Unable to submit stock entry ([#25033](https://github.com/frappe/erpnext/pull/25033))
|
||||
- BOM cost test case ([#25242](https://github.com/frappe/erpnext/pull/25242))
|
||||
- Filter for employees in salary slip ([#25361](https://github.com/frappe/erpnext/pull/25361))
|
||||
- Added correct path in hooks ([#24862](https://github.com/frappe/erpnext/pull/24862))
|
||||
- Patch regional fields for old companies ([#24988](https://github.com/frappe/erpnext/pull/24988))
|
||||
- consolidated sales invoice posting date ([#25119](https://github.com/frappe/erpnext/pull/25119))
|
||||
- Don't set "Company:company:default_currency" as default for currency link fields ([#25095](https://github.com/frappe/erpnext/pull/25095))
|
||||
- Healthcare lab module rename fields ([#25276](https://github.com/frappe/erpnext/pull/25276))
|
||||
- Error message compensatory leave request ([#25206](https://github.com/frappe/erpnext/pull/25206))
|
||||
- Adding company link to e invoice settings patch condition ([#25301](https://github.com/frappe/erpnext/pull/25301))
|
||||
- Membership and Donation API fixes ([#24900](https://github.com/frappe/erpnext/pull/24900))
|
||||
- Set correct ack no. on irn generation ([#25251](https://github.com/frappe/erpnext/pull/25251))
|
||||
- Report Issue Summary fix for zero issues ([#24934](https://github.com/frappe/erpnext/pull/24934))
|
||||
- Validation msg for TransDocNo e-invoicing ([#25121](https://github.com/frappe/erpnext/pull/25121))
|
||||
- Correct state code for 'Other Territory' ([#24993](https://github.com/frappe/erpnext/pull/24993))
|
||||
- Commit individual SLE rename for large datasets (develop) ([#25084](https://github.com/frappe/erpnext/pull/25084))
|
||||
- Remove shipping address GSTIN validation for e-invoice ([#25153](https://github.com/frappe/erpnext/pull/25153))
|
||||
- Period list for exponential smoothing forecasting report ([#24982](https://github.com/frappe/erpnext/pull/24982))
|
||||
- Customer creation from shopping cart ([#25136](https://github.com/frappe/erpnext/pull/25136))
|
||||
- Simplified logic for additional salary ([#24824](https://github.com/frappe/erpnext/pull/24824))
|
||||
- Item wise tax rate for consolidated POS invoice ([#25029](https://github.com/frappe/erpnext/pull/25029))
|
||||
- Column width in Recruitment analytics report ([#25003](https://github.com/frappe/erpnext/pull/25003))
|
||||
- Filter Bank Account drop-down list in Bank Reconciliation Tool ([#24873](https://github.com/frappe/erpnext/pull/24873))
|
||||
- Payroll issues ([#24540](https://github.com/frappe/erpnext/pull/24540))
|
||||
- PO not created against all selected suppliers (drop shipping) ([#24863](https://github.com/frappe/erpnext/pull/24863))
|
||||
- Can't multiply sequence by non-int of type 'float' ([#25092](https://github.com/frappe/erpnext/pull/25092))
|
||||
- Make Discharge Schedule Date as Datetime ([#24940](https://github.com/frappe/erpnext/pull/24940))
|
||||
- Serial no trim issue ([#24949](https://github.com/frappe/erpnext/pull/24949))
|
||||
56
erpnext/change_log/v13/v13_2_0.md
Normal file
56
erpnext/change_log/v13/v13_2_0.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Version 13.2.0 Release Notes
|
||||
|
||||
### Features & Enhancements
|
||||
|
||||
- Employee Hours Utilization Report ([#25209](https://github.com/frappe/erpnext/pull/25209))
|
||||
- Delayed Tasks Summary Report ([#25024](https://github.com/frappe/erpnext/pull/25024))
|
||||
- Project Profitability Report ([#24944](https://github.com/frappe/erpnext/pull/24944))
|
||||
- Timer in LMS Quiz ([#24246](https://github.com/frappe/erpnext/pull/24246))
|
||||
- Role to allow over billing, delivery, receipt ([#24854](https://github.com/frappe/erpnext/pull/24854))
|
||||
- Auto calculate distance for e-way bill generations ([#25480](https://github.com/frappe/erpnext/pull/25480))
|
||||
- Add total available stock field in PO ([#24878](https://github.com/frappe/erpnext/pull/24878))
|
||||
- Refactored Setup Taxes and Charges ([#24805](https://github.com/frappe/erpnext/pull/24805))
|
||||
- Inpatient Occupancy Table Editable for Healthcare Admin ([#24989](https://github.com/frappe/erpnext/pull/24989))
|
||||
- Added Disable Rounded Total in sales transactions ([#25362](https://github.com/frappe/erpnext/pull/25362))
|
||||
|
||||
|
||||
### Fixes
|
||||
|
||||
- Incorrect GL Entry validation ([#25474](https://github.com/frappe/erpnext/pull/25474))
|
||||
- Cannot create item variants ([#25433](https://github.com/frappe/erpnext/pull/25433))
|
||||
- Leave policy in leave allocation ([#25334](https://github.com/frappe/erpnext/pull/25334))
|
||||
- Let Administrator delete company transactions ([#25300](https://github.com/frappe/erpnext/pull/25300))
|
||||
- Display reconcile tool when closing balance 0 ([#25417](https://github.com/frappe/erpnext/pull/25417))
|
||||
- Bulk Salary Structure Assignment ([#25389](https://github.com/frappe/erpnext/pull/25389))
|
||||
- Payment amount showing in foreign currency ([#25518](https://github.com/frappe/erpnext/pull/25518))
|
||||
- Commit changes to shipment status in database ([#25374](https://github.com/frappe/erpnext/pull/25374))
|
||||
- Add amend perm for loan and system manager for loan doctypes ([#25393](https://github.com/frappe/erpnext/pull/25393))
|
||||
- Cashier query in POS Opening/Closing Entry ([#25398](https://github.com/frappe/erpnext/pull/25398))
|
||||
- Apply single transaction threshold on net_total instead of supplier credit amount ([#25243](https://github.com/frappe/erpnext/pull/25243))
|
||||
- Update allocated amount after paid amount is changed in PE ([#25528](https://github.com/frappe/erpnext/pull/25528))
|
||||
- Remove non-standard module cards from Home Workspace ([#25391](https://github.com/frappe/erpnext/pull/25391))
|
||||
- Cannot scan spacebar character in pos ([#25479](https://github.com/frappe/erpnext/pull/25479))
|
||||
- Permission error after submitting exchange rate revaluation ([#25432](https://github.com/frappe/erpnext/pull/25432))
|
||||
- Equality check instead of assignment in cart ([#25372](https://github.com/frappe/erpnext/pull/25372))
|
||||
- Disable auto naming of customer during import ([#25152](https://github.com/frappe/erpnext/pull/25152))
|
||||
- Additional Salary component amount not getting set ([#25355](https://github.com/frappe/erpnext/pull/25355))
|
||||
- Round off values near to zero ([#25304](https://github.com/frappe/erpnext/pull/25304))
|
||||
- Allow to cancel loan with cancelled repayment entry ([#25508](https://github.com/frappe/erpnext/pull/25508))
|
||||
- Currency symbol in bank transaction list view ([#25336](https://github.com/frappe/erpnext/pull/25336))
|
||||
- Incorrect batch picked in subcontracted purchase receipt ([#25186](https://github.com/frappe/erpnext/pull/25186))
|
||||
- Issue in project custom status ([#25452](https://github.com/frappe/erpnext/pull/25452))
|
||||
- Shipment pickup_to, pickup_from functionality. ([#25359](https://github.com/frappe/erpnext/pull/25359))
|
||||
- Stock ledger entry created against draft stock entry ([#25539](https://github.com/frappe/erpnext/pull/25539))
|
||||
- Ageing errors in PSOA ([#25529](https://github.com/frappe/erpnext/pull/25529))
|
||||
- Permission error while adding weekly holidays ([#25450](https://github.com/frappe/erpnext/pull/25450))
|
||||
- Filter for employees in salary slip ([#25360](https://github.com/frappe/erpnext/pull/25360))
|
||||
- Backward compatibility for GSTR-1 report ([#25444](https://github.com/frappe/erpnext/pull/25444))
|
||||
- Incorrect incoming rate for the sales return ([#25145](https://github.com/frappe/erpnext/pull/25145))
|
||||
- POS print receipt ([#25328](https://github.com/frappe/erpnext/pull/25328))
|
||||
- Laboratory Module patch ([#25431](https://github.com/frappe/erpnext/pull/25431))
|
||||
- Performance: fetching exchange rate on every line item slows down PO ([#25345](https://github.com/frappe/erpnext/pull/25345))
|
||||
- Presentation currency in statement of accounts ([#25367](https://github.com/frappe/erpnext/pull/25367))
|
||||
- Serial No not updated correctly via Inter Company Stock Transfer ([#25006](https://github.com/frappe/erpnext/pull/25006))
|
||||
- Ignore Customer Group Perm on All Products page ([#25396](https://github.com/frappe/erpnext/pull/25396))
|
||||
- Change subcontracted item display ([#25425](https://github.com/frappe/erpnext/pull/25425))
|
||||
- Add company validation for e-invoicing ([#25348](https://github.com/frappe/erpnext/pull/25348))
|
||||
@@ -90,6 +90,8 @@ class AccountsController(TransactionBase):
|
||||
self.ensure_supplier_is_not_blocked()
|
||||
|
||||
self.validate_date_with_fiscal_year()
|
||||
self.validate_party_accounts()
|
||||
|
||||
self.validate_inter_company_reference()
|
||||
|
||||
self.set_incoming_rate()
|
||||
@@ -233,6 +235,23 @@ class AccountsController(TransactionBase):
|
||||
validate_fiscal_year(self.get(date_field), self.fiscal_year, self.company,
|
||||
self.meta.get_label(date_field), self)
|
||||
|
||||
def validate_party_accounts(self):
|
||||
if self.doctype not in ('Sales Invoice', 'Purchase Invoice'):
|
||||
return
|
||||
|
||||
if self.doctype == 'Sales Invoice':
|
||||
party_account_field = 'debit_to'
|
||||
item_field = 'income_account'
|
||||
else:
|
||||
party_account_field = 'credit_to'
|
||||
item_field = 'expense_account'
|
||||
|
||||
for item in self.get('items'):
|
||||
if item.get(item_field) == self.get(party_account_field):
|
||||
frappe.throw(_("Row {0}: {1} {2} cannot be same as {3} (Party Account) {4}").format(item.idx,
|
||||
frappe.bold(frappe.unscrub(item_field)), item.get(item_field),
|
||||
frappe.bold(frappe.unscrub(party_account_field)), self.get(party_account_field)))
|
||||
|
||||
def validate_inter_company_reference(self):
|
||||
if self.doctype not in ('Purchase Invoice', 'Purchase Receipt', 'Purchase Order'):
|
||||
return
|
||||
@@ -240,7 +259,7 @@ class AccountsController(TransactionBase):
|
||||
if self.is_internal_transfer():
|
||||
if not (self.get('inter_company_reference') or self.get('inter_company_invoice_reference')
|
||||
or self.get('inter_company_order_reference')):
|
||||
msg = _("Internal Sale or Delivery Reference missing. ")
|
||||
msg = _("Internal Sale or Delivery Reference missing.")
|
||||
msg += _("Please create purchase from internal sale or delivery document itself")
|
||||
frappe.throw(msg, title=_("Internal Sales Reference Missing"))
|
||||
|
||||
@@ -349,6 +368,11 @@ class AccountsController(TransactionBase):
|
||||
if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'):
|
||||
item.set('is_fixed_asset', ret.get('is_fixed_asset', 0))
|
||||
|
||||
# Double check for cost center
|
||||
# Items add via promotional scheme may not have cost center set
|
||||
if hasattr(item, 'cost_center') and not item.get('cost_center'):
|
||||
item.set('cost_center', self.get('cost_center') or erpnext.get_default_cost_center(self.company))
|
||||
|
||||
if ret.get("pricing_rules"):
|
||||
self.apply_pricing_rule_on_items(item, ret)
|
||||
self.set_pricing_rule_details(item, ret)
|
||||
@@ -904,29 +928,34 @@ class AccountsController(TransactionBase):
|
||||
date = self.get("due_date")
|
||||
due_date = date or posting_date
|
||||
|
||||
if party_account_currency == self.company_currency:
|
||||
grand_total = self.get("base_rounded_total") or self.base_grand_total
|
||||
else:
|
||||
grand_total = self.get("rounded_total") or self.grand_total
|
||||
base_grand_total = self.get("base_rounded_total") or self.base_grand_total
|
||||
grand_total = self.get("rounded_total") or self.grand_total
|
||||
|
||||
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
|
||||
grand_total = grand_total - flt(self.write_off_amount)
|
||||
|
||||
if self.get("total_advance"):
|
||||
grand_total -= self.get("total_advance")
|
||||
if party_account_currency == self.company_currency:
|
||||
base_grand_total -= self.get("total_advance")
|
||||
grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total"))
|
||||
else:
|
||||
grand_total -= self.get("total_advance")
|
||||
base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
|
||||
|
||||
if not self.get("payment_schedule"):
|
||||
if self.get("payment_terms_template"):
|
||||
data = get_payment_terms(self.payment_terms_template, posting_date, grand_total)
|
||||
data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total)
|
||||
for item in data:
|
||||
self.append("payment_schedule", item)
|
||||
else:
|
||||
data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total)
|
||||
data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total)
|
||||
self.append("payment_schedule", data)
|
||||
else:
|
||||
for d in self.get("payment_schedule"):
|
||||
if d.invoice_portion:
|
||||
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
||||
d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
||||
d.outstanding = d.payment_amount
|
||||
|
||||
def set_due_date(self):
|
||||
@@ -963,22 +992,28 @@ class AccountsController(TransactionBase):
|
||||
|
||||
if self.get("payment_schedule"):
|
||||
total = 0
|
||||
base_total = 0
|
||||
for d in self.get("payment_schedule"):
|
||||
total += flt(d.payment_amount)
|
||||
base_total += flt(d.base_payment_amount)
|
||||
|
||||
if party_account_currency == self.company_currency:
|
||||
total = flt(total, self.precision("base_grand_total"))
|
||||
grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total'))
|
||||
else:
|
||||
total = flt(total, self.precision("grand_total"))
|
||||
grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total'))
|
||||
|
||||
if self.get("total_advance"):
|
||||
grand_total -= self.get("total_advance")
|
||||
base_grand_total = self.get("base_rounded_total") or self.base_grand_total
|
||||
grand_total = self.get("rounded_total") or self.grand_total
|
||||
|
||||
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
|
||||
grand_total = grand_total - flt(self.write_off_amount)
|
||||
if total != flt(grand_total, self.precision("grand_total")):
|
||||
|
||||
if self.get("total_advance"):
|
||||
if party_account_currency == self.company_currency:
|
||||
base_grand_total -= self.get("total_advance")
|
||||
grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total"))
|
||||
else:
|
||||
grand_total -= self.get("total_advance")
|
||||
base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
|
||||
print(grand_total, base_grand_total)
|
||||
if total != flt(grand_total, self.precision("grand_total")) or \
|
||||
base_total != flt(base_grand_total, self.precision("base_grand_total")):
|
||||
frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
|
||||
|
||||
def is_rounded_total_disabled(self):
|
||||
@@ -1218,7 +1253,7 @@ def update_invoice_status():
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_date=None):
|
||||
def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):
|
||||
if not terms_template:
|
||||
return
|
||||
|
||||
@@ -1226,14 +1261,14 @@ def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_
|
||||
|
||||
schedule = []
|
||||
for d in terms_doc.get("terms"):
|
||||
term_details = get_payment_term_details(d, posting_date, grand_total, bill_date)
|
||||
term_details = get_payment_term_details(d, posting_date, grand_total, base_grand_total, bill_date)
|
||||
schedule.append(term_details)
|
||||
|
||||
return schedule
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_term_details(term, posting_date=None, grand_total=None, bill_date=None):
|
||||
def get_payment_term_details(term, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):
|
||||
term_details = frappe._dict()
|
||||
if isinstance(term, text_type):
|
||||
term = frappe.get_doc("Payment Term", term)
|
||||
@@ -1242,9 +1277,9 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, bill_dat
|
||||
term_details.description = term.description
|
||||
term_details.invoice_portion = term.invoice_portion
|
||||
term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100
|
||||
term_details.base_payment_amount = flt(term.invoice_portion) * flt(base_grand_total) / 100
|
||||
term_details.discount_type = term.discount_type
|
||||
term_details.discount = term.discount
|
||||
# term_details.discounted_amount = flt(grand_total) * (term.discount / 100) if term.discount_type == 'Percentage' else discount
|
||||
term_details.outstanding = term_details.payment_amount
|
||||
term_details.mode_of_payment = term.mode_of_payment
|
||||
|
||||
|
||||
@@ -838,9 +838,10 @@ class BuyingController(StockController):
|
||||
if not self.get("items"):
|
||||
return
|
||||
|
||||
earliest_schedule_date = min([d.schedule_date for d in self.get("items")])
|
||||
if earliest_schedule_date:
|
||||
self.schedule_date = earliest_schedule_date
|
||||
if any(d.schedule_date for d in self.get("items")):
|
||||
# Select earliest schedule_date.
|
||||
self.schedule_date = min(d.schedule_date for d in self.get("items")
|
||||
if d.schedule_date is not None)
|
||||
|
||||
if self.schedule_date:
|
||||
for d in self.get('items'):
|
||||
|
||||
@@ -262,7 +262,7 @@ def copy_attributes_to_variant(item, variant):
|
||||
# copy non no-copy fields
|
||||
|
||||
exclude_fields = ["naming_series", "item_code", "item_name", "published_in_website",
|
||||
"opening_stock", "variant_of", "valuation_rate"]
|
||||
"opening_stock", "variant_of", "valuation_rate", "has_variants", "attributes"]
|
||||
|
||||
if item.variant_based_on=='Manufacturer':
|
||||
# don't copy manufacturer values if based on part no
|
||||
|
||||
@@ -292,11 +292,14 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
cond = """(`tabProject`.customer = %s or
|
||||
ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer")))
|
||||
|
||||
fields = get_fields("Project", ["name"])
|
||||
fields = get_fields("Project", ["name", "project_name"])
|
||||
searchfields = frappe.get_meta("Project").get_search_fields()
|
||||
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
||||
|
||||
return frappe.db.sql("""select {fields} from `tabProject`
|
||||
where `tabProject`.status not in ("Completed", "Cancelled")
|
||||
and {cond} `tabProject`.name like %(txt)s {match_cond}
|
||||
where
|
||||
`tabProject`.status not in ("Completed", "Cancelled")
|
||||
and {cond} {match_cond} {scond}
|
||||
order by
|
||||
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
|
||||
idx desc,
|
||||
@@ -304,6 +307,7 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
limit {start}, {page_len}""".format(
|
||||
fields=", ".join(['`tabProject`.{0}'.format(f) for f in fields]),
|
||||
cond=cond,
|
||||
scond=searchfields,
|
||||
match_cond=get_match_cond(doctype),
|
||||
start=start,
|
||||
page_len=page_len), {
|
||||
|
||||
@@ -98,7 +98,12 @@ status_map = {
|
||||
["Draft", None],
|
||||
["Submitted", "eval:self.docstatus == 1"],
|
||||
["Queued", "eval:self.status == 'Queued'"],
|
||||
["Failed", "eval:self.status == 'Failed'"],
|
||||
["Cancelled", "eval:self.docstatus == 2"],
|
||||
],
|
||||
"Transaction Deletion Record": [
|
||||
["Draft", None],
|
||||
["Completed", "eval:self.docstatus == 1"],
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,9 @@ class EducationSettings(Document):
|
||||
def validate(self):
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
if self.get('instructor_created_by')=='Naming Series':
|
||||
make_property_setter('Instructor', "naming_series", "hidden", 0, "Check")
|
||||
make_property_setter('Instructor', "naming_series", "hidden", 0, "Check", validate_fields_for_doctype=False)
|
||||
else:
|
||||
make_property_setter('Instructor', "naming_series", "hidden", 1, "Check")
|
||||
make_property_setter('Instructor', "naming_series", "hidden", 1, "Check", validate_fields_for_doctype=False)
|
||||
|
||||
def update_website_context(context):
|
||||
context["lms_enabled"] = frappe.get_doc("Education Settings").enable_lms
|
||||
@@ -335,13 +335,13 @@ def get_url(shopify_settings):
|
||||
|
||||
if not last_order_id:
|
||||
if shopify_settings.sync_based_on == 'Date':
|
||||
url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&created_at_min={0}&since_id=0".format(
|
||||
url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&created_at_min={0}&since_id=0".format(
|
||||
get_datetime(shopify_settings.from_date)), shopify_settings)
|
||||
else:
|
||||
url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format(
|
||||
url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(
|
||||
shopify_settings.from_order_id), shopify_settings)
|
||||
else:
|
||||
url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings)
|
||||
url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings)
|
||||
|
||||
return url
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, base64, hashlib, hmac, json
|
||||
from frappe.utils import cstr
|
||||
from frappe import _
|
||||
|
||||
def verify_request():
|
||||
@@ -146,22 +147,19 @@ def rename_address(address, customer):
|
||||
|
||||
def link_items(items_list, woocommerce_settings, sys_lang):
|
||||
for item_data in items_list:
|
||||
item_woo_com_id = item_data.get("product_id")
|
||||
item_woo_com_id = cstr(item_data.get("product_id"))
|
||||
|
||||
if frappe.get_value("Item", {"woocommerce_id": item_woo_com_id}):
|
||||
#Edit Item
|
||||
item = frappe.get_doc("Item", {"woocommerce_id": item_woo_com_id})
|
||||
else:
|
||||
if not frappe.db.get_value("Item", {"woocommerce_id": item_woo_com_id}, 'name'):
|
||||
#Create Item
|
||||
item = frappe.new_doc("Item")
|
||||
item.item_code = _("woocommerce - {0}", sys_lang).format(item_woo_com_id)
|
||||
item.stock_uom = woocommerce_settings.uom or _("Nos", sys_lang)
|
||||
item.item_group = _("WooCommerce Products", sys_lang)
|
||||
|
||||
item.item_name = item_data.get("name")
|
||||
item.item_code = _("woocommerce - {0}", sys_lang).format(item_data.get("product_id"))
|
||||
item.woocommerce_id = item_data.get("product_id")
|
||||
item.item_group = _("WooCommerce Products", sys_lang)
|
||||
item.stock_uom = woocommerce_settings.uom or _("Nos", sys_lang)
|
||||
item.flags.ignore_mandatory = True
|
||||
item.save()
|
||||
item.item_name = item_data.get("name")
|
||||
item.woocommerce_id = item_woo_com_id
|
||||
item.flags.ignore_mandatory = True
|
||||
item.save()
|
||||
|
||||
def create_sales_order(order, woocommerce_settings, customer_name, sys_lang):
|
||||
new_sales_order = frappe.new_doc("Sales Order")
|
||||
@@ -194,12 +192,12 @@ def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_l
|
||||
|
||||
for item in order.get("line_items"):
|
||||
woocomm_item_id = item.get("product_id")
|
||||
found_item = frappe.get_doc("Item", {"woocommerce_id": woocomm_item_id})
|
||||
found_item = frappe.get_doc("Item", {"woocommerce_id": cstr(woocomm_item_id)})
|
||||
|
||||
ordered_items_tax = item.get("total_tax")
|
||||
|
||||
new_sales_order.append("items",{
|
||||
"item_code": found_item.item_code,
|
||||
new_sales_order.append("items", {
|
||||
"item_code": found_item.name,
|
||||
"item_name": found_item.item_name,
|
||||
"description": found_item.item_name,
|
||||
"delivery_date": new_sales_order.delivery_date,
|
||||
@@ -207,7 +205,7 @@ def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_l
|
||||
"qty": item.get("quantity"),
|
||||
"rate": item.get("price"),
|
||||
"warehouse": woocommerce_settings.warehouse or default_warehouse
|
||||
})
|
||||
})
|
||||
|
||||
add_tax_details(new_sales_order, ordered_items_tax, "Ordered Item tax", woocommerce_settings.tax_account)
|
||||
|
||||
|
||||
@@ -17,10 +17,12 @@ class AmazonMWSSettings(Document):
|
||||
else:
|
||||
self.enable_sync = 0
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_products_details(self):
|
||||
if self.enable_amazon == 1:
|
||||
frappe.enqueue('erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_methods.get_products_details')
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_order_details(self):
|
||||
if self.enable_amazon == 1:
|
||||
after_date = dateutil.parser.parse(self.after_date).strftime("%Y-%m-%d")
|
||||
@@ -40,4 +42,4 @@ def setup_custom_fields():
|
||||
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)]
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields)
|
||||
create_custom_fields(custom_fields)
|
||||
|
||||
@@ -19,7 +19,7 @@ class TestMpesaSettings(unittest.TestCase):
|
||||
mode_of_payment = frappe.get_doc("Mode of Payment", "Mpesa-_Test")
|
||||
self.assertTrue(frappe.db.exists("Payment Gateway Account", {'payment_gateway': "Mpesa-_Test"}))
|
||||
self.assertTrue(mode_of_payment.name)
|
||||
self.assertEquals(mode_of_payment.type, "Phone")
|
||||
self.assertEqual(mode_of_payment.type, "Phone")
|
||||
|
||||
def test_processing_of_account_balance(self):
|
||||
mpesa_doc = create_mpesa_settings(payment_gateway_name="_Account Balance")
|
||||
@@ -31,11 +31,11 @@ class TestMpesaSettings(unittest.TestCase):
|
||||
|
||||
# test integration request creation and successful update of the status on receiving callback response
|
||||
self.assertTrue(integration_request)
|
||||
self.assertEquals(integration_request.status, "Completed")
|
||||
self.assertEqual(integration_request.status, "Completed")
|
||||
|
||||
# test formatting of account balance received as string to json with appropriate currency symbol
|
||||
mpesa_doc.reload()
|
||||
self.assertEquals(mpesa_doc.account_balance, dumps({
|
||||
self.assertEqual(mpesa_doc.account_balance, dumps({
|
||||
"Working Account": {
|
||||
"current_balance": "Sh 481,000.00",
|
||||
"available_balance": "Sh 481,000.00",
|
||||
@@ -60,7 +60,7 @@ class TestMpesaSettings(unittest.TestCase):
|
||||
|
||||
pr = pos_invoice.create_payment_request()
|
||||
# test payment request creation
|
||||
self.assertEquals(pr.payment_gateway, "Mpesa-Payment")
|
||||
self.assertEqual(pr.payment_gateway, "Mpesa-Payment")
|
||||
|
||||
# submitting payment request creates integration requests with random id
|
||||
integration_req_ids = frappe.get_all("Integration Request", filters={
|
||||
@@ -75,12 +75,12 @@ class TestMpesaSettings(unittest.TestCase):
|
||||
|
||||
# test integration request creation and successful update of the status on receiving callback response
|
||||
self.assertTrue(integration_request)
|
||||
self.assertEquals(integration_request.status, "Completed")
|
||||
self.assertEqual(integration_request.status, "Completed")
|
||||
|
||||
pos_invoice.reload()
|
||||
integration_request.reload()
|
||||
self.assertEquals(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R")
|
||||
self.assertEquals(integration_request.status, "Completed")
|
||||
self.assertEqual(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R")
|
||||
self.assertEqual(integration_request.status, "Completed")
|
||||
|
||||
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
|
||||
integration_request.delete()
|
||||
@@ -104,7 +104,7 @@ class TestMpesaSettings(unittest.TestCase):
|
||||
|
||||
pr = pos_invoice.create_payment_request()
|
||||
# test payment request creation
|
||||
self.assertEquals(pr.payment_gateway, "Mpesa-Payment")
|
||||
self.assertEqual(pr.payment_gateway, "Mpesa-Payment")
|
||||
|
||||
# submitting payment request creates integration requests with random id
|
||||
integration_req_ids = frappe.get_all("Integration Request", filters={
|
||||
@@ -126,12 +126,12 @@ class TestMpesaSettings(unittest.TestCase):
|
||||
verify_transaction(**callback_response)
|
||||
# test completion of integration request
|
||||
integration_request = frappe.get_doc("Integration Request", integration_req_ids[i])
|
||||
self.assertEquals(integration_request.status, "Completed")
|
||||
self.assertEqual(integration_request.status, "Completed")
|
||||
integration_requests.append(integration_request)
|
||||
|
||||
# check receipt number once all the integration requests are completed
|
||||
pos_invoice.reload()
|
||||
self.assertEquals(pos_invoice.mpesa_receipt_number, ', '.join(mpesa_receipt_numbers))
|
||||
self.assertEqual(pos_invoice.mpesa_receipt_number, ', '.join(mpesa_receipt_numbers))
|
||||
|
||||
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
|
||||
[d.delete() for d in integration_requests]
|
||||
@@ -155,7 +155,7 @@ class TestMpesaSettings(unittest.TestCase):
|
||||
|
||||
pr = pos_invoice.create_payment_request()
|
||||
# test payment request creation
|
||||
self.assertEquals(pr.payment_gateway, "Mpesa-Payment")
|
||||
self.assertEqual(pr.payment_gateway, "Mpesa-Payment")
|
||||
|
||||
# submitting payment request creates integration requests with random id
|
||||
integration_req_ids = frappe.get_all("Integration Request", filters={
|
||||
@@ -175,7 +175,7 @@ class TestMpesaSettings(unittest.TestCase):
|
||||
verify_transaction(**callback_response)
|
||||
# test completion of integration request
|
||||
integration_request = frappe.get_doc("Integration Request", integration_req_ids[0])
|
||||
self.assertEquals(integration_request.status, "Completed")
|
||||
self.assertEqual(integration_request.status, "Completed")
|
||||
|
||||
# now one request is completed
|
||||
# second integration request fails
|
||||
@@ -187,7 +187,7 @@ class TestMpesaSettings(unittest.TestCase):
|
||||
'name': ['not in', integration_req_ids]
|
||||
}, pluck="name")
|
||||
|
||||
self.assertEquals(len(new_integration_req_ids), 1)
|
||||
self.assertEqual(len(new_integration_req_ids), 1)
|
||||
|
||||
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
|
||||
frappe.db.sql("delete from `tabIntegration Request` where integration_request_service = 'Mpesa'")
|
||||
|
||||
@@ -90,9 +90,9 @@ def add_bank_accounts(response, bank, company):
|
||||
"bank": bank["bank_name"],
|
||||
"account": default_gl_account.account,
|
||||
"account_name": account["name"],
|
||||
"account_type": account["type"] or "",
|
||||
"account_subtype": account["subtype"] or "",
|
||||
"mask": account["mask"] or "",
|
||||
"account_type": account.get("type", ""),
|
||||
"account_subtype": account.get("subtype", ""),
|
||||
"mask": account.get("mask", ""),
|
||||
"integration_id": account["id"],
|
||||
"is_company_account": 1,
|
||||
"company": company
|
||||
|
||||
@@ -30,14 +30,14 @@ class ShopifySettings(Document):
|
||||
webhooks = ["orders/create", "orders/paid", "orders/fulfilled"]
|
||||
# url = get_shopify_url('admin/webhooks.json', self)
|
||||
created_webhooks = [d.method for d in self.webhooks]
|
||||
url = get_shopify_url('admin/api/2020-04/webhooks.json', self)
|
||||
url = get_shopify_url('admin/api/2021-04/webhooks.json', self)
|
||||
for method in webhooks:
|
||||
session = get_request_session()
|
||||
try:
|
||||
res = session.post(url, data=json.dumps({
|
||||
"webhook": {
|
||||
"topic": method,
|
||||
"address": get_webhook_address(connector_name='shopify_connection', method='store_request_data'),
|
||||
"address": get_webhook_address(connector_name='shopify_connection', method='store_request_data', force_https=True),
|
||||
"format": "json"
|
||||
}
|
||||
}), headers=get_header(self))
|
||||
@@ -56,7 +56,7 @@ class ShopifySettings(Document):
|
||||
deleted_webhooks = []
|
||||
|
||||
for d in self.webhooks:
|
||||
url = get_shopify_url('admin/api/2020-04/webhooks/{0}.json'.format(d.webhook_id), self)
|
||||
url = get_shopify_url('admin/api/2021-04/webhooks/{0}.json'.format(d.webhook_id), self)
|
||||
try:
|
||||
res = session.delete(url, headers=get_header(self))
|
||||
res.raise_for_status()
|
||||
|
||||
@@ -32,10 +32,12 @@ def create_customer(shopify_customer, shopify_settings):
|
||||
raise e
|
||||
|
||||
def create_customer_address(customer, shopify_customer):
|
||||
if not shopify_customer.get("addresses"):
|
||||
return
|
||||
addresses = shopify_customer.get("addresses", [])
|
||||
|
||||
for i, address in enumerate(shopify_customer.get("addresses")):
|
||||
if not addresses and "default_address" in shopify_customer:
|
||||
addresses.append(shopify_customer["default_address"])
|
||||
|
||||
for i, address in enumerate(addresses):
|
||||
address_title, address_type = get_address_title_and_type(customer.customer_name, i)
|
||||
try :
|
||||
frappe.get_doc({
|
||||
|
||||
@@ -8,7 +8,7 @@ from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings impo
|
||||
shopify_variants_attr_list = ["option1", "option2", "option3"]
|
||||
|
||||
def sync_item_from_shopify(shopify_settings, item):
|
||||
url = get_shopify_url("admin/api/2020-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
|
||||
url = get_shopify_url("admin/api/2021-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
|
||||
session = get_request_session()
|
||||
|
||||
try:
|
||||
|
||||
@@ -28,7 +28,7 @@ def validate_webhooks_request(doctype, hmac_key, secret_key='secret'):
|
||||
|
||||
return innerfn
|
||||
|
||||
def get_webhook_address(connector_name, method, exclude_uri=False):
|
||||
def get_webhook_address(connector_name, method, exclude_uri=False, force_https=False):
|
||||
endpoint = "erpnext.erpnext_integrations.connectors.{0}.{1}".format(connector_name, method)
|
||||
|
||||
if exclude_uri:
|
||||
@@ -39,7 +39,11 @@ def get_webhook_address(connector_name, method, exclude_uri=False):
|
||||
except RuntimeError:
|
||||
url = "http://localhost:8000"
|
||||
|
||||
server_url = '{uri.scheme}://{uri.netloc}/api/method/{endpoint}'.format(uri=urlparse(url), endpoint=endpoint)
|
||||
url_data = urlparse(url)
|
||||
scheme = "https" if force_https else url_data.scheme
|
||||
netloc = url_data.netloc
|
||||
|
||||
server_url = f"{scheme}://{netloc}/api/method/{endpoint}"
|
||||
|
||||
return server_url
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class TestClinicalProcedure(unittest.TestCase):
|
||||
|
||||
procedure_template.disabled = 1
|
||||
procedure_template.save()
|
||||
self.assertEquals(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1)
|
||||
self.assertEqual(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1)
|
||||
|
||||
def test_consumables(self):
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
|
||||
@@ -18,7 +18,7 @@ class TestLabTest(unittest.TestCase):
|
||||
|
||||
lab_template.disabled = 1
|
||||
lab_template.save()
|
||||
self.assertEquals(frappe.db.get_value('Item', lab_template.item, 'disabled'), 1)
|
||||
self.assertEqual(frappe.db.get_value('Item', lab_template.item, 'disabled'), 1)
|
||||
|
||||
lab_template.reload()
|
||||
|
||||
@@ -57,7 +57,7 @@ class TestLabTest(unittest.TestCase):
|
||||
|
||||
# sample collection should not be created
|
||||
lab_test.reload()
|
||||
self.assertEquals(lab_test.sample, None)
|
||||
self.assertEqual(lab_test.sample, None)
|
||||
|
||||
def test_create_lab_tests_from_sales_invoice(self):
|
||||
sales_invoice = create_sales_invoice()
|
||||
|
||||
@@ -20,13 +20,13 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0)
|
||||
appointment = create_appointment(patient, practitioner, nowdate())
|
||||
self.assertEquals(appointment.status, 'Open')
|
||||
self.assertEqual(appointment.status, 'Open')
|
||||
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2))
|
||||
self.assertEquals(appointment.status, 'Scheduled')
|
||||
self.assertEqual(appointment.status, 'Scheduled')
|
||||
encounter = create_encounter(appointment)
|
||||
self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
|
||||
self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
|
||||
encounter.cancel()
|
||||
self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
|
||||
self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
|
||||
|
||||
def test_start_encounter(self):
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
|
||||
@@ -18,24 +18,24 @@ class TestTherapyPlan(unittest.TestCase):
|
||||
|
||||
def test_status(self):
|
||||
plan = create_therapy_plan()
|
||||
self.assertEquals(plan.status, 'Not Started')
|
||||
self.assertEqual(plan.status, 'Not Started')
|
||||
|
||||
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company')
|
||||
frappe.get_doc(session).submit()
|
||||
self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress')
|
||||
self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress')
|
||||
|
||||
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company')
|
||||
frappe.get_doc(session).submit()
|
||||
self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
|
||||
self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
|
||||
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
appointment = create_appointment(patient, practitioner, nowdate())
|
||||
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name)
|
||||
session = frappe.get_doc(session)
|
||||
session.submit()
|
||||
self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
|
||||
self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed')
|
||||
session.cancel()
|
||||
self.assertEquals(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
|
||||
self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
|
||||
|
||||
def test_therapy_plan_from_template(self):
|
||||
patient = create_patient()
|
||||
@@ -49,7 +49,7 @@ class TestTherapyPlan(unittest.TestCase):
|
||||
si.save()
|
||||
|
||||
therapy_plan_template_amt = frappe.db.get_value('Therapy Plan Template', template, 'total_amount')
|
||||
self.assertEquals(si.items[0].amount, therapy_plan_template_amt)
|
||||
self.assertEqual(si.items[0].amount, therapy_plan_template_amt)
|
||||
|
||||
|
||||
def create_therapy_plan(template=None):
|
||||
|
||||
@@ -13,7 +13,7 @@ class TestTherapyType(unittest.TestCase):
|
||||
|
||||
therapy_type.disabled = 1
|
||||
therapy_type.save()
|
||||
self.assertEquals(frappe.db.get_value('Item', therapy_type.item, 'disabled'), 1)
|
||||
self.assertEqual(frappe.db.get_value('Item', therapy_type.item, 'disabled'), 1)
|
||||
|
||||
def create_therapy_type():
|
||||
exercise = create_exercise_type()
|
||||
|
||||
@@ -50,6 +50,7 @@ class TherapyType(Document):
|
||||
|
||||
self.db_set('change_in_item', 0)
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_exercises(self):
|
||||
exercises = self.get_exercises_for_body_parts()
|
||||
last_idx = max([cint(d.idx) for d in self.get('exercises')] or [0,])
|
||||
|
||||
@@ -365,10 +365,8 @@ scheduler_events = {
|
||||
"erpnext.setup.doctype.email_digest.email_digest.send",
|
||||
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms",
|
||||
"erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation",
|
||||
"erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment.automatically_allocate_leaves_based_on_leave_policy",
|
||||
"erpnext.hr.utils.generate_leave_encashment",
|
||||
"erpnext.hr.utils.allocate_earned_leaves",
|
||||
"erpnext.hr.utils.grant_leaves_automatically",
|
||||
"erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall",
|
||||
"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
|
||||
"erpnext.crm.doctype.lead.lead.daily_open_lead"
|
||||
@@ -426,7 +424,8 @@ regional_overrides = {
|
||||
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
|
||||
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
|
||||
'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries',
|
||||
'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields'
|
||||
'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields',
|
||||
'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount'
|
||||
},
|
||||
'United Arab Emirates': {
|
||||
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data',
|
||||
|
||||
@@ -68,19 +68,19 @@ class TestCompensatoryLeaveRequest(unittest.TestCase):
|
||||
filters = dict(transaction_name=compensatory_leave_request.leave_allocation)
|
||||
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters)
|
||||
|
||||
self.assertEquals(len(leave_ledger_entry), 1)
|
||||
self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
|
||||
self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
|
||||
self.assertEquals(leave_ledger_entry[0].leaves, 1)
|
||||
self.assertEqual(len(leave_ledger_entry), 1)
|
||||
self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
|
||||
self.assertEqual(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
|
||||
self.assertEqual(leave_ledger_entry[0].leaves, 1)
|
||||
|
||||
# check reverse leave ledger entry on cancellation
|
||||
compensatory_leave_request.cancel()
|
||||
leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=filters, order_by = 'creation desc')
|
||||
|
||||
self.assertEquals(len(leave_ledger_entry), 2)
|
||||
self.assertEquals(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
|
||||
self.assertEquals(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
|
||||
self.assertEquals(leave_ledger_entry[0].leaves, -1)
|
||||
self.assertEqual(len(leave_ledger_entry), 2)
|
||||
self.assertEqual(leave_ledger_entry[0].employee, compensatory_leave_request.employee)
|
||||
self.assertEqual(leave_ledger_entry[0].leave_type, compensatory_leave_request.leave_type)
|
||||
self.assertEqual(leave_ledger_entry[0].leaves, -1)
|
||||
|
||||
def get_compensatory_leave_request(employee, leave_date=today()):
|
||||
prev_comp_leave_req = frappe.db.get_value('Compensatory Leave Request',
|
||||
|
||||
@@ -31,7 +31,8 @@ class Department(NestedSet):
|
||||
return new
|
||||
|
||||
def on_update(self):
|
||||
NestedSet.on_update(self)
|
||||
if not frappe.local.flags.ignore_update_nsm:
|
||||
super(Department, self).on_update()
|
||||
|
||||
def on_trash(self):
|
||||
super(Department, self).on_trash()
|
||||
|
||||
@@ -182,6 +182,10 @@
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Sales User"
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
@@ -191,4 +195,4 @@
|
||||
"track_changes": 0,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,12 @@ def get_data():
|
||||
},
|
||||
'transactions': [
|
||||
{
|
||||
'label': _('Leave and Attendance'),
|
||||
'items': ['Attendance', 'Attendance Request', 'Leave Application', 'Leave Allocation', 'Employee Checkin']
|
||||
'label': _('Attendance'),
|
||||
'items': ['Attendance', 'Attendance Request', 'Employee Checkin']
|
||||
},
|
||||
{
|
||||
'label': _('Leave'),
|
||||
'items': ['Leave Application', 'Leave Allocation', 'Leave Policy Assignment']
|
||||
},
|
||||
{
|
||||
'label': _('Lifecycle'),
|
||||
@@ -30,10 +34,6 @@ def get_data():
|
||||
'label': _('Benefit'),
|
||||
'items': ['Employee Benefit Application', 'Employee Benefit Claim']
|
||||
},
|
||||
{
|
||||
'label': _('Evaluation'),
|
||||
'items': ['Appraisal']
|
||||
},
|
||||
{
|
||||
'label': _('Payroll'),
|
||||
'items': ['Salary Structure Assignment', 'Salary Slip', 'Additional Salary', 'Timesheet','Employee Incentive', 'Retention Bonus', 'Bank Account']
|
||||
@@ -42,5 +42,9 @@ def get_data():
|
||||
'label': _('Training'),
|
||||
'items': ['Training Event', 'Training Result', 'Training Feedback', 'Employee Skill Map']
|
||||
},
|
||||
{
|
||||
'label': _('Evaluation'),
|
||||
'items': ['Appraisal']
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ frappe.ui.form.on('Employee Advance', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('salary_component', function(doc) {
|
||||
frm.set_query('salary_component', function() {
|
||||
return {
|
||||
filters: {
|
||||
"type": "Deduction"
|
||||
@@ -44,48 +44,49 @@ frappe.ui.form.on('Employee Advance', {
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.docstatus===1
|
||||
&& (flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount))
|
||||
&& frappe.model.can_create("Payment Entry")) {
|
||||
if (frm.doc.docstatus === 1 &&
|
||||
(flt(frm.doc.paid_amount) < flt(frm.doc.advance_amount)) &&
|
||||
frappe.model.can_create("Payment Entry")) {
|
||||
frm.add_custom_button(__('Payment'),
|
||||
function() { frm.events.make_payment_entry(frm); }, __('Create'));
|
||||
}
|
||||
else if (
|
||||
frm.doc.docstatus === 1
|
||||
&& flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount)
|
||||
&& frappe.model.can_create("Expense Claim")
|
||||
function () {
|
||||
frm.events.make_payment_entry(frm);
|
||||
}, __('Create'));
|
||||
} else if (
|
||||
frm.doc.docstatus === 1 &&
|
||||
flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount) &&
|
||||
frappe.model.can_create("Expense Claim")
|
||||
) {
|
||||
frm.add_custom_button(
|
||||
__("Expense Claim"),
|
||||
function() {
|
||||
function () {
|
||||
frm.events.make_expense_claim(frm);
|
||||
},
|
||||
__('Create')
|
||||
);
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus === 1
|
||||
&& (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) {
|
||||
if (frm.doc.docstatus === 1 &&
|
||||
(flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.paid_amount) != flt(frm.doc.return_amount))) {
|
||||
|
||||
if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")){
|
||||
frm.add_custom_button(__("Return"), function() {
|
||||
if (frm.doc.repay_unclaimed_amount_from_salary == 0 && frappe.model.can_create("Journal Entry")) {
|
||||
frm.add_custom_button(__("Return"), function() {
|
||||
frm.trigger('make_return_entry');
|
||||
}, __('Create'));
|
||||
}else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")){
|
||||
frm.add_custom_button(__("Deduction from salary"), function() {
|
||||
} else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")) {
|
||||
frm.add_custom_button(__("Deduction from salary"), function() {
|
||||
frm.events.make_deduction_via_additional_salary(frm);
|
||||
}, __('Create'));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
make_deduction_via_additional_salary: function(frm){
|
||||
make_deduction_via_additional_salary: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.hr.doctype.employee_advance.employee_advance.create_return_through_additional_salary",
|
||||
args: {
|
||||
doc: frm.doc
|
||||
},
|
||||
callback: function (r){
|
||||
callback: function(r) {
|
||||
var doclist = frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||
}
|
||||
@@ -94,7 +95,7 @@ frappe.ui.form.on('Employee Advance', {
|
||||
|
||||
make_payment_entry: function(frm) {
|
||||
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
|
||||
if(frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) {
|
||||
if (frm.doc.__onload && frm.doc.__onload.make_payment_via_journal_entry) {
|
||||
method = "erpnext.hr.doctype.employee_advance.employee_advance.make_bank_entry";
|
||||
}
|
||||
return frappe.call({
|
||||
@@ -148,11 +149,11 @@ frappe.ui.form.on('Employee Advance', {
|
||||
});
|
||||
},
|
||||
|
||||
employee: function (frm) {
|
||||
employee: function(frm) {
|
||||
if (frm.doc.employee) {
|
||||
frappe.run_serially([
|
||||
() => frm.trigger('get_employee_currency'),
|
||||
() => frm.trigger('get_pending_amount')
|
||||
() => frm.trigger('get_employee_currency'),
|
||||
() => frm.trigger('get_pending_amount')
|
||||
]);
|
||||
}
|
||||
},
|
||||
@@ -199,7 +200,7 @@ frappe.ui.form.on('Employee Advance', {
|
||||
} else {
|
||||
frm.set_value("exchange_rate", 1.0);
|
||||
frm.set_df_property('exchange_rate', 'hidden', 1);
|
||||
frm.set_df_property("exchange_rate", "description", "" );
|
||||
frm.set_df_property("exchange_rate", "description", "");
|
||||
}
|
||||
frm.refresh_fields();
|
||||
}
|
||||
@@ -215,8 +216,8 @@ frappe.ui.form.on('Employee Advance', {
|
||||
callback: function(r) {
|
||||
frm.set_value("exchange_rate", flt(r.message));
|
||||
frm.set_df_property('exchange_rate', 'hidden', 0);
|
||||
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
|
||||
+ " = [?] " + company_currency);
|
||||
frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency +
|
||||
" = [?] " + company_currency);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ from frappe import _
|
||||
def get_data():
|
||||
return {
|
||||
'fieldname': 'employee_advance',
|
||||
'non_standard_fieldnames': {
|
||||
'Payment Entry': 'reference_name',
|
||||
'Journal Entry': 'reference_name'
|
||||
},
|
||||
'non_standard_fieldnames': {
|
||||
'Payment Entry': 'reference_name',
|
||||
'Journal Entry': 'reference_name'
|
||||
},
|
||||
'transactions': [
|
||||
{
|
||||
'items': ['Expense Claim']
|
||||
|
||||
0
erpnext/hr/doctype/employee_referral/__init__.py
Normal file
0
erpnext/hr/doctype/employee_referral/__init__.py
Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user