Merge remote-tracking branch 'upstream/develop' into esbuild
This commit is contained in:
commit
69eb6d476e
38
.github/helper/semgrep_rules/README.md
vendored
Normal file
38
.github/helper/semgrep_rules/README.md
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
# Semgrep linting
|
||||
|
||||
## What is semgrep?
|
||||
Semgrep or "semantic grep" is language agnostic static analysis tool. In simple terms semgrep is syntax-aware `grep`, so unlike regex it doesn't get confused by different ways of writing same thing or whitespaces or code split in multiple lines etc.
|
||||
|
||||
Example:
|
||||
|
||||
To check if a translate function is using f-string or not the regex would be `r"_\(\s*f[\"']"` while equivalent rule in semgrep would be `_(f"...")`. As semgrep knows grammer of language it takes care of unnecessary whitespace, type of quotation marks etc.
|
||||
|
||||
You can read more such examples in `.github/helper/semgrep_rules` directory.
|
||||
|
||||
# Why/when to use this?
|
||||
We want to maintain quality of contributions, at the same time remembering all the good practices can be pain to deal with while evaluating contributions. Using semgrep if you can translate "best practice" into a rule then it can automate the task for us.
|
||||
|
||||
## Running locally
|
||||
|
||||
Install semgrep using homebrew `brew install semgrep` or pip `pip install semgrep`.
|
||||
|
||||
To run locally use following command:
|
||||
|
||||
`semgrep --config=.github/helper/semgrep_rules [file/folder names]`
|
||||
|
||||
## Testing
|
||||
semgrep allows testing the tests. Refer to this page: https://semgrep.dev/docs/writing-rules/testing-rules/
|
||||
|
||||
When writing new rules you should write few positive and few negative cases as shown in the guide and current tests.
|
||||
|
||||
To run current tests: `semgrep --test --test-ignore-todo .github/helper/semgrep_rules`
|
||||
|
||||
|
||||
## Reference
|
||||
|
||||
If you are new to Semgrep read following pages to get started on writing/modifying rules:
|
||||
|
||||
- https://semgrep.dev/docs/getting-started/
|
||||
- https://semgrep.dev/docs/writing-rules/rule-syntax
|
||||
- https://semgrep.dev/docs/writing-rules/pattern-examples/
|
||||
- https://semgrep.dev/docs/writing-rules/rule-ideas/#common-use-cases
|
28
.github/helper/semgrep_rules/frappe_correctness.py
vendored
Normal file
28
.github/helper/semgrep_rules/frappe_correctness.py
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
import frappe
|
||||
from frappe import _, flt
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
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'
|
||||
|
||||
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")
|
||||
|
||||
class TestDoc(Document):
|
||||
pass
|
||||
|
||||
def validate(self):
|
||||
#ruleid: frappe-modifying-child-tables-while-iterating
|
||||
for item in self.child_table:
|
||||
if item.value < 0:
|
||||
self.remove(item)
|
74
.github/helper/semgrep_rules/frappe_correctness.yml
vendored
Normal file
74
.github/helper/semgrep_rules/frappe_correctness.yml
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
# This file specifies rules for correctness according to how frappe doctype data model works.
|
||||
|
||||
rules:
|
||||
- id: frappe-modifying-after-submit
|
||||
patterns:
|
||||
- pattern: self.$ATTR = ...
|
||||
- pattern-inside: |
|
||||
def on_submit(self, ...):
|
||||
...
|
||||
- metavariable-regex:
|
||||
metavariable: '$ATTR'
|
||||
# this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me)
|
||||
regex: '^(?!status_updater)(.*)$'
|
||||
message: |
|
||||
Doctype modified after submission. Please check if modification of self.$ATTR is commited to database.
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-modifying-after-cancel
|
||||
patterns:
|
||||
- pattern: self.$ATTR = ...
|
||||
- pattern-inside: |
|
||||
def on_cancel(self, ...):
|
||||
...
|
||||
- metavariable-regex:
|
||||
metavariable: '$ATTR'
|
||||
regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
|
||||
message: |
|
||||
Doctype modified after cancellation. Please check if modification of self.$ATTR is commited to database.
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-print-function-in-doctypes
|
||||
pattern: print(...)
|
||||
message: |
|
||||
Did you mean to leave this print statement in? Consider using msgprint or logger instead of print statement.
|
||||
languages: [python]
|
||||
severity: WARNING
|
||||
paths:
|
||||
exclude:
|
||||
- test_*.py
|
||||
include:
|
||||
- "*/**/doctype/*"
|
||||
|
||||
- id: frappe-modifying-child-tables-while-iterating
|
||||
pattern-either:
|
||||
- pattern: |
|
||||
for $ROW in self.$TABLE:
|
||||
...
|
||||
self.remove(...)
|
||||
- pattern: |
|
||||
for $ROW in self.$TABLE:
|
||||
...
|
||||
self.append(...)
|
||||
message: |
|
||||
Child table being modified while iterating on it.
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
paths:
|
||||
include:
|
||||
- "*/**/doctype/*"
|
||||
|
||||
- id: frappe-same-key-assigned-twice
|
||||
pattern-either:
|
||||
- pattern: |
|
||||
{..., $X: $A, ..., $X: $B, ...}
|
||||
- pattern: |
|
||||
dict(..., ($X, $A), ..., ($X, $B), ...)
|
||||
- pattern: |
|
||||
_dict(..., ($X, $A), ..., ($X, $B), ...)
|
||||
message: |
|
||||
key `$X` is uselessly assigned twice. This could be a potential bug.
|
||||
languages: [python]
|
||||
severity: ERROR
|
6
.github/helper/semgrep_rules/security.py
vendored
Normal file
6
.github/helper/semgrep_rules/security.py
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
def function_name(input):
|
||||
# ruleid: frappe-codeinjection-eval
|
||||
eval(input)
|
||||
|
||||
# ok: frappe-codeinjection-eval
|
||||
eval("1 + 1")
|
25
.github/helper/semgrep_rules/security.yml
vendored
Normal file
25
.github/helper/semgrep_rules/security.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
rules:
|
||||
- id: frappe-codeinjection-eval
|
||||
patterns:
|
||||
- pattern-not: eval("...")
|
||||
- pattern: eval(...)
|
||||
message: |
|
||||
Detected the use of eval(). eval() can be dangerous if used to evaluate
|
||||
dynamic content. Avoid it or use safe_eval().
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-sqli-format-strings
|
||||
patterns:
|
||||
- pattern-inside: |
|
||||
@frappe.whitelist()
|
||||
def $FUNC(...):
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: frappe.db.sql("..." % ...)
|
||||
- pattern: frappe.db.sql(f"...", ...)
|
||||
- pattern: frappe.db.sql("...".format(...), ...)
|
||||
message: |
|
||||
Detected use of raw string formatting for SQL queries. This can lead to sql injection vulnerabilities. Refer security guidelines - https://github.com/frappe/erpnext/wiki/Code-Security-Guidelines
|
||||
languages: [python]
|
||||
severity: WARNING
|
37
.github/helper/semgrep_rules/translate.js
vendored
Normal file
37
.github/helper/semgrep_rules/translate.js
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
// ruleid: frappe-translation-empty-string
|
||||
__("")
|
||||
// ruleid: frappe-translation-empty-string
|
||||
__('')
|
||||
|
||||
// ok: frappe-translation-js-formatting
|
||||
__('Welcome {0}, get started with ERPNext in just a few clicks.', [full_name]);
|
||||
|
||||
// ruleid: frappe-translation-js-formatting
|
||||
__(`Welcome ${full_name}, get started with ERPNext in just a few clicks.`);
|
||||
|
||||
// ok: frappe-translation-js-formatting
|
||||
__('This is fine');
|
||||
|
||||
|
||||
// ok: frappe-translation-trailing-spaces
|
||||
__('This is fine');
|
||||
|
||||
// ruleid: frappe-translation-trailing-spaces
|
||||
__(' this is not ok ');
|
||||
// ruleid: frappe-translation-trailing-spaces
|
||||
__('this is not ok ');
|
||||
// ruleid: frappe-translation-trailing-spaces
|
||||
__(' this is not ok');
|
||||
|
||||
// ok: frappe-translation-js-splitting
|
||||
__('You have {0} subscribers in your mailing list.', [subscribers.length])
|
||||
|
||||
// todoruleid: frappe-translation-js-splitting
|
||||
__('You have') + subscribers.length + __('subscribers in your mailing list.')
|
||||
|
||||
// ruleid: frappe-translation-js-splitting
|
||||
__('You have' + 'subscribers in your mailing list.')
|
||||
|
||||
// ruleid: frappe-translation-js-splitting
|
||||
__('You have {0} subscribers' +
|
||||
'in your mailing list', [subscribers.length])
|
53
.github/helper/semgrep_rules/translate.py
vendored
Normal file
53
.github/helper/semgrep_rules/translate.py
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
# Examples taken from https://frappeframework.com/docs/user/en/translations
|
||||
# This file is used for testing the tests.
|
||||
|
||||
from frappe import _
|
||||
|
||||
full_name = "Jon Doe"
|
||||
# ok: frappe-translation-python-formatting
|
||||
_('Welcome {0}, get started with ERPNext in just a few clicks.').format(full_name)
|
||||
|
||||
# ruleid: frappe-translation-python-formatting
|
||||
_('Welcome %s, get started with ERPNext in just a few clicks.' % full_name)
|
||||
# ruleid: frappe-translation-python-formatting
|
||||
_('Welcome %(name)s, get started with ERPNext in just a few clicks.' % {'name': full_name})
|
||||
|
||||
# ruleid: frappe-translation-python-formatting
|
||||
_('Welcome {0}, get started with ERPNext in just a few clicks.'.format(full_name))
|
||||
|
||||
|
||||
subscribers = ["Jon", "Doe"]
|
||||
# ok: frappe-translation-python-formatting
|
||||
_('You have {0} subscribers in your mailing list.').format(len(subscribers))
|
||||
|
||||
# ruleid: frappe-translation-python-splitting
|
||||
_('You have') + len(subscribers) + _('subscribers in your mailing list.')
|
||||
|
||||
# ruleid: frappe-translation-python-splitting
|
||||
_('You have {0} subscribers \
|
||||
in your mailing list').format(len(subscribers))
|
||||
|
||||
# ok: frappe-translation-python-splitting
|
||||
_('You have {0} subscribers') \
|
||||
+ 'in your mailing list'
|
||||
|
||||
# ruleid: frappe-translation-trailing-spaces
|
||||
msg = _(" You have {0} pending invoice ")
|
||||
# ruleid: frappe-translation-trailing-spaces
|
||||
msg = _("You have {0} pending invoice ")
|
||||
# ruleid: frappe-translation-trailing-spaces
|
||||
msg = _(" You have {0} pending invoice")
|
||||
|
||||
# ok: frappe-translation-trailing-spaces
|
||||
msg = ' ' + _("You have {0} pending invoices") + ' '
|
||||
|
||||
# ruleid: frappe-translation-python-formatting
|
||||
_(f"can not format like this - {subscribers}")
|
||||
# ruleid: frappe-translation-python-splitting
|
||||
_(f"what" + f"this is also not cool")
|
||||
|
||||
|
||||
# ruleid: frappe-translation-empty-string
|
||||
_("")
|
||||
# ruleid: frappe-translation-empty-string
|
||||
_('')
|
63
.github/helper/semgrep_rules/translate.yml
vendored
Normal file
63
.github/helper/semgrep_rules/translate.yml
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
rules:
|
||||
- id: frappe-translation-empty-string
|
||||
pattern-either:
|
||||
- pattern: _("")
|
||||
- pattern: __("")
|
||||
message: |
|
||||
Empty string is useless for translation.
|
||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||
languages: [python, javascript, json]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-translation-trailing-spaces
|
||||
pattern-either:
|
||||
- pattern: _("=~/(^[ \t]+|[ \t]+$)/")
|
||||
- pattern: __("=~/(^[ \t]+|[ \t]+$)/")
|
||||
message: |
|
||||
Trailing or leading whitespace not allowed in translate strings.
|
||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||
languages: [python, javascript, json]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-translation-python-formatting
|
||||
pattern-either:
|
||||
- pattern: _("..." % ...)
|
||||
- pattern: _("...".format(...))
|
||||
- pattern: _(f"...")
|
||||
message: |
|
||||
Only positional formatters are allowed and formatting should not be done before translating.
|
||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-translation-js-formatting
|
||||
patterns:
|
||||
- pattern: __(`...`)
|
||||
- pattern-not: __("...")
|
||||
message: |
|
||||
Template strings are not allowed for text formatting.
|
||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||
languages: [javascript, json]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-translation-python-splitting
|
||||
pattern-either:
|
||||
- pattern: _(...) + ... + _(...)
|
||||
- pattern: _("..." + "...")
|
||||
- pattern-regex: '_\([^\)]*\\\s*'
|
||||
message: |
|
||||
Do not split strings inside translate function. Do not concatenate using translate functions.
|
||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-translation-js-splitting
|
||||
pattern-either:
|
||||
- pattern-regex: '__\([^\)]*[\+\\]\s*'
|
||||
- pattern: __('...' + '...')
|
||||
- pattern: __('...') + __('...')
|
||||
message: |
|
||||
Do not split strings inside translate function. Do not concatenate using translate functions.
|
||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||
languages: [javascript, json]
|
||||
severity: ERROR
|
31
.github/helper/semgrep_rules/ux.py
vendored
Normal file
31
.github/helper/semgrep_rules/ux.py
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
import frappe
|
||||
from frappe import msgprint, throw, _
|
||||
|
||||
|
||||
# ruleid: frappe-missing-translate-function
|
||||
throw("Error Occured")
|
||||
|
||||
# ruleid: frappe-missing-translate-function
|
||||
frappe.throw("Error Occured")
|
||||
|
||||
# ruleid: frappe-missing-translate-function
|
||||
frappe.msgprint("Useful message")
|
||||
|
||||
# ruleid: frappe-missing-translate-function
|
||||
msgprint("Useful message")
|
||||
|
||||
|
||||
# ok: frappe-missing-translate-function
|
||||
translatedmessage = _("Hello")
|
||||
|
||||
# ok: frappe-missing-translate-function
|
||||
throw(translatedmessage)
|
||||
|
||||
# ok: frappe-missing-translate-function
|
||||
msgprint(translatedmessage)
|
||||
|
||||
# ok: frappe-missing-translate-function
|
||||
msgprint(_("Helpful message"))
|
||||
|
||||
# ok: frappe-missing-translate-function
|
||||
frappe.throw(_("Error occured"))
|
15
.github/helper/semgrep_rules/ux.yml
vendored
Normal file
15
.github/helper/semgrep_rules/ux.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
rules:
|
||||
- id: frappe-missing-translate-function
|
||||
pattern-either:
|
||||
- patterns:
|
||||
- pattern: frappe.msgprint("...", ...)
|
||||
- pattern-not: frappe.msgprint(_("..."), ...)
|
||||
- pattern-not: frappe.msgprint(__("..."), ...)
|
||||
- patterns:
|
||||
- pattern: frappe.throw("...", ...)
|
||||
- pattern-not: frappe.throw(_("..."), ...)
|
||||
- pattern-not: frappe.throw(__("..."), ...)
|
||||
message: |
|
||||
All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
|
||||
languages: [python, javascript, json]
|
||||
severity: ERROR
|
20
.github/workflows/ci-tests.yml
vendored
20
.github/workflows/ci-tests.yml
vendored
@ -80,15 +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==2.2.0
|
||||
pip install coverage==4.5.4
|
||||
coveralls
|
||||
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
|
||||
|
||||
|
24
.github/workflows/semgrep.yml
vendored
Normal file
24
.github/workflows/semgrep.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: Semgrep
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
jobs:
|
||||
semgrep:
|
||||
name: Frappe Linter
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup python3
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Run semgrep
|
||||
run: |
|
||||
python -m pip install -q semgrep
|
||||
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
|
||||
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
|
||||
[[ -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.1'
|
||||
|
||||
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()
|
||||
@ -214,6 +214,7 @@ class Account(NestedSet):
|
||||
if parent_value_changed:
|
||||
doc.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def convert_group_to_ledger(self):
|
||||
if self.check_if_child_exists():
|
||||
throw(_("Account with child nodes cannot be converted to ledger"))
|
||||
@ -224,6 +225,7 @@ class Account(NestedSet):
|
||||
self.save()
|
||||
return 1
|
||||
|
||||
@frappe.whitelist()
|
||||
def convert_ledger_to_group(self):
|
||||
if self.check_gle_exists():
|
||||
throw(_("Account with existing transaction can not be converted to group."))
|
||||
|
@ -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:
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -39,6 +39,7 @@ class AccountingPeriod(Document):
|
||||
frappe.throw(_("Accounting Period overlaps with {0}")
|
||||
.format(existing_accounting_period[0].get("name")), OverlapError)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_doctypes_for_closing(self):
|
||||
docs_for_closing = []
|
||||
doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \
|
||||
|
@ -12,6 +12,7 @@
|
||||
"frozen_accounts_modifier",
|
||||
"determine_address_tax_category_from",
|
||||
"over_billing_allowance",
|
||||
"role_allowed_to_over_bill",
|
||||
"column_break_4",
|
||||
"credit_controller",
|
||||
"check_supplier_invoice_uniqueness",
|
||||
@ -226,6 +227,13 @@
|
||||
"fieldname": "delete_linked_ledger_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction"
|
||||
},
|
||||
{
|
||||
"description": "Users with this role are allowed to over bill above the allowance percentage",
|
||||
"fieldname": "role_allowed_to_over_bill",
|
||||
"fieldtype": "Link",
|
||||
"label": "Role Allowed to Over Bill ",
|
||||
"options": "Role"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@ -233,7 +241,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-05 13:04:00.118892",
|
||||
"modified": "2021-03-11 18:52:05.601996",
|
||||
"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)
|
||||
|
@ -42,10 +42,9 @@ let add_fields_to_mapping_table = function (frm) {
|
||||
});
|
||||
});
|
||||
|
||||
frappe.meta.get_docfield("Bank Transaction Mapping", "bank_transaction_field",
|
||||
frm.doc.name).options = options;
|
||||
|
||||
frm.fields_dict.bank_transaction_mapping.grid.refresh();
|
||||
frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property(
|
||||
'bank_transaction_field', 'options', options
|
||||
);
|
||||
};
|
||||
|
||||
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
||||
|
@ -12,6 +12,7 @@ form_grid_templates = {
|
||||
}
|
||||
|
||||
class BankClearance(Document):
|
||||
@frappe.whitelist()
|
||||
def get_payment_entries(self):
|
||||
if not (self.from_date and self.to_date):
|
||||
frappe.throw(_("From Date and To Date are Mandatory"))
|
||||
@ -108,6 +109,7 @@ class BankClearance(Document):
|
||||
row.update(d)
|
||||
self.total_amount += flt(amount)
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_clearance_date(self):
|
||||
clearance_date_updated = False
|
||||
for d in self.get('payment_entries'):
|
||||
|
@ -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",
|
||||
|
@ -532,43 +532,4 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
</table>
|
||||
`);
|
||||
},
|
||||
|
||||
show_missing_link_values(frm, missing_link_values) {
|
||||
let can_be_created_automatically = missing_link_values.every(
|
||||
(d) => d.has_one_mandatory_field
|
||||
);
|
||||
|
||||
let html = missing_link_values
|
||||
.map((d) => {
|
||||
let doctype = d.doctype;
|
||||
let values = d.missing_values;
|
||||
return `
|
||||
<h5>${doctype}</h5>
|
||||
<ul>${values.map((v) => `<li>${v}</li>`).join("")}</ul>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
if (can_be_created_automatically) {
|
||||
// prettier-ignore
|
||||
let message = __('There are some linked records which needs to be created before we can import your file. Do you want to create the following missing records automatically?');
|
||||
frappe.confirm(message + html, () => {
|
||||
frm.call("create_missing_link_values", {
|
||||
missing_link_values,
|
||||
}).then((r) => {
|
||||
let records = r.message;
|
||||
frappe.msgprint(__(
|
||||
"Created {0} records successfully.", [
|
||||
records.length,
|
||||
]
|
||||
));
|
||||
});
|
||||
});
|
||||
} else {
|
||||
frappe.msgprint(
|
||||
// prettier-ignore
|
||||
__('The following records needs to be created before we can import your file.') + html
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
@ -175,22 +175,24 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "deposit",
|
||||
"oldfieldname": "debit",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Deposit"
|
||||
"label": "Deposit",
|
||||
"oldfieldname": "debit",
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "withdrawal",
|
||||
"oldfieldname": "credit",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Withdrawal"
|
||||
"label": "Withdrawal",
|
||||
"oldfieldname": "credit",
|
||||
"options": "currency"
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-30 19:40:54.221070",
|
||||
"modified": "2021-04-14 17:31:58.963529",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Transaction",
|
||||
|
@ -15,12 +15,14 @@ from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profi
|
||||
test_dependencies = ["Item", "Cost Center"]
|
||||
|
||||
class TestBankTransaction(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
make_pos_profile()
|
||||
add_transactions()
|
||||
add_vouchers()
|
||||
|
||||
def tearDown(self):
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
for bt in frappe.get_all("Bank Transaction"):
|
||||
doc = frappe.get_doc("Bank Transaction", bt.name)
|
||||
doc.cancel()
|
||||
@ -33,9 +35,6 @@ class TestBankTransaction(unittest.TestCase):
|
||||
# Delete POS Profile
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
frappe.flags.test_bank_transactions_created = False
|
||||
frappe.flags.test_payments_created = False
|
||||
|
||||
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
|
||||
def test_linked_payments(self):
|
||||
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"))
|
||||
@ -44,8 +43,8 @@ class TestBankTransaction(unittest.TestCase):
|
||||
|
||||
# This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment
|
||||
def test_reconcile(self):
|
||||
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"))
|
||||
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200))
|
||||
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"))
|
||||
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1700))
|
||||
vouchers = json.dumps([{
|
||||
"payment_doctype":"Payment Entry",
|
||||
"payment_name":payment.name,
|
||||
@ -62,7 +61,6 @@ class TestBankTransaction(unittest.TestCase):
|
||||
def test_debit_credit_output(self):
|
||||
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"))
|
||||
linked_payments = get_linked_payments(bank_transaction.name, ['payment_entry', 'exact_match'])
|
||||
print(linked_payments)
|
||||
self.assertTrue(linked_payments[0][3])
|
||||
|
||||
# Check error if already reconciled
|
||||
@ -116,10 +114,6 @@ def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
|
||||
pass
|
||||
|
||||
def add_transactions():
|
||||
if frappe.flags.test_bank_transactions_created:
|
||||
return
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
create_bank_account()
|
||||
|
||||
doc = frappe.get_doc({
|
||||
@ -172,14 +166,8 @@ def add_transactions():
|
||||
}).insert()
|
||||
doc.submit()
|
||||
|
||||
frappe.flags.test_bank_transactions_created = True
|
||||
|
||||
def add_vouchers():
|
||||
if frappe.flags.test_payments_created:
|
||||
return
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
try:
|
||||
frappe.get_doc({
|
||||
"doctype": "Supplier",
|
||||
@ -272,13 +260,6 @@ def add_vouchers():
|
||||
except frappe.DuplicateEntryError:
|
||||
pass
|
||||
|
||||
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080)
|
||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "Fayva Oct 18"
|
||||
pe.reference_date = "2018-10-29"
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
|
||||
mode_of_payment = frappe.get_doc({
|
||||
"doctype": "Mode of Payment",
|
||||
"name": "Cash"
|
||||
@ -291,14 +272,12 @@ def add_vouchers():
|
||||
})
|
||||
mode_of_payment.save()
|
||||
|
||||
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_submit=1)
|
||||
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
|
||||
si.is_pos = 1
|
||||
si.append("payments", {
|
||||
"mode_of_payment": "Cash",
|
||||
"account": "_Test Bank - _TC",
|
||||
"amount": 109080
|
||||
})
|
||||
si.save()
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
frappe.flags.test_payments_created = True
|
||||
|
@ -57,6 +57,7 @@ class CForm(Document):
|
||||
total = sum([flt(d.grand_total) for d in self.get('invoices')])
|
||||
frappe.db.set(self, 'total_invoiced_amount', total)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_invoice_details(self, invoice_no):
|
||||
""" Pull details from invoices for referrence """
|
||||
if invoice_no:
|
||||
|
@ -293,6 +293,11 @@ def validate_accounts(file_name):
|
||||
accounts_dict = {}
|
||||
for account in accounts:
|
||||
accounts_dict.setdefault(account["account_name"], account)
|
||||
if not hasattr(account, "parent_account"):
|
||||
msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
|
||||
msg += "<br><br>"
|
||||
msg += _("Alternatively, you can download the template and fill your data in.")
|
||||
frappe.throw(msg, title=_("Parent Account Missing"))
|
||||
if account["parent_account"] and accounts_dict.get(account["parent_account"]):
|
||||
accounts_dict[account["parent_account"]]["is_group"] = 1
|
||||
|
||||
|
@ -50,6 +50,7 @@ class CostCenter(NestedSet):
|
||||
frappe.throw(_("{0} is not a group node. Please select a group node as parent cost center").format(
|
||||
frappe.bold(self.parent_cost_center)))
|
||||
|
||||
@frappe.whitelist()
|
||||
def convert_group_to_ledger(self):
|
||||
if self.check_if_child_exists():
|
||||
frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes"))
|
||||
@ -60,6 +61,7 @@ class CostCenter(NestedSet):
|
||||
self.save()
|
||||
return 1
|
||||
|
||||
@frappe.whitelist()
|
||||
def convert_ledger_to_group(self):
|
||||
if cint(self.enable_distributed_cost_center):
|
||||
frappe.throw(_("Cost Center with enabled distributed cost center can not be converted to group"))
|
||||
|
@ -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,24 @@ 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 = []
|
||||
self.validate_mandatory()
|
||||
@ -95,6 +113,7 @@ class ExchangeRateRevaluation(Document):
|
||||
message = _("No outstanding invoices found")
|
||||
frappe.msgprint(message)
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_jv_entry(self):
|
||||
if self.total_gain_loss == 0:
|
||||
return
|
||||
|
@ -12,6 +12,7 @@ from frappe.model.document import Document
|
||||
class FiscalYearIncorrectDate(frappe.ValidationError): pass
|
||||
|
||||
class FiscalYear(Document):
|
||||
@frappe.whitelist()
|
||||
def set_as_default(self):
|
||||
frappe.db.set_value("Global Defaults", None, "current_fiscal_year", self.name)
|
||||
global_defaults = frappe.get_doc("Global Defaults")
|
||||
@ -54,7 +55,7 @@ class FiscalYear(Document):
|
||||
def on_update(self):
|
||||
check_duplicate_fiscal_year(self)
|
||||
frappe.cache().delete_value("fiscal_years")
|
||||
|
||||
|
||||
def on_trash(self):
|
||||
global_defaults = frappe.get_doc("Global Defaults")
|
||||
if global_defaults.current_fiscal_year == self.name:
|
||||
|
@ -290,4 +290,8 @@ def rename_temporarily_named_docs(doctype):
|
||||
oldname = doc.name
|
||||
set_name_from_naming_options(frappe.get_meta(doctype).autoname, doc)
|
||||
newname = doc.name
|
||||
frappe.db.sql("""UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s""".format(doctype), (newname, oldname))
|
||||
frappe.db.sql(
|
||||
"UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s".format(doctype),
|
||||
(newname, oldname),
|
||||
auto_commit=True
|
||||
)
|
||||
|
@ -125,6 +125,7 @@ class InvoiceDiscounting(AccountsController):
|
||||
|
||||
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding='No')
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_disbursement_entry(self):
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.voucher_type = 'Journal Entry'
|
||||
@ -174,6 +175,7 @@ class InvoiceDiscounting(AccountsController):
|
||||
|
||||
return je
|
||||
|
||||
@frappe.whitelist()
|
||||
def close_loan(self):
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.voucher_type = 'Journal Entry'
|
||||
|
@ -327,18 +327,16 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
||||
}
|
||||
|
||||
setup_balance_formatter() {
|
||||
var me = this;
|
||||
$.each(["balance", "party_balance"], function(i, field) {
|
||||
var df = frappe.meta.get_docfield("Journal Entry Account", field, me.frm.doc.name);
|
||||
df.formatter = function(value, df, options, doc) {
|
||||
var currency = frappe.meta.get_field_currency(df, doc);
|
||||
var dr_or_cr = value ? ('<label>' + (value > 0.0 ? __("Dr") : __("Cr")) + '</label>') : "";
|
||||
return "<div style='text-align: right'>"
|
||||
+ ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency))
|
||||
+ " " + dr_or_cr
|
||||
+ "</div>";
|
||||
}
|
||||
})
|
||||
const formatter = function(value, df, options, doc) {
|
||||
var currency = frappe.meta.get_field_currency(df, doc);
|
||||
var dr_or_cr = value ? ('<label>' + (value > 0.0 ? __("Dr") : __("Cr")) + '</label>') : "";
|
||||
return "<div style='text-align: right'>"
|
||||
+ ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency))
|
||||
+ " " + dr_or_cr
|
||||
+ "</div>";
|
||||
};
|
||||
this.frm.fields_dict.accounts.grid.update_docfield_property('balance', 'formatter', formatter);
|
||||
this.frm.fields_dict.accounts.grid.update_docfield_property('party_balance', 'formatter', formatter);
|
||||
}
|
||||
|
||||
reference_name(doc, cdt, cdn) {
|
||||
@ -431,15 +429,6 @@ cur_frm.cscript.validate = function(doc,cdt,cdn) {
|
||||
cur_frm.cscript.update_totals(doc);
|
||||
}
|
||||
|
||||
cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
|
||||
if(doc.select_print_heading){
|
||||
// print heading
|
||||
cur_frm.pformat.print_heading = doc.select_print_heading;
|
||||
}
|
||||
else
|
||||
cur_frm.pformat.print_heading = __("Journal Entry");
|
||||
}
|
||||
|
||||
frappe.ui.form.on("Journal Entry Account", {
|
||||
party: function(frm, cdt, cdn) {
|
||||
var d = frappe.get_doc(cdt, cdn);
|
||||
@ -511,8 +500,11 @@ $.extend(erpnext.journal_entry, {
|
||||
};
|
||||
|
||||
$.each(field_label_map, function (fieldname, label) {
|
||||
var df = frappe.meta.get_docfield("Journal Entry Account", fieldname, frm.doc.name);
|
||||
df.label = frm.doc.multi_currency ? (label + " in Account Currency") : label;
|
||||
frm.fields_dict.accounts.grid.update_docfield_property(
|
||||
fieldname,
|
||||
'label',
|
||||
frm.doc.multi_currency ? (label + " in Account Currency") : label
|
||||
);
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -564,6 +564,7 @@ class JournalEntry(AccountsController):
|
||||
if gl_map:
|
||||
make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj, update_outstanding=update_outstanding)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_balance(self):
|
||||
if not self.get('accounts'):
|
||||
msgprint(_("'Entries' cannot be empty"), raise_exception=True)
|
||||
@ -591,6 +592,7 @@ class JournalEntry(AccountsController):
|
||||
|
||||
self.validate_total_debit_and_credit()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_outstanding_invoices(self):
|
||||
self.set('accounts', [])
|
||||
total = 0
|
||||
|
@ -280,7 +280,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-24 14:06:54.833738",
|
||||
"modified": "2020-06-26 14:06:54.833738",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry Account",
|
||||
|
@ -8,6 +8,7 @@ from frappe.utils import (flt, add_months)
|
||||
from frappe.model.document import Document
|
||||
|
||||
class MonthlyDistribution(Document):
|
||||
@frappe.whitelist()
|
||||
def get_months(self):
|
||||
month_list = ['January','February','March','April','May','June','July','August','September',
|
||||
'October','November','December']
|
||||
|
@ -167,6 +167,7 @@ class OpeningInvoiceCreationTool(Document):
|
||||
|
||||
return invoice
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_invoices(self):
|
||||
self.validate_company()
|
||||
invoices = self.get_invoices()
|
||||
|
@ -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}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -637,13 +637,13 @@ frappe.ui.form.on('Payment Entry', {
|
||||
let to_field = fields[key][1];
|
||||
|
||||
if (filters[from_field] && !filters[to_field]) {
|
||||
frappe.throw(__("Error: {0} is mandatory field",
|
||||
[to_field.replace(/_/g, " ")]
|
||||
));
|
||||
frappe.throw(
|
||||
__("Error: {0} is mandatory field", [to_field.replace(/_/g, " ")])
|
||||
);
|
||||
} else if (filters[from_field] && filters[from_field] > filters[to_field]) {
|
||||
frappe.throw(__("{0}: {1} must be less than {2}",
|
||||
[key, from_field.replace(/_/g, " "), to_field.replace(/_/g, " ")]
|
||||
));
|
||||
frappe.throw(
|
||||
__("{0}: {1} must be less than {2}", [key, from_field.replace(/_/g, " "), to_field.replace(/_/g, " ")])
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -692,6 +692,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
c.total_amount = d.invoice_amount;
|
||||
c.outstanding_amount = d.outstanding_amount;
|
||||
c.bill_no = d.bill_no;
|
||||
c.payment_term = d.payment_term;
|
||||
c.allocated_amount = d.allocated_amount;
|
||||
|
||||
if(!in_list(["Sales Order", "Purchase Order", "Expense Claim", "Fees"], d.voucher_type)) {
|
||||
if(flt(d.outstanding_amount) > 0)
|
||||
@ -741,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 || [],
|
||||
@ -774,12 +776,15 @@ frappe.ui.form.on('Payment Entry', {
|
||||
} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
|
||||
if(paid_amount > total_negative_outstanding) {
|
||||
if(total_negative_outstanding == 0) {
|
||||
frappe.msgprint(__("Cannot {0} {1} {2} without any negative outstanding invoice",
|
||||
[frm.doc.payment_type,
|
||||
(frm.doc.party_type=="Customer" ? "to" : "from"), frm.doc.party_type]));
|
||||
frappe.msgprint(
|
||||
__("Cannot {0} {1} {2} without any negative outstanding invoice", [frm.doc.payment_type,
|
||||
(frm.doc.party_type=="Customer" ? "to" : "from"), frm.doc.party_type])
|
||||
);
|
||||
return false
|
||||
} else {
|
||||
frappe.msgprint(__("Paid Amount cannot be greater than total negative outstanding amount {0}", [total_negative_outstanding]));
|
||||
frappe.msgprint(
|
||||
__("Paid Amount cannot be greater than total negative outstanding amount {0}", [total_negative_outstanding])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@ -791,21 +796,19 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
|
||||
$.each(frm.doc.references || [], function(i, row) {
|
||||
row.allocated_amount = 0 //If allocate payment amount checkbox is unchecked, set zero to allocate amount
|
||||
if(frappe.flags.allocate_payment_amount != 0){
|
||||
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;
|
||||
}
|
||||
if (frappe.flags.allocate_payment_amount == 0) {
|
||||
//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 || 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));
|
||||
}
|
||||
}
|
||||
|
@ -333,33 +333,50 @@ class PaymentEntry(AccountsController):
|
||||
invoice_payment_amount_map = {}
|
||||
invoice_paid_amount_map = {}
|
||||
|
||||
for reference in self.get('references'):
|
||||
if reference.payment_term and reference.reference_name:
|
||||
key = (reference.payment_term, reference.reference_name)
|
||||
for ref in self.get('references'):
|
||||
if ref.payment_term and ref.reference_name:
|
||||
key = (ref.payment_term, ref.reference_name)
|
||||
invoice_payment_amount_map.setdefault(key, 0.0)
|
||||
invoice_payment_amount_map[key] += reference.allocated_amount
|
||||
invoice_payment_amount_map[key] += ref.allocated_amount
|
||||
|
||||
if not invoice_paid_amount_map.get(key):
|
||||
payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': reference.reference_name},
|
||||
fields=['paid_amount', 'payment_amount', 'payment_term'])
|
||||
payment_schedule = frappe.get_all(
|
||||
'Payment Schedule',
|
||||
filters={'parent': ref.reference_name},
|
||||
fields=['paid_amount', 'payment_amount', 'payment_term', 'discount', 'outstanding']
|
||||
)
|
||||
for term in payment_schedule:
|
||||
invoice_key = (term.payment_term, reference.reference_name)
|
||||
invoice_key = (term.payment_term, ref.reference_name)
|
||||
invoice_paid_amount_map.setdefault(invoice_key, {})
|
||||
invoice_paid_amount_map[invoice_key]['outstanding'] = term.payment_amount - term.paid_amount
|
||||
invoice_paid_amount_map[invoice_key]['outstanding'] = term.outstanding
|
||||
invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100)
|
||||
|
||||
for key, allocated_amount in iteritems(invoice_payment_amount_map):
|
||||
outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
|
||||
discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get('discounted_amt'))
|
||||
|
||||
for key, amount in iteritems(invoice_payment_amount_map):
|
||||
if cancel:
|
||||
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` - %s
|
||||
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabPayment Schedule`
|
||||
SET
|
||||
paid_amount = `paid_amount` - %s,
|
||||
discounted_amount = `discounted_amount` - %s,
|
||||
outstanding = `outstanding` + %s
|
||||
WHERE parent = %s and payment_term = %s""",
|
||||
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]))
|
||||
else:
|
||||
outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
|
||||
|
||||
if amount > outstanding:
|
||||
if allocated_amount > outstanding:
|
||||
frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0]))
|
||||
|
||||
if amount and outstanding:
|
||||
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s
|
||||
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
|
||||
if allocated_amount and outstanding:
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabPayment Schedule`
|
||||
SET
|
||||
paid_amount = `paid_amount` + %s,
|
||||
discounted_amount = `discounted_amount` + %s,
|
||||
outstanding = `outstanding` - %s
|
||||
WHERE parent = %s and payment_term = %s""",
|
||||
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]))
|
||||
|
||||
def set_status(self):
|
||||
if self.docstatus == 2:
|
||||
@ -708,6 +725,8 @@ def get_outstanding_reference_documents(args):
|
||||
outstanding_invoices = get_outstanding_invoices(args.get("party_type"), args.get("party"),
|
||||
args.get("party_account"), filters=args, condition=condition)
|
||||
|
||||
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
|
||||
|
||||
for d in outstanding_invoices:
|
||||
d["exchange_rate"] = 1
|
||||
if party_account_currency != company_currency:
|
||||
@ -735,6 +754,46 @@ def get_outstanding_reference_documents(args):
|
||||
return data
|
||||
|
||||
|
||||
def split_invoices_based_on_payment_terms(outstanding_invoices):
|
||||
invoice_ref_based_on_payment_terms = {}
|
||||
for idx, d in enumerate(outstanding_invoices):
|
||||
if d.voucher_type in ['Sales Invoice', 'Purchase Invoice']:
|
||||
payment_term_template = frappe.db.get_value(d.voucher_type, d.voucher_no, 'payment_terms_template')
|
||||
if payment_term_template:
|
||||
allocate_payment_based_on_payment_terms = frappe.db.get_value(
|
||||
'Payment Terms Template', payment_term_template, 'allocate_payment_based_on_payment_terms')
|
||||
if allocate_payment_based_on_payment_terms:
|
||||
payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': d.voucher_no}, fields=["*"])
|
||||
|
||||
for payment_term in payment_schedule:
|
||||
if payment_term.outstanding > 0.1:
|
||||
invoice_ref_based_on_payment_terms.setdefault(idx, [])
|
||||
invoice_ref_based_on_payment_terms[idx].append(frappe._dict({
|
||||
'due_date': d.due_date,
|
||||
'currency': d.currency,
|
||||
'voucher_no': d.voucher_no,
|
||||
'voucher_type': d.voucher_type,
|
||||
'posting_date': d.posting_date,
|
||||
'invoice_amount': flt(d.invoice_amount),
|
||||
'outstanding_amount': flt(d.outstanding_amount),
|
||||
'payment_amount': payment_term.payment_amount,
|
||||
'payment_term': payment_term.payment_term,
|
||||
'allocated_amount': payment_term.outstanding
|
||||
}))
|
||||
|
||||
if invoice_ref_based_on_payment_terms:
|
||||
for idx, ref in invoice_ref_based_on_payment_terms.items():
|
||||
voucher_no = outstanding_invoices[idx]['voucher_no']
|
||||
voucher_type = outstanding_invoices[idx]['voucher_type']
|
||||
|
||||
frappe.msgprint(_("Spliting {} {} into {} rows as per payment terms").format(
|
||||
voucher_type, voucher_no, len(ref)), alert=True)
|
||||
|
||||
outstanding_invoices.pop(idx - 1)
|
||||
outstanding_invoices += invoice_ref_based_on_payment_terms[idx]
|
||||
|
||||
return outstanding_invoices
|
||||
|
||||
def get_orders_to_be_billed(posting_date, party_type, party,
|
||||
company, party_account_currency, company_currency, cost_center=None, filters=None):
|
||||
if party_type == "Customer":
|
||||
@ -1091,6 +1150,8 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
paid_amount, received_amount = set_paid_amount_and_received_amount(
|
||||
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc)
|
||||
|
||||
paid_amount, received_amount, discount_amount = apply_early_payment_discount(paid_amount, received_amount, doc)
|
||||
|
||||
pe = frappe.new_doc("Payment Entry")
|
||||
pe.payment_type = payment_type
|
||||
pe.company = doc.company
|
||||
@ -1160,11 +1221,20 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
|
||||
pe.setup_party_account_field()
|
||||
pe.set_missing_values()
|
||||
|
||||
if party_account and bank:
|
||||
if dt == "Employee Advance":
|
||||
reference_doc = doc
|
||||
pe.set_exchange_rate(ref_doc=reference_doc)
|
||||
pe.set_amounts()
|
||||
if discount_amount:
|
||||
pe.set_gain_or_loss(account_details={
|
||||
'account': frappe.get_cached_value('Company', pe.company, "default_discount_account"),
|
||||
'cost_center': pe.cost_center or frappe.get_cached_value('Company', pe.company, "cost_center"),
|
||||
'amount': discount_amount * (-1 if payment_type == "Pay" else 1)
|
||||
})
|
||||
pe.set_difference_amount()
|
||||
|
||||
return pe
|
||||
|
||||
def get_bank_cash_account(doc, bank_account):
|
||||
@ -1285,6 +1355,33 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta
|
||||
paid_amount = received_amount * doc.get('exchange_rate', 1)
|
||||
return paid_amount, received_amount
|
||||
|
||||
def apply_early_payment_discount(paid_amount, received_amount, doc):
|
||||
total_discount = 0
|
||||
if doc.doctype in ['Sales Invoice', 'Purchase Invoice'] and doc.payment_schedule:
|
||||
for term in doc.payment_schedule:
|
||||
if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date:
|
||||
if term.discount_type == 'Percentage':
|
||||
discount_amount = flt(doc.get('grand_total')) * (term.discount / 100)
|
||||
else:
|
||||
discount_amount = term.discount
|
||||
|
||||
discount_amount_in_foreign_currency = discount_amount * doc.get('conversion_rate', 1)
|
||||
|
||||
if doc.doctype == 'Sales Invoice':
|
||||
paid_amount -= discount_amount
|
||||
received_amount -= discount_amount_in_foreign_currency
|
||||
else:
|
||||
received_amount -= discount_amount
|
||||
paid_amount -= discount_amount_in_foreign_currency
|
||||
|
||||
total_discount += discount_amount
|
||||
|
||||
if total_discount:
|
||||
money = frappe.utils.fmt_money(total_discount, currency=doc.get('currency'))
|
||||
frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1)
|
||||
|
||||
return paid_amount, received_amount, total_discount
|
||||
|
||||
def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
|
||||
references = []
|
||||
for payment_term in payment_schedule:
|
||||
|
@ -193,6 +193,34 @@ class TestPaymentEntry(unittest.TestCase):
|
||||
self.assertEqual(si.payment_schedule[0].paid_amount, 200.0)
|
||||
self.assertEqual(si.payment_schedule[1].paid_amount, 36.0)
|
||||
|
||||
def test_payment_entry_against_payment_terms_with_discount(self):
|
||||
si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
|
||||
create_payment_terms_template_with_discount()
|
||||
si.payment_terms_template = 'Test Discount Template'
|
||||
|
||||
frappe.db.set_value('Company', si.company, 'default_discount_account', 'Write Off - _TC')
|
||||
|
||||
si.append('taxes', {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account Service Tax - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "Service Tax",
|
||||
"rate": 18
|
||||
})
|
||||
si.save()
|
||||
|
||||
si.submit()
|
||||
|
||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
|
||||
pe.submit()
|
||||
si.load_from_db()
|
||||
|
||||
self.assertEqual(pe.references[0].payment_term, '30 Credit Days with 10% Discount')
|
||||
self.assertEqual(si.payment_schedule[0].payment_amount, 236.0)
|
||||
self.assertEqual(si.payment_schedule[0].paid_amount, 212.40)
|
||||
self.assertEqual(si.payment_schedule[0].outstanding, 0)
|
||||
self.assertEqual(si.payment_schedule[0].discounted_amount, 23.6)
|
||||
|
||||
|
||||
def test_payment_against_purchase_invoice_to_check_status(self):
|
||||
pi = make_purchase_invoice(supplier="_Test Supplier USD", debit_to="_Test Payable USD - _TC",
|
||||
@ -591,6 +619,26 @@ def create_payment_terms_template():
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
def create_payment_terms_template_with_discount():
|
||||
|
||||
create_payment_term('30 Credit Days with 10% Discount')
|
||||
|
||||
if not frappe.db.exists('Payment Terms Template', 'Test Discount Template'):
|
||||
payment_term_template = frappe.get_doc({
|
||||
'doctype': 'Payment Terms Template',
|
||||
'template_name': 'Test Discount Template',
|
||||
'allocate_payment_based_on_payment_terms': 1,
|
||||
'terms': [{
|
||||
'doctype': 'Payment Terms Template Detail',
|
||||
'payment_term': '30 Credit Days with 10% Discount',
|
||||
'invoice_portion': 100,
|
||||
'credit_days_based_on': 'Day(s) after invoice date',
|
||||
'credit_days': 2,
|
||||
'discount': 10,
|
||||
'discount_validity_based_on': 'Day(s) after invoice date',
|
||||
'discount_validity': 1
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
def create_payment_term(name):
|
||||
if not frappe.db.exists('Payment Term', name):
|
||||
|
@ -58,7 +58,7 @@
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Total Amount",
|
||||
"label": "Grand Total",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@ -92,9 +92,10 @@
|
||||
"options": "Payment Term"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-13 12:07:19.362539",
|
||||
"modified": "2021-02-10 11:25:47.144392",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry Reference",
|
||||
|
@ -234,8 +234,9 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
});
|
||||
|
||||
if (invoices) {
|
||||
frappe.meta.get_docfield("Payment Reconciliation Payment", "invoice_number",
|
||||
me.frm.doc.name).options = "\n" + invoices.join("\n");
|
||||
this.frm.fields_dict.payments.grid.update_docfield_property(
|
||||
'invoice_number', 'options', "\n" + invoices.join("\n")
|
||||
);
|
||||
|
||||
$.each(me.frm.doc.payments || [], function(i, p) {
|
||||
if(!in_list(invoices, cstr(p.invoice_number))) p.invoice_number = null;
|
||||
|
@ -11,6 +11,7 @@ from erpnext.accounts.utils import (get_outstanding_invoices,
|
||||
from erpnext.controllers.accounts_controller import get_advance_payment_entries
|
||||
|
||||
class PaymentReconciliation(Document):
|
||||
@frappe.whitelist()
|
||||
def get_unreconciled_entries(self):
|
||||
self.get_nonreconciled_payment_entries()
|
||||
self.get_invoice_entries()
|
||||
@ -147,6 +148,7 @@ class PaymentReconciliation(Document):
|
||||
ent.currency = e.get('currency')
|
||||
ent.outstanding_amount = e.get('outstanding_amount')
|
||||
|
||||
@frappe.whitelist()
|
||||
def reconcile(self, args):
|
||||
for e in self.get('payments'):
|
||||
e.invoice_type = None
|
||||
@ -197,6 +199,7 @@ class PaymentReconciliation(Document):
|
||||
'difference_account': row.difference_account
|
||||
})
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_difference_amount(self, child_row):
|
||||
if child_row.get("reference_type") != 'Payment Entry': return
|
||||
|
||||
|
@ -6,12 +6,25 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"payment_term",
|
||||
"section_break_15",
|
||||
"description",
|
||||
"section_break_4",
|
||||
"due_date",
|
||||
"invoice_portion",
|
||||
"payment_amount",
|
||||
"mode_of_payment",
|
||||
"paid_amount"
|
||||
"column_break_5",
|
||||
"invoice_portion",
|
||||
"section_break_6",
|
||||
"discount_type",
|
||||
"discount_date",
|
||||
"column_break_9",
|
||||
"discount",
|
||||
"section_break_9",
|
||||
"payment_amount",
|
||||
"outstanding",
|
||||
"paid_amount",
|
||||
"discounted_amount",
|
||||
"column_break_3",
|
||||
"base_payment_amount"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -25,6 +38,7 @@
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fetch_from": "payment_term.description",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
@ -62,14 +76,90 @@
|
||||
"options": "Mode of Payment"
|
||||
},
|
||||
{
|
||||
"depends_on": "paid_amount",
|
||||
"fieldname": "paid_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Paid Amount"
|
||||
"label": "Paid Amount",
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "discounted_amount",
|
||||
"fieldname": "discounted_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Discounted Amount",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "payment_amount",
|
||||
"fieldname": "outstanding",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Outstanding",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_5",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "discount",
|
||||
"fieldname": "discount_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Discount Date",
|
||||
"mandatory_depends_on": "discount"
|
||||
},
|
||||
{
|
||||
"default": "Percentage",
|
||||
"fetch_from": "payment_term.discount_type",
|
||||
"fieldname": "discount_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Discount Type",
|
||||
"options": "Percentage\nAmount"
|
||||
},
|
||||
{
|
||||
"fetch_from": "payment_term.discount",
|
||||
"fieldname": "discount",
|
||||
"fieldtype": "Float",
|
||||
"label": "Discount"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_9",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_15",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"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": "2020-03-13 17:58:24.729526",
|
||||
"modified": "2021-04-28 05:41:35.084233",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Schedule",
|
||||
|
@ -1,2 +1,22 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.ui.form.on('Payment Term', {
|
||||
onload(frm) {
|
||||
frm.trigger('set_dynamic_description');
|
||||
},
|
||||
discount(frm) {
|
||||
frm.trigger('set_dynamic_description');
|
||||
},
|
||||
discount_type(frm) {
|
||||
frm.trigger('set_dynamic_description');
|
||||
},
|
||||
set_dynamic_description(frm) {
|
||||
if (frm.doc.discount) {
|
||||
let description = __("{0}% of total invoice value will be given as discount.", [frm.doc.discount]);
|
||||
if (frm.doc.discount_type == 'Amount') {
|
||||
description = __("{0} will be given as discount.", [fmt_money(frm.doc.discount)]);
|
||||
}
|
||||
frm.set_df_property("discount", "description", description);
|
||||
}
|
||||
}
|
||||
});
|
@ -1,386 +1,166 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:payment_term_name",
|
||||
"beta": 0,
|
||||
"creation": "2017-08-10 15:24:54.876365",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:payment_term_name",
|
||||
"creation": "2017-08-10 15:24:54.876365",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"payment_term_name",
|
||||
"invoice_portion",
|
||||
"mode_of_payment",
|
||||
"column_break_3",
|
||||
"due_date_based_on",
|
||||
"credit_days",
|
||||
"credit_months",
|
||||
"section_break_8",
|
||||
"discount_type",
|
||||
"discount",
|
||||
"column_break_11",
|
||||
"discount_validity_based_on",
|
||||
"discount_validity",
|
||||
"section_break_6",
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "payment_term_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Payment Term Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"bold": 1,
|
||||
"fieldname": "payment_term_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Payment Term Name",
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"description": "Provide the invoice portion in percent",
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "invoice_portion",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Invoice Portion",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"bold": 1,
|
||||
"fieldname": "invoice_portion",
|
||||
"fieldtype": "Float",
|
||||
"label": "Invoice Portion (%)"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Mode of Payment",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Mode of Payment",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"label": "Mode of Payment",
|
||||
"options": "Mode of Payment"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "due_date_based_on",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Due Date Based On",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"bold": 1,
|
||||
"fieldname": "due_date_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Due Date Based On",
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
|
||||
},
|
||||
{
|
||||
"description": "Give number of days according to prior selection",
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
|
||||
"fieldname": "credit_days",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Credit Days",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"bold": 1,
|
||||
"depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
|
||||
"fieldname": "credit_days",
|
||||
"fieldtype": "Int",
|
||||
"label": "Credit Days"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
|
||||
"fieldname": "credit_months",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Credit Months",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
|
||||
"fieldname": "credit_months",
|
||||
"fieldtype": "Int",
|
||||
"label": "Credit Months"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Description",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"bold": 1,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Discount Settings"
|
||||
},
|
||||
{
|
||||
"default": "Percentage",
|
||||
"fieldname": "discount_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Discount Type",
|
||||
"options": "Percentage\nAmount"
|
||||
},
|
||||
{
|
||||
"fieldname": "discount",
|
||||
"fieldtype": "Float",
|
||||
"label": "Discount"
|
||||
},
|
||||
{
|
||||
"default": "Day(s) after invoice date",
|
||||
"depends_on": "discount",
|
||||
"fieldname": "discount_validity_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Discount Validity Based On",
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
|
||||
},
|
||||
{
|
||||
"depends_on": "discount",
|
||||
"fieldname": "discount_validity",
|
||||
"fieldtype": "Int",
|
||||
"label": "Discount Validity",
|
||||
"mandatory_depends_on": "discount"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2020-10-14 10:47:32.830478",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Term",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2021-02-15 20:30:56.256403",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Term",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -3,11 +3,6 @@
|
||||
|
||||
frappe.ui.form.on('Payment Terms Template', {
|
||||
setup: function(frm) {
|
||||
frm.add_fetch("payment_term", "description", "description");
|
||||
frm.add_fetch("payment_term", "invoice_portion", "invoice_portion");
|
||||
frm.add_fetch("payment_term", "due_date_based_on", "due_date_based_on");
|
||||
frm.add_fetch("payment_term", "credit_days", "credit_days");
|
||||
frm.add_fetch("payment_term", "credit_months", "credit_months");
|
||||
frm.add_fetch("payment_term", "mode_of_payment", "mode_of_payment");
|
||||
|
||||
}
|
||||
});
|
||||
|
@ -13,7 +13,6 @@ from frappe import _
|
||||
class PaymentTermsTemplate(Document):
|
||||
def validate(self):
|
||||
self.validate_invoice_portion()
|
||||
self.validate_credit_days()
|
||||
self.check_duplicate_terms()
|
||||
|
||||
def validate_invoice_portion(self):
|
||||
@ -24,11 +23,6 @@ class PaymentTermsTemplate(Document):
|
||||
if flt(total_portion, 2) != 100.00:
|
||||
frappe.msgprint(_('Combined invoice portion must equal 100%'), raise_exception=1, indicator='red')
|
||||
|
||||
def validate_credit_days(self):
|
||||
for term in self.terms:
|
||||
if cint(term.credit_days) < 0:
|
||||
frappe.msgprint(_('Credit Days cannot be a negative number'), raise_exception=1, indicator='red')
|
||||
|
||||
def check_duplicate_terms(self):
|
||||
terms = []
|
||||
for term in self.terms:
|
||||
|
@ -1,278 +1,164 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "",
|
||||
"beta": 0,
|
||||
"creation": "2017-08-10 15:34:09.409562",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"creation": "2017-08-10 15:34:09.409562",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"payment_term",
|
||||
"section_break_13",
|
||||
"description",
|
||||
"section_break_4",
|
||||
"invoice_portion",
|
||||
"mode_of_payment",
|
||||
"column_break_3",
|
||||
"due_date_based_on",
|
||||
"credit_days",
|
||||
"credit_months",
|
||||
"section_break_8",
|
||||
"discount_type",
|
||||
"discount",
|
||||
"column_break_11",
|
||||
"discount_validity_based_on",
|
||||
"discount_validity"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "payment_term",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Payment Term",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Payment Term",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "payment_term",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Payment Term",
|
||||
"options": "Payment Term"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Description",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fetch_from": "payment_term.description",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"default": "0",
|
||||
"fieldname": "invoice_portion",
|
||||
"fieldtype": "Percent",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Invoice Portion",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fetch_from": "payment_term.invoice_portion",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "invoice_portion",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Invoice Portion (%)",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "due_date_based_on",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Due Date Based On",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fetch_from": "payment_term.due_date_based_on",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "due_date_based_on",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Due Date Based On",
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"default": "0",
|
||||
"depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
|
||||
"fieldname": "credit_days",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Credit Days",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"default": "0",
|
||||
"depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
|
||||
"fetch_from": "payment_term.credit_days",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "credit_days",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Credit Days",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
|
||||
"fieldname": "credit_months",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Credit Months",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
|
||||
"fetch_from": "payment_term.credit_months",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "credit_months",
|
||||
"fieldtype": "Int",
|
||||
"label": "Credit Months",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Mode of Payment",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Mode of Payment",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fetch_from": "payment_term.mode_of_payment",
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"label": "Mode of Payment",
|
||||
"options": "Mode of Payment"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Discount Settings"
|
||||
},
|
||||
{
|
||||
"default": "Percentage",
|
||||
"fetch_from": "payment_term.discount_type",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "discount_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Discount Type",
|
||||
"options": "Percentage\nAmount"
|
||||
},
|
||||
{
|
||||
"fetch_from": "payment_term.discount",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "discount",
|
||||
"fieldtype": "Float",
|
||||
"label": "Discount"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Day(s) after invoice date",
|
||||
"depends_on": "discount",
|
||||
"fetch_from": "payment_term.discount_validity_based_on",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "discount_validity_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Discount Validity Based On",
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_13",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"depends_on": "discount",
|
||||
"fetch_from": "payment_term.discount_validity",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "discount_validity",
|
||||
"fieldtype": "Int",
|
||||
"label": "Discount Validity",
|
||||
"mandatory_depends_on": "discount"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"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": "2018-08-21 16:15:55.143025",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Terms Template Detail",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-24 11:56:12.410807",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Terms Template Detail",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -102,14 +102,14 @@ class PeriodClosingVoucher(AccountsController):
|
||||
make_gl_entries(gl_entries)
|
||||
|
||||
def get_pl_balances(self, dimension_fields):
|
||||
"""Get balance for pl accounts"""
|
||||
"""Get balance for Profit and Loss accounts, only including valid transactions (not cancelled)"""
|
||||
return frappe.db.sql("""
|
||||
select
|
||||
t1.account, t2.account_currency, {dimension_fields},
|
||||
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as balance_in_account_currency,
|
||||
sum(t1.debit) - sum(t1.credit) as balance_in_company_currency
|
||||
from `tabGL Entry` t1, `tabAccount` t2
|
||||
where t1.account = t2.name and t2.report_type = 'Profit and Loss'
|
||||
where t1.is_cancelled = 0 and t1.account = t2.name and t2.report_type = 'Profit and Loss'
|
||||
and t2.docstatus < 2 and t2.company = %s
|
||||
and t1.posting_date between %s and %s
|
||||
group by t1.account, {dimension_fields}
|
||||
|
@ -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,33 +16,13 @@ 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:
|
||||
invalid_row = {'idx': d.idx}
|
||||
pos_invoice = frappe.db.get_values("POS Invoice", d.pos_invoice,
|
||||
pos_invoice = frappe.db.get_values("POS Invoice", d.pos_invoice,
|
||||
["consolidated_invoice", "pos_profile", "docstatus", "owner"], as_dict=1)[0]
|
||||
if pos_invoice.consolidated_invoice:
|
||||
invalid_row.setdefault('msg', []).append(_('POS Invoice is {}').format(frappe.bold("already consolidated")))
|
||||
@ -68,17 +48,22 @@ class POSClosingEntry(StatusUpdater):
|
||||
|
||||
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_reconciliation_details(self):
|
||||
currency = frappe.get_cached_value('Company', self.company, "default_currency")
|
||||
return frappe.render_template("erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html",
|
||||
{"data": self, "currency": currency})
|
||||
|
||||
|
||||
def on_submit(self):
|
||||
consolidate_pos_invoices(closing_entry=self)
|
||||
|
||||
|
||||
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
|
||||
@ -88,8 +73,8 @@ class POSClosingEntry(StatusUpdater):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
|
||||
cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'])
|
||||
return [c['user'] for c in cashiers_list]
|
||||
cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'], as_list=1)
|
||||
return [c for c in cashiers_list]
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_pos_invoices(start, end, pos_profile, user):
|
||||
|
@ -8,6 +8,7 @@ frappe.listview_settings['POS Closing Entry'] = {
|
||||
"Draft": "red",
|
||||
"Submitted": "blue",
|
||||
"Queued": "orange",
|
||||
"Failed": "red",
|
||||
"Cancelled": "red"
|
||||
|
||||
};
|
||||
|
@ -5,12 +5,21 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import unittest
|
||||
from frappe.utils import nowdate
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
|
||||
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import make_closing_entry_from_opening
|
||||
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
|
||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||
|
||||
class TestPOSClosingEntry(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Make stock available for POS Sales
|
||||
make_stock_entry(target="_Test Warehouse - _TC", qty=2, basic_rate=100)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
def test_pos_closing_entry(self):
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
@ -41,9 +50,6 @@ class TestPOSClosingEntry(unittest.TestCase):
|
||||
self.assertEqual(pcv_doc.total_quantity, 2)
|
||||
self.assertEqual(pcv_doc.net_total, 6700)
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
def test_cancelling_of_pos_closing_entry(self):
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
@ -84,8 +90,6 @@ class TestPOSClosingEntry(unittest.TestCase):
|
||||
self.assertEqual(si_doc.docstatus, 2)
|
||||
self.assertEqual(pos_inv1.status, 'Paid')
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
def init_user_and_profile(**args):
|
||||
user = 'test@example.com'
|
||||
@ -103,4 +107,4 @@ def init_user_and_profile(**args):
|
||||
|
||||
pos_profile.save()
|
||||
|
||||
return test_user, pos_profile
|
||||
return test_user, pos_profile
|
||||
|
@ -57,7 +57,7 @@ class POSInvoice(SalesInvoice):
|
||||
self.apply_loyalty_points()
|
||||
self.check_phone_payments()
|
||||
self.set_status(update=True)
|
||||
|
||||
|
||||
def before_cancel(self):
|
||||
if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1:
|
||||
pos_closing_entry = frappe.get_all(
|
||||
@ -96,31 +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
|
||||
@ -128,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 = []
|
||||
@ -203,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:
|
||||
@ -221,7 +230,7 @@ class POSInvoice(SalesInvoice):
|
||||
base_grand_total = flt(self.base_rounded_total) or flt(self.base_grand_total)
|
||||
if not flt(self.change_amount) and grand_total < flt(self.paid_amount):
|
||||
self.change_amount = flt(self.paid_amount - grand_total + flt(self.write_off_amount))
|
||||
self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount))
|
||||
self.base_change_amount = flt(self.base_paid_amount) - base_grand_total + flt(self.base_write_off_amount)
|
||||
|
||||
if flt(self.change_amount) and not self.account_for_change_amount:
|
||||
frappe.msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
|
||||
@ -355,6 +364,7 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
return profile
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_missing_values(self, for_validate=False):
|
||||
profile = self.set_pos_fields(for_validate)
|
||||
|
||||
@ -377,12 +387,20 @@ class POSInvoice(SalesInvoice):
|
||||
"allow_print_before_pay": profile.get("allow_print_before_pay")
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def reset_mode_of_payments(self):
|
||||
if self.pos_profile:
|
||||
pos_profile = frappe.get_cached_doc('POS Profile', self.pos_profile)
|
||||
update_multi_mode_option(self, pos_profile)
|
||||
self.paid_amount = 0
|
||||
|
||||
def set_account_for_mode_of_payment(self):
|
||||
self.payments = [d for d in self.payments if d.amount or d.base_amount or d.default]
|
||||
for pay in self.payments:
|
||||
if not pay.account:
|
||||
pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_payment_request(self):
|
||||
for pay in self.payments:
|
||||
if pay.type == "Phone":
|
||||
@ -400,7 +418,7 @@ class POSInvoice(SalesInvoice):
|
||||
pay_req.request_phone_payment()
|
||||
|
||||
return pay_req
|
||||
|
||||
|
||||
def get_new_payment_request(self, mop):
|
||||
payment_gateway_account = frappe.db.get_value("Payment Gateway Account", {
|
||||
"payment_account": mop.account,
|
||||
|
@ -10,8 +10,14 @@ 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):
|
||||
if frappe.session.user != "Administrator":
|
||||
frappe.set_user("Administrator")
|
||||
@ -316,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
|
||||
|
@ -12,8 +12,8 @@ from frappe.utils.background_jobs import enqueue
|
||||
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
|
||||
|
||||
from six import iteritems
|
||||
import json
|
||||
import six
|
||||
|
||||
class POSInvoiceMergeLog(Document):
|
||||
def validate(self):
|
||||
@ -78,8 +78,11 @@ class POSInvoiceMergeLog(Document):
|
||||
sales_invoice = self.merge_pos_invoice_into(sales_invoice, data)
|
||||
|
||||
sales_invoice.is_consolidated = 1
|
||||
sales_invoice.set_posting_time = 1
|
||||
sales_invoice.posting_date = getdate(self.posting_date)
|
||||
sales_invoice.save()
|
||||
sales_invoice.submit()
|
||||
|
||||
self.consolidated_invoice = sales_invoice.name
|
||||
|
||||
return sales_invoice.name
|
||||
@ -91,10 +94,13 @@ class POSInvoiceMergeLog(Document):
|
||||
credit_note = self.merge_pos_invoice_into(credit_note, data)
|
||||
|
||||
credit_note.is_consolidated = 1
|
||||
credit_note.set_posting_time = 1
|
||||
credit_note.posting_date = getdate(self.posting_date)
|
||||
# TODO: return could be against multiple sales invoice which could also have been consolidated?
|
||||
# credit_note.return_against = self.consolidated_invoice
|
||||
credit_note.save()
|
||||
credit_note.submit()
|
||||
|
||||
self.consolidated_credit_note = credit_note.name
|
||||
|
||||
return credit_note.name
|
||||
@ -131,12 +137,14 @@ class POSInvoiceMergeLog(Document):
|
||||
if t.account_head == tax.account_head and t.cost_center == tax.cost_center:
|
||||
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount)
|
||||
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount)
|
||||
update_item_wise_tax_detail(t, tax)
|
||||
found = True
|
||||
if not found:
|
||||
tax.charge_type = 'Actual'
|
||||
tax.included_in_print_rate = 0
|
||||
tax.tax_amount = tax.tax_amount_after_discount_amount
|
||||
tax.base_tax_amount = tax.base_tax_amount_after_discount_amount
|
||||
tax.item_wise_tax_detail = tax.item_wise_tax_detail
|
||||
taxes.append(tax)
|
||||
|
||||
for payment in doc.get('payments'):
|
||||
@ -168,11 +176,9 @@ class POSInvoiceMergeLog(Document):
|
||||
sales_invoice = frappe.new_doc('Sales Invoice')
|
||||
sales_invoice.customer = self.customer
|
||||
sales_invoice.is_pos = 1
|
||||
# date can be pos closing date?
|
||||
sales_invoice.posting_date = getdate(nowdate())
|
||||
|
||||
return sales_invoice
|
||||
|
||||
|
||||
def update_pos_invoices(self, invoice_docs, sales_invoice='', credit_note=''):
|
||||
for doc in invoice_docs:
|
||||
doc.load_from_db()
|
||||
@ -187,6 +193,26 @@ class POSInvoiceMergeLog(Document):
|
||||
si.flags.ignore_validate = True
|
||||
si.cancel()
|
||||
|
||||
def update_item_wise_tax_detail(consolidate_tax_row, tax_row):
|
||||
consolidated_tax_detail = json.loads(consolidate_tax_row.item_wise_tax_detail)
|
||||
tax_row_detail = json.loads(tax_row.item_wise_tax_detail)
|
||||
|
||||
if not consolidated_tax_detail:
|
||||
consolidated_tax_detail = {}
|
||||
|
||||
for item_code, tax_data in tax_row_detail.items():
|
||||
if consolidated_tax_detail.get(item_code):
|
||||
consolidated_tax_data = consolidated_tax_detail.get(item_code)
|
||||
consolidated_tax_detail.update({
|
||||
item_code: [consolidated_tax_data[0], consolidated_tax_data[1] + tax_data[1]]
|
||||
})
|
||||
else:
|
||||
consolidated_tax_detail.update({
|
||||
item_code: [tax_data[0], tax_data[1]]
|
||||
})
|
||||
|
||||
consolidate_tax_row.item_wise_tax_detail = json.dumps(consolidated_tax_detail, separators=(',', ':'))
|
||||
|
||||
def get_all_unconsolidated_invoices():
|
||||
filters = {
|
||||
'consolidated_invoice': [ 'in', [ '', None ]],
|
||||
@ -208,13 +234,13 @@ 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, closing_entry)
|
||||
enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry)
|
||||
else:
|
||||
create_merge_logs(invoice_by_customer, closing_entry)
|
||||
|
||||
@ -225,50 +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, closing_entry)
|
||||
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(nowdate())
|
||||
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()
|
||||
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status='Submitted')
|
||||
closing_entry.update_opening_entry()
|
||||
merge_log.set('pos_invoices', invoices)
|
||||
merge_log.save(ignore_permissions=True)
|
||||
merge_log.submit()
|
||||
|
||||
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()
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status='Submitted')
|
||||
closing_entry.db_set('error_message', '')
|
||||
closing_entry.update_opening_entry()
|
||||
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status='Cancelled')
|
||||
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)
|
||||
|
||||
def enqueue_job(job, invoice_by_customer, closing_entry):
|
||||
if closing_entry:
|
||||
closing_entry.set_status(update=True, status='Failed')
|
||||
closing_entry.db_set('error_message', error_message)
|
||||
raise
|
||||
|
||||
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,
|
||||
now=frappe.conf.developer_mode or frappe.flags.in_test
|
||||
)
|
||||
|
||||
@ -286,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
|
@ -5,6 +5,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest
|
||||
import json
|
||||
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
|
||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
|
||||
@ -14,85 +15,136 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
|
||||
def test_consolidated_invoice_creation(self):
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
try:
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
|
||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||
pos_inv.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||
})
|
||||
pos_inv.submit()
|
||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||
pos_inv.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||
})
|
||||
pos_inv.submit()
|
||||
|
||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||
pos_inv2.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
|
||||
})
|
||||
pos_inv2.submit()
|
||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||
pos_inv2.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
|
||||
})
|
||||
pos_inv2.submit()
|
||||
|
||||
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
||||
pos_inv3.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
|
||||
})
|
||||
pos_inv3.submit()
|
||||
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
||||
pos_inv3.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
|
||||
})
|
||||
pos_inv3.submit()
|
||||
|
||||
consolidate_pos_invoices()
|
||||
consolidate_pos_invoices()
|
||||
|
||||
pos_inv.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||
pos_inv.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||
|
||||
pos_inv3.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
||||
pos_inv3.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
||||
|
||||
self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
|
||||
self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
|
||||
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
def test_consolidated_credit_note_creation(self):
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
try:
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
|
||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||
pos_inv.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||
})
|
||||
pos_inv.submit()
|
||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||
pos_inv.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||
})
|
||||
pos_inv.submit()
|
||||
|
||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||
pos_inv2.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
|
||||
})
|
||||
pos_inv2.submit()
|
||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||
pos_inv2.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
|
||||
})
|
||||
pos_inv2.submit()
|
||||
|
||||
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
||||
pos_inv3.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
|
||||
})
|
||||
pos_inv3.submit()
|
||||
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
||||
pos_inv3.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
|
||||
})
|
||||
pos_inv3.submit()
|
||||
|
||||
pos_inv_cn = make_sales_return(pos_inv.name)
|
||||
pos_inv_cn.set("payments", [])
|
||||
pos_inv_cn.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300
|
||||
})
|
||||
pos_inv_cn.paid_amount = -300
|
||||
pos_inv_cn.submit()
|
||||
pos_inv_cn = make_sales_return(pos_inv.name)
|
||||
pos_inv_cn.set("payments", [])
|
||||
pos_inv_cn.append('payments', {
|
||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300
|
||||
})
|
||||
pos_inv_cn.paid_amount = -300
|
||||
pos_inv_cn.submit()
|
||||
|
||||
consolidate_pos_invoices()
|
||||
consolidate_pos_invoices()
|
||||
|
||||
pos_inv.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||
pos_inv.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||
|
||||
pos_inv3.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
||||
pos_inv3.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
||||
|
||||
pos_inv_cn.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
|
||||
self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return"))
|
||||
pos_inv_cn.load_from_db()
|
||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
|
||||
self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return"))
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
def test_consolidated_invoice_item_taxes(self):
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
try:
|
||||
inv = create_pos_invoice(qty=1, rate=100, do_not_save=True)
|
||||
|
||||
inv.append("taxes", {
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 9
|
||||
})
|
||||
inv.insert()
|
||||
inv.submit()
|
||||
|
||||
inv2 = create_pos_invoice(qty=1, rate=100, do_not_save=True)
|
||||
inv2.get('items')[0].item_code = '_Test Item 2'
|
||||
inv2.append("taxes", {
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"charge_type": "On Net Total",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"rate": 5
|
||||
})
|
||||
inv2.insert()
|
||||
inv2.submit()
|
||||
|
||||
consolidate_pos_invoices()
|
||||
inv.load_from_db()
|
||||
|
||||
consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
|
||||
item_wise_tax_detail = json.loads(consolidated_invoice.get('taxes')[0].item_wise_tax_detail)
|
||||
|
||||
tax_rate, amount = item_wise_tax_detail.get('_Test Item')
|
||||
self.assertEqual(tax_rate, 9)
|
||||
self.assertEqual(amount, 9)
|
||||
|
||||
tax_rate2, amount2 = item_wise_tax_detail.get('_Test Item 2')
|
||||
self.assertEqual(tax_rate2, 5)
|
||||
self.assertEqual(amount2, 5)
|
||||
finally:
|
||||
frappe.set_user("Administrator")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||
|
||||
|
@ -62,14 +62,15 @@ class POSProfile(Document):
|
||||
|
||||
if len(default_mode) > 1:
|
||||
frappe.throw(_("You can only select one mode of payment as default"))
|
||||
|
||||
|
||||
invalid_modes = []
|
||||
for d in self.payments:
|
||||
account = frappe.db.get_value(
|
||||
"Mode of Payment Account",
|
||||
"Mode of Payment Account",
|
||||
{"parent": d.mode_of_payment, "company": self.company},
|
||||
"default_account"
|
||||
)
|
||||
|
||||
if not account:
|
||||
invalid_modes.append(get_link_to_form("Mode of Payment", d.mode_of_payment))
|
||||
|
||||
|
@ -92,11 +92,21 @@ def make_pos_profile(**args):
|
||||
"write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC"
|
||||
})
|
||||
|
||||
payments = [{
|
||||
mode_of_payment = frappe.get_doc("Mode of Payment", "Cash")
|
||||
company = args.company or "_Test Company"
|
||||
default_account = args.income_account or "Sales - _TC"
|
||||
|
||||
if not frappe.db.get_value("Mode of Payment Account", {"company": company, "parent": "Cash"}):
|
||||
mode_of_payment.append("accounts", {
|
||||
"company": company,
|
||||
"default_account": default_account
|
||||
})
|
||||
mode_of_payment.save()
|
||||
|
||||
pos_profile.append("payments", {
|
||||
'mode_of_payment': 'Cash',
|
||||
'default': 1
|
||||
}]
|
||||
pos_profile.set("payments", payments)
|
||||
})
|
||||
|
||||
if not frappe.db.exists("POS Profile", args.name or "_Test POS Profile"):
|
||||
pos_profile.insert()
|
||||
|
@ -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) {
|
||||
@ -16,8 +24,43 @@ frappe.ui.form.on('POS Settings', {
|
||||
}
|
||||
});
|
||||
|
||||
frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""].concat(fields);
|
||||
frm.fields_dict.invoice_fields.grid.update_docfield_property(
|
||||
'fieldname', 'options', [""].concat(fields)
|
||||
);
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
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",
|
||||
|
@ -44,6 +44,14 @@
|
||||
"column_break_21",
|
||||
"min_amt",
|
||||
"max_amt",
|
||||
"product_discount_scheme_section",
|
||||
"same_item",
|
||||
"free_item",
|
||||
"free_qty",
|
||||
"free_item_rate",
|
||||
"column_break_42",
|
||||
"free_item_uom",
|
||||
"is_recursive",
|
||||
"section_break_23",
|
||||
"valid_from",
|
||||
"valid_upto",
|
||||
@ -62,13 +70,6 @@
|
||||
"discount_amount",
|
||||
"discount_percentage",
|
||||
"for_price_list",
|
||||
"product_discount_scheme_section",
|
||||
"same_item",
|
||||
"free_item",
|
||||
"free_qty",
|
||||
"column_break_51",
|
||||
"free_item_uom",
|
||||
"free_item_rate",
|
||||
"section_break_13",
|
||||
"threshold_percentage",
|
||||
"priority",
|
||||
@ -458,10 +459,6 @@
|
||||
"fieldtype": "Float",
|
||||
"label": "Qty"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_51",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "free_item_uom",
|
||||
"fieldtype": "Link",
|
||||
@ -552,19 +549,33 @@
|
||||
"fieldname": "promotional_scheme",
|
||||
"fieldtype": "Link",
|
||||
"label": "Promotional Scheme",
|
||||
"options": "Promotional Scheme"
|
||||
"no_copy": 1,
|
||||
"options": "Promotional Scheme",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"description": "Simple Python Expression, Example: territory != 'All Territories'",
|
||||
"fieldname": "condition",
|
||||
"fieldtype": "Code",
|
||||
"label": "Condition"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_42",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on",
|
||||
"fieldname": "is_recursive",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Recursive"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-01 23:18:38.717613",
|
||||
"modified": "2021-03-06 22:01:24.840422",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule",
|
||||
|
@ -237,6 +237,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
||||
"doctype": args.doctype,
|
||||
"has_margin": False,
|
||||
"name": args.name,
|
||||
"free_item_data": [],
|
||||
"parent": args.parent,
|
||||
"parenttype": args.parenttype,
|
||||
"child_docname": args.get('child_docname')
|
||||
|
@ -328,6 +328,21 @@ class TestPricingRule(unittest.TestCase):
|
||||
self.assertEquals(item.discount_amount, 110)
|
||||
self.assertEquals(item.rate, 990)
|
||||
|
||||
def test_pricing_rule_with_margin_and_discount_amount(self):
|
||||
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
|
||||
make_pricing_rule(selling=1, margin_type="Percentage", margin_rate_or_amount=10,
|
||||
rate_or_discount="Discount Amount", discount_amount=110)
|
||||
si = create_sales_invoice(do_not_save=True)
|
||||
si.items[0].price_list_rate = 1000
|
||||
si.payment_schedule = []
|
||||
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)
|
||||
|
||||
def test_pricing_rule_for_product_discount_on_same_item(self):
|
||||
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
|
||||
test_record = {
|
||||
@ -560,6 +575,7 @@ def make_pricing_rule(**args):
|
||||
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
|
||||
"condition": args.condition or '',
|
||||
"priority": 1,
|
||||
"discount_amount": args.discount_amount or 0.0,
|
||||
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
|
||||
})
|
||||
|
||||
|
@ -173,7 +173,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
|
||||
if parenttype in ["Customer Group", "Item Group", "Territory"]:
|
||||
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
|
||||
root_name = frappe.db.get_list(parenttype,
|
||||
{"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1)
|
||||
{"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1, ignore_permissions=True)
|
||||
|
||||
if root_name and root_name[0][0]:
|
||||
parent_groups.append(root_name[0][0])
|
||||
@ -367,7 +367,7 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args):
|
||||
|
||||
if items and doc.get("items"):
|
||||
for row in doc.get('items'):
|
||||
if row.get(apply_on) not in items: continue
|
||||
if (row.get(apply_on) or args.get(apply_on)) not in items: continue
|
||||
|
||||
if pr_doc.mixed_conditions:
|
||||
amt = args.get('qty') * args.get("price_list_rate")
|
||||
@ -471,7 +471,7 @@ def apply_pricing_rule_on_transaction(doc):
|
||||
|
||||
if not d.get(pr_field): continue
|
||||
|
||||
if d.validate_applied_rule and doc.get(field) < d.get(pr_field):
|
||||
if d.validate_applied_rule and doc.get(field) is not None and doc.get(field) < d.get(pr_field):
|
||||
frappe.msgprint(_("User has not applied rule on the invoice {0}")
|
||||
.format(doc.name))
|
||||
else:
|
||||
@ -479,7 +479,7 @@ def apply_pricing_rule_on_transaction(doc):
|
||||
|
||||
doc.calculate_taxes_and_totals()
|
||||
elif d.price_or_product_discount == 'Product':
|
||||
item_details = frappe._dict({'parenttype': doc.doctype})
|
||||
item_details = frappe._dict({'parenttype': doc.doctype, 'free_item_data': []})
|
||||
get_product_discount_rule(d, item_details, doc=doc)
|
||||
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
|
||||
doc.set_missing_values()
|
||||
@ -508,9 +508,16 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
||||
frappe.throw(_("Free item not set in the pricing rule {0}")
|
||||
.format(get_link_to_form("Pricing Rule", pricing_rule.name)))
|
||||
|
||||
item_details.free_item_data = {
|
||||
qty = pricing_rule.free_qty or 1
|
||||
if pricing_rule.is_recursive:
|
||||
transaction_qty = args.get('qty') if args else doc.total_qty
|
||||
if transaction_qty:
|
||||
qty = flt(transaction_qty) * qty
|
||||
|
||||
free_item_data_args = {
|
||||
'item_code': free_item,
|
||||
'qty': pricing_rule.free_qty or 1,
|
||||
'qty': qty,
|
||||
'pricing_rules': pricing_rule.name,
|
||||
'rate': pricing_rule.free_item_rate or 0,
|
||||
'price_list_rate': pricing_rule.free_item_rate or 0,
|
||||
'is_free_item': 1
|
||||
@ -519,24 +526,26 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
||||
item_data = frappe.get_cached_value('Item', free_item, ['item_name',
|
||||
'description', 'stock_uom'], as_dict=1)
|
||||
|
||||
item_details.free_item_data.update(item_data)
|
||||
item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom
|
||||
item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item,
|
||||
item_details.free_item_data['uom']).get("conversion_factor", 1)
|
||||
free_item_data_args.update(item_data)
|
||||
free_item_data_args['uom'] = pricing_rule.free_item_uom or item_data.stock_uom
|
||||
free_item_data_args['conversion_factor'] = get_conversion_factor(free_item,
|
||||
free_item_data_args['uom']).get("conversion_factor", 1)
|
||||
|
||||
if item_details.get("parenttype") == 'Purchase Order':
|
||||
item_details.free_item_data['schedule_date'] = doc.schedule_date if doc else today()
|
||||
free_item_data_args['schedule_date'] = doc.schedule_date if doc else today()
|
||||
|
||||
if item_details.get("parenttype") == 'Sales Order':
|
||||
item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today()
|
||||
free_item_data_args['delivery_date'] = doc.delivery_date if doc else today()
|
||||
|
||||
item_details.free_item_data.append(free_item_data_args)
|
||||
|
||||
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
|
||||
if pricing_rule_args.get('item_code'):
|
||||
items = [d.item_code for d in doc.items
|
||||
if d.item_code == (pricing_rule_args.get("item_code")) and d.is_free_item]
|
||||
if pricing_rule_args:
|
||||
items = tuple([(d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item])
|
||||
|
||||
if not items:
|
||||
doc.append('items', pricing_rule_args)
|
||||
for args in pricing_rule_args:
|
||||
if not items or (args.get('item_code'), args.get('pricing_rules')) not in items:
|
||||
doc.append('items', args)
|
||||
|
||||
def get_pricing_rule_items(pr_doc):
|
||||
apply_on_data = []
|
||||
|
@ -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>
|
||||
@ -38,30 +38,30 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}</td>
|
||||
{{ frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }}</td>
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}</td>
|
||||
{{ frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }}</td>
|
||||
{% else %}
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td><b>{{ frappe.format(row.account, {fieldtype: "Link"}) or " " }}</b></td>
|
||||
<td style="text-align: right">
|
||||
{{ row.account and frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}
|
||||
{{ row.account and frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }}
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{{ row.account and frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}
|
||||
{{ row.account and frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }}
|
||||
</td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(row.balance, filters.presentation_currency) }}
|
||||
{{ frappe.utils.fmt_money(row.balance, currency=filters.presentation_currency) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</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>
|
||||
|
@ -92,7 +92,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
||||
frm.refresh_field('customers');
|
||||
}
|
||||
else{
|
||||
frappe.msgprint('No Customers found with selected options.');
|
||||
frappe.throw('No Customers found with selected options.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
@ -126,9 +135,11 @@ def get_customers_based_on_sales_person(sales_person):
|
||||
sales_person_records = frappe._dict()
|
||||
for d in records:
|
||||
sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
|
||||
customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
|
||||
if sales_person_records.get('Customer'):
|
||||
return frappe.get_list('Customer', fields=['name', 'email_id'], \
|
||||
filters=[['name', 'in', list(sales_person_records['Customer'])]])
|
||||
return customers
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_recipients_and_cc(customer, doc):
|
||||
recipients = []
|
||||
@ -165,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'], \
|
||||
@ -197,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 ''
|
||||
|
@ -9,19 +9,19 @@ from frappe.utils import cstr
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe.model.document import Document
|
||||
|
||||
pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group'
|
||||
pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group',
|
||||
'apply_rule_on_other', 'other_brand', 'selling', 'buying', 'applicable_for', 'valid_from',
|
||||
'valid_upto', 'customer', 'customer_group', 'territory', 'sales_partner', 'campaign', 'supplier',
|
||||
'supplier_group', 'company', 'currency']
|
||||
'supplier_group', 'company', 'currency', 'apply_multiple_pricing_rules']
|
||||
|
||||
other_fields = ['min_qty', 'max_qty', 'min_amt',
|
||||
'max_amt', 'priority','warehouse', 'threshold_percentage', 'rule_description']
|
||||
|
||||
price_discount_fields = ['rate_or_discount', 'apply_discount_on', 'apply_discount_on_rate',
|
||||
'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule']
|
||||
'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule', 'apply_multiple_pricing_rules']
|
||||
|
||||
product_discount_fields = ['free_item', 'free_qty', 'free_item_uom',
|
||||
'free_item_rate', 'same_item']
|
||||
'free_item_rate', 'same_item', 'is_recursive', 'apply_multiple_pricing_rules']
|
||||
|
||||
class PromotionalScheme(Document):
|
||||
def validate(self):
|
||||
@ -111,4 +111,4 @@ def get_args_for_pricing_rule(doc):
|
||||
for d in pricing_rule_fields:
|
||||
args[d] = doc.get(d)
|
||||
|
||||
return args
|
||||
return args
|
||||
|
@ -1,792 +1,181 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"actions": [],
|
||||
"creation": "2019-03-24 14:48:59.649168",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"disable",
|
||||
"apply_multiple_pricing_rules",
|
||||
"column_break_2",
|
||||
"rule_description",
|
||||
"section_break_2",
|
||||
"min_qty",
|
||||
"max_qty",
|
||||
"column_break_3",
|
||||
"min_amount",
|
||||
"max_amount",
|
||||
"section_break_6",
|
||||
"rate_or_discount",
|
||||
"column_break_10",
|
||||
"rate",
|
||||
"discount_amount",
|
||||
"discount_percentage",
|
||||
"section_break_11",
|
||||
"warehouse",
|
||||
"threshold_percentage",
|
||||
"validate_applied_rule",
|
||||
"column_break_14",
|
||||
"priority",
|
||||
"apply_discount_on_rate"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "disable",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Disable",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Disable"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "rule_description",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Rule Description",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 1,
|
||||
"default": "0",
|
||||
"fieldname": "min_qty",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Min Qty",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Min Qty"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 1,
|
||||
"default": "0",
|
||||
"fieldname": "max_qty",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Max Qty",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Max Qty"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fieldname": "min_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Min Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Min Amount"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"depends_on": "",
|
||||
"fieldname": "max_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Max Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Max Amount"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Discount Percentage",
|
||||
"depends_on": "",
|
||||
"fieldname": "rate_or_discount",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Discount Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nRate\nDiscount Percentage\nDiscount Amount",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "\nRate\nDiscount Percentage\nDiscount Amount"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "column_break_10",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"depends_on": "eval:doc.rate_or_discount==\"Rate\"",
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Rate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Rate"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.rate_or_discount==\"Discount Amount\"",
|
||||
"fieldname": "discount_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Discount Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Discount Amount"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.rate_or_discount==\"Discount Percentage\"",
|
||||
"fieldname": "discount_percentage",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Discount Percentage",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Discount Percentage"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_11",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Warehouse",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Warehouse",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "threshold_percentage",
|
||||
"fieldtype": "Percent",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Threshold for Suggestion",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Threshold for Suggestion"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "validate_applied_rule",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Validate Applied Rule",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Validate Applied Rule"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "priority",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Priority",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"depends_on": "priority",
|
||||
"fieldname": "apply_multiple_pricing_rules",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Apply Multiple Pricing Rules",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Apply Multiple Pricing Rules"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"depends_on": "eval:in_list(['Discount Percentage', 'Discount Amount'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules",
|
||||
"fieldname": "apply_discount_on_rate",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Apply Discount on Rate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Apply Discount on Rate"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-03-24 14:48:59.649168",
|
||||
"links": [],
|
||||
"modified": "2021-03-07 11:56:23.424137",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Promotional Scheme Price Discount",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"sort_order": "DESC"
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2019-03-24 14:48:59.649168",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"disable",
|
||||
"apply_multiple_pricing_rules",
|
||||
"column_break_2",
|
||||
"rule_description",
|
||||
"section_break_1",
|
||||
@ -25,7 +27,7 @@
|
||||
"threshold_percentage",
|
||||
"column_break_15",
|
||||
"priority",
|
||||
"apply_multiple_pricing_rules"
|
||||
"is_recursive"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -152,10 +154,19 @@
|
||||
"fieldname": "apply_multiple_pricing_rules",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply Multiple Pricing Rules"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on",
|
||||
"fieldname": "is_recursive",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Recursive"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"modified": "2019-07-21 00:00:56.674284",
|
||||
"links": [],
|
||||
"modified": "2021-03-06 21:58:18.162346",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Promotional Scheme Product Discount",
|
||||
|
@ -496,15 +496,6 @@ cur_frm.fields_dict['items'].grid.get_field('project').get_query = function(doc,
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
|
||||
if(doc.select_print_heading){
|
||||
// print heading
|
||||
cur_frm.pformat.print_heading = doc.select_print_heading;
|
||||
}
|
||||
else
|
||||
cur_frm.pformat.print_heading = __("Purchase Invoice");
|
||||
}
|
||||
|
||||
frappe.ui.form.on("Purchase Invoice", {
|
||||
setup: function(frm) {
|
||||
frm.custom_make_buttons = {
|
||||
@ -523,8 +514,30 @@ 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) {
|
||||
if(frm.doc.__onload && frm.is_new()) {
|
||||
if(frm.doc.supplier) {
|
||||
frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0;
|
||||
}
|
||||
@ -548,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 ...")
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -127,7 +127,6 @@
|
||||
"write_off_cost_center",
|
||||
"advances_section",
|
||||
"allocate_advances_automatically",
|
||||
"adjust_advance_taxes",
|
||||
"get_advances",
|
||||
"advances",
|
||||
"payment_schedule_section",
|
||||
@ -164,7 +163,8 @@
|
||||
"to_date",
|
||||
"column_break_114",
|
||||
"auto_repeat",
|
||||
"update_auto_repeat_reference"
|
||||
"update_auto_repeat_reference",
|
||||
"per_received"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -1326,13 +1326,6 @@
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Taxes paid while advance payment will be adjusted against this invoice",
|
||||
"fieldname": "adjust_advance_taxes",
|
||||
"fieldtype": "Check",
|
||||
"label": "Adjust Advance Taxes"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_internal_supplier",
|
||||
"description": "Unrealized Profit / Loss account for intra-company transfers",
|
||||
@ -1372,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-09 21:12:30.422084",
|
||||
"modified": "2021-03-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()
|
||||
@ -898,7 +898,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
acc_settings.submit_journal_entries = 1
|
||||
acc_settings.save()
|
||||
|
||||
item = create_item("_Test Item for Deferred Accounting")
|
||||
item = create_item("_Test Item for Deferred Accounting", is_purchase_item=True)
|
||||
item.enable_deferred_expense = 1
|
||||
item.deferred_expense_account = deferred_account
|
||||
item.save()
|
||||
|
@ -43,7 +43,7 @@
|
||||
}
|
||||
],
|
||||
"grand_total": 0,
|
||||
"naming_series": "_T-BILL",
|
||||
"naming_series": "T-PINV-",
|
||||
"taxes": [
|
||||
{
|
||||
"account_head": "_Test Account Shipping Charges - _TC",
|
||||
@ -167,7 +167,7 @@
|
||||
}
|
||||
],
|
||||
"grand_total": 0,
|
||||
"naming_series": "_T-Purchase Invoice-",
|
||||
"naming_series": "T-PINV-",
|
||||
"taxes": [
|
||||
{
|
||||
"account_head": "_Test Account Shipping Charges - _TC",
|
||||
|
@ -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",
|
||||
|
@ -1,14 +1,14 @@
|
||||
var globalOnload = frappe.listview_settings['Sales Invoice'].onload;
|
||||
frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
|
||||
frappe.listview_settings['Sales Invoice'].onload = function (list_view) {
|
||||
|
||||
// Provision in case onload event is added to sales_invoice.js in future
|
||||
if (globalOnload) {
|
||||
globalOnload(doclist);
|
||||
globalOnload(list_view);
|
||||
}
|
||||
|
||||
const action = () => {
|
||||
const selected_docs = doclist.get_checked_items();
|
||||
const docnames = doclist.get_checked_items(true);
|
||||
const selected_docs = list_view.get_checked_items();
|
||||
const docnames = list_view.get_checked_items(true);
|
||||
|
||||
for (let doc of selected_docs) {
|
||||
if (doc.docstatus !== 1) {
|
||||
@ -19,7 +19,7 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
|
||||
frappe.call({
|
||||
method: 'erpnext.regional.india.utils.generate_ewb_json',
|
||||
args: {
|
||||
'dt': doclist.doctype,
|
||||
'dt': list_view.doctype,
|
||||
'dn': docnames
|
||||
},
|
||||
callback: function(r) {
|
||||
@ -35,5 +35,140 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
|
||||
});
|
||||
};
|
||||
|
||||
doclist.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false);
|
||||
list_view.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false);
|
||||
|
||||
const generate_irns = () => {
|
||||
const docnames = list_view.get_checked_items(true);
|
||||
if (docnames && docnames.length) {
|
||||
frappe.call({
|
||||
method: 'erpnext.regional.india.e_invoice.utils.generate_einvoices',
|
||||
args: { docnames },
|
||||
freeze: true,
|
||||
freeze_message: __('Generating E-Invoices...')
|
||||
});
|
||||
} else {
|
||||
frappe.msgprint({
|
||||
message: __('Please select at least one sales invoice to generate IRN'),
|
||||
title: __('No Invoice Selected'),
|
||||
indicator: 'red'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const cancel_irns = () => {
|
||||
const docnames = list_view.get_checked_items(true);
|
||||
|
||||
const fields = [
|
||||
{
|
||||
"label": "Reason",
|
||||
"fieldname": "reason",
|
||||
"fieldtype": "Select",
|
||||
"reqd": 1,
|
||||
"default": "1-Duplicate",
|
||||
"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
|
||||
},
|
||||
{
|
||||
"label": "Remark",
|
||||
"fieldname": "remark",
|
||||
"fieldtype": "Data",
|
||||
"reqd": 1
|
||||
}
|
||||
];
|
||||
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __("Cancel IRN"),
|
||||
fields: fields,
|
||||
primary_action: function() {
|
||||
const data = d.get_values();
|
||||
frappe.call({
|
||||
method: 'erpnext.regional.india.e_invoice.utils.cancel_irns',
|
||||
args: {
|
||||
doctype: list_view.doctype,
|
||||
docnames,
|
||||
reason: data.reason.split('-')[0],
|
||||
remark: data.remark
|
||||
},
|
||||
freeze: true,
|
||||
freeze_message: __('Cancelling E-Invoices...'),
|
||||
});
|
||||
d.hide();
|
||||
},
|
||||
primary_action_label: __('Submit')
|
||||
});
|
||||
d.show();
|
||||
};
|
||||
|
||||
let einvoicing_enabled = false;
|
||||
frappe.db.get_single_value("E Invoice Settings", "enable").then(enabled => {
|
||||
einvoicing_enabled = enabled;
|
||||
});
|
||||
|
||||
list_view.$result.on("change", "input[type=checkbox]", () => {
|
||||
if (einvoicing_enabled) {
|
||||
const docnames = list_view.get_checked_items(true);
|
||||
// show/hide e-invoicing actions when no sales invoices are checked
|
||||
if (docnames && docnames.length) {
|
||||
// prevent adding actions twice if e-invoicing action group already exists
|
||||
if (list_view.page.get_inner_group_button(__('E-Invoicing')).length == 0) {
|
||||
list_view.page.add_inner_button(__('Generate IRNs'), generate_irns, __('E-Invoicing'));
|
||||
list_view.page.add_inner_button(__('Cancel IRNs'), cancel_irns, __('E-Invoicing'));
|
||||
}
|
||||
} else {
|
||||
list_view.page.remove_inner_button(__('Generate IRNs'), __('E-Invoicing'));
|
||||
list_view.page.remove_inner_button(__('Cancel IRNs'), __('E-Invoicing'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.realtime.on("bulk_einvoice_generation_complete", (data) => {
|
||||
const { failures, user, invoices } = data;
|
||||
|
||||
if (invoices.length != failures.length) {
|
||||
frappe.msgprint({
|
||||
message: __('{0} e-invoices generated successfully', [invoices.length]),
|
||||
title: __('Bulk E-Invoice Generation Complete'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
|
||||
if (failures && failures.length && user == frappe.session.user) {
|
||||
let message = `
|
||||
Failed to generate IRNs for following ${failures.length} sales invoices:
|
||||
<ul style="padding-left: 20px; padding-top: 5px;">
|
||||
${failures.map(d => `<li>${d.docname}</li>`).join('')}
|
||||
</ul>
|
||||
`;
|
||||
frappe.msgprint({
|
||||
message: message,
|
||||
title: __('Bulk E-Invoice Generation Complete'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.realtime.on("bulk_einvoice_cancellation_complete", (data) => {
|
||||
const { failures, user, invoices } = data;
|
||||
|
||||
if (invoices.length != failures.length) {
|
||||
frappe.msgprint({
|
||||
message: __('{0} e-invoices cancelled successfully', [invoices.length]),
|
||||
title: __('Bulk E-Invoice Cancellation Complete'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
|
||||
if (failures && failures.length && user == frappe.session.user) {
|
||||
let message = `
|
||||
Failed to cancel IRNs for following ${failures.length} sales invoices:
|
||||
<ul style="padding-left: 20px; padding-top: 5px;">
|
||||
${failures.map(d => `<li>${d.docname}</li>`).join('')}
|
||||
</ul>
|
||||
`;
|
||||
frappe.msgprint({
|
||||
message: message,
|
||||
title: __('Bulk E-Invoice Cancellation Complete'),
|
||||
indicator: 'orange'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
@ -1,10 +1,6 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
// print heading
|
||||
frappe.provide('cur_frm.pformat')
|
||||
cur_frm.pformat.print_heading = 'Invoice';
|
||||
|
||||
{% include 'erpnext/selling/sales_common.js' %};
|
||||
frappe.provide("erpnext.accounts");
|
||||
|
||||
@ -917,7 +913,7 @@ frappe.ui.form.on('Sales Invoice Timesheet', {
|
||||
},
|
||||
callback: function(r, rt) {
|
||||
if(r.message){
|
||||
data = r.message;
|
||||
let data = r.message;
|
||||
frappe.model.set_value(cdt, cdn, "billing_hours", data.billing_hours);
|
||||
frappe.model.set_value(cdt, cdn, "billing_amount", data.billing_amount);
|
||||
frappe.model.set_value(cdt, cdn, "timesheet_detail", data.timesheet_detail);
|
||||
|
@ -118,6 +118,7 @@
|
||||
"in_words",
|
||||
"total_advance",
|
||||
"outstanding_amount",
|
||||
"disable_rounded_total",
|
||||
"advances_section",
|
||||
"allocate_advances_automatically",
|
||||
"get_advances",
|
||||
@ -1109,6 +1110,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.disable_rounded_total",
|
||||
"fieldname": "base_rounding_adjustment",
|
||||
"fieldtype": "Currency",
|
||||
"hide_days": 1,
|
||||
@ -1120,6 +1122,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.disable_rounded_total",
|
||||
"fieldname": "base_rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"hide_days": 1,
|
||||
@ -1168,6 +1171,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.disable_rounded_total",
|
||||
"fieldname": "rounding_adjustment",
|
||||
"fieldtype": "Currency",
|
||||
"hide_days": 1,
|
||||
@ -1180,6 +1184,7 @@
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"depends_on": "eval:!doc.disable_rounded_total",
|
||||
"fieldname": "rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"hide_days": 1,
|
||||
@ -1945,6 +1950,13 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Set Target Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "grand_total",
|
||||
"fieldname": "disable_rounded_total",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable Rounded Total"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@ -1952,13 +1964,12 @@
|
||||
"is_submittable": 1,
|
||||
"links": [
|
||||
{
|
||||
"custom": 1,
|
||||
"group": "Reference",
|
||||
"link_doctype": "POS Invoice",
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2021-02-01 15:42:26.261540",
|
||||
"modified": "2021-04-15 23:57:58.766651",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
@ -24,6 +24,7 @@ from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.contacts.doctype.address.address import get_address_display
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
|
||||
|
||||
from erpnext.healthcare.utils import manage_invoice_submit_cancel
|
||||
|
||||
@ -45,7 +46,6 @@ class SalesInvoice(SellingController):
|
||||
'target_parent_dt': 'Sales Order',
|
||||
'target_parent_field': 'per_billed',
|
||||
'source_field': 'amount',
|
||||
'join_field': 'so_detail',
|
||||
'percent_join_field': 'sales_order',
|
||||
'status_field': 'billing_status',
|
||||
'keyword': 'Billed',
|
||||
@ -211,6 +211,9 @@ class SalesInvoice(SellingController):
|
||||
# this sequence because outstanding may get -ve
|
||||
self.make_gl_entries()
|
||||
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
@ -272,7 +275,7 @@ class SalesInvoice(SellingController):
|
||||
pluck="pos_closing_entry"
|
||||
)
|
||||
if pos_closing_entry:
|
||||
msg = _("To cancel a {} you need to cancel the POS Closing Entry {}. ").format(
|
||||
msg = _("To cancel a {} you need to cancel the POS Closing Entry {}.").format(
|
||||
frappe.bold("Consolidated Sales Invoice"),
|
||||
get_link_to_form("POS Closing Entry", pos_closing_entry[0])
|
||||
)
|
||||
@ -390,6 +393,7 @@ class SalesInvoice(SellingController):
|
||||
if validate_against_credit_limit:
|
||||
check_credit_limit(self.customer, self.company, bypass_credit_limit_check_at_sales_order)
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_missing_values(self, for_validate=False):
|
||||
pos = self.set_pos_fields(for_validate)
|
||||
|
||||
@ -544,12 +548,12 @@ class SalesInvoice(SellingController):
|
||||
frappe.throw(_("Debit To is required"), title=_("Account Missing"))
|
||||
|
||||
if account.report_type != "Balance Sheet":
|
||||
msg = _("Please ensure {} account is a Balance Sheet account. ").format(frappe.bold("Debit To"))
|
||||
msg = _("Please ensure {} account is a Balance Sheet account.").format(frappe.bold("Debit To")) + " "
|
||||
msg += _("You can change the parent account to a Balance Sheet account or select a different account.")
|
||||
frappe.throw(msg, title=_("Invalid Account"))
|
||||
|
||||
if self.customer and account.account_type != "Receivable":
|
||||
msg = _("Please ensure {} account is a Receivable account. ").format(frappe.bold("Debit To"))
|
||||
msg = _("Please ensure {} account is a Receivable account.").format(frappe.bold("Debit To")) + " "
|
||||
msg += _("Change the account type to Receivable or select a different account.")
|
||||
frappe.throw(msg, title=_("Invalid Account"))
|
||||
|
||||
@ -729,6 +733,7 @@ class SalesInvoice(SellingController):
|
||||
else:
|
||||
self.calculate_billing_amount_for_timesheet()
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_timesheet_data(self):
|
||||
self.set('timesheets', [])
|
||||
if self.project:
|
||||
@ -1286,6 +1291,7 @@ class SalesInvoice(SellingController):
|
||||
break
|
||||
|
||||
# Healthcare
|
||||
@frappe.whitelist()
|
||||
def set_healthcare_services(self, checked_values):
|
||||
self.set("items", [])
|
||||
from erpnext.stock.get_item_details import get_item_details
|
||||
|
@ -31,7 +31,7 @@
|
||||
"base_grand_total": 561.8,
|
||||
"grand_total": 561.8,
|
||||
"is_pos": 0,
|
||||
"naming_series": "_T-Sales Invoice-",
|
||||
"naming_series": "T-SINV-",
|
||||
"base_net_total": 500.0,
|
||||
"taxes": [
|
||||
{
|
||||
@ -104,7 +104,7 @@
|
||||
"base_grand_total": 630.0,
|
||||
"grand_total": 630.0,
|
||||
"is_pos": 0,
|
||||
"naming_series": "_T-Sales Invoice-",
|
||||
"naming_series": "T-SINV-",
|
||||
"base_net_total": 500.0,
|
||||
"taxes": [
|
||||
{
|
||||
@ -175,7 +175,7 @@
|
||||
],
|
||||
"grand_total": 0,
|
||||
"is_pos": 0,
|
||||
"naming_series": "_T-Sales Invoice-",
|
||||
"naming_series": "T-SINV-",
|
||||
"taxes": [
|
||||
{
|
||||
"account_head": "_Test Account Shipping Charges - _TC",
|
||||
@ -301,7 +301,7 @@
|
||||
],
|
||||
"grand_total": 0,
|
||||
"is_pos": 0,
|
||||
"naming_series": "_T-Sales Invoice-",
|
||||
"naming_series": "T-SINV-",
|
||||
"taxes": [
|
||||
{
|
||||
"account_head": "_Test Account Excise Duty - _TC",
|
||||
|
@ -1166,10 +1166,12 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
def test_create_so_with_margin(self):
|
||||
si = create_sales_invoice(item_code="_Test Item", qty=1, do_not_submit=True)
|
||||
price_list_rate = 100
|
||||
price_list_rate = flt(100) * flt(si.plc_conversion_rate)
|
||||
si.items[0].price_list_rate = price_list_rate
|
||||
si.items[0].margin_type = 'Percentage'
|
||||
si.items[0].margin_rate_or_amount = 25
|
||||
si.items[0].discount_amount = 0.0
|
||||
si.items[0].discount_percentage = 0.0
|
||||
si.save()
|
||||
self.assertEqual(si.get("items")[0].rate, flt((price_list_rate*25)/100 + price_list_rate))
|
||||
|
||||
@ -1877,7 +1879,17 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
def test_einvoice_submission_without_irn(self):
|
||||
# init
|
||||
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1)
|
||||
einvoice_settings = frappe.get_doc('E Invoice Settings')
|
||||
einvoice_settings.enable = 1
|
||||
einvoice_settings.applicable_from = nowdate()
|
||||
einvoice_settings.append('credentials', {
|
||||
'company': '_Test Company',
|
||||
'gstin': '27AAECE4835E1ZR',
|
||||
'username': 'test',
|
||||
'password': 'test'
|
||||
})
|
||||
einvoice_settings.save()
|
||||
|
||||
country = frappe.flags.country
|
||||
frappe.flags.country = 'India'
|
||||
|
||||
@ -1888,7 +1900,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.submit()
|
||||
|
||||
# reset
|
||||
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0)
|
||||
einvoice_settings = frappe.get_doc('E Invoice Settings')
|
||||
einvoice_settings.enable = 0
|
||||
frappe.flags.country = country
|
||||
|
||||
def test_einvoice_json(self):
|
||||
@ -2115,6 +2128,7 @@ def create_sales_invoice(**args):
|
||||
si.return_against = args.return_against
|
||||
si.currency=args.currency or "INR"
|
||||
si.conversion_rate = args.conversion_rate or 1
|
||||
si.naming_series = args.naming_series or "T-SINV-"
|
||||
|
||||
si.append("items", {
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
|
@ -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
|
||||
|
||||
|
@ -14,10 +14,15 @@ test_records = frappe.get_test_records('Tax Rule')
|
||||
from six import iteritems
|
||||
|
||||
class TestTaxRule(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
frappe.db.set_value("Shopping Cart Settings", None, "enabled", 0)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
frappe.db.sql("delete from `tabTax Rule`")
|
||||
|
||||
def tearDown(self):
|
||||
def setUp(self):
|
||||
frappe.db.sql("delete from `tabTax Rule`")
|
||||
|
||||
def test_conflict(self):
|
||||
|
@ -251,7 +251,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
|
||||
threshold = tax_details.get('threshold', 0)
|
||||
cumulative_threshold = tax_details.get('cumulative_threshold', 0)
|
||||
|
||||
if ((threshold and supp_credit_amt >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
|
||||
if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
|
||||
if ldc and is_valid_certificate(
|
||||
ldc.valid_from, ldc.valid_upto,
|
||||
inv.posting_date, tax_deducted,
|
||||
|
@ -87,50 +87,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
def test_single_threshold_tds_with_previous_vouchers(self):
|
||||
invoices = []
|
||||
frappe.db.set_value("Supplier", "Test TDS Supplier2", "tax_withholding_category", "Single Threshold TDS")
|
||||
pi = create_purchase_invoice(supplier="Test TDS Supplier2")
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
pi = create_purchase_invoice(supplier="Test TDS Supplier2")
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
self.assertEqual(pi.taxes_and_charges_deducted, 2000)
|
||||
self.assertEqual(pi.grand_total, 8000)
|
||||
|
||||
# delete invoices to avoid clashing
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
def test_single_threshold_tds_with_previous_vouchers_and_no_tds(self):
|
||||
invoices = []
|
||||
doc = create_supplier(supplier_name = "Test TDS Supplier ABC",
|
||||
tax_withholding_category="Single Threshold TDS")
|
||||
supplier = doc.name
|
||||
|
||||
pi = create_purchase_invoice(supplier=supplier)
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
# TDS not applied
|
||||
pi = create_purchase_invoice(supplier=supplier, do_not_apply_tds=True)
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
pi = create_purchase_invoice(supplier=supplier)
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
self.assertEqual(pi.taxes_and_charges_deducted, 2000)
|
||||
self.assertEqual(pi.grand_total, 8000)
|
||||
|
||||
# delete invoices to avoid clashing
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
def test_cumulative_threshold_tcs(self):
|
||||
frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS")
|
||||
invoices = []
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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.due_date, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
|
||||
from `tab{0}` si, `tabPayment Schedule` ps
|
||||
where
|
||||
si.name = ps.parent and
|
||||
@ -395,13 +395,13 @@ class ReceivablePayableReport(object):
|
||||
"invoiced": invoiced,
|
||||
"invoice_grand_total": row.invoiced,
|
||||
"payment_term": d.description,
|
||||
"paid": d.paid_amount,
|
||||
"paid": d.paid_amount + d.discounted_amount,
|
||||
"credit_note": 0.0,
|
||||
"outstanding": invoiced - d.paid_amount
|
||||
"outstanding": invoiced - d.paid_amount - d.discounted_amount
|
||||
}))
|
||||
|
||||
if d.paid_amount:
|
||||
row['paid'] -= d.paid_amount
|
||||
row['paid'] -= d.paid_amount + d.discounted_amount
|
||||
|
||||
def allocate_closing_to_term(self, row, term, key):
|
||||
if row[key]:
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user