Merge branch 'develop' of https://github.com/frappe/erpnext into term_loan_enhancement
This commit is contained in:
commit
be5e6f02ae
47
.github/ISSUE_TEMPLATE/bug_report.md
vendored
47
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,47 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Report a bug encountered while using ERPNext
|
|
||||||
labels: bug
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Welcome to ERPNext issue tracker! Before creating an issue, please heed the following:
|
|
||||||
|
|
||||||
1. This tracker should only be used to report bugs and request features / enhancements to ERPNext
|
|
||||||
- For questions and general support, checkout the manual https://erpnext.com/docs/user/manual/en or use https://discuss.erpnext.com
|
|
||||||
- For documentation issues, refer to https://github.com/frappe/erpnext_com
|
|
||||||
2. Use the search function before creating a new issue. Duplicates will be closed and directed to
|
|
||||||
the original discussion.
|
|
||||||
3. When making a bug report, make sure you provide all required information. The easier it is for
|
|
||||||
maintainers to reproduce, the faster it'll be fixed.
|
|
||||||
4. If you think you know what the reason for the bug is, share it with us. Maybe put in a PR 😉
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Description of the issue
|
|
||||||
|
|
||||||
## Context information (for bug reports)
|
|
||||||
|
|
||||||
**Output of `bench version`**
|
|
||||||
```
|
|
||||||
(paste here)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Steps to reproduce the issue
|
|
||||||
|
|
||||||
1.
|
|
||||||
2.
|
|
||||||
3.
|
|
||||||
|
|
||||||
### Observed result
|
|
||||||
|
|
||||||
### Expected result
|
|
||||||
|
|
||||||
### Stacktrace / full error message
|
|
||||||
|
|
||||||
```
|
|
||||||
(paste here)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Additional information
|
|
||||||
|
|
||||||
OS version / distribution, `ERPNext` install method, etc.
|
|
||||||
87
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
87
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Report a bug encountered while using ERPNext
|
||||||
|
labels: ["bug"]
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Welcome to ERPNext issue tracker! Before creating an issue, please heed the following:
|
||||||
|
|
||||||
|
1. This tracker should only be used to report bugs and request features / enhancements to ERPNext
|
||||||
|
- For questions and general support, checkout the [user manual](https://docs.erpnext.com/) or use [forum](https://discuss.erpnext.com)
|
||||||
|
- For documentation issues, propose edit on [documentation site](https://docs.erpnext.com/) directly.
|
||||||
|
2. When making a bug report, make sure you provide all required information. The easier it is for
|
||||||
|
maintainers to reproduce, the faster it'll be fixed.
|
||||||
|
3. If you think you know what the reason for the bug is, share it with us. Maybe put in a PR 😉
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: bug-info
|
||||||
|
attributes:
|
||||||
|
label: Information about bug
|
||||||
|
description: Also tell us, what did you expect to happen?
|
||||||
|
placeholder: Please provide as much information as possible.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: module
|
||||||
|
attributes:
|
||||||
|
label: Module
|
||||||
|
description: Select affected module of ERPNext.
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- accounts
|
||||||
|
- stock
|
||||||
|
- buying
|
||||||
|
- selling
|
||||||
|
- ecommerce
|
||||||
|
- manufacturing
|
||||||
|
- HR
|
||||||
|
- projects
|
||||||
|
- support
|
||||||
|
- assets
|
||||||
|
- integrations
|
||||||
|
- quality
|
||||||
|
- regional
|
||||||
|
- portal
|
||||||
|
- agriculture
|
||||||
|
- education
|
||||||
|
- non-profit
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: exact-version
|
||||||
|
attributes:
|
||||||
|
label: Version
|
||||||
|
description: Share exact version number of Frappe and ERPNext you are using.
|
||||||
|
placeholder: |
|
||||||
|
Frappe version -
|
||||||
|
ERPNext Verion -
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: install-method
|
||||||
|
attributes:
|
||||||
|
label: Installation method
|
||||||
|
options:
|
||||||
|
- docker
|
||||||
|
- easy-install
|
||||||
|
- manual install
|
||||||
|
- FrappeCloud
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Relevant log output / Stack trace / Full Error Message.
|
||||||
|
description: Please copy and paste any relevant log output. This will be automatically formatted.
|
||||||
|
render: shell
|
||||||
|
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/frappe/erpnext/blob/develop/CODE_OF_CONDUCT.md)
|
||||||
3
.github/ISSUE_TEMPLATE/feature_request.md
vendored
3
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,7 +1,10 @@
|
|||||||
---
|
---
|
||||||
name: Feature request
|
name: Feature request
|
||||||
about: Suggest an idea to improve ERPNext
|
about: Suggest an idea to improve ERPNext
|
||||||
|
title: ''
|
||||||
labels: feature-request
|
labels: feature-request
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
name: Question about using ERPNext
|
|
||||||
about: This is not the appropriate channel
|
|
||||||
labels: invalid
|
|
||||||
---
|
|
||||||
|
|
||||||
Please post on our forums:
|
|
||||||
|
|
||||||
for questions about using `ERPNext`: https://discuss.erpnext.com
|
|
||||||
|
|
||||||
for questions about using the `Frappe Framework`: ~~https://discuss.frappe.io~~ => [stackoverflow](https://stackoverflow.com/questions/tagged/frappe) tagged under `frappe`
|
|
||||||
|
|
||||||
for questions about using `bench`, probably the best place to start is the [bench repo](https://github.com/frappe/bench)
|
|
||||||
|
|
||||||
For documentation issues, use the [ERPNext Documentation](https://erpnext.com/docs/) or [Frappe Framework Documentation](https://frappe.io/docs/user/en) or the [developer cheetsheet](https://github.com/frappe/frappe/wiki/Developer-Cheatsheet)
|
|
||||||
|
|
||||||
> **Posts that are not bug reports or feature requests will not be addressed on this issue tracker.**
|
|
||||||
56
.github/stale.yml
vendored
56
.github/stale.yml
vendored
@ -1,34 +1,36 @@
|
|||||||
# Configuration for probot-stale - https://github.com/probot/stale
|
# Configuration for probot-stale - https://github.com/probot/stale
|
||||||
|
|
||||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
|
||||||
daysUntilStale: 15
|
|
||||||
|
|
||||||
# Number of days of inactivity before a stale Issue or Pull Request is closed.
|
|
||||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
|
||||||
daysUntilClose: 3
|
|
||||||
|
|
||||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
|
||||||
exemptLabels:
|
|
||||||
- hotfix
|
|
||||||
|
|
||||||
# Set to true to ignore issues in a project (defaults to false)
|
|
||||||
exemptProjects: false
|
|
||||||
|
|
||||||
# Set to true to ignore issues in a milestone (defaults to false)
|
|
||||||
exemptMilestones: true
|
|
||||||
|
|
||||||
# Label to use when marking as stale
|
# Label to use when marking as stale
|
||||||
staleLabel: inactive
|
staleLabel: inactive
|
||||||
|
|
||||||
# Comment to post when marking as stale. Set to `false` to disable
|
|
||||||
markComment: >
|
|
||||||
This pull request has been automatically marked as stale because it has not had
|
|
||||||
recent activity. It will be closed within a week if no further activity occurs, but it
|
|
||||||
only takes a comment to keep a contribution alive :) Also, even if it is closed,
|
|
||||||
you can always reopen the PR when you're ready. Thank you for contributing.
|
|
||||||
|
|
||||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||||
limitPerRun: 30
|
limitPerRun: 10
|
||||||
|
|
||||||
# Limit to only `issues` or `pulls`
|
# Set to true to ignore issues in a project (defaults to false)
|
||||||
only: pulls
|
exemptProjects: true
|
||||||
|
|
||||||
|
# Set to true to ignore issues in a milestone (defaults to false)
|
||||||
|
exemptMilestones: true
|
||||||
|
|
||||||
|
pulls:
|
||||||
|
daysUntilStale: 15
|
||||||
|
daysUntilClose: 3
|
||||||
|
exemptLabels:
|
||||||
|
- hotfix
|
||||||
|
markComment: >
|
||||||
|
This pull request has been automatically marked as inactive because it has
|
||||||
|
not had recent activity. It will be closed within 3 days if no further
|
||||||
|
activity occurs, but it only takes a comment to keep a contribution alive
|
||||||
|
:) Also, even if it is closed, you can always reopen the PR when you're
|
||||||
|
ready. Thank you for contributing.
|
||||||
|
|
||||||
|
issues:
|
||||||
|
daysUntilStale: 60
|
||||||
|
daysUntilClose: 7
|
||||||
|
exemptLabels:
|
||||||
|
- valid
|
||||||
|
- to-validate
|
||||||
|
markComment: >
|
||||||
|
This issue has been automatically marked as inactive because it has not had
|
||||||
|
recent activity and it wasn't validated by maintainer team. It will be
|
||||||
|
closed within a week if no further activity occurs.
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
[](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml)
|
[](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml)
|
||||||
[](https://www.codetriage.com/frappe/erpnext)
|
[](https://www.codetriage.com/frappe/erpnext)
|
||||||
[](https://codecov.io/gh/frappe/erpnext)
|
[](https://codecov.io/gh/frappe/erpnext)
|
||||||
|
[](https://hub.docker.com/r/frappe/erpnext-worker)
|
||||||
|
|
||||||
[https://erpnext.com](https://erpnext.com)
|
[https://erpnext.com](https://erpnext.com)
|
||||||
|
|
||||||
|
|||||||
10
codecov.yml
10
codecov.yml
@ -8,6 +8,16 @@ coverage:
|
|||||||
target: auto
|
target: auto
|
||||||
threshold: 0.5%
|
threshold: 0.5%
|
||||||
|
|
||||||
|
patch:
|
||||||
|
default:
|
||||||
|
target: 85%
|
||||||
|
threshold: 0%
|
||||||
|
base: auto
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
if_ci_failed: ignore
|
||||||
|
only_pulls: true
|
||||||
|
|
||||||
comment:
|
comment:
|
||||||
layout: "diff, files"
|
layout: "diff, files"
|
||||||
require_changes: true
|
require_changes: true
|
||||||
|
|||||||
1
dev-requirements.txt
Normal file
1
dev-requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
hypothesis~=6.31.0
|
||||||
@ -4,7 +4,7 @@ import frappe
|
|||||||
|
|
||||||
from erpnext.hooks import regional_overrides
|
from erpnext.hooks import regional_overrides
|
||||||
|
|
||||||
__version__ = '13.9.0'
|
__version__ = '14.0.0-dev'
|
||||||
|
|
||||||
def get_default_company(user=None):
|
def get_default_company(user=None):
|
||||||
'''Get default company for user'''
|
'''Get default company for user'''
|
||||||
@ -55,9 +55,9 @@ def set_perpetual_inventory(enable=1, company=None):
|
|||||||
company.enable_perpetual_inventory = enable
|
company.enable_perpetual_inventory = enable
|
||||||
company.save()
|
company.save()
|
||||||
|
|
||||||
def encode_company_abbr(name, company):
|
def encode_company_abbr(name, company=None, abbr=None):
|
||||||
'''Returns name encoded with company abbreviation'''
|
'''Returns name encoded with company abbreviation'''
|
||||||
company_abbr = frappe.get_cached_value('Company', company, "abbr")
|
company_abbr = abbr or frappe.get_cached_value('Company', company, "abbr")
|
||||||
parts = name.rsplit(" - ", 1)
|
parts = name.rsplit(" - ", 1)
|
||||||
|
|
||||||
if parts[-1].lower() != company_abbr.lower():
|
if parts[-1].lower() != company_abbr.lower():
|
||||||
|
|||||||
@ -374,12 +374,13 @@ def make_gl_entries(doc, credit_account, debit_account, against,
|
|||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if frappe.flags.in_test:
|
if frappe.flags.in_test:
|
||||||
|
traceback = frappe.get_traceback()
|
||||||
|
frappe.log_error(title=_('Error while processing deferred accounting for Invoice {0}').format(doc.name), message=traceback)
|
||||||
raise e
|
raise e
|
||||||
else:
|
else:
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
traceback = frappe.get_traceback()
|
traceback = frappe.get_traceback()
|
||||||
frappe.log_error(message=traceback)
|
frappe.log_error(title=_('Error while processing deferred accounting for Invoice {0}').format(doc.name), message=traceback)
|
||||||
|
|
||||||
frappe.flags.deferred_accounting_error = True
|
frappe.flags.deferred_accounting_error = True
|
||||||
|
|
||||||
def send_mail(deferred_process):
|
def send_mail(deferred_process):
|
||||||
@ -446,10 +447,12 @@ def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
|
|||||||
|
|
||||||
if submit:
|
if submit:
|
||||||
journal_entry.submit()
|
journal_entry.submit()
|
||||||
|
|
||||||
|
frappe.db.commit()
|
||||||
except Exception:
|
except Exception:
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
traceback = frappe.get_traceback()
|
traceback = frappe.get_traceback()
|
||||||
frappe.log_error(message=traceback)
|
frappe.log_error(title=_('Error while processing deferred accounting for Invoice {0}').format(doc.name), message=traceback)
|
||||||
|
|
||||||
frappe.flags.deferred_accounting_error = True
|
frappe.flags.deferred_accounting_error = True
|
||||||
|
|
||||||
|
|||||||
@ -78,6 +78,7 @@ frappe.treeview_settings["Account"] = {
|
|||||||
const format = (value, currency) => format_currency(Math.abs(value), currency);
|
const format = (value, currency) => format_currency(Math.abs(value), currency);
|
||||||
|
|
||||||
if (account.balance!==undefined) {
|
if (account.balance!==undefined) {
|
||||||
|
node.parent && node.parent.find('.balance-area').remove();
|
||||||
$('<span class="balance-area pull-right">'
|
$('<span class="balance-area pull-right">'
|
||||||
+ (account.balance_in_account_currency ?
|
+ (account.balance_in_account_currency ?
|
||||||
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
|
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
|
||||||
@ -175,7 +176,7 @@ frappe.treeview_settings["Account"] = {
|
|||||||
&& node.expandable && !node.hide_add;
|
&& node.expandable && !node.hide_add;
|
||||||
},
|
},
|
||||||
click: function() {
|
click: function() {
|
||||||
var me = frappe.treeview_settings['Account'].treeview;
|
var me = frappe.views.trees['Account'];
|
||||||
me.new_node();
|
me.new_node();
|
||||||
},
|
},
|
||||||
btnClass: "hidden-xs"
|
btnClass: "hidden-xs"
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
QUnit.module('accounts');
|
|
||||||
|
|
||||||
QUnit.test("test account", function(assert) {
|
|
||||||
assert.expect(4);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => frappe.set_route('Tree', 'Account'),
|
|
||||||
() => frappe.timeout(3),
|
|
||||||
() => frappe.click_button('Expand All'),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => frappe.click_link('Debtors'),
|
|
||||||
() => frappe.click_button('Edit'),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => {
|
|
||||||
assert.ok(cur_frm.doc.root_type=='Asset');
|
|
||||||
assert.ok(cur_frm.doc.report_type=='Balance Sheet');
|
|
||||||
assert.ok(cur_frm.doc.account_type=='Receivable');
|
|
||||||
},
|
|
||||||
() => frappe.click_button('Ledger'),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => {
|
|
||||||
// check if general ledger report shown
|
|
||||||
assert.deepEqual(frappe.get_route(), ['query-report', 'General Ledger']);
|
|
||||||
window.history.back();
|
|
||||||
return frappe.timeout(1);
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
QUnit.module('accounts');
|
|
||||||
|
|
||||||
QUnit.test("test account with number", function(assert) {
|
|
||||||
assert.expect(7);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => frappe.set_route('Tree', 'Account'),
|
|
||||||
() => frappe.click_link('Income'),
|
|
||||||
() => frappe.click_button('Add Child'),
|
|
||||||
() => frappe.timeout(.5),
|
|
||||||
() => {
|
|
||||||
cur_dialog.fields_dict.account_name.$input.val("Test Income");
|
|
||||||
cur_dialog.fields_dict.account_number.$input.val("4010");
|
|
||||||
},
|
|
||||||
() => frappe.click_button('Create New'),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => {
|
|
||||||
assert.ok($('a:contains("4010 - Test Income"):visible').length!=0, "Account created with number");
|
|
||||||
},
|
|
||||||
() => frappe.click_link('4010 - Test Income'),
|
|
||||||
() => frappe.click_button('Edit'),
|
|
||||||
() => frappe.timeout(.5),
|
|
||||||
() => frappe.click_button('Update Account Number'),
|
|
||||||
() => frappe.timeout(.5),
|
|
||||||
() => {
|
|
||||||
cur_dialog.fields_dict.account_number.$input.val("4020");
|
|
||||||
},
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => cur_dialog.primary_action(),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => cur_frm.refresh_fields(),
|
|
||||||
() => frappe.timeout(.5),
|
|
||||||
() => {
|
|
||||||
var abbr = frappe.get_abbr(frappe.defaults.get_default("Company"));
|
|
||||||
var new_account = "4020 - Test Income - " + abbr;
|
|
||||||
assert.ok(cur_frm.doc.name==new_account, "Account renamed");
|
|
||||||
assert.ok(cur_frm.doc.account_name=="Test Income", "account name remained same");
|
|
||||||
assert.ok(cur_frm.doc.account_number=="4020", "Account number updated to 4020");
|
|
||||||
},
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => frappe.click_button('Menu'),
|
|
||||||
() => frappe.click_link('Rename'),
|
|
||||||
() => frappe.timeout(.5),
|
|
||||||
() => {
|
|
||||||
cur_dialog.fields_dict.new_name.$input.val("4030 - Test Income");
|
|
||||||
},
|
|
||||||
() => frappe.timeout(.5),
|
|
||||||
() => frappe.click_button("Rename"),
|
|
||||||
() => frappe.timeout(2),
|
|
||||||
() => {
|
|
||||||
assert.ok(cur_frm.doc.account_name=="Test Income", "account name remained same");
|
|
||||||
assert.ok(cur_frm.doc.account_number=="4030", "Account number updated to 4030");
|
|
||||||
},
|
|
||||||
() => frappe.timeout(.5),
|
|
||||||
() => frappe.click_button('Chart of Accounts'),
|
|
||||||
() => frappe.timeout(.5),
|
|
||||||
() => frappe.click_button('Menu'),
|
|
||||||
() => frappe.click_link('Refresh'),
|
|
||||||
() => frappe.click_button('Expand All'),
|
|
||||||
() => frappe.click_link('4030 - Test Income'),
|
|
||||||
() => frappe.click_button('Delete'),
|
|
||||||
() => frappe.click_button('Yes'),
|
|
||||||
() => frappe.timeout(.5),
|
|
||||||
() => {
|
|
||||||
assert.ok($('a:contains("4030 - Test Account"):visible').length==0, "Account deleted");
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
QUnit.module('accounts');
|
|
||||||
QUnit.test("test account", assert => {
|
|
||||||
assert.expect(3);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => frappe.set_route('Tree', 'Account'),
|
|
||||||
() => frappe.click_button('Expand All'),
|
|
||||||
() => frappe.click_link('Duties and Taxes - '+ frappe.get_abbr(frappe.defaults.get_default("Company"))),
|
|
||||||
() => {
|
|
||||||
if($('a:contains("CGST"):visible').length == 0){
|
|
||||||
return frappe.map_tax.make('CGST', 9);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
if($('a:contains("SGST"):visible').length == 0){
|
|
||||||
return frappe.map_tax.make('SGST', 9);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
if($('a:contains("IGST"):visible').length == 0){
|
|
||||||
return frappe.map_tax.make('IGST', 18);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
assert.ok($('a:contains("CGST"):visible').length!=0, "CGST Checked");
|
|
||||||
assert.ok($('a:contains("SGST"):visible').length!=0, "SGST Checked");
|
|
||||||
assert.ok($('a:contains("IGST"):visible').length!=0, "IGST Checked");
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
frappe.map_tax = {
|
|
||||||
make:function(text,rate){
|
|
||||||
return frappe.run_serially([
|
|
||||||
() => frappe.click_button('Add Child'),
|
|
||||||
() => frappe.timeout(0.2),
|
|
||||||
() => cur_dialog.set_value('account_name',text),
|
|
||||||
() => cur_dialog.set_value('account_type','Tax'),
|
|
||||||
() => cur_dialog.set_value('tax_rate',rate),
|
|
||||||
() => cur_dialog.set_value('account_currency','INR'),
|
|
||||||
() => frappe.click_button('Create New'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -8,7 +8,7 @@ frappe.ui.form.on('Accounting Dimension Filter', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let help_content =
|
let help_content =
|
||||||
`<table class="table table-bordered" style="background-color: #f9f9f9;">
|
`<table class="table table-bordered" style="background-color: var(--scrollbar-track-color);">
|
||||||
<tr><td>
|
<tr><td>
|
||||||
<p>
|
<p>
|
||||||
<i class="fa fa-hand-right"></i>
|
<i class="fa fa-hand-right"></i>
|
||||||
|
|||||||
@ -10,6 +10,8 @@ from frappe.custom.doctype.property_setter.property_setter import make_property_
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint
|
||||||
|
|
||||||
|
from erpnext.stock.utils import check_pending_reposting
|
||||||
|
|
||||||
|
|
||||||
class AccountsSettings(Document):
|
class AccountsSettings(Document):
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
@ -19,9 +21,13 @@ class AccountsSettings(Document):
|
|||||||
frappe.db.set_default("add_taxes_from_item_tax_template",
|
frappe.db.set_default("add_taxes_from_item_tax_template",
|
||||||
self.get("add_taxes_from_item_tax_template", 0))
|
self.get("add_taxes_from_item_tax_template", 0))
|
||||||
|
|
||||||
|
frappe.db.set_default("enable_common_party_accounting",
|
||||||
|
self.get("enable_common_party_accounting", 0))
|
||||||
|
|
||||||
self.validate_stale_days()
|
self.validate_stale_days()
|
||||||
self.enable_payment_schedule_in_print()
|
self.enable_payment_schedule_in_print()
|
||||||
self.toggle_discount_accounting_fields()
|
self.toggle_discount_accounting_fields()
|
||||||
|
self.validate_pending_reposts()
|
||||||
|
|
||||||
def validate_stale_days(self):
|
def validate_stale_days(self):
|
||||||
if not self.allow_stale and cint(self.stale_days) <= 0:
|
if not self.allow_stale and cint(self.stale_days) <= 0:
|
||||||
@ -53,3 +59,8 @@ class AccountsSettings(Document):
|
|||||||
make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
|
make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
|
||||||
|
|
||||||
make_property_setter("Item", "default_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
|
make_property_setter("Item", "default_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_pending_reposts(self):
|
||||||
|
if self.acc_frozen_upto:
|
||||||
|
check_pending_reposting(self.acc_frozen_upto)
|
||||||
|
|||||||
@ -1,35 +0,0 @@
|
|||||||
QUnit.module('accounts');
|
|
||||||
|
|
||||||
QUnit.test("test: Accounts Settings doesn't allow negatives", function (assert) {
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
assert.expect(2);
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
() => frappe.set_route('Form', 'Accounts Settings', 'Accounts Settings'),
|
|
||||||
() => frappe.timeout(2),
|
|
||||||
() => unchecked_if_checked(cur_frm, 'Allow Stale Exchange Rates', frappe.click_check),
|
|
||||||
() => cur_frm.set_value('stale_days', 0),
|
|
||||||
() => frappe.click_button('Save'),
|
|
||||||
() => frappe.timeout(2),
|
|
||||||
() => {
|
|
||||||
assert.ok(cur_dialog);
|
|
||||||
},
|
|
||||||
() => frappe.click_button('Close'),
|
|
||||||
() => cur_frm.set_value('stale_days', -1),
|
|
||||||
() => frappe.click_button('Save'),
|
|
||||||
() => frappe.timeout(2),
|
|
||||||
() => {
|
|
||||||
assert.ok(cur_dialog);
|
|
||||||
},
|
|
||||||
() => frappe.click_button('Close'),
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
const unchecked_if_checked = function(frm, field_name, fn){
|
|
||||||
if (frm.doc.allow_stale) {
|
|
||||||
return fn(field_name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
0
erpnext/accounts/doctype/advance_tax/__init__.py
Normal file
0
erpnext/accounts/doctype/advance_tax/__init__.py
Normal file
56
erpnext/accounts/doctype/advance_tax/advance_tax.json
Normal file
56
erpnext/accounts/doctype/advance_tax/advance_tax.json
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"creation": "2021-11-25 10:24:39.836195",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"reference_type",
|
||||||
|
"reference_name",
|
||||||
|
"reference_detail",
|
||||||
|
"account_head",
|
||||||
|
"allocated_amount"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "reference_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Reference Type",
|
||||||
|
"options": "DocType"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reference_name",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Reference Name",
|
||||||
|
"options": "reference_type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reference_detail",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Reference Detail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "account_head",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Account Head",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "allocated_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Allocated Amount",
|
||||||
|
"options": "party_account_currency"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-11-25 10:27:51.712286",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Advance Tax",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC"
|
||||||
|
}
|
||||||
9
erpnext/accounts/doctype/advance_tax/advance_tax.py
Normal file
9
erpnext/accounts/doctype/advance_tax/advance_tax.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
class AdvanceTax(Document):
|
||||||
|
pass
|
||||||
@ -25,8 +25,7 @@
|
|||||||
"allocated_amount",
|
"allocated_amount",
|
||||||
"column_break_13",
|
"column_break_13",
|
||||||
"base_tax_amount",
|
"base_tax_amount",
|
||||||
"base_total",
|
"base_total"
|
||||||
"base_allocated_amount"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -168,12 +167,6 @@
|
|||||||
"label": "Allocated Amount",
|
"label": "Allocated Amount",
|
||||||
"options": "currency"
|
"options": "currency"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "base_allocated_amount",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"label": "Allocated Amount (Company Currency)",
|
|
||||||
"options": "Company:company:default_currency"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fetch_from": "account_head.account_currency",
|
"fetch_from": "account_head.account_currency",
|
||||||
"fieldname": "currency",
|
"fieldname": "currency",
|
||||||
@ -186,7 +179,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-06-09 11:46:58.373170",
|
"modified": "2021-11-25 11:10:10.945027",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Advance Taxes and Charges",
|
"name": "Advance Taxes and Charges",
|
||||||
|
|||||||
@ -218,6 +218,8 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
|
|||||||
# updated clear date of all the vouchers based on the bank transaction
|
# updated clear date of all the vouchers based on the bank transaction
|
||||||
vouchers = json.loads(vouchers)
|
vouchers = json.loads(vouchers)
|
||||||
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
|
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
|
||||||
|
company_account = frappe.db.get_value('Bank Account', transaction.bank_account, 'account')
|
||||||
|
|
||||||
if transaction.unallocated_amount == 0:
|
if transaction.unallocated_amount == 0:
|
||||||
frappe.throw(_("This bank transaction is already fully reconciled"))
|
frappe.throw(_("This bank transaction is already fully reconciled"))
|
||||||
total_amount = 0
|
total_amount = 0
|
||||||
@ -226,7 +228,7 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
|
|||||||
total_amount += get_paid_amount(frappe._dict({
|
total_amount += get_paid_amount(frappe._dict({
|
||||||
'payment_document': voucher['payment_doctype'],
|
'payment_document': voucher['payment_doctype'],
|
||||||
'payment_entry': voucher['payment_name'],
|
'payment_entry': voucher['payment_name'],
|
||||||
}), transaction.currency)
|
}), transaction.currency, company_account)
|
||||||
|
|
||||||
if total_amount > transaction.unallocated_amount:
|
if total_amount > transaction.unallocated_amount:
|
||||||
frappe.throw(_("The Sum Total of Amounts of All Selected Vouchers Should be Less than the Unallocated Amount of the Bank Transaction"))
|
frappe.throw(_("The Sum Total of Amounts of All Selected Vouchers Should be Less than the Unallocated Amount of the Bank Transaction"))
|
||||||
@ -261,7 +263,7 @@ def get_linked_payments(bank_transaction_name, document_types = None):
|
|||||||
return matching
|
return matching
|
||||||
|
|
||||||
def check_matching(bank_account, company, transaction, document_types):
|
def check_matching(bank_account, company, transaction, document_types):
|
||||||
# combine all types of vocuhers
|
# combine all types of vouchers
|
||||||
subquery = get_queries(bank_account, company, transaction, document_types)
|
subquery = get_queries(bank_account, company, transaction, document_types)
|
||||||
filters = {
|
filters = {
|
||||||
"amount": transaction.unallocated_amount,
|
"amount": transaction.unallocated_amount,
|
||||||
@ -343,13 +345,11 @@ def get_pe_matching_query(amount_condition, account_from_to, transaction):
|
|||||||
def get_je_matching_query(amount_condition, transaction):
|
def get_je_matching_query(amount_condition, transaction):
|
||||||
# get matching journal entry query
|
# get matching journal entry query
|
||||||
|
|
||||||
|
# We have mapping at the bank level
|
||||||
|
# So one bank could have both types of bank accounts like asset and liability
|
||||||
|
# So cr_or_dr should be judged only on basis of withdrawal and deposit and not account type
|
||||||
company_account = frappe.get_value("Bank Account", transaction.bank_account, "account")
|
company_account = frappe.get_value("Bank Account", transaction.bank_account, "account")
|
||||||
root_type = frappe.get_value("Account", company_account, "root_type")
|
cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit"
|
||||||
|
|
||||||
if root_type == "Liability":
|
|
||||||
cr_or_dr = "debit" if transaction.withdrawal > 0 else "credit"
|
|
||||||
else:
|
|
||||||
cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit"
|
|
||||||
|
|
||||||
return f"""
|
return f"""
|
||||||
|
|
||||||
@ -434,7 +434,7 @@ def get_pi_matching_query(amount_condition):
|
|||||||
|
|
||||||
def get_ec_matching_query(bank_account, company, amount_condition):
|
def get_ec_matching_query(bank_account, company, amount_condition):
|
||||||
# get matching Expense Claim query
|
# get matching Expense Claim query
|
||||||
mode_of_payments = [x["parent"] for x in frappe.db.get_list("Mode of Payment Account",
|
mode_of_payments = [x["parent"] for x in frappe.db.get_all("Mode of Payment Account",
|
||||||
filters={"default_account": bank_account}, fields=["parent"])]
|
filters={"default_account": bank_account}, fields=["parent"])]
|
||||||
mode_of_payments = '(\'' + '\', \''.join(mode_of_payments) + '\' )'
|
mode_of_payments = '(\'' + '\', \''.join(mode_of_payments) + '\' )'
|
||||||
company_currency = get_company_currency(company)
|
company_currency = get_company_currency(company)
|
||||||
|
|||||||
@ -102,7 +102,7 @@ def get_total_allocated_amount(payment_entry):
|
|||||||
AND
|
AND
|
||||||
bt.docstatus = 1""", (payment_entry.payment_document, payment_entry.payment_entry), as_dict=True)
|
bt.docstatus = 1""", (payment_entry.payment_document, payment_entry.payment_entry), as_dict=True)
|
||||||
|
|
||||||
def get_paid_amount(payment_entry, currency):
|
def get_paid_amount(payment_entry, currency, bank_account):
|
||||||
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
|
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
|
||||||
|
|
||||||
paid_amount_field = "paid_amount"
|
paid_amount_field = "paid_amount"
|
||||||
@ -115,7 +115,7 @@ def get_paid_amount(payment_entry, currency):
|
|||||||
payment_entry.payment_entry, paid_amount_field)
|
payment_entry.payment_entry, paid_amount_field)
|
||||||
|
|
||||||
elif payment_entry.payment_document == "Journal Entry":
|
elif payment_entry.payment_document == "Journal Entry":
|
||||||
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_credit")
|
return frappe.db.get_value('Journal Entry Account', {'parent': payment_entry.payment_entry, 'account': bank_account}, "sum(credit_in_account_currency)")
|
||||||
|
|
||||||
elif payment_entry.payment_document == "Expense Claim":
|
elif payment_entry.payment_document == "Expense Claim":
|
||||||
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_amount_reimbursed")
|
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_amount_reimbursed")
|
||||||
|
|||||||
@ -5,10 +5,10 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.utils import now_datetime
|
||||||
|
|
||||||
from erpnext.accounts.doctype.fiscal_year.fiscal_year import FiscalYearIncorrectDate
|
from erpnext.accounts.doctype.fiscal_year.fiscal_year import FiscalYearIncorrectDate
|
||||||
|
|
||||||
test_records = frappe.get_test_records('Fiscal Year')
|
|
||||||
test_ignore = ["Company"]
|
test_ignore = ["Company"]
|
||||||
|
|
||||||
class TestFiscalYear(unittest.TestCase):
|
class TestFiscalYear(unittest.TestCase):
|
||||||
@ -25,3 +25,29 @@ class TestFiscalYear(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
|
|
||||||
self.assertRaises(FiscalYearIncorrectDate, fy.insert)
|
self.assertRaises(FiscalYearIncorrectDate, fy.insert)
|
||||||
|
|
||||||
|
|
||||||
|
def test_record_generator():
|
||||||
|
test_records = [
|
||||||
|
{
|
||||||
|
"doctype": "Fiscal Year",
|
||||||
|
"year": "_Test Short Fiscal Year 2011",
|
||||||
|
"is_short_year": 1,
|
||||||
|
"year_end_date": "2011-04-01",
|
||||||
|
"year_start_date": "2011-12-31"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
start = 2012
|
||||||
|
end = now_datetime().year + 5
|
||||||
|
for year in range(start, end):
|
||||||
|
test_records.append({
|
||||||
|
"doctype": "Fiscal Year",
|
||||||
|
"year": f"_Test Fiscal Year {year}",
|
||||||
|
"year_start_date": f"{year}-01-01",
|
||||||
|
"year_end_date": f"{year}-12-31"
|
||||||
|
})
|
||||||
|
|
||||||
|
return test_records
|
||||||
|
|
||||||
|
test_records = test_record_generator()
|
||||||
|
|||||||
@ -1,69 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"doctype": "Fiscal Year",
|
|
||||||
"year": "_Test Short Fiscal Year 2011",
|
|
||||||
"is_short_year": 1,
|
|
||||||
"year_end_date": "2011-04-01",
|
|
||||||
"year_start_date": "2011-12-31"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Fiscal Year",
|
|
||||||
"year": "_Test Fiscal Year 2012",
|
|
||||||
"year_end_date": "2012-12-31",
|
|
||||||
"year_start_date": "2012-01-01"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Fiscal Year",
|
|
||||||
"year": "_Test Fiscal Year 2013",
|
|
||||||
"year_end_date": "2013-12-31",
|
|
||||||
"year_start_date": "2013-01-01"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Fiscal Year",
|
|
||||||
"year": "_Test Fiscal Year 2014",
|
|
||||||
"year_end_date": "2014-12-31",
|
|
||||||
"year_start_date": "2014-01-01"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Fiscal Year",
|
|
||||||
"year": "_Test Fiscal Year 2015",
|
|
||||||
"year_end_date": "2015-12-31",
|
|
||||||
"year_start_date": "2015-01-01"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Fiscal Year",
|
|
||||||
"year": "_Test Fiscal Year 2016",
|
|
||||||
"year_end_date": "2016-12-31",
|
|
||||||
"year_start_date": "2016-01-01"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Fiscal Year",
|
|
||||||
"year": "_Test Fiscal Year 2017",
|
|
||||||
"year_end_date": "2017-12-31",
|
|
||||||
"year_start_date": "2017-01-01"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Fiscal Year",
|
|
||||||
"year": "_Test Fiscal Year 2018",
|
|
||||||
"year_end_date": "2018-12-31",
|
|
||||||
"year_start_date": "2018-01-01"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Fiscal Year",
|
|
||||||
"year": "_Test Fiscal Year 2019",
|
|
||||||
"year_end_date": "2019-12-31",
|
|
||||||
"year_start_date": "2019-01-01"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Fiscal Year",
|
|
||||||
"year": "_Test Fiscal Year 2020",
|
|
||||||
"year_end_date": "2020-12-31",
|
|
||||||
"year_start_date": "2020-01-01"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doctype": "Fiscal Year",
|
|
||||||
"year": "_Test Fiscal Year 2021",
|
|
||||||
"year_end_date": "2021-12-31",
|
|
||||||
"year_start_date": "2021-01-01"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
QUnit.module('Journal Entry');
|
|
||||||
|
|
||||||
QUnit.test("test journal entry", function(assert) {
|
|
||||||
assert.expect(2);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Journal Entry', [
|
|
||||||
{posting_date:frappe.datetime.add_days(frappe.datetime.nowdate(), 0)},
|
|
||||||
{accounts: [
|
|
||||||
[
|
|
||||||
{'account':'Debtors - '+frappe.get_abbr(frappe.defaults.get_default('Company'))},
|
|
||||||
{'party_type':'Customer'},
|
|
||||||
{'party':'Test Customer 1'},
|
|
||||||
{'credit_in_account_currency':1000},
|
|
||||||
{'is_advance':'Yes'},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{'account':'HDFC - '+frappe.get_abbr(frappe.defaults.get_default('Company'))},
|
|
||||||
{'debit_in_account_currency':1000},
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
{cheque_no:1234},
|
|
||||||
{cheque_date: frappe.datetime.add_days(frappe.datetime.nowdate(), -1)},
|
|
||||||
{user_remark: 'Test'},
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => {
|
|
||||||
// get_item_details
|
|
||||||
assert.ok(cur_frm.doc.total_debit==1000, "total debit correct");
|
|
||||||
assert.ok(cur_frm.doc.total_credit==1000, "total credit correct");
|
|
||||||
},
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -6,7 +6,7 @@ frappe.provide("erpnext.accounts.dimensions");
|
|||||||
frappe.ui.form.on('Loyalty Program', {
|
frappe.ui.form.on('Loyalty Program', {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
var help_content =
|
var help_content =
|
||||||
`<table class="table table-bordered" style="background-color: #f9f9f9;">
|
`<table class="table table-bordered" style="background-color: var(--scrollbar-track-color);">
|
||||||
<tr><td>
|
<tr><td>
|
||||||
<h4>
|
<h4>
|
||||||
<i class="fa fa-hand-right"></i>
|
<i class="fa fa-hand-right"></i>
|
||||||
|
|||||||
@ -159,7 +159,8 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
frappe.scrub(row.party_type): row.party,
|
frappe.scrub(row.party_type): row.party,
|
||||||
"is_pos": 0,
|
"is_pos": 0,
|
||||||
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
|
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
|
||||||
"update_stock": 0
|
"update_stock": 0,
|
||||||
|
"invoice_number": row.invoice_number
|
||||||
})
|
})
|
||||||
|
|
||||||
accounting_dimension = get_accounting_dimensions()
|
accounting_dimension = get_accounting_dimensions()
|
||||||
@ -200,10 +201,13 @@ def start_import(invoices):
|
|||||||
names = []
|
names = []
|
||||||
for idx, d in enumerate(invoices):
|
for idx, d in enumerate(invoices):
|
||||||
try:
|
try:
|
||||||
|
invoice_number = None
|
||||||
|
if d.invoice_number:
|
||||||
|
invoice_number = d.invoice_number
|
||||||
publish(idx, len(invoices), d.doctype)
|
publish(idx, len(invoices), d.doctype)
|
||||||
doc = frappe.get_doc(d)
|
doc = frappe.get_doc(d)
|
||||||
doc.flags.ignore_mandatory = True
|
doc.flags.ignore_mandatory = True
|
||||||
doc.insert()
|
doc.insert(set_name=invoice_number)
|
||||||
doc.submit()
|
doc.submit()
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
names.append(doc.name)
|
names.append(doc.name)
|
||||||
|
|||||||
@ -18,10 +18,10 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
|||||||
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
||||||
make_company()
|
make_company()
|
||||||
|
|
||||||
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None):
|
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None):
|
||||||
doc = frappe.get_single("Opening Invoice Creation Tool")
|
doc = frappe.get_single("Opening Invoice Creation Tool")
|
||||||
args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
|
args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
|
||||||
party_1=party_1, party_2=party_2)
|
party_1=party_1, party_2=party_2, invoice_number=invoice_number)
|
||||||
doc.update(args)
|
doc.update(args)
|
||||||
return doc.make_invoices()
|
return doc.make_invoices()
|
||||||
|
|
||||||
@ -92,6 +92,20 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
|||||||
# teardown
|
# teardown
|
||||||
frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account)
|
frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account)
|
||||||
|
|
||||||
|
def test_renaming_of_invoice_using_invoice_number_field(self):
|
||||||
|
company = "_Test Opening Invoice Company"
|
||||||
|
party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
|
||||||
|
self.make_invoices(company=company, party_1=party_1, party_2=party_2, invoice_number="TEST-NEW-INV-11")
|
||||||
|
|
||||||
|
sales_inv1 = frappe.get_all('Sales Invoice', filters={'customer':'Customer A'})[0].get("name")
|
||||||
|
sales_inv2 = frappe.get_all('Sales Invoice', filters={'customer':'Customer B'})[0].get("name")
|
||||||
|
self.assertEqual(sales_inv1, "TEST-NEW-INV-11")
|
||||||
|
|
||||||
|
#teardown
|
||||||
|
for inv in [sales_inv1, sales_inv2]:
|
||||||
|
doc = frappe.get_doc('Sales Invoice', inv)
|
||||||
|
doc.cancel()
|
||||||
|
|
||||||
def get_opening_invoice_creation_dict(**args):
|
def get_opening_invoice_creation_dict(**args):
|
||||||
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
|
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
|
||||||
company = args.get("company", "_Test Company")
|
company = args.get("company", "_Test Company")
|
||||||
@ -107,7 +121,8 @@ def get_opening_invoice_creation_dict(**args):
|
|||||||
"item_name": "Opening Item",
|
"item_name": "Opening Item",
|
||||||
"due_date": "2016-09-10",
|
"due_date": "2016-09-10",
|
||||||
"posting_date": "2016-09-05",
|
"posting_date": "2016-09-05",
|
||||||
"temporary_opening_account": get_temporary_opening_account(company)
|
"temporary_opening_account": get_temporary_opening_account(company),
|
||||||
|
"invoice_number": args.get("invoice_number")
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"qty": 2.0,
|
"qty": 2.0,
|
||||||
@ -116,7 +131,8 @@ def get_opening_invoice_creation_dict(**args):
|
|||||||
"item_name": "Opening Item",
|
"item_name": "Opening Item",
|
||||||
"due_date": "2016-09-10",
|
"due_date": "2016-09-10",
|
||||||
"posting_date": "2016-09-05",
|
"posting_date": "2016-09-05",
|
||||||
"temporary_opening_account": get_temporary_opening_account(company)
|
"temporary_opening_account": get_temporary_opening_account(company),
|
||||||
|
"invoice_number": None
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
@ -132,7 +148,7 @@ def make_company():
|
|||||||
company.company_name = "_Test Opening Invoice Company"
|
company.company_name = "_Test Opening Invoice Company"
|
||||||
company.abbr = "_TOIC"
|
company.abbr = "_TOIC"
|
||||||
company.default_currency = "INR"
|
company.default_currency = "INR"
|
||||||
company.country = "India"
|
company.country = "Pakistan"
|
||||||
company.insert()
|
company.insert()
|
||||||
return company
|
return company
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"creation": "2017-08-29 04:26:36.159247",
|
"creation": "2017-08-29 04:26:36.159247",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
|
"invoice_number",
|
||||||
"party_type",
|
"party_type",
|
||||||
"party",
|
"party",
|
||||||
"temporary_opening_account",
|
"temporary_opening_account",
|
||||||
@ -103,10 +105,18 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Reference number of the invoice from the previous system",
|
||||||
|
"fieldname": "invoice_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Invoice Number"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2019-07-25 15:00:00.460695",
|
"links": [],
|
||||||
|
"modified": "2021-12-17 19:25:06.053187",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Opening Invoice Creation Tool Item",
|
"name": "Opening Invoice Creation Tool Item",
|
||||||
|
|||||||
@ -25,3 +25,17 @@ class PartyLink(Document):
|
|||||||
if existing_party_link:
|
if existing_party_link:
|
||||||
frappe.throw(_('{} {} is already linked with another {}')
|
frappe.throw(_('{} {} is already linked with another {}')
|
||||||
.format(self.primary_role, self.primary_party, existing_party_link[0]))
|
.format(self.primary_role, self.primary_party, existing_party_link[0]))
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_party_link(primary_role, primary_party, secondary_party):
|
||||||
|
party_link = frappe.new_doc('Party Link')
|
||||||
|
party_link.primary_role = primary_role
|
||||||
|
party_link.primary_party = primary_party
|
||||||
|
party_link.secondary_role = 'Customer' if primary_role == 'Supplier' else 'Supplier'
|
||||||
|
party_link.secondary_party = secondary_party
|
||||||
|
|
||||||
|
party_link.save(ignore_permissions=True)
|
||||||
|
|
||||||
|
return party_link
|
||||||
|
|
||||||
|
|||||||
@ -61,7 +61,6 @@
|
|||||||
"taxes_and_charges_section",
|
"taxes_and_charges_section",
|
||||||
"purchase_taxes_and_charges_template",
|
"purchase_taxes_and_charges_template",
|
||||||
"sales_taxes_and_charges_template",
|
"sales_taxes_and_charges_template",
|
||||||
"advance_tax_account",
|
|
||||||
"column_break_55",
|
"column_break_55",
|
||||||
"apply_tax_withholding_amount",
|
"apply_tax_withholding_amount",
|
||||||
"tax_withholding_category",
|
"tax_withholding_category",
|
||||||
@ -685,15 +684,6 @@
|
|||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hide_border": 1
|
"hide_border": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"depends_on": "eval:doc.apply_tax_withholding_amount",
|
|
||||||
"description": "Provisional tax account for advance tax. Taxes are parked in this account until payments are allocated to invoices",
|
|
||||||
"fieldname": "advance_tax_account",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Advance Tax Account",
|
|
||||||
"mandatory_depends_on": "eval:doc.apply_tax_withholding_amount",
|
|
||||||
"options": "Account"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'",
|
"depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'",
|
||||||
"fieldname": "received_amount_after_tax",
|
"fieldname": "received_amount_after_tax",
|
||||||
@ -730,7 +720,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-10-22 17:50:24.632806",
|
"modified": "2021-11-24 18:58:24.919764",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry",
|
"name": "Payment Entry",
|
||||||
|
|||||||
@ -20,7 +20,7 @@ from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_ban
|
|||||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||||
get_party_tax_withholding_details,
|
get_party_tax_withholding_details,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries, process_gl_map
|
||||||
from erpnext.accounts.party import get_party_account
|
from erpnext.accounts.party import get_party_account
|
||||||
from erpnext.accounts.utils import get_account_currency, get_balance_on, get_outstanding_invoices
|
from erpnext.accounts.utils import get_account_currency, get_balance_on, get_outstanding_invoices
|
||||||
from erpnext.controllers.accounts_controller import (
|
from erpnext.controllers.accounts_controller import (
|
||||||
@ -339,7 +339,7 @@ class PaymentEntry(AccountsController):
|
|||||||
for k, v in no_oustanding_refs.items():
|
for k, v in no_oustanding_refs.items():
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
|
_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
|
||||||
.format(k, frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold("negative outstanding amount"))
|
.format(_(k), frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold(_("negative outstanding amount")))
|
||||||
+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
|
+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
|
||||||
title=_("Warning"), indicator="orange")
|
title=_("Warning"), indicator="orange")
|
||||||
|
|
||||||
@ -433,23 +433,12 @@ class PaymentEntry(AccountsController):
|
|||||||
if not self.apply_tax_withholding_amount:
|
if not self.apply_tax_withholding_amount:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.advance_tax_account:
|
|
||||||
frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction"))
|
|
||||||
|
|
||||||
net_total = self.paid_amount
|
net_total = self.paid_amount
|
||||||
|
|
||||||
for reference in self.get("references"):
|
|
||||||
net_total_for_tds = 0
|
|
||||||
if reference.reference_doctype == 'Purchase Order':
|
|
||||||
net_total_for_tds += flt(frappe.db.get_value('Purchase Order', reference.reference_name, 'net_total'))
|
|
||||||
|
|
||||||
if net_total_for_tds:
|
|
||||||
net_total = net_total_for_tds
|
|
||||||
|
|
||||||
# Adding args as purchase invoice to get TDS amount
|
# Adding args as purchase invoice to get TDS amount
|
||||||
args = frappe._dict({
|
args = frappe._dict({
|
||||||
'company': self.company,
|
'company': self.company,
|
||||||
'doctype': 'Purchase Invoice',
|
'doctype': 'Payment Entry',
|
||||||
'supplier': self.party,
|
'supplier': self.party,
|
||||||
'posting_date': self.posting_date,
|
'posting_date': self.posting_date,
|
||||||
'net_total': net_total
|
'net_total': net_total
|
||||||
@ -461,7 +450,6 @@ class PaymentEntry(AccountsController):
|
|||||||
return
|
return
|
||||||
|
|
||||||
tax_withholding_details.update({
|
tax_withholding_details.update({
|
||||||
'add_deduct_tax': 'Add',
|
|
||||||
'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company)
|
'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -623,7 +611,7 @@ class PaymentEntry(AccountsController):
|
|||||||
|
|
||||||
if not total_negative_outstanding:
|
if not total_negative_outstanding:
|
||||||
frappe.throw(_("Cannot {0} {1} {2} without any negative outstanding invoice")
|
frappe.throw(_("Cannot {0} {1} {2} without any negative outstanding invoice")
|
||||||
.format(self.payment_type, ("to" if self.party_type=="Customer" else "from"),
|
.format(_(self.payment_type), (_("to") if self.party_type=="Customer" else _("from")),
|
||||||
self.party_type), InvalidPaymentEntry)
|
self.party_type), InvalidPaymentEntry)
|
||||||
|
|
||||||
elif paid_amount - additional_charges > total_negative_outstanding:
|
elif paid_amount - additional_charges > total_negative_outstanding:
|
||||||
@ -689,6 +677,7 @@ class PaymentEntry(AccountsController):
|
|||||||
self.add_deductions_gl_entries(gl_entries)
|
self.add_deductions_gl_entries(gl_entries)
|
||||||
self.add_tax_gl_entries(gl_entries)
|
self.add_tax_gl_entries(gl_entries)
|
||||||
|
|
||||||
|
gl_entries = process_gl_map(gl_entries)
|
||||||
make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj)
|
make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj)
|
||||||
|
|
||||||
def add_party_gl_entries(self, gl_entries):
|
def add_party_gl_entries(self, gl_entries):
|
||||||
@ -752,7 +741,8 @@ class PaymentEntry(AccountsController):
|
|||||||
"against": self.party if self.payment_type=="Pay" else self.paid_to,
|
"against": self.party if self.payment_type=="Pay" else self.paid_to,
|
||||||
"credit_in_account_currency": self.paid_amount,
|
"credit_in_account_currency": self.paid_amount,
|
||||||
"credit": self.base_paid_amount,
|
"credit": self.base_paid_amount,
|
||||||
"cost_center": self.cost_center
|
"cost_center": self.cost_center,
|
||||||
|
"post_net_value": True
|
||||||
}, item=self)
|
}, item=self)
|
||||||
)
|
)
|
||||||
if self.payment_type in ("Receive", "Internal Transfer"):
|
if self.payment_type in ("Receive", "Internal Transfer"):
|
||||||
@ -782,14 +772,10 @@ class PaymentEntry(AccountsController):
|
|||||||
rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
|
rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
|
||||||
against = self.party or self.paid_to
|
against = self.party or self.paid_to
|
||||||
|
|
||||||
payment_or_advance_account = self.get_party_account_for_taxes()
|
payment_account = self.get_party_account_for_taxes()
|
||||||
tax_amount = d.tax_amount
|
tax_amount = d.tax_amount
|
||||||
base_tax_amount = d.base_tax_amount
|
base_tax_amount = d.base_tax_amount
|
||||||
|
|
||||||
if self.advance_tax_account:
|
|
||||||
tax_amount = -1 * tax_amount
|
|
||||||
base_tax_amount = -1 * base_tax_amount
|
|
||||||
|
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict({
|
self.get_gl_dict({
|
||||||
"account": d.account_head,
|
"account": d.account_head,
|
||||||
@ -798,19 +784,21 @@ class PaymentEntry(AccountsController):
|
|||||||
dr_or_cr + "_in_account_currency": base_tax_amount
|
dr_or_cr + "_in_account_currency": base_tax_amount
|
||||||
if account_currency==self.company_currency
|
if account_currency==self.company_currency
|
||||||
else d.tax_amount,
|
else d.tax_amount,
|
||||||
"cost_center": d.cost_center
|
"cost_center": d.cost_center,
|
||||||
|
"post_net_value": True,
|
||||||
}, account_currency, item=d))
|
}, account_currency, item=d))
|
||||||
|
|
||||||
if not d.included_in_paid_amount or self.advance_tax_account:
|
if not d.included_in_paid_amount:
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict({
|
self.get_gl_dict({
|
||||||
"account": payment_or_advance_account,
|
"account": payment_account,
|
||||||
"against": against,
|
"against": against,
|
||||||
rev_dr_or_cr: tax_amount,
|
rev_dr_or_cr: tax_amount,
|
||||||
rev_dr_or_cr + "_in_account_currency": base_tax_amount
|
rev_dr_or_cr + "_in_account_currency": base_tax_amount
|
||||||
if account_currency==self.company_currency
|
if account_currency==self.company_currency
|
||||||
else d.tax_amount,
|
else d.tax_amount,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
|
"post_net_value": True,
|
||||||
}, account_currency, item=d))
|
}, account_currency, item=d))
|
||||||
|
|
||||||
def add_deductions_gl_entries(self, gl_entries):
|
def add_deductions_gl_entries(self, gl_entries):
|
||||||
@ -832,9 +820,7 @@ class PaymentEntry(AccountsController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_party_account_for_taxes(self):
|
def get_party_account_for_taxes(self):
|
||||||
if self.advance_tax_account:
|
if self.payment_type == 'Receive':
|
||||||
return self.advance_tax_account
|
|
||||||
elif self.payment_type == 'Receive':
|
|
||||||
return self.paid_to
|
return self.paid_to
|
||||||
elif self.payment_type in ('Pay', 'Internal Transfer'):
|
elif self.payment_type in ('Pay', 'Internal Transfer'):
|
||||||
return self.paid_from
|
return self.paid_from
|
||||||
@ -1106,7 +1092,7 @@ def get_outstanding_reference_documents(args):
|
|||||||
|
|
||||||
if not data:
|
if not data:
|
||||||
frappe.msgprint(_("No outstanding invoices found for the {0} {1} which qualify the filters you have specified.")
|
frappe.msgprint(_("No outstanding invoices found for the {0} {1} which qualify the filters you have specified.")
|
||||||
.format(args.get("party_type").lower(), frappe.bold(args.get("party"))))
|
.format(_(args.get("party_type")).lower(), frappe.bold(args.get("party"))))
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -1599,13 +1585,6 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
|||||||
})
|
})
|
||||||
pe.set_difference_amount()
|
pe.set_difference_amount()
|
||||||
|
|
||||||
if doc.doctype == 'Purchase Order' and doc.apply_tds:
|
|
||||||
pe.apply_tax_withholding_amount = 1
|
|
||||||
pe.tax_withholding_category = doc.tax_withholding_category
|
|
||||||
|
|
||||||
if not pe.advance_tax_account:
|
|
||||||
pe.advance_tax_account = frappe.db.get_value('Company', pe.company, 'unrealized_profit_loss_account')
|
|
||||||
|
|
||||||
return pe
|
return pe
|
||||||
|
|
||||||
def get_bank_cash_account(doc, bank_account):
|
def get_bank_cash_account(doc, bank_account):
|
||||||
|
|||||||
@ -1,55 +0,0 @@
|
|||||||
QUnit.module('Payment Entry');
|
|
||||||
|
|
||||||
QUnit.test("test payment entry", function(assert) {
|
|
||||||
assert.expect(6);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Sales Invoice', [
|
|
||||||
{customer: 'Test Customer 1'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{'item_code': 'Test Product 1'},
|
|
||||||
{'qty': 1},
|
|
||||||
{'rate': 101},
|
|
||||||
]
|
|
||||||
]}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => frappe.tests.click_button('Close'),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => frappe.click_button('Make'),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => frappe.click_link('Payment'),
|
|
||||||
() => frappe.timeout(2),
|
|
||||||
() => {
|
|
||||||
assert.equal(frappe.get_route()[1], 'Payment Entry',
|
|
||||||
'made payment entry');
|
|
||||||
assert.equal(cur_frm.doc.party, 'Test Customer 1',
|
|
||||||
'customer set in payment entry');
|
|
||||||
assert.equal(cur_frm.doc.paid_amount, 101,
|
|
||||||
'paid amount set in payment entry');
|
|
||||||
assert.equal(cur_frm.doc.references[0].allocated_amount, 101,
|
|
||||||
'amount allocated against sales invoice');
|
|
||||||
},
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => cur_frm.set_value('paid_amount', 100),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => {
|
|
||||||
frappe.model.set_value("Payment Entry Reference", cur_frm.doc.references[0].name,
|
|
||||||
"allocated_amount", 101);
|
|
||||||
},
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => frappe.click_button('Write Off Difference Amount'),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => {
|
|
||||||
assert.equal(cur_frm.doc.difference_amount, 0, 'difference amount is zero');
|
|
||||||
assert.equal(cur_frm.doc.deductions[0].amount, 1, 'Write off amount = 1');
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
QUnit.module('Payment Entry');
|
|
||||||
|
|
||||||
QUnit.test("test payment entry", function(assert) {
|
|
||||||
assert.expect(7 );
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Purchase Invoice', [
|
|
||||||
{supplier: 'Test Supplier'},
|
|
||||||
{bill_no: 'in1234'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{'qty': 2},
|
|
||||||
{'item_code': 'Test Product 1'},
|
|
||||||
{'rate':1000},
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
{update_stock:1},
|
|
||||||
{supplier_address: 'Test1-Billing'},
|
|
||||||
{contact_person: 'Contact 3-Test Supplier'},
|
|
||||||
{tc_name: 'Test Term 1'},
|
|
||||||
{terms: 'This is just a Test'}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => frappe.click_button('Make'),
|
|
||||||
() => frappe.timeout(2),
|
|
||||||
() => frappe.click_link('Payment'),
|
|
||||||
() => frappe.timeout(3),
|
|
||||||
() => cur_frm.set_value('mode_of_payment','Cash'),
|
|
||||||
() => frappe.timeout(3),
|
|
||||||
() => {
|
|
||||||
assert.equal(frappe.get_route()[1], 'Payment Entry',
|
|
||||||
'made payment entry');
|
|
||||||
assert.equal(cur_frm.doc.party, 'Test Supplier',
|
|
||||||
'supplier set in payment entry');
|
|
||||||
assert.equal(cur_frm.doc.paid_amount, 2000,
|
|
||||||
'paid amount set in payment entry');
|
|
||||||
assert.equal(cur_frm.doc.references[0].allocated_amount, 2000,
|
|
||||||
'amount allocated against purchase invoice');
|
|
||||||
assert.equal(cur_frm.doc.references[0].bill_no, 'in1234',
|
|
||||||
'invoice number allocated against purchase invoice');
|
|
||||||
assert.equal(cur_frm.get_field('total_allocated_amount').value, 2000,
|
|
||||||
'correct amount allocated in Write Off');
|
|
||||||
assert.equal(cur_frm.get_field('unallocated_amount').value, 0,
|
|
||||||
'correct amount unallocated in Write Off');
|
|
||||||
},
|
|
||||||
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(3),
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
QUnit.module('Accounts');
|
|
||||||
|
|
||||||
QUnit.test("test payment entry", function(assert) {
|
|
||||||
assert.expect(1);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Payment Entry', [
|
|
||||||
{payment_type:'Receive'},
|
|
||||||
{mode_of_payment:'Cash'},
|
|
||||||
{party_type:'Customer'},
|
|
||||||
{party:'Test Customer 3'},
|
|
||||||
{paid_amount:675},
|
|
||||||
{reference_no:123},
|
|
||||||
{reference_date: frappe.datetime.add_days(frappe.datetime.nowdate(), 0)},
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => {
|
|
||||||
// get_item_details
|
|
||||||
assert.ok(cur_frm.doc.total_allocated_amount==675, "Allocated AmountCorrect");
|
|
||||||
},
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
QUnit.module('Payment Entry');
|
|
||||||
|
|
||||||
QUnit.test("test payment entry", function(assert) {
|
|
||||||
assert.expect(8);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Sales Invoice', [
|
|
||||||
{customer: 'Test Customer 1'},
|
|
||||||
{company: 'For Testing'},
|
|
||||||
{currency: 'INR'},
|
|
||||||
{selling_price_list: '_Test Price List'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{'qty': 1},
|
|
||||||
{'item_code': 'Test Product 1'},
|
|
||||||
]
|
|
||||||
]}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(1.5),
|
|
||||||
() => frappe.click_button('Close'),
|
|
||||||
() => frappe.timeout(0.5),
|
|
||||||
() => frappe.click_button('Make'),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => frappe.click_link('Payment'),
|
|
||||||
() => frappe.timeout(2),
|
|
||||||
() => cur_frm.set_value("paid_to", "_Test Cash - FT"),
|
|
||||||
() => frappe.timeout(0.5),
|
|
||||||
() => {
|
|
||||||
assert.equal(frappe.get_route()[1], 'Payment Entry', 'made payment entry');
|
|
||||||
assert.equal(cur_frm.doc.party, 'Test Customer 1', 'customer set in payment entry');
|
|
||||||
assert.equal(cur_frm.doc.paid_from, 'Debtors - FT', 'customer account set in payment entry');
|
|
||||||
assert.equal(cur_frm.doc.paid_amount, 100, 'paid amount set in payment entry');
|
|
||||||
assert.equal(cur_frm.doc.references[0].allocated_amount, 100,
|
|
||||||
'amount allocated against sales invoice');
|
|
||||||
},
|
|
||||||
() => cur_frm.set_value('paid_amount', 95),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => {
|
|
||||||
frappe.model.set_value("Payment Entry Reference",
|
|
||||||
cur_frm.doc.references[0].name, "allocated_amount", 100);
|
|
||||||
},
|
|
||||||
() => frappe.timeout(.5),
|
|
||||||
() => {
|
|
||||||
assert.equal(cur_frm.doc.difference_amount, 5, 'difference amount is 5');
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
frappe.db.set_value("Company", "For Testing", "write_off_account", "_Test Write Off - FT");
|
|
||||||
frappe.timeout(1);
|
|
||||||
frappe.db.set_value("Company", "For Testing",
|
|
||||||
"exchange_gain_loss_account", "_Test Exchange Gain/Loss - FT");
|
|
||||||
},
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => frappe.click_button('Write Off Difference Amount'),
|
|
||||||
() => frappe.timeout(2),
|
|
||||||
() => {
|
|
||||||
assert.equal(cur_frm.doc.difference_amount, 0, 'difference amount is zero');
|
|
||||||
assert.equal(cur_frm.doc.deductions[0].amount, 5, 'Write off amount = 5');
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -548,10 +548,14 @@ def make_payment_order(source_name, target_doc=None):
|
|||||||
|
|
||||||
return doclist
|
return doclist
|
||||||
|
|
||||||
def validate_payment(doc, method=""):
|
def validate_payment(doc, method=None):
|
||||||
if not frappe.db.has_column(doc.reference_doctype, 'status'):
|
if doc.reference_doctype != "Payment Request" or (
|
||||||
|
frappe.db.get_value(doc.reference_doctype, doc.reference_docname, 'status')
|
||||||
|
!= "Paid"
|
||||||
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
status = frappe.db.get_value(doc.reference_doctype, doc.reference_docname, 'status')
|
frappe.throw(
|
||||||
if status == 'Paid':
|
_("The Payment Request {0} is already paid, cannot process payment twice")
|
||||||
frappe.throw(_("The Payment Request {0} is already paid, cannot process payment twice").format(doc.reference_docname))
|
.format(doc.reference_docname)
|
||||||
|
)
|
||||||
|
|||||||
@ -88,9 +88,10 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
|
|
||||||
for acc in pl_accounts:
|
for acc in pl_accounts:
|
||||||
if flt(acc.bal_in_company_currency):
|
if flt(acc.bal_in_company_currency):
|
||||||
|
cost_center = acc.cost_center if self.cost_center_wise_pnl else company_cost_center
|
||||||
gl_entry = self.get_gl_dict({
|
gl_entry = self.get_gl_dict({
|
||||||
"account": self.closing_account_head,
|
"account": self.closing_account_head,
|
||||||
"cost_center": acc.cost_center or company_cost_center,
|
"cost_center": cost_center,
|
||||||
"finance_book": acc.finance_book,
|
"finance_book": acc.finance_book,
|
||||||
"account_currency": acc.account_currency,
|
"account_currency": acc.account_currency,
|
||||||
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,
|
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,
|
||||||
|
|||||||
@ -66,8 +66,8 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
|||||||
company = create_company()
|
company = create_company()
|
||||||
surplus_account = create_account()
|
surplus_account = create_account()
|
||||||
|
|
||||||
cost_center1 = create_cost_center("Test Cost Center 1")
|
cost_center1 = create_cost_center("Main")
|
||||||
cost_center2 = create_cost_center("Test Cost Center 2")
|
cost_center2 = create_cost_center("Western Branch")
|
||||||
|
|
||||||
create_sales_invoice(
|
create_sales_invoice(
|
||||||
company=company,
|
company=company,
|
||||||
@ -86,7 +86,10 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
|||||||
debit_to="Debtors - TPC"
|
debit_to="Debtors - TPC"
|
||||||
)
|
)
|
||||||
|
|
||||||
pcv = self.make_period_closing_voucher()
|
pcv = self.make_period_closing_voucher(submit=False)
|
||||||
|
pcv.cost_center_wise_pnl = 1
|
||||||
|
pcv.save()
|
||||||
|
pcv.submit()
|
||||||
surplus_account = pcv.closing_account_head
|
surplus_account = pcv.closing_account_head
|
||||||
|
|
||||||
expected_gle = (
|
expected_gle = (
|
||||||
@ -149,7 +152,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(pcv_gle, expected_gle)
|
self.assertEqual(pcv_gle, expected_gle)
|
||||||
|
|
||||||
def make_period_closing_voucher(self):
|
def make_period_closing_voucher(self, submit=True):
|
||||||
surplus_account = create_account()
|
surplus_account = create_account()
|
||||||
cost_center = create_cost_center("Test Cost Center 1")
|
cost_center = create_cost_center("Test Cost Center 1")
|
||||||
pcv = frappe.get_doc({
|
pcv = frappe.get_doc({
|
||||||
@ -163,7 +166,8 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
|||||||
"remarks": "test"
|
"remarks": "test"
|
||||||
})
|
})
|
||||||
pcv.insert()
|
pcv.insert()
|
||||||
pcv.submit()
|
if submit:
|
||||||
|
pcv.submit()
|
||||||
|
|
||||||
return pcv
|
return pcv
|
||||||
|
|
||||||
|
|||||||
@ -171,6 +171,7 @@
|
|||||||
"sales_team_section_break",
|
"sales_team_section_break",
|
||||||
"sales_partner",
|
"sales_partner",
|
||||||
"column_break10",
|
"column_break10",
|
||||||
|
"amount_eligible_for_commission",
|
||||||
"commission_rate",
|
"commission_rate",
|
||||||
"total_commission",
|
"total_commission",
|
||||||
"section_break2",
|
"section_break2",
|
||||||
@ -1561,16 +1562,23 @@
|
|||||||
"label": "Coupon Code",
|
"label": "Coupon Code",
|
||||||
"options": "Coupon Code",
|
"options": "Coupon Code",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amount_eligible_for_commission",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Amount Eligible for Commission",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-08-27 20:12:57.306772",
|
"modified": "2021-10-05 12:11:53.871828",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice",
|
"name": "POS Invoice",
|
||||||
"name_case": "Title Case",
|
"name_case": "Title Case",
|
||||||
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -46,6 +46,7 @@
|
|||||||
"base_amount",
|
"base_amount",
|
||||||
"pricing_rules",
|
"pricing_rules",
|
||||||
"is_free_item",
|
"is_free_item",
|
||||||
|
"grant_commission",
|
||||||
"section_break_21",
|
"section_break_21",
|
||||||
"net_rate",
|
"net_rate",
|
||||||
"net_amount",
|
"net_amount",
|
||||||
@ -800,14 +801,22 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "grant_commission",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Grant Commission",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-01-04 17:34:49.924531",
|
"modified": "2021-10-05 12:23:47.506290",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice Item",
|
"name": "POS Invoice Item",
|
||||||
|
"naming_rule": "Random",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
|
|||||||
@ -110,9 +110,15 @@ class POSInvoiceMergeLog(Document):
|
|||||||
|
|
||||||
def merge_pos_invoice_into(self, invoice, data):
|
def merge_pos_invoice_into(self, invoice, data):
|
||||||
items, payments, taxes = [], [], []
|
items, payments, taxes = [], [], []
|
||||||
|
|
||||||
loyalty_amount_sum, loyalty_points_sum = 0, 0
|
loyalty_amount_sum, loyalty_points_sum = 0, 0
|
||||||
|
|
||||||
rounding_adjustment, base_rounding_adjustment = 0, 0
|
rounding_adjustment, base_rounding_adjustment = 0, 0
|
||||||
rounded_total, base_rounded_total = 0, 0
|
rounded_total, base_rounded_total = 0, 0
|
||||||
|
|
||||||
|
loyalty_amount_sum, loyalty_points_sum, idx = 0, 0, 1
|
||||||
|
|
||||||
|
|
||||||
for doc in data:
|
for doc in data:
|
||||||
map_doc(doc, invoice, table_map={ "doctype": invoice.doctype })
|
map_doc(doc, invoice, table_map={ "doctype": invoice.doctype })
|
||||||
|
|
||||||
@ -146,6 +152,8 @@ class POSInvoiceMergeLog(Document):
|
|||||||
found = True
|
found = True
|
||||||
if not found:
|
if not found:
|
||||||
tax.charge_type = 'Actual'
|
tax.charge_type = 'Actual'
|
||||||
|
tax.idx = idx
|
||||||
|
idx += 1
|
||||||
tax.included_in_print_rate = 0
|
tax.included_in_print_rate = 0
|
||||||
tax.tax_amount = tax.tax_amount_after_discount_amount
|
tax.tax_amount = tax.tax_amount_after_discount_amount
|
||||||
tax.base_tax_amount = tax.base_tax_amount_after_discount_amount
|
tax.base_tax_amount = tax.base_tax_amount_after_discount_amount
|
||||||
@ -163,8 +171,8 @@ class POSInvoiceMergeLog(Document):
|
|||||||
payments.append(payment)
|
payments.append(payment)
|
||||||
rounding_adjustment += doc.rounding_adjustment
|
rounding_adjustment += doc.rounding_adjustment
|
||||||
rounded_total += doc.rounded_total
|
rounded_total += doc.rounded_total
|
||||||
base_rounding_adjustment += doc.rounding_adjustment
|
base_rounding_adjustment += doc.base_rounding_adjustment
|
||||||
base_rounded_total += doc.rounded_total
|
base_rounded_total += doc.base_rounded_total
|
||||||
|
|
||||||
|
|
||||||
if loyalty_points_sum:
|
if loyalty_points_sum:
|
||||||
@ -176,9 +184,9 @@ class POSInvoiceMergeLog(Document):
|
|||||||
invoice.set('payments', payments)
|
invoice.set('payments', payments)
|
||||||
invoice.set('taxes', taxes)
|
invoice.set('taxes', taxes)
|
||||||
invoice.set('rounding_adjustment',rounding_adjustment)
|
invoice.set('rounding_adjustment',rounding_adjustment)
|
||||||
invoice.set('rounding_adjustment',base_rounding_adjustment)
|
invoice.set('base_rounding_adjustment',base_rounding_adjustment)
|
||||||
invoice.set('base_rounded_total',base_rounded_total)
|
|
||||||
invoice.set('rounded_total',rounded_total)
|
invoice.set('rounded_total',rounded_total)
|
||||||
|
invoice.set('base_rounded_total',base_rounded_total)
|
||||||
invoice.additional_discount_percentage = 0
|
invoice.additional_discount_percentage = 0
|
||||||
invoice.discount_amount = 0.0
|
invoice.discount_amount = 0.0
|
||||||
invoice.taxes_and_charges = None
|
invoice.taxes_and_charges = None
|
||||||
|
|||||||
@ -3,22 +3,20 @@
|
|||||||
|
|
||||||
{% include "erpnext/public/js/controllers/accounts.js" %}
|
{% include "erpnext/public/js/controllers/accounts.js" %}
|
||||||
|
|
||||||
frappe.ui.form.on("POS Profile", "onload", function(frm) {
|
|
||||||
frm.set_query("selling_price_list", function() {
|
|
||||||
return { filters: { selling: 1 } };
|
|
||||||
});
|
|
||||||
|
|
||||||
frm.set_query("tc_name", function() {
|
|
||||||
return { filters: { selling: 1 } };
|
|
||||||
});
|
|
||||||
|
|
||||||
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
|
||||||
return erpnext.queries.warehouse(frm.doc);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
frappe.ui.form.on('POS Profile', {
|
frappe.ui.form.on('POS Profile', {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
|
frm.set_query("selling_price_list", function() {
|
||||||
|
return { filters: { selling: 1 } };
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("tc_name", function() {
|
||||||
|
return { filters: { selling: 1 } };
|
||||||
|
});
|
||||||
|
|
||||||
|
erpnext.queries.setup_queries(frm, "Warehouse", function() {
|
||||||
|
return erpnext.queries.warehouse(frm.doc);
|
||||||
|
});
|
||||||
|
|
||||||
frm.set_query("print_format", function() {
|
frm.set_query("print_format", function() {
|
||||||
return {
|
return {
|
||||||
filters: [
|
filters: [
|
||||||
@ -27,10 +25,16 @@ frappe.ui.form.on('POS Profile', {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query("account_for_change_amount", function() {
|
frm.set_query("account_for_change_amount", function(doc) {
|
||||||
|
if (!doc.company) {
|
||||||
|
frappe.throw(__('Please set Company'));
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
account_type: ['in', ["Cash", "Bank"]]
|
account_type: ['in', ["Cash", "Bank"]],
|
||||||
|
is_group: 0,
|
||||||
|
company: doc.company
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -45,7 +49,7 @@ frappe.ui.form.on('POS Profile', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query('company_address', function(doc) {
|
frm.set_query('company_address', function(doc) {
|
||||||
if(!doc.company) {
|
if (!doc.company) {
|
||||||
frappe.throw(__('Please set Company'));
|
frappe.throw(__('Please set Company'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,11 +62,79 @@ frappe.ui.form.on('POS Profile', {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query('income_account', function(doc) {
|
||||||
|
if (!doc.company) {
|
||||||
|
frappe.throw(__('Please set Company'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'is_group': 0,
|
||||||
|
'company': doc.company,
|
||||||
|
'account_type': "Income Account"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query('cost_center', function(doc) {
|
||||||
|
if (!doc.company) {
|
||||||
|
frappe.throw(__('Please set Company'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'company': doc.company,
|
||||||
|
'is_group': 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query('expense_account', function(doc) {
|
||||||
|
if (!doc.company) {
|
||||||
|
frappe.throw(__('Please set Company'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"report_type": "Profit and Loss",
|
||||||
|
"company": doc.company,
|
||||||
|
"is_group": 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("select_print_heading", function() {
|
||||||
|
return {
|
||||||
|
filters: [
|
||||||
|
['Print Heading', 'docstatus', '!=', 2]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("write_off_account", function(doc) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'report_type': 'Profit and Loss',
|
||||||
|
'is_group': 0,
|
||||||
|
'company': doc.company
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("write_off_cost_center", function(doc) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'is_group': 0,
|
||||||
|
'company': doc.company
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
if(frm.doc.company) {
|
if (frm.doc.company) {
|
||||||
frm.trigger("toggle_display_account_head");
|
frm.trigger("toggle_display_account_head");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -76,71 +148,4 @@ frappe.ui.form.on('POS Profile', {
|
|||||||
frm.toggle_display('expense_account',
|
frm.toggle_display('expense_account',
|
||||||
erpnext.is_perpetual_inventory_enabled(frm.doc.company));
|
erpnext.is_perpetual_inventory_enabled(frm.doc.company));
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
// Income Account
|
|
||||||
// --------------------------------
|
|
||||||
cur_frm.fields_dict['income_account'].get_query = function(doc,cdt,cdn) {
|
|
||||||
return{
|
|
||||||
filters:{
|
|
||||||
'is_group': 0,
|
|
||||||
'company': doc.company,
|
|
||||||
'account_type': "Income Account"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Cost Center
|
|
||||||
// -----------------------------
|
|
||||||
cur_frm.fields_dict['cost_center'].get_query = function(doc,cdt,cdn) {
|
|
||||||
return{
|
|
||||||
filters:{
|
|
||||||
'company': doc.company,
|
|
||||||
'is_group': 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Expense Account
|
|
||||||
// -----------------------------
|
|
||||||
cur_frm.fields_dict["expense_account"].get_query = function(doc) {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
"report_type": "Profit and Loss",
|
|
||||||
"company": doc.company,
|
|
||||||
"is_group": 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// ------------------ Get Print Heading ------------------------------------
|
|
||||||
cur_frm.fields_dict['select_print_heading'].get_query = function(doc, cdt, cdn) {
|
|
||||||
return{
|
|
||||||
filters:[
|
|
||||||
['Print Heading', 'docstatus', '!=', 2]
|
|
||||||
]
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
cur_frm.fields_dict.write_off_account.get_query = function(doc) {
|
|
||||||
return{
|
|
||||||
filters:{
|
|
||||||
'report_type': 'Profit and Loss',
|
|
||||||
'is_group': 0,
|
|
||||||
'company': doc.company
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Write off cost center
|
|
||||||
// -----------------------
|
|
||||||
cur_frm.fields_dict.write_off_cost_center.get_query = function(doc) {
|
|
||||||
return{
|
|
||||||
filters:{
|
|
||||||
'is_group': 0,
|
|
||||||
'company': doc.company
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@ frappe.ui.form.on('Pricing Rule', {
|
|||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
var help_content =
|
var help_content =
|
||||||
`<table class="table table-bordered" style="background-color: #f9f9f9;">
|
`<table class="table table-bordered" style="background-color: var(--scrollbar-track-color);">
|
||||||
<tr><td>
|
<tr><td>
|
||||||
<h4>
|
<h4>
|
||||||
<i class="fa fa-hand-right"></i>
|
<i class="fa fa-hand-right"></i>
|
||||||
|
|||||||
@ -543,6 +543,75 @@ class TestPricingRule(unittest.TestCase):
|
|||||||
frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
|
frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
|
||||||
item.delete()
|
item.delete()
|
||||||
|
|
||||||
|
def test_pricing_rule_for_different_currency(self):
|
||||||
|
make_item("Test Sanitizer Item")
|
||||||
|
|
||||||
|
pricing_rule_record = {
|
||||||
|
"doctype": "Pricing Rule",
|
||||||
|
"title": "_Test Sanitizer Rule",
|
||||||
|
"apply_on": "Item Code",
|
||||||
|
"items": [{
|
||||||
|
"item_code": "Test Sanitizer Item",
|
||||||
|
}],
|
||||||
|
"selling": 1,
|
||||||
|
"currency": "INR",
|
||||||
|
"rate_or_discount": "Rate",
|
||||||
|
"rate": 0,
|
||||||
|
"priority": 2,
|
||||||
|
"margin_type": "Percentage",
|
||||||
|
"margin_rate_or_amount": 0.0,
|
||||||
|
"company": "_Test Company"
|
||||||
|
}
|
||||||
|
|
||||||
|
rule = frappe.get_doc(pricing_rule_record)
|
||||||
|
rule.rate_or_discount = 'Rate'
|
||||||
|
rule.rate = 100.0
|
||||||
|
rule.insert()
|
||||||
|
|
||||||
|
rule1 = frappe.get_doc(pricing_rule_record)
|
||||||
|
rule1.currency = 'USD'
|
||||||
|
rule1.rate_or_discount = 'Rate'
|
||||||
|
rule1.rate = 2.0
|
||||||
|
rule1.priority = 1
|
||||||
|
rule1.insert()
|
||||||
|
|
||||||
|
args = frappe._dict({
|
||||||
|
"item_code": "Test Sanitizer Item",
|
||||||
|
"company": "_Test Company",
|
||||||
|
"price_list": "_Test Price List",
|
||||||
|
"currency": "USD",
|
||||||
|
"doctype": "Sales Invoice",
|
||||||
|
"conversion_rate": 1,
|
||||||
|
"price_list_currency": "_Test Currency",
|
||||||
|
"plc_conversion_rate": 1,
|
||||||
|
"order_type": "Sales",
|
||||||
|
"customer": "_Test Customer",
|
||||||
|
"name": None,
|
||||||
|
"transaction_date": frappe.utils.nowdate()
|
||||||
|
})
|
||||||
|
|
||||||
|
details = get_item_details(args)
|
||||||
|
self.assertEqual(details.price_list_rate, 2.0)
|
||||||
|
|
||||||
|
|
||||||
|
args = frappe._dict({
|
||||||
|
"item_code": "Test Sanitizer Item",
|
||||||
|
"company": "_Test Company",
|
||||||
|
"price_list": "_Test Price List",
|
||||||
|
"currency": "INR",
|
||||||
|
"doctype": "Sales Invoice",
|
||||||
|
"conversion_rate": 1,
|
||||||
|
"price_list_currency": "_Test Currency",
|
||||||
|
"plc_conversion_rate": 1,
|
||||||
|
"order_type": "Sales",
|
||||||
|
"customer": "_Test Customer",
|
||||||
|
"name": None,
|
||||||
|
"transaction_date": frappe.utils.nowdate()
|
||||||
|
})
|
||||||
|
|
||||||
|
details = get_item_details(args)
|
||||||
|
self.assertEqual(details.price_list_rate, 100.0)
|
||||||
|
|
||||||
def test_pricing_rule_for_transaction(self):
|
def test_pricing_rule_for_transaction(self):
|
||||||
make_item("Water Flask 1")
|
make_item("Water Flask 1")
|
||||||
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
|
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
QUnit.module('Pricing Rule');
|
|
||||||
|
|
||||||
QUnit.test("test pricing rule", function(assert) {
|
|
||||||
assert.expect(2);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make("Pricing Rule", [
|
|
||||||
{title: 'Test Pricing Rule'},
|
|
||||||
{item_code:'Test Product 2'},
|
|
||||||
{selling:1},
|
|
||||||
{applicable_for:'Customer'},
|
|
||||||
{customer:'Test Customer 3'},
|
|
||||||
{currency: frappe.defaults.get_default("currency")}
|
|
||||||
{min_qty:1},
|
|
||||||
{max_qty:20},
|
|
||||||
{valid_upto: frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)},
|
|
||||||
{discount_percentage:10},
|
|
||||||
{for_price_list:'Standard Selling'}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
assert.ok(cur_frm.doc.item_code=='Test Product 2');
|
|
||||||
assert.ok(cur_frm.doc.customer=='Test Customer 3');
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
QUnit.module('Pricing Rule');
|
|
||||||
|
|
||||||
QUnit.test("test pricing rule with different currency", function(assert) {
|
|
||||||
assert.expect(3);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make("Pricing Rule", [
|
|
||||||
{title: 'Test Pricing Rule 2'},
|
|
||||||
{apply_on: 'Item Code'},
|
|
||||||
{item_code:'Test Product 4'},
|
|
||||||
{selling:1},
|
|
||||||
{priority: 1},
|
|
||||||
{min_qty:1},
|
|
||||||
{max_qty:20},
|
|
||||||
{valid_upto: frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)},
|
|
||||||
{margin_type: 'Amount'},
|
|
||||||
{margin_rate_or_amount: 20},
|
|
||||||
{rate_or_discount: 'Rate'},
|
|
||||||
{rate:200},
|
|
||||||
{currency:'USD'}
|
|
||||||
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
() => {
|
|
||||||
assert.ok(cur_frm.doc.item_code=='Test Product 4');
|
|
||||||
},
|
|
||||||
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Sales Order', [
|
|
||||||
{customer: 'Test Customer 1'},
|
|
||||||
{currency: 'INR'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{'delivery_date': frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)},
|
|
||||||
{'qty': 5},
|
|
||||||
{'item_code': "Test Product 4"}
|
|
||||||
]
|
|
||||||
]}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
() => {
|
|
||||||
// get_item_details
|
|
||||||
assert.ok(cur_frm.doc.items[0].pricing_rule=='Test Pricing Rule 2', "Pricing rule correct");
|
|
||||||
// margin not applied because different currency in pricing rule
|
|
||||||
assert.ok(cur_frm.doc.items[0].margin_type==null, "Margin correct");
|
|
||||||
},
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
QUnit.module('Pricing Rule');
|
|
||||||
|
|
||||||
QUnit.test("test pricing rule with same currency", function(assert) {
|
|
||||||
assert.expect(4);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make("Pricing Rule", [
|
|
||||||
{title: 'Test Pricing Rule 1'},
|
|
||||||
{apply_on: 'Item Code'},
|
|
||||||
{item_code:'Test Product 4'},
|
|
||||||
{selling:1},
|
|
||||||
{min_qty:1},
|
|
||||||
{max_qty:20},
|
|
||||||
{valid_upto: frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)},
|
|
||||||
{rate_or_discount: 'Rate'},
|
|
||||||
{rate:200},
|
|
||||||
{currency:'USD'}
|
|
||||||
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
() => {
|
|
||||||
assert.ok(cur_frm.doc.item_code=='Test Product 4');
|
|
||||||
},
|
|
||||||
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Sales Order', [
|
|
||||||
{customer: 'Test Customer 1'},
|
|
||||||
{currency: 'USD'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{'delivery_date': frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)},
|
|
||||||
{'qty': 5},
|
|
||||||
{'item_code': "Test Product 4"}
|
|
||||||
]
|
|
||||||
]}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
() => {
|
|
||||||
// get_item_details
|
|
||||||
assert.ok(cur_frm.doc.items[0].pricing_rule=='Test Pricing Rule 1', "Pricing rule correct");
|
|
||||||
assert.ok(cur_frm.doc.items[0].price_list_rate==200, "Item rate correct");
|
|
||||||
// get_total
|
|
||||||
assert.ok(cur_frm.doc.total== 1000, "Total correct");
|
|
||||||
},
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -264,6 +264,11 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
|
|||||||
else:
|
else:
|
||||||
p.variant_of = None
|
p.variant_of = None
|
||||||
|
|
||||||
|
if len(pricing_rules) > 1:
|
||||||
|
filtered_rules = list(filter(lambda x: x.currency==args.get('currency'), pricing_rules))
|
||||||
|
if filtered_rules:
|
||||||
|
pricing_rules = filtered_rules
|
||||||
|
|
||||||
# find pricing rule with highest priority
|
# find pricing rule with highest priority
|
||||||
if pricing_rules:
|
if pricing_rules:
|
||||||
max_priority = max(cint(p.priority) for p in pricing_rules)
|
max_priority = max(cint(p.priority) for p in pricing_rules)
|
||||||
|
|||||||
@ -20,6 +20,9 @@ price_discount_fields = ['rate_or_discount', 'apply_discount_on', 'apply_discoun
|
|||||||
product_discount_fields = ['free_item', 'free_qty', 'free_item_uom',
|
product_discount_fields = ['free_item', 'free_qty', 'free_item_uom',
|
||||||
'free_item_rate', 'same_item', 'is_recursive', 'apply_multiple_pricing_rules']
|
'free_item_rate', 'same_item', 'is_recursive', 'apply_multiple_pricing_rules']
|
||||||
|
|
||||||
|
class TransactionExists(frappe.ValidationError):
|
||||||
|
pass
|
||||||
|
|
||||||
class PromotionalScheme(Document):
|
class PromotionalScheme(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if not self.selling and not self.buying:
|
if not self.selling and not self.buying:
|
||||||
@ -28,6 +31,40 @@ class PromotionalScheme(Document):
|
|||||||
or self.product_discount_slabs):
|
or self.product_discount_slabs):
|
||||||
frappe.throw(_("Price or product discount slabs are required"))
|
frappe.throw(_("Price or product discount slabs are required"))
|
||||||
|
|
||||||
|
self.validate_applicable_for()
|
||||||
|
self.validate_pricing_rules()
|
||||||
|
|
||||||
|
def validate_applicable_for(self):
|
||||||
|
if self.applicable_for:
|
||||||
|
applicable_for = frappe.scrub(self.applicable_for)
|
||||||
|
|
||||||
|
if not self.get(applicable_for):
|
||||||
|
msg = (f'The field {frappe.bold(self.applicable_for)} is required')
|
||||||
|
frappe.throw(_(msg))
|
||||||
|
|
||||||
|
def validate_pricing_rules(self):
|
||||||
|
if self.is_new():
|
||||||
|
return
|
||||||
|
|
||||||
|
transaction_exists = False
|
||||||
|
docnames = []
|
||||||
|
|
||||||
|
# If user has changed applicable for
|
||||||
|
if self._doc_before_save.applicable_for == self.applicable_for:
|
||||||
|
return
|
||||||
|
|
||||||
|
docnames = frappe.get_all('Pricing Rule',
|
||||||
|
filters= {'promotional_scheme': self.name})
|
||||||
|
|
||||||
|
for docname in docnames:
|
||||||
|
if frappe.db.exists('Pricing Rule Detail',
|
||||||
|
{'pricing_rule': docname.name, 'docstatus': ('<', 2)}):
|
||||||
|
raise_for_transaction_exists(self.name)
|
||||||
|
|
||||||
|
if docnames and not transaction_exists:
|
||||||
|
for docname in docnames:
|
||||||
|
frappe.delete_doc('Pricing Rule', docname.name)
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
pricing_rules = frappe.get_all(
|
pricing_rules = frappe.get_all(
|
||||||
'Pricing Rule',
|
'Pricing Rule',
|
||||||
@ -67,6 +104,13 @@ class PromotionalScheme(Document):
|
|||||||
{'promotional_scheme': self.name}):
|
{'promotional_scheme': self.name}):
|
||||||
frappe.delete_doc('Pricing Rule', rule.name)
|
frappe.delete_doc('Pricing Rule', rule.name)
|
||||||
|
|
||||||
|
def raise_for_transaction_exists(name):
|
||||||
|
msg = (f"""You can't change the {frappe.bold(_('Applicable For'))}
|
||||||
|
because transactions are present against the Promotional Scheme {frappe.bold(name)}. """)
|
||||||
|
msg += 'Kindly disable this Promotional Scheme and create new for new Applicable For.'
|
||||||
|
|
||||||
|
frappe.throw(_(msg), TransactionExists)
|
||||||
|
|
||||||
def get_pricing_rules(doc, rules=None):
|
def get_pricing_rules(doc, rules=None):
|
||||||
if rules is None:
|
if rules is None:
|
||||||
rules = {}
|
rules = {}
|
||||||
@ -84,45 +128,59 @@ def _get_pricing_rules(doc, child_doc, discount_fields, rules=None):
|
|||||||
new_doc = []
|
new_doc = []
|
||||||
args = get_args_for_pricing_rule(doc)
|
args = get_args_for_pricing_rule(doc)
|
||||||
applicable_for = frappe.scrub(doc.get('applicable_for'))
|
applicable_for = frappe.scrub(doc.get('applicable_for'))
|
||||||
|
|
||||||
for idx, d in enumerate(doc.get(child_doc)):
|
for idx, d in enumerate(doc.get(child_doc)):
|
||||||
if d.name in rules:
|
if d.name in rules:
|
||||||
for applicable_for_value in args.get(applicable_for):
|
if not args.get(applicable_for):
|
||||||
temp_args = args.copy()
|
docname = get_pricing_rule_docname(d)
|
||||||
docname = frappe.get_all(
|
pr = prepare_pricing_rule(args, doc, child_doc, discount_fields, d, docname)
|
||||||
'Pricing Rule',
|
|
||||||
fields = ["promotional_scheme_id", "name", applicable_for],
|
|
||||||
filters = {
|
|
||||||
'promotional_scheme_id': d.name,
|
|
||||||
applicable_for: applicable_for_value
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if docname:
|
|
||||||
pr = frappe.get_doc('Pricing Rule', docname[0].get('name'))
|
|
||||||
temp_args[applicable_for] = applicable_for_value
|
|
||||||
pr = set_args(temp_args, pr, doc, child_doc, discount_fields, d)
|
|
||||||
else:
|
|
||||||
pr = frappe.new_doc("Pricing Rule")
|
|
||||||
pr.title = doc.name
|
|
||||||
temp_args[applicable_for] = applicable_for_value
|
|
||||||
pr = set_args(temp_args, pr, doc, child_doc, discount_fields, d)
|
|
||||||
|
|
||||||
new_doc.append(pr)
|
new_doc.append(pr)
|
||||||
|
else:
|
||||||
|
for applicable_for_value in args.get(applicable_for):
|
||||||
|
docname = get_pricing_rule_docname(d, applicable_for, applicable_for_value)
|
||||||
|
pr = prepare_pricing_rule(args, doc, child_doc, discount_fields,
|
||||||
|
d, docname, applicable_for, applicable_for_value)
|
||||||
|
new_doc.append(pr)
|
||||||
|
|
||||||
else:
|
elif args.get(applicable_for):
|
||||||
applicable_for_values = args.get(applicable_for) or []
|
applicable_for_values = args.get(applicable_for) or []
|
||||||
for applicable_for_value in applicable_for_values:
|
for applicable_for_value in applicable_for_values:
|
||||||
pr = frappe.new_doc("Pricing Rule")
|
pr = prepare_pricing_rule(args, doc, child_doc, discount_fields,
|
||||||
pr.title = doc.name
|
d, applicable_for=applicable_for, value= applicable_for_value)
|
||||||
temp_args = args.copy()
|
|
||||||
temp_args[applicable_for] = applicable_for_value
|
|
||||||
pr = set_args(temp_args, pr, doc, child_doc, discount_fields, d)
|
|
||||||
new_doc.append(pr)
|
new_doc.append(pr)
|
||||||
|
else:
|
||||||
|
pr = prepare_pricing_rule(args, doc, child_doc, discount_fields, d)
|
||||||
|
new_doc.append(pr)
|
||||||
|
|
||||||
return new_doc
|
return new_doc
|
||||||
|
|
||||||
|
def get_pricing_rule_docname(row: dict, applicable_for: str = None, applicable_for_value: str = None) -> str:
|
||||||
|
fields = ['promotional_scheme_id', 'name']
|
||||||
|
filters = {
|
||||||
|
'promotional_scheme_id': row.name
|
||||||
|
}
|
||||||
|
|
||||||
|
if applicable_for:
|
||||||
|
fields.append(applicable_for)
|
||||||
|
filters[applicable_for] = applicable_for_value
|
||||||
|
|
||||||
|
docname = frappe.get_all('Pricing Rule', fields = fields, filters = filters)
|
||||||
|
return docname[0].name if docname else ''
|
||||||
|
|
||||||
|
def prepare_pricing_rule(args, doc, child_doc, discount_fields, d, docname=None, applicable_for=None, value=None):
|
||||||
|
if docname:
|
||||||
|
pr = frappe.get_doc("Pricing Rule", docname)
|
||||||
|
else:
|
||||||
|
pr = frappe.new_doc("Pricing Rule")
|
||||||
|
|
||||||
|
pr.title = doc.name
|
||||||
|
temp_args = args.copy()
|
||||||
|
|
||||||
|
if value:
|
||||||
|
temp_args[applicable_for] = value
|
||||||
|
|
||||||
|
return set_args(temp_args, pr, doc, child_doc, discount_fields, d)
|
||||||
|
|
||||||
def set_args(args, pr, doc, child_doc, discount_fields, child_doc_fields):
|
def set_args(args, pr, doc, child_doc, discount_fields, child_doc_fields):
|
||||||
pr.update(args)
|
pr.update(args)
|
||||||
@ -145,6 +203,7 @@ def set_args(args, pr, doc, child_doc, discount_fields, child_doc_fields):
|
|||||||
apply_on: d.get(apply_on),
|
apply_on: d.get(apply_on),
|
||||||
'uom': d.uom
|
'uom': d.uom
|
||||||
})
|
})
|
||||||
|
|
||||||
return pr
|
return pr
|
||||||
|
|
||||||
def get_args_for_pricing_rule(doc):
|
def get_args_for_pricing_rule(doc):
|
||||||
|
|||||||
@ -5,10 +5,17 @@ import unittest
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.promotional_scheme.promotional_scheme import TransactionExists
|
||||||
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
|
|
||||||
|
|
||||||
class TestPromotionalScheme(unittest.TestCase):
|
class TestPromotionalScheme(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
if frappe.db.exists('Promotional Scheme', '_Test Scheme'):
|
||||||
|
frappe.delete_doc('Promotional Scheme', '_Test Scheme')
|
||||||
|
|
||||||
def test_promotional_scheme(self):
|
def test_promotional_scheme(self):
|
||||||
ps = make_promotional_scheme()
|
ps = make_promotional_scheme(applicable_for='Customer', customer='_Test Customer')
|
||||||
price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name", "creation"],
|
price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name", "creation"],
|
||||||
filters = {'promotional_scheme': ps.name})
|
filters = {'promotional_scheme': ps.name})
|
||||||
self.assertTrue(len(price_rules),1)
|
self.assertTrue(len(price_rules),1)
|
||||||
@ -39,22 +46,62 @@ class TestPromotionalScheme(unittest.TestCase):
|
|||||||
filters = {'promotional_scheme': ps.name})
|
filters = {'promotional_scheme': ps.name})
|
||||||
self.assertEqual(price_rules, [])
|
self.assertEqual(price_rules, [])
|
||||||
|
|
||||||
def make_promotional_scheme():
|
def test_promotional_scheme_without_applicable_for(self):
|
||||||
|
ps = make_promotional_scheme()
|
||||||
|
price_rules = frappe.get_all('Pricing Rule', filters = {'promotional_scheme': ps.name})
|
||||||
|
|
||||||
|
self.assertTrue(len(price_rules), 1)
|
||||||
|
frappe.delete_doc('Promotional Scheme', ps.name)
|
||||||
|
|
||||||
|
price_rules = frappe.get_all('Pricing Rule', filters = {'promotional_scheme': ps.name})
|
||||||
|
self.assertEqual(price_rules, [])
|
||||||
|
|
||||||
|
def test_change_applicable_for_in_promotional_scheme(self):
|
||||||
|
ps = make_promotional_scheme()
|
||||||
|
price_rules = frappe.get_all('Pricing Rule', filters = {'promotional_scheme': ps.name})
|
||||||
|
self.assertTrue(len(price_rules), 1)
|
||||||
|
|
||||||
|
so = make_sales_order(qty=5, currency='USD', do_not_save=True)
|
||||||
|
so.set_missing_values()
|
||||||
|
so.save()
|
||||||
|
self.assertEqual(price_rules[0].name, so.pricing_rules[0].pricing_rule)
|
||||||
|
|
||||||
|
ps.applicable_for = 'Customer'
|
||||||
|
ps.append('customer', {
|
||||||
|
'customer': '_Test Customer'
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertRaises(TransactionExists, ps.save)
|
||||||
|
|
||||||
|
frappe.delete_doc('Sales Order', so.name)
|
||||||
|
frappe.delete_doc('Promotional Scheme', ps.name)
|
||||||
|
price_rules = frappe.get_all('Pricing Rule', filters = {'promotional_scheme': ps.name})
|
||||||
|
self.assertEqual(price_rules, [])
|
||||||
|
|
||||||
|
def make_promotional_scheme(**args):
|
||||||
|
args = frappe._dict(args)
|
||||||
|
|
||||||
ps = frappe.new_doc('Promotional Scheme')
|
ps = frappe.new_doc('Promotional Scheme')
|
||||||
ps.name = '_Test Scheme'
|
ps.name = '_Test Scheme'
|
||||||
ps.append('items',{
|
ps.append('items',{
|
||||||
'item_code': '_Test Item'
|
'item_code': '_Test Item'
|
||||||
})
|
})
|
||||||
|
|
||||||
ps.selling = 1
|
ps.selling = 1
|
||||||
ps.append('price_discount_slabs',{
|
ps.append('price_discount_slabs',{
|
||||||
'min_qty': 4,
|
'min_qty': 4,
|
||||||
|
'validate_applied_rule': 0,
|
||||||
'discount_percentage': 20,
|
'discount_percentage': 20,
|
||||||
'rule_description': 'Test'
|
'rule_description': 'Test'
|
||||||
})
|
})
|
||||||
ps.applicable_for = 'Customer'
|
|
||||||
ps.append('customer',{
|
ps.company = '_Test Company'
|
||||||
'customer': "_Test Customer"
|
if args.applicable_for:
|
||||||
})
|
ps.applicable_for = args.applicable_for
|
||||||
|
ps.append(frappe.scrub(args.applicable_for), {
|
||||||
|
frappe.scrub(args.applicable_for): args.get(frappe.scrub(args.applicable_for))
|
||||||
|
})
|
||||||
|
|
||||||
ps.save()
|
ps.save()
|
||||||
|
|
||||||
return ps
|
return ps
|
||||||
|
|||||||
@ -136,7 +136,7 @@
|
|||||||
"label": "Threshold for Suggestion"
|
"label": "Threshold for Suggestion"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "0",
|
||||||
"fieldname": "validate_applied_rule",
|
"fieldname": "validate_applied_rule",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Validate Applied Rule"
|
"label": "Validate Applied Rule"
|
||||||
@ -169,7 +169,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-08-19 15:49:29.598727",
|
"modified": "2021-11-16 00:25:33.843996",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Promotional Scheme Price Discount",
|
"name": "Promotional Scheme Price Discount",
|
||||||
|
|||||||
@ -130,6 +130,7 @@
|
|||||||
"allocate_advances_automatically",
|
"allocate_advances_automatically",
|
||||||
"get_advances",
|
"get_advances",
|
||||||
"advances",
|
"advances",
|
||||||
|
"advance_tax",
|
||||||
"payment_schedule_section",
|
"payment_schedule_section",
|
||||||
"payment_terms_template",
|
"payment_terms_template",
|
||||||
"ignore_default_payment_terms_template",
|
"ignore_default_payment_terms_template",
|
||||||
@ -1408,13 +1409,21 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_147",
|
"fieldname": "column_break_147",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "advance_tax",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Advance Tax",
|
||||||
|
"options": "Advance Tax",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-10-12 20:55:16.145651",
|
"modified": "2021-11-25 13:31:02.716727",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
|||||||
@ -114,6 +114,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.set_status()
|
self.set_status()
|
||||||
self.validate_purchase_receipt_if_update_stock()
|
self.validate_purchase_receipt_if_update_stock()
|
||||||
validate_inter_company_party(self.doctype, self.supplier, self.company, self.inter_company_invoice_reference)
|
validate_inter_company_party(self.doctype, self.supplier, self.company, self.inter_company_invoice_reference)
|
||||||
|
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||||
|
self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse")
|
||||||
|
self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
|
||||||
|
|
||||||
def validate_release_date(self):
|
def validate_release_date(self):
|
||||||
if self.release_date and getdate(nowdate()) >= getdate(self.release_date):
|
if self.release_date and getdate(nowdate()) >= getdate(self.release_date):
|
||||||
@ -294,8 +297,15 @@ class PurchaseInvoice(BuyingController):
|
|||||||
item.expense_account = stock_not_billed_account
|
item.expense_account = stock_not_billed_account
|
||||||
|
|
||||||
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
|
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
|
||||||
item.expense_account = get_asset_category_account('fixed_asset_account', item=item.item_code,
|
asset_category_account = get_asset_category_account('fixed_asset_account', item=item.item_code,
|
||||||
company = self.company)
|
company = self.company)
|
||||||
|
if not asset_category_account:
|
||||||
|
form_link = get_link_to_form('Asset Category', asset_category)
|
||||||
|
throw(
|
||||||
|
_("Please set Fixed Asset Account in {} against {}.").format(form_link, self.company),
|
||||||
|
title=_("Missing Account")
|
||||||
|
)
|
||||||
|
item.expense_account = asset_category_account
|
||||||
elif item.is_fixed_asset and item.pr_detail:
|
elif item.is_fixed_asset and item.pr_detail:
|
||||||
item.expense_account = asset_received_but_not_billed
|
item.expense_account = asset_received_but_not_billed
|
||||||
elif not item.expense_account and for_validate:
|
elif not item.expense_account and for_validate:
|
||||||
@ -427,6 +437,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
self.update_project()
|
self.update_project()
|
||||||
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||||
|
self.update_advance_tax_references()
|
||||||
|
|
||||||
self.process_common_party_accounting()
|
self.process_common_party_accounting()
|
||||||
|
|
||||||
@ -472,8 +483,6 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.make_exchange_gain_loss_gl_entries(gl_entries)
|
self.make_exchange_gain_loss_gl_entries(gl_entries)
|
||||||
self.make_internal_transfer_gl_entries(gl_entries)
|
self.make_internal_transfer_gl_entries(gl_entries)
|
||||||
|
|
||||||
self.allocate_advance_taxes(gl_entries)
|
|
||||||
|
|
||||||
gl_entries = make_regional_gl_entries(gl_entries, self)
|
gl_entries = make_regional_gl_entries(gl_entries, self)
|
||||||
|
|
||||||
gl_entries = merge_similar_entries(gl_entries)
|
gl_entries = merge_similar_entries(gl_entries)
|
||||||
@ -729,7 +738,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"account": self.stock_received_but_not_billed,
|
"account": self.stock_received_but_not_billed,
|
||||||
"against": self.supplier,
|
"against": self.supplier,
|
||||||
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
|
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
|
||||||
"remarks": self.remarks or "Accounting Entry for Stock",
|
"remarks": self.remarks or _("Accounting Entry for Stock"),
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
"project": item.project or self.project
|
"project": item.project or self.project
|
||||||
}, item=item)
|
}, item=item)
|
||||||
@ -937,7 +946,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"cost_center": tax.cost_center,
|
"cost_center": tax.cost_center,
|
||||||
"against": self.supplier,
|
"against": self.supplier,
|
||||||
"credit": valuation_tax[tax.name],
|
"credit": valuation_tax[tax.name],
|
||||||
"remarks": self.remarks or "Accounting Entry for Stock"
|
"remarks": self.remarks or _("Accounting Entry for Stock")
|
||||||
}, item=tax))
|
}, item=tax))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -1074,6 +1083,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||||
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
|
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
|
||||||
|
self.update_advance_tax_references(cancel=1)
|
||||||
|
|
||||||
def update_project(self):
|
def update_project(self):
|
||||||
project_list = []
|
project_list = []
|
||||||
@ -1150,7 +1160,10 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if not self.tax_withholding_category:
|
if not self.tax_withholding_category:
|
||||||
return
|
return
|
||||||
|
|
||||||
tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category)
|
tax_withholding_details, advance_taxes = get_party_tax_withholding_details(self, self.tax_withholding_category)
|
||||||
|
|
||||||
|
# Adjust TDS paid on advances
|
||||||
|
self.allocate_advance_tds(tax_withholding_details, advance_taxes)
|
||||||
|
|
||||||
if not tax_withholding_details:
|
if not tax_withholding_details:
|
||||||
return
|
return
|
||||||
@ -1174,6 +1187,39 @@ class PurchaseInvoice(BuyingController):
|
|||||||
# calculate totals again after applying TDS
|
# calculate totals again after applying TDS
|
||||||
self.calculate_taxes_and_totals()
|
self.calculate_taxes_and_totals()
|
||||||
|
|
||||||
|
def allocate_advance_tds(self, tax_withholding_details, advance_taxes):
|
||||||
|
self.set('advance_tax', [])
|
||||||
|
for tax in advance_taxes:
|
||||||
|
allocated_amount = 0
|
||||||
|
pending_amount = flt(tax.tax_amount - tax.allocated_amount)
|
||||||
|
if flt(tax_withholding_details.get('tax_amount')) >= pending_amount:
|
||||||
|
tax_withholding_details['tax_amount'] -= pending_amount
|
||||||
|
allocated_amount = pending_amount
|
||||||
|
elif flt(tax_withholding_details.get('tax_amount')) and flt(tax_withholding_details.get('tax_amount')) < pending_amount:
|
||||||
|
allocated_amount = tax_withholding_details['tax_amount']
|
||||||
|
tax_withholding_details['tax_amount'] = 0
|
||||||
|
|
||||||
|
self.append('advance_tax', {
|
||||||
|
'reference_type': 'Payment Entry',
|
||||||
|
'reference_name': tax.parent,
|
||||||
|
'reference_detail': tax.name,
|
||||||
|
'account_head': tax.account_head,
|
||||||
|
'allocated_amount': allocated_amount
|
||||||
|
})
|
||||||
|
|
||||||
|
def update_advance_tax_references(self, cancel=0):
|
||||||
|
for tax in self.get('advance_tax'):
|
||||||
|
at = frappe.qb.DocType("Advance Taxes and Charges").as_("at")
|
||||||
|
|
||||||
|
if cancel:
|
||||||
|
frappe.qb.update(at).set(
|
||||||
|
at.allocated_amount, at.allocated_amount - tax.allocated_amount
|
||||||
|
).where(at.name == tax.reference_detail).run()
|
||||||
|
else:
|
||||||
|
frappe.qb.update(at).set(
|
||||||
|
at.allocated_amount, at.allocated_amount + tax.allocated_amount
|
||||||
|
).where(at.name == tax.reference_detail).run()
|
||||||
|
|
||||||
def set_status(self, update=False, status=None, update_modified=True):
|
def set_status(self, update=False, status=None, update_modified=True):
|
||||||
if self.is_new():
|
if self.is_new():
|
||||||
if self.get('amended_from'):
|
if self.get('amended_from'):
|
||||||
|
|||||||
@ -1,74 +0,0 @@
|
|||||||
QUnit.module('Purchase Invoice');
|
|
||||||
|
|
||||||
QUnit.test("test purchase invoice", function(assert) {
|
|
||||||
assert.expect(9);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Purchase Invoice', [
|
|
||||||
{supplier: 'Test Supplier'},
|
|
||||||
{bill_no: 'in123'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{'qty': 5},
|
|
||||||
{'item_code': 'Test Product 1'},
|
|
||||||
{'rate':100},
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
{update_stock:1},
|
|
||||||
{supplier_address: 'Test1-Billing'},
|
|
||||||
{contact_person: 'Contact 3-Test Supplier'},
|
|
||||||
{taxes_and_charges: 'TEST In State GST - FT'},
|
|
||||||
{tc_name: 'Test Term 1'},
|
|
||||||
{terms: 'This is Test'},
|
|
||||||
{payment_terms_template: '_Test Payment Term Template UI'}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => {
|
|
||||||
// get_item_details
|
|
||||||
assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct");
|
|
||||||
// get tax details
|
|
||||||
assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST - FT', "Tax details correct");
|
|
||||||
// get tax account head details
|
|
||||||
assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct");
|
|
||||||
// grand_total Calculated
|
|
||||||
assert.ok(cur_frm.doc.grand_total==590, "Grad Total correct");
|
|
||||||
|
|
||||||
assert.ok(cur_frm.doc.payment_terms_template, "Payment Terms Template is correct");
|
|
||||||
assert.ok(cur_frm.doc.payment_schedule.length > 0, "Payment Term Schedule is not empty");
|
|
||||||
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
let date = cur_frm.doc.due_date;
|
|
||||||
frappe.tests.set_control('due_date', frappe.datetime.add_days(date, 1));
|
|
||||||
frappe.timeout(0.5);
|
|
||||||
assert.ok(cur_dialog && cur_dialog.is_visible, 'Message is displayed to user');
|
|
||||||
},
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => frappe.tests.click_button('Close'),
|
|
||||||
() => frappe.timeout(0.5),
|
|
||||||
() => frappe.tests.set_form_values(cur_frm, [{'payment_terms_schedule': ''}]),
|
|
||||||
() => {
|
|
||||||
let date = cur_frm.doc.due_date;
|
|
||||||
frappe.tests.set_control('due_date', frappe.datetime.add_days(date, 1));
|
|
||||||
frappe.timeout(0.5);
|
|
||||||
assert.ok(cur_dialog && cur_dialog.is_visible, 'Message is displayed to user');
|
|
||||||
},
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => frappe.tests.click_button('Close'),
|
|
||||||
() => frappe.timeout(0.5),
|
|
||||||
() => frappe.tests.set_form_values(cur_frm, [{'payment_schedule': []}]),
|
|
||||||
() => {
|
|
||||||
let date = cur_frm.doc.due_date;
|
|
||||||
frappe.tests.set_control('due_date', frappe.datetime.add_days(date, 1));
|
|
||||||
frappe.timeout(0.5);
|
|
||||||
assert.ok(!cur_dialog, 'Message is not shown');
|
|
||||||
},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -13,6 +13,7 @@ from erpnext.accounts.doctype.account.test_account import create_account, get_in
|
|||||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||||
from erpnext.controllers.accounts_controller import get_payment_terms
|
from erpnext.controllers.accounts_controller import get_payment_terms
|
||||||
|
from erpnext.controllers.buying_controller import QtyMismatchError
|
||||||
from erpnext.exceptions import InvalidCurrency
|
from erpnext.exceptions import InvalidCurrency
|
||||||
from erpnext.projects.doctype.project.test_project import make_project
|
from erpnext.projects.doctype.project.test_project import make_project
|
||||||
from erpnext.stock.doctype.item.test_item import create_item
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
@ -35,6 +36,27 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
def tearDownClass(self):
|
def tearDownClass(self):
|
||||||
unlink_payment_on_cancel_of_invoice(0)
|
unlink_payment_on_cancel_of_invoice(0)
|
||||||
|
|
||||||
|
def test_purchase_invoice_received_qty(self):
|
||||||
|
"""
|
||||||
|
1. Test if received qty is validated against accepted + rejected
|
||||||
|
2. Test if received qty is auto set on save
|
||||||
|
"""
|
||||||
|
pi = make_purchase_invoice(
|
||||||
|
qty=1,
|
||||||
|
rejected_qty=1,
|
||||||
|
received_qty=3,
|
||||||
|
item_code="_Test Item Home Desktop 200",
|
||||||
|
rejected_warehouse = "_Test Rejected Warehouse - _TC",
|
||||||
|
update_stock=True, do_not_save=True)
|
||||||
|
self.assertRaises(QtyMismatchError, pi.save)
|
||||||
|
|
||||||
|
pi.items[0].received_qty = 0
|
||||||
|
pi.save()
|
||||||
|
self.assertEqual(pi.items[0].received_qty, 2)
|
||||||
|
|
||||||
|
# teardown
|
||||||
|
pi.delete()
|
||||||
|
|
||||||
def test_gl_entries_without_perpetual_inventory(self):
|
def test_gl_entries_without_perpetual_inventory(self):
|
||||||
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
|
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
|
||||||
pi = frappe.copy_doc(test_records[0])
|
pi = frappe.copy_doc(test_records[0])
|
||||||
@ -811,29 +833,12 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
pi.shipping_rule = shipping_rule.name
|
pi.shipping_rule = shipping_rule.name
|
||||||
pi.insert()
|
pi.insert()
|
||||||
|
|
||||||
shipping_amount = 0.0
|
|
||||||
for condition in shipping_rule.get("conditions"):
|
|
||||||
if not condition.to_value or (flt(condition.from_value) <= pi.net_total <= flt(condition.to_value)):
|
|
||||||
shipping_amount = condition.shipping_amount
|
|
||||||
|
|
||||||
shipping_charge = {
|
|
||||||
"doctype": "Purchase Taxes and Charges",
|
|
||||||
"category": "Valuation and Total",
|
|
||||||
"charge_type": "Actual",
|
|
||||||
"account_head": shipping_rule.account,
|
|
||||||
"cost_center": shipping_rule.cost_center,
|
|
||||||
"tax_amount": shipping_amount,
|
|
||||||
"description": shipping_rule.name,
|
|
||||||
"add_deduct_tax": "Add"
|
|
||||||
}
|
|
||||||
pi.append("taxes", shipping_charge)
|
|
||||||
pi.save()
|
pi.save()
|
||||||
|
|
||||||
self.assertEqual(pi.net_total, 1250)
|
self.assertEqual(pi.net_total, 1250)
|
||||||
|
|
||||||
self.assertEqual(pi.total_taxes_and_charges, 462.3)
|
self.assertEqual(pi.total_taxes_and_charges, 354.1)
|
||||||
self.assertEqual(pi.grand_total, 1712.3)
|
self.assertEqual(pi.grand_total, 1604.1)
|
||||||
|
|
||||||
def test_make_pi_without_terms(self):
|
def test_make_pi_without_terms(self):
|
||||||
pi = make_purchase_invoice(do_not_save=1)
|
pi = make_purchase_invoice(do_not_save=1)
|
||||||
@ -981,7 +986,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
pi = make_purchase_invoice(item=item.name, qty=1, rate=100, do_not_save=True)
|
pi = make_purchase_invoice(item=item.name, qty=1, rate=100, do_not_save=True)
|
||||||
pi.set_posting_time = 1
|
pi.set_posting_time = 1
|
||||||
pi.posting_date = '2019-03-15'
|
pi.posting_date = '2019-01-10'
|
||||||
pi.items[0].enable_deferred_expense = 1
|
pi.items[0].enable_deferred_expense = 1
|
||||||
pi.items[0].service_start_date = "2019-01-10"
|
pi.items[0].service_start_date = "2019-01-10"
|
||||||
pi.items[0].service_end_date = "2019-03-15"
|
pi.items[0].service_end_date = "2019-03-15"
|
||||||
@ -1155,25 +1160,21 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
# Create Purchase Order with TDS applied
|
# Create Purchase Order with TDS applied
|
||||||
po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item',
|
po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item',
|
||||||
posting_date='2021-09-15')
|
posting_date='2021-09-15')
|
||||||
po.apply_tds = 1
|
|
||||||
po.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
|
|
||||||
po.save()
|
po.save()
|
||||||
po.submit()
|
po.submit()
|
||||||
|
|
||||||
# Update Unrealized Profit / Loss Account which is used as default advance tax account
|
|
||||||
frappe.db.set_value('Company', '_Test Company', 'unrealized_profit_loss_account', '_Test Account Excise Duty - _TC')
|
|
||||||
|
|
||||||
# Create Payment Entry Against the order
|
# Create Payment Entry Against the order
|
||||||
payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name)
|
payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name)
|
||||||
payment_entry.paid_from = 'Cash - _TC'
|
payment_entry.paid_from = 'Cash - _TC'
|
||||||
|
payment_entry.apply_tax_withholding_amount = 1
|
||||||
|
payment_entry.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
|
||||||
payment_entry.save()
|
payment_entry.save()
|
||||||
payment_entry.submit()
|
payment_entry.submit()
|
||||||
|
|
||||||
# Check GLE for Payment Entry
|
# Check GLE for Payment Entry
|
||||||
expected_gle = [
|
expected_gle = [
|
||||||
['_Test Account Excise Duty - _TC', 3000, 0],
|
|
||||||
['Cash - _TC', 0, 27000],
|
['Cash - _TC', 0, 27000],
|
||||||
['Creditors - _TC', 27000, 0],
|
['Creditors - _TC', 30000, 0],
|
||||||
['TDS Payable - _TC', 0, 3000],
|
['TDS Payable - _TC', 0, 3000],
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1199,9 +1200,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
# Zero net effect on final TDS Payable on invoice
|
# Zero net effect on final TDS Payable on invoice
|
||||||
expected_gle = [
|
expected_gle = [
|
||||||
['_Test Account Cost for Goods Sold - _TC', 30000],
|
['_Test Account Cost for Goods Sold - _TC', 30000],
|
||||||
['_Test Account Excise Duty - _TC', -3000],
|
['Creditors - _TC', -30000]
|
||||||
['Creditors - _TC', -27000],
|
|
||||||
['TDS Payable - _TC', 0]
|
|
||||||
]
|
]
|
||||||
|
|
||||||
gl_entries = frappe.db.sql("""select account, sum(debit - credit) as amount
|
gl_entries = frappe.db.sql("""select account, sum(debit - credit) as amount
|
||||||
@ -1214,6 +1213,14 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(expected_gle[i][0], gle.account)
|
self.assertEqual(expected_gle[i][0], gle.account)
|
||||||
self.assertEqual(expected_gle[i][1], gle.amount)
|
self.assertEqual(expected_gle[i][1], gle.amount)
|
||||||
|
|
||||||
|
payment_entry.load_from_db()
|
||||||
|
self.assertEqual(payment_entry.taxes[0].allocated_amount, 3000)
|
||||||
|
|
||||||
|
purchase_invoice.cancel()
|
||||||
|
|
||||||
|
payment_entry.load_from_db()
|
||||||
|
self.assertEqual(payment_entry.taxes[0].allocated_amount, 0)
|
||||||
|
|
||||||
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||||
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
|
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
|
||||||
from `tabGL Entry`
|
from `tabGL Entry`
|
||||||
|
|||||||
@ -22,10 +22,10 @@
|
|||||||
"received_qty",
|
"received_qty",
|
||||||
"qty",
|
"qty",
|
||||||
"rejected_qty",
|
"rejected_qty",
|
||||||
"stock_uom",
|
|
||||||
"col_break2",
|
"col_break2",
|
||||||
"uom",
|
"uom",
|
||||||
"conversion_factor",
|
"conversion_factor",
|
||||||
|
"stock_uom",
|
||||||
"stock_qty",
|
"stock_qty",
|
||||||
"sec_break1",
|
"sec_break1",
|
||||||
"price_list_rate",
|
"price_list_rate",
|
||||||
@ -175,7 +175,8 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "received_qty",
|
"fieldname": "received_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Received Qty"
|
"label": "Received Qty",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bold": 1,
|
"bold": 1,
|
||||||
@ -223,7 +224,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "stock_qty",
|
"fieldname": "stock_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Stock Qty",
|
"label": "Accepted Qty in Stock UOM",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
@ -870,10 +871,11 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-09-01 16:04:03.538643",
|
"modified": "2021-11-15 17:04:07.191013",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Item",
|
"name": "Purchase Invoice Item",
|
||||||
|
"naming_rule": "Random",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
QUnit.module('Sales Taxes and Charges Template');
|
|
||||||
|
|
||||||
QUnit.test("test sales taxes and charges template", function(assert) {
|
|
||||||
assert.expect(2);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Purchase Taxes and Charges Template', [
|
|
||||||
{title: "TEST In State GST"},
|
|
||||||
{taxes:[
|
|
||||||
[
|
|
||||||
{charge_type:"On Net Total"},
|
|
||||||
{account_head:"CGST - "+frappe.get_abbr(frappe.defaults.get_default("Company")) }
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{charge_type:"On Net Total"},
|
|
||||||
{account_head:"SGST - "+frappe.get_abbr(frappe.defaults.get_default("Company")) }
|
|
||||||
]
|
|
||||||
]}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
assert.ok(cur_frm.doc.title=='TEST In State GST');
|
|
||||||
assert.ok(cur_frm.doc.name=='TEST In State GST - FT');
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -516,15 +516,6 @@ cur_frm.fields_dict.write_off_cost_center.get_query = function(doc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// project name
|
|
||||||
//--------------------------
|
|
||||||
cur_frm.fields_dict['project'].get_query = function(doc, cdt, cdn) {
|
|
||||||
return{
|
|
||||||
query: "erpnext.controllers.queries.get_project_name",
|
|
||||||
filters: {'customer': doc.customer}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Income Account in Details Table
|
// Income Account in Details Table
|
||||||
// --------------------------------
|
// --------------------------------
|
||||||
cur_frm.set_query("income_account", "items", function(doc) {
|
cur_frm.set_query("income_account", "items", function(doc) {
|
||||||
@ -978,7 +969,7 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.is_debit_note) {
|
if (frm.doc.is_debit_note) {
|
||||||
frm.set_df_property('return_against', 'label', 'Adjustment Against');
|
frm.set_df_property('return_against', 'label', __('Adjustment Against'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frappe.boot.active_domains.includes("Healthcare")) {
|
if (frappe.boot.active_domains.includes("Healthcare")) {
|
||||||
@ -988,10 +979,10 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
if (cint(frm.doc.docstatus==0) && cur_frm.page.current_view_name!=="pos" && !frm.doc.is_return) {
|
if (cint(frm.doc.docstatus==0) && cur_frm.page.current_view_name!=="pos" && !frm.doc.is_return) {
|
||||||
frm.add_custom_button(__('Healthcare Services'), function() {
|
frm.add_custom_button(__('Healthcare Services'), function() {
|
||||||
get_healthcare_services_to_invoice(frm);
|
get_healthcare_services_to_invoice(frm);
|
||||||
},"Get Items From");
|
},__("Get Items From"));
|
||||||
frm.add_custom_button(__('Prescriptions'), function() {
|
frm.add_custom_button(__('Prescriptions'), function() {
|
||||||
get_drugs_to_invoice(frm);
|
get_drugs_to_invoice(frm);
|
||||||
},"Get Items From");
|
},__("Get Items From"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@ -182,6 +182,7 @@
|
|||||||
"sales_team_section_break",
|
"sales_team_section_break",
|
||||||
"sales_partner",
|
"sales_partner",
|
||||||
"column_break10",
|
"column_break10",
|
||||||
|
"amount_eligible_for_commission",
|
||||||
"commission_rate",
|
"commission_rate",
|
||||||
"total_commission",
|
"total_commission",
|
||||||
"section_break2",
|
"section_break2",
|
||||||
@ -2019,6 +2020,12 @@
|
|||||||
"label": "Total Billing Hours",
|
"label": "Total Billing Hours",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "amount_eligible_for_commission",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Amount Eligible for Commission",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
@ -2031,7 +2038,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2021-10-11 20:19:38.667508",
|
"modified": "2021-10-21 20:19:38.667508",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
|||||||
@ -155,6 +155,8 @@ class SalesInvoice(SellingController):
|
|||||||
if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points and not self.is_consolidated:
|
if self.redeem_loyalty_points and self.loyalty_program and self.loyalty_points and not self.is_consolidated:
|
||||||
validate_loyalty_points(self, self.loyalty_points)
|
validate_loyalty_points(self, self.loyalty_points)
|
||||||
|
|
||||||
|
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||||
|
|
||||||
def validate_fixed_asset(self):
|
def validate_fixed_asset(self):
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if d.is_fixed_asset and d.meta.get_field("asset") and d.asset:
|
if d.is_fixed_asset and d.meta.get_field("asset") and d.asset:
|
||||||
@ -842,8 +844,6 @@ class SalesInvoice(SellingController):
|
|||||||
self.make_exchange_gain_loss_gl_entries(gl_entries)
|
self.make_exchange_gain_loss_gl_entries(gl_entries)
|
||||||
self.make_internal_transfer_gl_entries(gl_entries)
|
self.make_internal_transfer_gl_entries(gl_entries)
|
||||||
|
|
||||||
self.allocate_advance_taxes(gl_entries)
|
|
||||||
|
|
||||||
self.make_item_gl_entries(gl_entries)
|
self.make_item_gl_entries(gl_entries)
|
||||||
self.make_discount_gl_entries(gl_entries)
|
self.make_discount_gl_entries(gl_entries)
|
||||||
|
|
||||||
@ -1049,6 +1049,8 @@ class SalesInvoice(SellingController):
|
|||||||
frappe.flags.is_reverse_depr_entry = False
|
frappe.flags.is_reverse_depr_entry = False
|
||||||
asset.flags.ignore_validate_update_after_submit = True
|
asset.flags.ignore_validate_update_after_submit = True
|
||||||
schedule.journal_entry = None
|
schedule.journal_entry = None
|
||||||
|
depreciation_amount = self.get_depreciation_amount_in_je(reverse_journal_entry)
|
||||||
|
asset.finance_books[0].value_after_depreciation += depreciation_amount
|
||||||
asset.save()
|
asset.save()
|
||||||
|
|
||||||
def get_posting_date_of_sales_invoice(self):
|
def get_posting_date_of_sales_invoice(self):
|
||||||
@ -1071,6 +1073,12 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_depreciation_amount_in_je(self, journal_entry):
|
||||||
|
if journal_entry.accounts[0].debit_in_account_currency:
|
||||||
|
return journal_entry.accounts[0].debit_in_account_currency
|
||||||
|
else:
|
||||||
|
return journal_entry.accounts[0].credit_in_account_currency
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def enable_discount_accounting(self):
|
def enable_discount_accounting(self):
|
||||||
if not hasattr(self, "_enable_discount_accounting"):
|
if not hasattr(self, "_enable_discount_accounting"):
|
||||||
|
|||||||
@ -1,73 +0,0 @@
|
|||||||
QUnit.module('Sales Invoice');
|
|
||||||
|
|
||||||
QUnit.test("test sales Invoice", function(assert) {
|
|
||||||
assert.expect(9);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Sales Invoice', [
|
|
||||||
{customer: 'Test Customer 1'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{'qty': 5},
|
|
||||||
{'item_code': 'Test Product 1'},
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
{update_stock:1},
|
|
||||||
{customer_address: 'Test1-Billing'},
|
|
||||||
{shipping_address_name: 'Test1-Shipping'},
|
|
||||||
{contact_person: 'Contact 1-Test Customer 1'},
|
|
||||||
{taxes_and_charges: 'TEST In State GST - FT'},
|
|
||||||
{tc_name: 'Test Term 1'},
|
|
||||||
{terms: 'This is Test'},
|
|
||||||
{payment_terms_template: '_Test Payment Term Template UI'}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => {
|
|
||||||
// get_item_details
|
|
||||||
assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct");
|
|
||||||
// get tax details
|
|
||||||
assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST - FT', "Tax details correct");
|
|
||||||
// get tax account head details
|
|
||||||
assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct");
|
|
||||||
// grand_total Calculated
|
|
||||||
assert.ok(cur_frm.doc.grand_total==590, "Grand Total correct");
|
|
||||||
|
|
||||||
assert.ok(cur_frm.doc.payment_terms_template, "Payment Terms Template is correct");
|
|
||||||
assert.ok(cur_frm.doc.payment_schedule.length > 0, "Payment Term Schedule is not empty");
|
|
||||||
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
let date = cur_frm.doc.due_date;
|
|
||||||
frappe.tests.set_control('due_date', frappe.datetime.add_days(date, 1));
|
|
||||||
frappe.timeout(0.5);
|
|
||||||
assert.ok(cur_dialog && cur_dialog.is_visible, 'Message is displayed to user');
|
|
||||||
},
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => frappe.tests.click_button('Close'),
|
|
||||||
() => frappe.timeout(0.5),
|
|
||||||
() => frappe.tests.set_form_values(cur_frm, [{'payment_terms_schedule': ''}]),
|
|
||||||
() => {
|
|
||||||
let date = cur_frm.doc.due_date;
|
|
||||||
frappe.tests.set_control('due_date', frappe.datetime.add_days(date, 1));
|
|
||||||
frappe.timeout(0.5);
|
|
||||||
assert.ok(cur_dialog && cur_dialog.is_visible, 'Message is displayed to user');
|
|
||||||
},
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => frappe.tests.click_button('Close'),
|
|
||||||
() => frappe.timeout(0.5),
|
|
||||||
() => frappe.tests.set_form_values(cur_frm, [{'payment_schedule': []}]),
|
|
||||||
() => {
|
|
||||||
let date = cur_frm.doc.due_date;
|
|
||||||
frappe.tests.set_control('due_date', frappe.datetime.add_days(date, 1));
|
|
||||||
frappe.timeout(0.5);
|
|
||||||
assert.ok(!cur_dialog, 'Message is not shown');
|
|
||||||
},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -1603,28 +1603,12 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
si.shipping_rule = shipping_rule.name
|
si.shipping_rule = shipping_rule.name
|
||||||
si.insert()
|
si.insert()
|
||||||
|
|
||||||
shipping_amount = 0.0
|
|
||||||
for condition in shipping_rule.get("conditions"):
|
|
||||||
if not condition.to_value or (flt(condition.from_value) <= si.net_total <= flt(condition.to_value)):
|
|
||||||
shipping_amount = condition.shipping_amount
|
|
||||||
|
|
||||||
shipping_charge = {
|
|
||||||
"doctype": "Sales Taxes and Charges",
|
|
||||||
"category": "Valuation and Total",
|
|
||||||
"charge_type": "Actual",
|
|
||||||
"account_head": shipping_rule.account,
|
|
||||||
"cost_center": shipping_rule.cost_center,
|
|
||||||
"tax_amount": shipping_amount,
|
|
||||||
"description": shipping_rule.name
|
|
||||||
}
|
|
||||||
si.append("taxes", shipping_charge)
|
|
||||||
si.save()
|
si.save()
|
||||||
|
|
||||||
self.assertEqual(si.net_total, 1250)
|
self.assertEqual(si.net_total, 1250)
|
||||||
|
|
||||||
self.assertEqual(si.total_taxes_and_charges, 577.05)
|
self.assertEqual(si.total_taxes_and_charges, 468.85)
|
||||||
self.assertEqual(si.grand_total, 1827.05)
|
self.assertEqual(si.grand_total, 1718.85)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -2316,6 +2300,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
|
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
|
||||||
make_customer,
|
make_customer,
|
||||||
)
|
)
|
||||||
|
from erpnext.accounts.doctype.party_link.party_link import create_party_link
|
||||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||||
|
|
||||||
# create a customer
|
# create a customer
|
||||||
@ -2324,13 +2309,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
supplier = create_supplier(supplier_name="_Test Common Supplier").name
|
supplier = create_supplier(supplier_name="_Test Common Supplier").name
|
||||||
|
|
||||||
# create a party link between customer & supplier
|
# create a party link between customer & supplier
|
||||||
# set primary role as supplier
|
party_link = create_party_link("Supplier", supplier, customer)
|
||||||
party_link = frappe.new_doc("Party Link")
|
|
||||||
party_link.primary_role = "Supplier"
|
|
||||||
party_link.primary_party = supplier
|
|
||||||
party_link.secondary_role = "Customer"
|
|
||||||
party_link.secondary_party = customer
|
|
||||||
party_link.save()
|
|
||||||
|
|
||||||
# enable common party accounting
|
# enable common party accounting
|
||||||
frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 1)
|
frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 1)
|
||||||
@ -2406,6 +2385,29 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
si.reload()
|
si.reload()
|
||||||
self.assertEqual(si.status, "Paid")
|
self.assertEqual(si.status, "Paid")
|
||||||
|
|
||||||
|
def test_sales_commission(self):
|
||||||
|
si = frappe.copy_doc(test_records[0])
|
||||||
|
item = copy.deepcopy(si.get('items')[0])
|
||||||
|
item.update({
|
||||||
|
"qty": 1,
|
||||||
|
"rate": 500,
|
||||||
|
"grant_commission": 1
|
||||||
|
})
|
||||||
|
si.append("items", item)
|
||||||
|
|
||||||
|
# Test valid values
|
||||||
|
for commission_rate, total_commission in ((0, 0), (10, 50), (100, 500)):
|
||||||
|
si.commission_rate = commission_rate
|
||||||
|
si.save()
|
||||||
|
self.assertEqual(si.amount_eligible_for_commission, 500)
|
||||||
|
self.assertEqual(si.total_commission, total_commission)
|
||||||
|
|
||||||
|
# Test invalid values
|
||||||
|
for commission_rate in (101, -1):
|
||||||
|
si.reload()
|
||||||
|
si.commission_rate = commission_rate
|
||||||
|
self.assertRaises(frappe.ValidationError, si.save)
|
||||||
|
|
||||||
def test_sales_invoice_submission_post_account_freezing_date(self):
|
def test_sales_invoice_submission_post_account_freezing_date(self):
|
||||||
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', add_days(getdate(), 1))
|
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', add_days(getdate(), 1))
|
||||||
si = create_sales_invoice(do_not_save=True)
|
si = create_sales_invoice(do_not_save=True)
|
||||||
@ -2418,6 +2420,32 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
|
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
|
||||||
|
|
||||||
|
def test_over_billing_case_against_delivery_note(self):
|
||||||
|
'''
|
||||||
|
Test a case where duplicating the item with qty = 1 in the invoice
|
||||||
|
allows overbilling even if it is disabled
|
||||||
|
'''
|
||||||
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
|
|
||||||
|
over_billing_allowance = frappe.db.get_single_value('Accounts Settings', 'over_billing_allowance')
|
||||||
|
frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', 0)
|
||||||
|
|
||||||
|
dn = create_delivery_note()
|
||||||
|
dn.submit()
|
||||||
|
|
||||||
|
si = make_sales_invoice(dn.name)
|
||||||
|
# make a copy of first item and add it to invoice
|
||||||
|
item_copy = frappe.copy_doc(si.items[0])
|
||||||
|
si.append('items', item_copy)
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
with self.assertRaises(frappe.ValidationError) as err:
|
||||||
|
si.submit()
|
||||||
|
|
||||||
|
self.assertTrue("cannot overbill" in str(err.exception).lower())
|
||||||
|
|
||||||
|
frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', over_billing_allowance)
|
||||||
|
|
||||||
def get_sales_invoice_for_e_invoice():
|
def get_sales_invoice_for_e_invoice():
|
||||||
si = make_sales_invoice_for_ewaybill()
|
si = make_sales_invoice_for_ewaybill()
|
||||||
si.naming_series = 'INV-2020-.#####'
|
si.naming_series = 'INV-2020-.#####'
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
QUnit.module('Sales Invoice');
|
|
||||||
|
|
||||||
QUnit.test("test sales Invoice", function(assert) {
|
|
||||||
assert.expect(4);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Sales Invoice', [
|
|
||||||
{customer: 'Test Customer 1'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{'qty': 5},
|
|
||||||
{'item_code': 'Test Product 1'},
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
{update_stock:1},
|
|
||||||
{customer_address: 'Test1-Billing'},
|
|
||||||
{shipping_address_name: 'Test1-Shipping'},
|
|
||||||
{contact_person: 'Contact 1-Test Customer 1'},
|
|
||||||
{taxes_and_charges: 'TEST In State GST - FT'},
|
|
||||||
{tc_name: 'Test Term 1'},
|
|
||||||
{terms: 'This is Test'}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => {
|
|
||||||
// get_item_details
|
|
||||||
assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct");
|
|
||||||
// get tax details
|
|
||||||
assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST - FT', "Tax details correct");
|
|
||||||
// get tax account head details
|
|
||||||
assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct");
|
|
||||||
// grand_total Calculated
|
|
||||||
assert.ok(cur_frm.doc.grand_total==590, "Grad Total correct");
|
|
||||||
|
|
||||||
},
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
QUnit.module('Accounts');
|
|
||||||
|
|
||||||
QUnit.test("test sales invoice with margin", function(assert) {
|
|
||||||
assert.expect(3);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Sales Invoice', [
|
|
||||||
{customer: 'Test Customer 1'},
|
|
||||||
{selling_price_list: 'Test-Selling-USD'},
|
|
||||||
{currency: 'USD'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{'item_code': 'Test Product 4'},
|
|
||||||
{'delivery_date': frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)},
|
|
||||||
{'qty': 1},
|
|
||||||
{'margin_type': 'Percentage'},
|
|
||||||
{'margin_rate_or_amount': 20}
|
|
||||||
]
|
|
||||||
]}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => {
|
|
||||||
assert.ok(cur_frm.doc.items[0].rate_with_margin == 240, "Margin rate correct");
|
|
||||||
assert.ok(cur_frm.doc.items[0].base_rate_with_margin == cur_frm.doc.conversion_rate * 240, "Base margin rate correct");
|
|
||||||
assert.ok(cur_frm.doc.total == 240, "Amount correct");
|
|
||||||
|
|
||||||
},
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
QUnit.module('Sales Invoice');
|
|
||||||
|
|
||||||
QUnit.test("test sales Invoice with payment", function(assert) {
|
|
||||||
assert.expect(4);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Sales Invoice', [
|
|
||||||
{customer: 'Test Customer 1'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{'qty': 5},
|
|
||||||
{'item_code': 'Test Product 1'},
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
{update_stock:1},
|
|
||||||
{customer_address: 'Test1-Billing'},
|
|
||||||
{shipping_address_name: 'Test1-Shipping'},
|
|
||||||
{contact_person: 'Contact 1-Test Customer 1'},
|
|
||||||
{taxes_and_charges: 'TEST In State GST - FT'},
|
|
||||||
{tc_name: 'Test Term 1'},
|
|
||||||
{terms: 'This is Test'},
|
|
||||||
{payment_terms_template: '_Test Payment Term Template UI'}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => {
|
|
||||||
// get_item_details
|
|
||||||
assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct");
|
|
||||||
// get tax details
|
|
||||||
assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST - FT', "Tax details correct");
|
|
||||||
// grand_total Calculated
|
|
||||||
assert.ok(cur_frm.doc.grand_total==590, "Grad Total correct");
|
|
||||||
|
|
||||||
},
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(2),
|
|
||||||
() => frappe.tests.click_button('Close'),
|
|
||||||
() => frappe.tests.click_button('Make'),
|
|
||||||
() => frappe.tests.click_link('Payment'),
|
|
||||||
() => frappe.timeout(0.2),
|
|
||||||
() => { cur_frm.set_value('mode_of_payment','Cash');},
|
|
||||||
() => { cur_frm.set_value('paid_to','Cash - '+frappe.get_abbr(frappe.defaults.get_default('Company')));},
|
|
||||||
() => {cur_frm.set_value('reference_no','TEST1234');},
|
|
||||||
() => {cur_frm.set_value('reference_date',frappe.datetime.add_days(frappe.datetime.nowdate(), 0));},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => {
|
|
||||||
// get payment details
|
|
||||||
assert.ok(cur_frm.doc.paid_amount==590, "Paid Amount Correct");
|
|
||||||
},
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
QUnit.module('Sales Invoice');
|
|
||||||
|
|
||||||
QUnit.test("test sales Invoice with payment request", function(assert) {
|
|
||||||
assert.expect(4);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Sales Invoice', [
|
|
||||||
{customer: 'Test Customer 1'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{'qty': 5},
|
|
||||||
{'item_code': 'Test Product 1'},
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
{update_stock:1},
|
|
||||||
{customer_address: 'Test1-Billing'},
|
|
||||||
{shipping_address_name: 'Test1-Shipping'},
|
|
||||||
{contact_person: 'Contact 1-Test Customer 1'},
|
|
||||||
{taxes_and_charges: 'TEST In State GST - FT'},
|
|
||||||
{tc_name: 'Test Term 1'},
|
|
||||||
{terms: 'This is Test'}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => {
|
|
||||||
// get_item_details
|
|
||||||
assert.ok(cur_frm.doc.items[0].item_name=='Test Product 1', "Item name correct");
|
|
||||||
// get tax details
|
|
||||||
assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST - FT', "Tax details correct");
|
|
||||||
// grand_total Calculated
|
|
||||||
assert.ok(cur_frm.doc.grand_total==590, "Grad Total correct");
|
|
||||||
|
|
||||||
},
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(2),
|
|
||||||
() => frappe.tests.click_button('Close'),
|
|
||||||
() => frappe.tests.click_button('Make'),
|
|
||||||
() => frappe.tests.click_link('Payment Request'),
|
|
||||||
() => frappe.timeout(0.2),
|
|
||||||
() => { cur_frm.set_value('print_format','GST Tax Invoice');},
|
|
||||||
() => { cur_frm.set_value('email_to','test@gmail.com');},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => {
|
|
||||||
// get payment details
|
|
||||||
assert.ok(cur_frm.doc.grand_total==590, "grand total Correct");
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
QUnit.module('Sales Invoice');
|
|
||||||
|
|
||||||
QUnit.test("test sales Invoice with serialize item", function(assert) {
|
|
||||||
assert.expect(5);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Sales Invoice', [
|
|
||||||
{customer: 'Test Customer 1'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{'qty': 2},
|
|
||||||
{'item_code': 'Test Product 4'},
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
{update_stock:1},
|
|
||||||
{customer_address: 'Test1-Billing'},
|
|
||||||
{shipping_address_name: 'Test1-Shipping'},
|
|
||||||
{contact_person: 'Contact 1-Test Customer 1'},
|
|
||||||
{taxes_and_charges: 'TEST In State GST - FT'},
|
|
||||||
{tc_name: 'Test Term 1'},
|
|
||||||
{terms: 'This is Test'}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => {
|
|
||||||
// get_item_details
|
|
||||||
assert.ok(cur_frm.doc.items[0].item_name=='Test Product 4', "Item name correct");
|
|
||||||
// get tax details
|
|
||||||
assert.ok(cur_frm.doc.taxes_and_charges=='TEST In State GST - FT', "Tax details correct");
|
|
||||||
// get tax account head details
|
|
||||||
assert.ok(cur_frm.doc.taxes[0].account_head=='CGST - '+frappe.get_abbr(frappe.defaults.get_default('Company')), " Account Head abbr correct");
|
|
||||||
// get batch number
|
|
||||||
assert.ok(cur_frm.doc.items[0].batch_no=='TEST-BATCH-001', " Batch Details correct");
|
|
||||||
// grand_total Calculated
|
|
||||||
assert.ok(cur_frm.doc.grand_total==218, "Grad Total correct");
|
|
||||||
|
|
||||||
},
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -47,6 +47,7 @@
|
|||||||
"pricing_rules",
|
"pricing_rules",
|
||||||
"stock_uom_rate",
|
"stock_uom_rate",
|
||||||
"is_free_item",
|
"is_free_item",
|
||||||
|
"grant_commission",
|
||||||
"section_break_21",
|
"section_break_21",
|
||||||
"net_rate",
|
"net_rate",
|
||||||
"net_amount",
|
"net_amount",
|
||||||
@ -828,15 +829,23 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Discount Account",
|
"label": "Discount Account",
|
||||||
"options": "Account"
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "grant_commission",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Grant Commission",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-08-19 13:41:53.435827",
|
"modified": "2021-10-05 12:24:54.968907",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Item",
|
"name": "Sales Invoice Item",
|
||||||
|
"naming_rule": "Random",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
QUnit.module('Sales Taxes and Charges Template');
|
|
||||||
|
|
||||||
QUnit.test("test sales taxes and charges template", function(assert) {
|
|
||||||
assert.expect(2);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Sales Taxes and Charges Template', [
|
|
||||||
{title: "TEST In State GST"},
|
|
||||||
{taxes:[
|
|
||||||
[
|
|
||||||
{charge_type:"On Net Total"},
|
|
||||||
{account_head:"CGST - "+frappe.get_abbr(frappe.defaults.get_default("Company")) }
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{charge_type:"On Net Total"},
|
|
||||||
{account_head:"SGST - "+frappe.get_abbr(frappe.defaults.get_default("Company")) }
|
|
||||||
]
|
|
||||||
]}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
assert.ok(cur_frm.doc.title=='TEST In State GST');
|
|
||||||
assert.ok(cur_frm.doc.name=='TEST In State GST - FT');
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
QUnit.module('Shipping Rule');
|
|
||||||
|
|
||||||
QUnit.test("test Shipping Rule", function(assert) {
|
|
||||||
assert.expect(1);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make("Shipping Rule", [
|
|
||||||
{label: "Next Day Shipping"},
|
|
||||||
{shipping_rule_type: "Selling"},
|
|
||||||
{calculate_based_on: 'Net Total'},
|
|
||||||
{conditions:[
|
|
||||||
[
|
|
||||||
{from_value:1},
|
|
||||||
{to_value:200},
|
|
||||||
{shipping_amount:100}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{from_value:201},
|
|
||||||
{to_value:2000},
|
|
||||||
{shipping_amount:50}
|
|
||||||
],
|
|
||||||
]},
|
|
||||||
{countries:[
|
|
||||||
[
|
|
||||||
{country:'India'}
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
{account:'Accounts Payable - '+frappe.get_abbr(frappe.defaults.get_default("Company"))},
|
|
||||||
{cost_center:'Main - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => {assert.ok(cur_frm.doc.name=='Next Day Shipping');},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
QUnit.module('Shipping Rule');
|
|
||||||
|
|
||||||
QUnit.test("test Shipping Rule", function(assert) {
|
|
||||||
assert.expect(1);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make("Shipping Rule", [
|
|
||||||
{label: "Two Day Shipping"},
|
|
||||||
{shipping_rule_type: "Buying"},
|
|
||||||
{fixed_shipping_amount: 0},
|
|
||||||
{conditions:[
|
|
||||||
[
|
|
||||||
{from_value:1},
|
|
||||||
{to_value:200},
|
|
||||||
{shipping_amount:100}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{from_value:201},
|
|
||||||
{to_value:3000},
|
|
||||||
{shipping_amount:200}
|
|
||||||
],
|
|
||||||
]},
|
|
||||||
{countries:[
|
|
||||||
[
|
|
||||||
{country:'India'}
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
{account:'Accounts Payable - '+frappe.get_abbr(frappe.defaults.get_default("Company"))},
|
|
||||||
{cost_center:'Main - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => {assert.ok(cur_frm.doc.name=='Two Day Shipping');},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -23,6 +23,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
|||||||
get_accounting_dimensions,
|
get_accounting_dimensions,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
|
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
|
||||||
|
from erpnext.accounts.party import get_party_account_currency
|
||||||
|
|
||||||
|
|
||||||
class Subscription(Document):
|
class Subscription(Document):
|
||||||
@ -355,6 +356,9 @@ class Subscription(Document):
|
|||||||
if frappe.db.get_value('Supplier', self.party, 'tax_withholding_category'):
|
if frappe.db.get_value('Supplier', self.party, 'tax_withholding_category'):
|
||||||
invoice.apply_tds = 1
|
invoice.apply_tds = 1
|
||||||
|
|
||||||
|
### Add party currency to invoice
|
||||||
|
invoice.currency = get_party_account_currency(self.party_type, self.party, self.company)
|
||||||
|
|
||||||
## Add dimensions in invoice for subscription:
|
## Add dimensions in invoice for subscription:
|
||||||
accounting_dimensions = get_accounting_dimensions()
|
accounting_dimensions = get_accounting_dimensions()
|
||||||
|
|
||||||
|
|||||||
@ -1,32 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// rename this file from _test_[name] to test_[name] to activate
|
|
||||||
// and remove above this line
|
|
||||||
|
|
||||||
QUnit.test("test: Subscription", function (assert) {
|
|
||||||
assert.expect(4);
|
|
||||||
let done = assert.async();
|
|
||||||
frappe.run_serially([
|
|
||||||
// insert a new Subscription
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make("Subscription", [
|
|
||||||
{reference_doctype: 'Sales Invoice'},
|
|
||||||
{reference_document: 'SINV-00004'},
|
|
||||||
{start_date: frappe.datetime.month_start()},
|
|
||||||
{end_date: frappe.datetime.month_end()},
|
|
||||||
{frequency: 'Weekly'}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
() => cur_frm.savesubmit(),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => frappe.click_button('Yes'),
|
|
||||||
() => frappe.timeout(2),
|
|
||||||
() => {
|
|
||||||
assert.ok(cur_frm.doc.frequency.includes("Weekly"), "Set frequency Weekly");
|
|
||||||
assert.ok(cur_frm.doc.reference_doctype.includes("Sales Invoice"), "Set base doctype Sales Invoice");
|
|
||||||
assert.equal(cur_frm.doc.docstatus, 1, "Submitted subscription");
|
|
||||||
assert.equal(cur_frm.doc.next_schedule_date,
|
|
||||||
frappe.datetime.add_days(frappe.datetime.get_today(), 7), "Set schedule date");
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -60,15 +60,38 @@ def create_plan():
|
|||||||
plan.billing_interval_count = 3
|
plan.billing_interval_count = 3
|
||||||
plan.insert()
|
plan.insert()
|
||||||
|
|
||||||
|
if not frappe.db.exists('Subscription Plan', '_Test Plan Multicurrency'):
|
||||||
|
plan = frappe.new_doc('Subscription Plan')
|
||||||
|
plan.plan_name = '_Test Plan Multicurrency'
|
||||||
|
plan.item = '_Test Non Stock Item'
|
||||||
|
plan.price_determination = "Fixed Rate"
|
||||||
|
plan.cost = 50
|
||||||
|
plan.currency = 'USD'
|
||||||
|
plan.billing_interval = 'Month'
|
||||||
|
plan.billing_interval_count = 1
|
||||||
|
plan.insert()
|
||||||
|
|
||||||
|
def create_parties():
|
||||||
if not frappe.db.exists('Supplier', '_Test Supplier'):
|
if not frappe.db.exists('Supplier', '_Test Supplier'):
|
||||||
supplier = frappe.new_doc('Supplier')
|
supplier = frappe.new_doc('Supplier')
|
||||||
supplier.supplier_name = '_Test Supplier'
|
supplier.supplier_name = '_Test Supplier'
|
||||||
supplier.supplier_group = 'All Supplier Groups'
|
supplier.supplier_group = 'All Supplier Groups'
|
||||||
supplier.insert()
|
supplier.insert()
|
||||||
|
|
||||||
|
if not frappe.db.exists('Customer', '_Test Subscription Customer'):
|
||||||
|
customer = frappe.new_doc('Customer')
|
||||||
|
customer.customer_name = '_Test Subscription Customer'
|
||||||
|
customer.billing_currency = 'USD'
|
||||||
|
customer.append('accounts', {
|
||||||
|
'company': '_Test Company',
|
||||||
|
'account': '_Test Receivable USD - _TC'
|
||||||
|
})
|
||||||
|
customer.insert()
|
||||||
|
|
||||||
class TestSubscription(unittest.TestCase):
|
class TestSubscription(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
create_plan()
|
create_plan()
|
||||||
|
create_parties()
|
||||||
|
|
||||||
def test_create_subscription_with_trial_with_correct_period(self):
|
def test_create_subscription_with_trial_with_correct_period(self):
|
||||||
subscription = frappe.new_doc('Subscription')
|
subscription = frappe.new_doc('Subscription')
|
||||||
@ -637,3 +660,22 @@ class TestSubscription(unittest.TestCase):
|
|||||||
|
|
||||||
subscription.process()
|
subscription.process()
|
||||||
self.assertEqual(len(subscription.invoices), 1)
|
self.assertEqual(len(subscription.invoices), 1)
|
||||||
|
|
||||||
|
def test_multicurrency_subscription(self):
|
||||||
|
subscription = frappe.new_doc('Subscription')
|
||||||
|
subscription.party_type = 'Customer'
|
||||||
|
subscription.party = '_Test Subscription Customer'
|
||||||
|
subscription.generate_invoice_at_period_start = 1
|
||||||
|
subscription.company = '_Test Company'
|
||||||
|
# select subscription start date as '2018-01-15'
|
||||||
|
subscription.start_date = '2018-01-01'
|
||||||
|
subscription.append('plans', {'plan': '_Test Plan Multicurrency', 'qty': 1})
|
||||||
|
subscription.save()
|
||||||
|
|
||||||
|
subscription.process()
|
||||||
|
self.assertEqual(len(subscription.invoices), 1)
|
||||||
|
self.assertEqual(subscription.status, 'Unpaid')
|
||||||
|
|
||||||
|
# Check the currency of the created invoice
|
||||||
|
currency = frappe.db.get_value('Sales Invoice', subscription.invoices[0].invoice, 'currency')
|
||||||
|
self.assertEqual(currency, 'USD')
|
||||||
@ -75,7 +75,8 @@
|
|||||||
"fieldname": "cost",
|
"fieldname": "cost",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Cost"
|
"label": "Cost",
|
||||||
|
"options": "currency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.price_determination==\"Based On Price List\"",
|
"depends_on": "eval:doc.price_determination==\"Based On Price List\"",
|
||||||
@ -147,7 +148,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-08-13 10:53:44.205774",
|
"modified": "2021-12-10 15:24:15.794477",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Subscription Plan",
|
"name": "Subscription Plan",
|
||||||
|
|||||||
@ -95,7 +95,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
|||||||
frappe.throw(_('Tax Withholding Category {} against Company {} for Customer {} should have Cumulative Threshold value.')
|
frappe.throw(_('Tax Withholding Category {} against Company {} for Customer {} should have Cumulative Threshold value.')
|
||||||
.format(tax_withholding_category, inv.company, party))
|
.format(tax_withholding_category, inv.company, party))
|
||||||
|
|
||||||
tax_amount, tax_deducted = get_tax_amount(
|
tax_amount, tax_deducted, tax_deducted_on_advances = get_tax_amount(
|
||||||
party_type, parties,
|
party_type, parties,
|
||||||
inv, tax_details,
|
inv, tax_details,
|
||||||
posting_date, pan_no
|
posting_date, pan_no
|
||||||
@ -106,7 +106,10 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
|||||||
else:
|
else:
|
||||||
tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted)
|
tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted)
|
||||||
|
|
||||||
return tax_row
|
if inv.doctype == 'Purchase Invoice':
|
||||||
|
return tax_row, tax_deducted_on_advances
|
||||||
|
else:
|
||||||
|
return tax_row
|
||||||
|
|
||||||
def get_tax_withholding_details(tax_withholding_category, posting_date, company):
|
def get_tax_withholding_details(tax_withholding_category, posting_date, company):
|
||||||
tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category)
|
tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category)
|
||||||
@ -194,6 +197,10 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
|||||||
advance_vouchers = get_advance_vouchers(parties, company=inv.company, from_date=tax_details.from_date,
|
advance_vouchers = get_advance_vouchers(parties, company=inv.company, from_date=tax_details.from_date,
|
||||||
to_date=tax_details.to_date, party_type=party_type)
|
to_date=tax_details.to_date, party_type=party_type)
|
||||||
taxable_vouchers = vouchers + advance_vouchers
|
taxable_vouchers = vouchers + advance_vouchers
|
||||||
|
tax_deducted_on_advances = 0
|
||||||
|
|
||||||
|
if inv.doctype == 'Purchase Invoice':
|
||||||
|
tax_deducted_on_advances = get_taxes_deducted_on_advances_allocated(inv, tax_details)
|
||||||
|
|
||||||
tax_deducted = 0
|
tax_deducted = 0
|
||||||
if taxable_vouchers:
|
if taxable_vouchers:
|
||||||
@ -223,7 +230,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
|||||||
if cint(tax_details.round_off_tax_amount):
|
if cint(tax_details.round_off_tax_amount):
|
||||||
tax_amount = round(tax_amount)
|
tax_amount = round(tax_amount)
|
||||||
|
|
||||||
return tax_amount, tax_deducted
|
return tax_amount, tax_deducted, tax_deducted_on_advances
|
||||||
|
|
||||||
def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'):
|
def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'):
|
||||||
dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit'
|
dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit'
|
||||||
@ -281,6 +288,29 @@ def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, pa
|
|||||||
|
|
||||||
return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""]
|
return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""]
|
||||||
|
|
||||||
|
def get_taxes_deducted_on_advances_allocated(inv, tax_details):
|
||||||
|
advances = [d.reference_name for d in inv.get('advances')]
|
||||||
|
tax_info = []
|
||||||
|
|
||||||
|
if advances:
|
||||||
|
pe = frappe.qb.DocType("Payment Entry").as_("pe")
|
||||||
|
at = frappe.qb.DocType("Advance Taxes and Charges").as_("at")
|
||||||
|
|
||||||
|
tax_info = frappe.qb.from_(at).inner_join(pe).on(
|
||||||
|
pe.name == at.parent
|
||||||
|
).select(
|
||||||
|
at.parent, at.name, at.tax_amount, at.allocated_amount
|
||||||
|
).where(
|
||||||
|
pe.tax_withholding_category == tax_details.get('tax_withholding_category')
|
||||||
|
).where(
|
||||||
|
at.parent.isin(advances)
|
||||||
|
).where(
|
||||||
|
at.account_head == tax_details.account_head
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
return tax_info
|
||||||
|
|
||||||
|
|
||||||
def get_deducted_tax(taxable_vouchers, tax_details):
|
def get_deducted_tax(taxable_vouchers, tax_details):
|
||||||
# check if TDS / TCS account is already charged on taxable vouchers
|
# check if TDS / TCS account is already charged on taxable vouchers
|
||||||
filters = {
|
filters = {
|
||||||
|
|||||||
@ -73,8 +73,28 @@ def process_gl_map(gl_map, merge_entries=True, precision=None):
|
|||||||
flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency)
|
flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency)
|
||||||
entry.credit_in_account_currency = 0.0
|
entry.credit_in_account_currency = 0.0
|
||||||
|
|
||||||
|
update_net_values(entry)
|
||||||
|
|
||||||
return gl_map
|
return gl_map
|
||||||
|
|
||||||
|
def update_net_values(entry):
|
||||||
|
# In some scenarios net value needs to be shown in the ledger
|
||||||
|
# This method updates net values as debit or credit
|
||||||
|
if entry.post_net_value and entry.debit and entry.credit:
|
||||||
|
if entry.debit > entry.credit:
|
||||||
|
entry.debit = entry.debit - entry.credit
|
||||||
|
entry.debit_in_account_currency = entry.debit_in_account_currency \
|
||||||
|
- entry.credit_in_account_currency
|
||||||
|
entry.credit = 0
|
||||||
|
entry.credit_in_account_currency = 0
|
||||||
|
else:
|
||||||
|
entry.credit = entry.credit - entry.debit
|
||||||
|
entry.credit_in_account_currency = entry.credit_in_account_currency \
|
||||||
|
- entry.debit_in_account_currency
|
||||||
|
|
||||||
|
entry.debit = 0
|
||||||
|
entry.debit_in_account_currency = 0
|
||||||
|
|
||||||
def merge_similar_entries(gl_map, precision=None):
|
def merge_similar_entries(gl_map, precision=None):
|
||||||
merged_gl_map = []
|
merged_gl_map = []
|
||||||
accounting_dimensions = get_accounting_dimensions()
|
accounting_dimensions = get_accounting_dimensions()
|
||||||
|
|||||||
@ -68,10 +68,12 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
|
|||||||
party_details["tax_category"] = get_address_tax_category(party.get("tax_category"),
|
party_details["tax_category"] = get_address_tax_category(party.get("tax_category"),
|
||||||
party_address, shipping_address if party_type != "Supplier" else party_address)
|
party_address, shipping_address if party_type != "Supplier" else party_address)
|
||||||
|
|
||||||
if not party_details.get("taxes_and_charges"):
|
tax_template = set_taxes(party.name, party_type, posting_date, company,
|
||||||
party_details["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company,
|
customer_group=party_details.customer_group, supplier_group=party_details.supplier_group, tax_category=party_details.tax_category,
|
||||||
customer_group=party_details.customer_group, supplier_group=party_details.supplier_group, tax_category=party_details.tax_category,
|
billing_address=party_address, shipping_address=shipping_address)
|
||||||
billing_address=party_address, shipping_address=shipping_address)
|
|
||||||
|
if tax_template:
|
||||||
|
party_details['taxes_and_charges'] = tax_template
|
||||||
|
|
||||||
if cint(fetch_payment_terms_template):
|
if cint(fetch_payment_terms_template):
|
||||||
party_details["payment_terms_template"] = get_payment_terms_template(party.name, party_type, company)
|
party_details["payment_terms_template"] = get_payment_terms_template(party.name, party_type, company)
|
||||||
|
|||||||
@ -109,7 +109,11 @@ class ReceivablePayableReport(object):
|
|||||||
invoiced = 0.0,
|
invoiced = 0.0,
|
||||||
paid = 0.0,
|
paid = 0.0,
|
||||||
credit_note = 0.0,
|
credit_note = 0.0,
|
||||||
outstanding = 0.0
|
outstanding = 0.0,
|
||||||
|
invoiced_in_account_currency = 0.0,
|
||||||
|
paid_in_account_currency = 0.0,
|
||||||
|
credit_note_in_account_currency = 0.0,
|
||||||
|
outstanding_in_account_currency = 0.0
|
||||||
)
|
)
|
||||||
self.get_invoices(gle)
|
self.get_invoices(gle)
|
||||||
|
|
||||||
@ -150,21 +154,28 @@ class ReceivablePayableReport(object):
|
|||||||
# gle_balance will be the total "debit - credit" for receivable type reports and
|
# gle_balance will be the total "debit - credit" for receivable type reports and
|
||||||
# and vice-versa for payable type reports
|
# and vice-versa for payable type reports
|
||||||
gle_balance = self.get_gle_balance(gle)
|
gle_balance = self.get_gle_balance(gle)
|
||||||
|
gle_balance_in_account_currency = self.get_gle_balance_in_account_currency(gle)
|
||||||
|
|
||||||
if gle_balance > 0:
|
if gle_balance > 0:
|
||||||
if gle.voucher_type in ('Journal Entry', 'Payment Entry') and gle.against_voucher:
|
if gle.voucher_type in ('Journal Entry', 'Payment Entry') and gle.against_voucher:
|
||||||
# debit against sales / purchase invoice
|
# debit against sales / purchase invoice
|
||||||
row.paid -= gle_balance
|
row.paid -= gle_balance
|
||||||
|
row.paid_in_account_currency -= gle_balance_in_account_currency
|
||||||
else:
|
else:
|
||||||
# invoice
|
# invoice
|
||||||
row.invoiced += gle_balance
|
row.invoiced += gle_balance
|
||||||
|
row.invoiced_in_account_currency += gle_balance_in_account_currency
|
||||||
else:
|
else:
|
||||||
# payment or credit note for receivables
|
# payment or credit note for receivables
|
||||||
if self.is_invoice(gle):
|
if self.is_invoice(gle):
|
||||||
# stand alone debit / credit note
|
# stand alone debit / credit note
|
||||||
row.credit_note -= gle_balance
|
row.credit_note -= gle_balance
|
||||||
|
row.credit_note_in_account_currency -= gle_balance_in_account_currency
|
||||||
else:
|
else:
|
||||||
# advance / unlinked payment or other adjustment
|
# advance / unlinked payment or other adjustment
|
||||||
row.paid -= gle_balance
|
row.paid -= gle_balance
|
||||||
|
row.paid_in_account_currency -= gle_balance_in_account_currency
|
||||||
|
|
||||||
if gle.cost_center:
|
if gle.cost_center:
|
||||||
row.cost_center = str(gle.cost_center)
|
row.cost_center = str(gle.cost_center)
|
||||||
|
|
||||||
@ -216,8 +227,13 @@ class ReceivablePayableReport(object):
|
|||||||
# as we can use this to filter out invoices without outstanding
|
# as we can use this to filter out invoices without outstanding
|
||||||
for key, row in self.voucher_balance.items():
|
for key, row in self.voucher_balance.items():
|
||||||
row.outstanding = flt(row.invoiced - row.paid - row.credit_note, self.currency_precision)
|
row.outstanding = flt(row.invoiced - row.paid - row.credit_note, self.currency_precision)
|
||||||
|
row.outstanding_in_account_currency = flt(row.invoiced_in_account_currency - row.paid_in_account_currency - \
|
||||||
|
row.credit_note_in_account_currency, self.currency_precision)
|
||||||
|
|
||||||
row.invoice_grand_total = row.invoiced
|
row.invoice_grand_total = row.invoiced
|
||||||
if abs(row.outstanding) > 1.0/10 ** self.currency_precision:
|
|
||||||
|
if (abs(row.outstanding) > 1.0/10 ** self.currency_precision) and \
|
||||||
|
(abs(row.outstanding_in_account_currency) > 1.0/10 ** self.currency_precision):
|
||||||
# non-zero oustanding, we must consider this row
|
# non-zero oustanding, we must consider this row
|
||||||
|
|
||||||
if self.is_invoice(row) and self.filters.based_on_payment_terms:
|
if self.is_invoice(row) and self.filters.based_on_payment_terms:
|
||||||
@ -529,7 +545,9 @@ class ReceivablePayableReport(object):
|
|||||||
|
|
||||||
def set_ageing(self, row):
|
def set_ageing(self, row):
|
||||||
if self.filters.ageing_based_on == "Due Date":
|
if self.filters.ageing_based_on == "Due Date":
|
||||||
entry_date = row.due_date
|
# use posting date as a fallback for advances posted via journal and payment entry
|
||||||
|
# when ageing viewed by due date
|
||||||
|
entry_date = row.due_date or row.posting_date
|
||||||
elif self.filters.ageing_based_on == "Supplier Invoice Date":
|
elif self.filters.ageing_based_on == "Supplier Invoice Date":
|
||||||
entry_date = row.bill_date
|
entry_date = row.bill_date
|
||||||
else:
|
else:
|
||||||
@ -583,12 +601,14 @@ class ReceivablePayableReport(object):
|
|||||||
else:
|
else:
|
||||||
select_fields = "debit, credit"
|
select_fields = "debit, credit"
|
||||||
|
|
||||||
|
doc_currency_fields = "debit_in_account_currency, credit_in_account_currency"
|
||||||
|
|
||||||
remarks = ", remarks" if self.filters.get("show_remarks") else ""
|
remarks = ", remarks" if self.filters.get("show_remarks") else ""
|
||||||
|
|
||||||
self.gl_entries = frappe.db.sql("""
|
self.gl_entries = frappe.db.sql("""
|
||||||
select
|
select
|
||||||
name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center,
|
name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center,
|
||||||
against_voucher_type, against_voucher, account_currency, {0} {remarks}
|
against_voucher_type, against_voucher, account_currency, {0}, {1} {remarks}
|
||||||
from
|
from
|
||||||
`tabGL Entry`
|
`tabGL Entry`
|
||||||
where
|
where
|
||||||
@ -596,8 +616,8 @@ class ReceivablePayableReport(object):
|
|||||||
and is_cancelled = 0
|
and is_cancelled = 0
|
||||||
and party_type=%s
|
and party_type=%s
|
||||||
and (party is not null and party != '')
|
and (party is not null and party != '')
|
||||||
{1} {2} {3}"""
|
{2} {3} {4}"""
|
||||||
.format(select_fields, date_condition, conditions, order_by, remarks=remarks), values, as_dict=True)
|
.format(select_fields, doc_currency_fields, date_condition, conditions, order_by, remarks=remarks), values, as_dict=True)
|
||||||
|
|
||||||
def get_sales_invoices_or_customers_based_on_sales_person(self):
|
def get_sales_invoices_or_customers_based_on_sales_person(self):
|
||||||
if self.filters.get("sales_person"):
|
if self.filters.get("sales_person"):
|
||||||
@ -718,6 +738,13 @@ class ReceivablePayableReport(object):
|
|||||||
# get the balance of the GL (debit - credit) or reverse balance based on report type
|
# get the balance of the GL (debit - credit) or reverse balance based on report type
|
||||||
return gle.get(self.dr_or_cr) - self.get_reverse_balance(gle)
|
return gle.get(self.dr_or_cr) - self.get_reverse_balance(gle)
|
||||||
|
|
||||||
|
def get_gle_balance_in_account_currency(self, gle):
|
||||||
|
# get the balance of the GL (debit - credit) or reverse balance based on report type
|
||||||
|
return gle.get(self.dr_or_cr + '_in_account_currency') - self.get_reverse_balance_in_account_currency(gle)
|
||||||
|
|
||||||
|
def get_reverse_balance_in_account_currency(self, gle):
|
||||||
|
return gle.get('debit_in_account_currency' if self.dr_or_cr=='credit' else 'credit_in_account_currency')
|
||||||
|
|
||||||
def get_reverse_balance(self, gle):
|
def get_reverse_balance(self, gle):
|
||||||
# get "credit" balance if report type is "debit" and vice versa
|
# get "credit" balance if report type is "debit" and vice versa
|
||||||
return gle.get('debit' if self.dr_or_cr=='credit' else 'credit')
|
return gle.get('debit' if self.dr_or_cr=='credit' else 'credit')
|
||||||
|
|||||||
@ -92,6 +92,11 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"label": __("Include Default Book Entries"),
|
"label": __("Include Default Book Entries"),
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"default": 1
|
"default": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "show_zero_values",
|
||||||
|
"label": __("Show zero values"),
|
||||||
|
"fieldtype": "Check"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"formatter": function(value, row, column, data, default_formatter) {
|
"formatter": function(value, row, column, data, default_formatter) {
|
||||||
|
|||||||
@ -22,7 +22,11 @@ from erpnext.accounts.report.cash_flow.cash_flow import (
|
|||||||
get_cash_flow_accounts,
|
get_cash_flow_accounts,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.report.cash_flow.cash_flow import get_report_summary as get_cash_flow_summary
|
from erpnext.accounts.report.cash_flow.cash_flow import get_report_summary as get_cash_flow_summary
|
||||||
from erpnext.accounts.report.financial_statements import get_fiscal_year_data, sort_accounts
|
from erpnext.accounts.report.financial_statements import (
|
||||||
|
filter_out_zero_value_rows,
|
||||||
|
get_fiscal_year_data,
|
||||||
|
sort_accounts,
|
||||||
|
)
|
||||||
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import (
|
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import (
|
||||||
get_chart_data as get_pl_chart_data,
|
get_chart_data as get_pl_chart_data,
|
||||||
)
|
)
|
||||||
@ -265,7 +269,7 @@ def get_columns(companies, filters):
|
|||||||
return columns
|
return columns
|
||||||
|
|
||||||
def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, ignore_closing_entries=False):
|
def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, ignore_closing_entries=False):
|
||||||
accounts, accounts_by_name = get_account_heads(root_type,
|
accounts, accounts_by_name, parent_children_map = get_account_heads(root_type,
|
||||||
companies, filters)
|
companies, filters)
|
||||||
|
|
||||||
if not accounts: return []
|
if not accounts: return []
|
||||||
@ -294,6 +298,8 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i
|
|||||||
|
|
||||||
out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters)
|
out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters)
|
||||||
|
|
||||||
|
out = filter_out_zero_value_rows(out, parent_children_map, show_zero_values=filters.get("show_zero_values"))
|
||||||
|
|
||||||
if out:
|
if out:
|
||||||
add_total_row(out, root_type, balance_must_be, companies, company_currency)
|
add_total_row(out, root_type, balance_must_be, companies, company_currency)
|
||||||
|
|
||||||
@ -364,13 +370,13 @@ def get_account_heads(root_type, companies, filters):
|
|||||||
accounts = get_accounts(root_type, filters)
|
accounts = get_accounts(root_type, filters)
|
||||||
|
|
||||||
if not accounts:
|
if not accounts:
|
||||||
return None, None
|
return None, None, None
|
||||||
|
|
||||||
accounts = update_parent_account_names(accounts)
|
accounts = update_parent_account_names(accounts)
|
||||||
|
|
||||||
accounts, accounts_by_name, parent_children_map = filter_accounts(accounts)
|
accounts, accounts_by_name, parent_children_map = filter_accounts(accounts)
|
||||||
|
|
||||||
return accounts, accounts_by_name
|
return accounts, accounts_by_name, parent_children_map
|
||||||
|
|
||||||
def update_parent_account_names(accounts):
|
def update_parent_account_names(accounts):
|
||||||
"""Update parent_account_name in accounts list.
|
"""Update parent_account_name in accounts list.
|
||||||
|
|||||||
@ -0,0 +1,114 @@
|
|||||||
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
function get_filters() {
|
||||||
|
let filters = [
|
||||||
|
{
|
||||||
|
"fieldname":"company",
|
||||||
|
"label": __("Company"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Company",
|
||||||
|
"default": frappe.defaults.get_user_default("Company"),
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"filter_based_on",
|
||||||
|
"label": __("Filter Based On"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": ["Fiscal Year", "Date Range"],
|
||||||
|
"default": ["Fiscal Year"],
|
||||||
|
"reqd": 1,
|
||||||
|
on_change: function() {
|
||||||
|
let filter_based_on = frappe.query_report.get_filter_value('filter_based_on');
|
||||||
|
frappe.query_report.toggle_filter_display('from_fiscal_year', filter_based_on === 'Date Range');
|
||||||
|
frappe.query_report.toggle_filter_display('to_fiscal_year', filter_based_on === 'Date Range');
|
||||||
|
frappe.query_report.toggle_filter_display('period_start_date', filter_based_on === 'Fiscal Year');
|
||||||
|
frappe.query_report.toggle_filter_display('period_end_date', filter_based_on === 'Fiscal Year');
|
||||||
|
|
||||||
|
frappe.query_report.refresh();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"period_start_date",
|
||||||
|
"label": __("Start Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"hidden": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"period_end_date",
|
||||||
|
"label": __("End Date"),
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"hidden": 1,
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"from_fiscal_year",
|
||||||
|
"label": __("Start Year"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Fiscal Year",
|
||||||
|
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"to_fiscal_year",
|
||||||
|
"label": __("End Year"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Fiscal Year",
|
||||||
|
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "periodicity",
|
||||||
|
"label": __("Periodicity"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": [
|
||||||
|
{ "value": "Monthly", "label": __("Monthly") },
|
||||||
|
{ "value": "Quarterly", "label": __("Quarterly") },
|
||||||
|
{ "value": "Half-Yearly", "label": __("Half-Yearly") },
|
||||||
|
{ "value": "Yearly", "label": __("Yearly") }
|
||||||
|
],
|
||||||
|
"default": "Monthly",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "type",
|
||||||
|
"label": __("Invoice Type"),
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": [
|
||||||
|
{ "value": "Revenue", "label": __("Revenue") },
|
||||||
|
{ "value": "Expense", "label": __("Expense") }
|
||||||
|
],
|
||||||
|
"default": "Revenue",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname" : "with_upcoming_postings",
|
||||||
|
"label": __("Show with upcoming revenue/expense"),
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.query_reports["Deferred Revenue and Expense"] = {
|
||||||
|
"filters": get_filters(),
|
||||||
|
"formatter": function(value, row, column, data, default_formatter){
|
||||||
|
return default_formatter(value, row, column, data);
|
||||||
|
},
|
||||||
|
onload: function(report){
|
||||||
|
let fiscal_year = frappe.defaults.get_user_default("fiscal_year");
|
||||||
|
|
||||||
|
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||||
|
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||||
|
frappe.query_report.set_filter_value({
|
||||||
|
period_start_date: fy.year_start_date,
|
||||||
|
period_end_date: fy.year_end_date
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2021-12-10 19:27:14.654220",
|
||||||
|
"disable_prepared_report": 0,
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"modified": "2021-12-10 19:27:14.654220",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Deferred Revenue and Expense",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "GL Entry",
|
||||||
|
"report_name": "Deferred Revenue and Expense",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "Accounts User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Accounts Manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Auditor"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,440 @@
|
|||||||
|
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# License: MIT. See LICENSE
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import _, qb
|
||||||
|
from frappe.query_builder import Column, functions
|
||||||
|
from frappe.utils import add_days, date_diff, flt, get_first_day, get_last_day, rounded
|
||||||
|
|
||||||
|
from erpnext.accounts.report.financial_statements import get_period_list
|
||||||
|
|
||||||
|
|
||||||
|
class Deferred_Item(object):
|
||||||
|
"""
|
||||||
|
Helper class for processing items with deferred revenue/expense
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, item, inv, gle_entries):
|
||||||
|
self.name = item
|
||||||
|
self.parent = inv.name
|
||||||
|
self.item_name = gle_entries[0].item_name
|
||||||
|
self.service_start_date = gle_entries[0].service_start_date
|
||||||
|
self.service_end_date = gle_entries[0].service_end_date
|
||||||
|
self.base_net_amount = gle_entries[0].base_net_amount
|
||||||
|
self.filters = inv.filters
|
||||||
|
self.period_list = inv.period_list
|
||||||
|
|
||||||
|
if gle_entries[0].deferred_revenue_account:
|
||||||
|
self.type = "Deferred Sale Item"
|
||||||
|
self.deferred_account = gle_entries[0].deferred_revenue_account
|
||||||
|
elif gle_entries[0].deferred_expense_account:
|
||||||
|
self.type = "Deferred Purchase Item"
|
||||||
|
self.deferred_account = gle_entries[0].deferred_expense_account
|
||||||
|
|
||||||
|
self.gle_entries = []
|
||||||
|
# holds period wise total for item
|
||||||
|
self.period_total = []
|
||||||
|
self.last_entry_date = self.service_start_date
|
||||||
|
|
||||||
|
if gle_entries:
|
||||||
|
self.gle_entries = gle_entries
|
||||||
|
for x in self.gle_entries:
|
||||||
|
if self.get_amount(x):
|
||||||
|
self.last_entry_date = x.gle_posting_date
|
||||||
|
|
||||||
|
def report_data(self):
|
||||||
|
"""
|
||||||
|
Generate report data for output
|
||||||
|
"""
|
||||||
|
ret_data = frappe._dict({"name": self.item_name})
|
||||||
|
for period in self.period_total:
|
||||||
|
ret_data[period.key] = period.total
|
||||||
|
ret_data.indent = 1
|
||||||
|
return ret_data
|
||||||
|
|
||||||
|
def get_amount(self, entry):
|
||||||
|
"""
|
||||||
|
For a given GL/Journal posting, get balance based on item type
|
||||||
|
"""
|
||||||
|
if self.type == "Deferred Sale Item":
|
||||||
|
return entry.debit - entry.credit
|
||||||
|
elif self.type == "Deferred Purchase Item":
|
||||||
|
return -(entry.credit - entry.debit)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def get_item_total(self):
|
||||||
|
"""
|
||||||
|
Helper method - calculate booked amount. Includes simulated postings as well
|
||||||
|
"""
|
||||||
|
total = 0
|
||||||
|
for gle_posting in self.gle_entries:
|
||||||
|
total += self.get_amount(gle_posting)
|
||||||
|
|
||||||
|
return total
|
||||||
|
|
||||||
|
def calculate_amount(self, start_date, end_date):
|
||||||
|
"""
|
||||||
|
start_date, end_date - datetime.datetime.date
|
||||||
|
return - estimated amount to post for given period
|
||||||
|
Calculated based on already booked amount and item service period
|
||||||
|
"""
|
||||||
|
total_months = (
|
||||||
|
(self.service_end_date.year - self.service_start_date.year) * 12
|
||||||
|
+ (self.service_end_date.month - self.service_start_date.month)
|
||||||
|
+ 1
|
||||||
|
)
|
||||||
|
|
||||||
|
prorate = date_diff(self.service_end_date, self.service_start_date) / date_diff(
|
||||||
|
get_last_day(self.service_end_date), get_first_day(self.service_start_date)
|
||||||
|
)
|
||||||
|
|
||||||
|
actual_months = rounded(total_months * prorate, 1)
|
||||||
|
|
||||||
|
already_booked_amount = self.get_item_total()
|
||||||
|
base_amount = self.base_net_amount / actual_months
|
||||||
|
|
||||||
|
if base_amount + already_booked_amount > self.base_net_amount:
|
||||||
|
base_amount = self.base_net_amount - already_booked_amount
|
||||||
|
|
||||||
|
if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date):
|
||||||
|
partial_month = flt(date_diff(end_date, start_date)) / flt(
|
||||||
|
date_diff(get_last_day(end_date), get_first_day(start_date))
|
||||||
|
)
|
||||||
|
base_amount *= rounded(partial_month, 1)
|
||||||
|
|
||||||
|
return base_amount
|
||||||
|
|
||||||
|
def make_dummy_gle(self, name, date, amount):
|
||||||
|
"""
|
||||||
|
return - frappe._dict() of a dummy gle entry
|
||||||
|
"""
|
||||||
|
entry = frappe._dict(
|
||||||
|
{"name": name, "gle_posting_date": date, "debit": 0, "credit": 0, "posted": "not"}
|
||||||
|
)
|
||||||
|
if self.type == "Deferred Sale Item":
|
||||||
|
entry.debit = amount
|
||||||
|
elif self.type == "Deferred Purchase Item":
|
||||||
|
entry.credit = amount
|
||||||
|
return entry
|
||||||
|
|
||||||
|
def simulate_future_posting(self):
|
||||||
|
"""
|
||||||
|
simulate future posting by creating dummy gl entries. starts from the last posting date.
|
||||||
|
"""
|
||||||
|
if add_days(self.last_entry_date, 1) < self.period_list[-1].to_date:
|
||||||
|
self.estimate_for_period_list = get_period_list(
|
||||||
|
self.filters.from_fiscal_year,
|
||||||
|
self.filters.to_fiscal_year,
|
||||||
|
add_days(self.last_entry_date, 1),
|
||||||
|
self.period_list[-1].to_date,
|
||||||
|
"Date Range",
|
||||||
|
"Monthly",
|
||||||
|
company=self.filters.company,
|
||||||
|
)
|
||||||
|
for period in self.estimate_for_period_list:
|
||||||
|
amount = self.calculate_amount(period.from_date, period.to_date)
|
||||||
|
gle = self.make_dummy_gle(period.key, period.to_date, amount)
|
||||||
|
self.gle_entries.append(gle)
|
||||||
|
|
||||||
|
def calculate_item_revenue_expense_for_period(self):
|
||||||
|
"""
|
||||||
|
calculate item postings for each period and update period_total list
|
||||||
|
"""
|
||||||
|
for period in self.period_list:
|
||||||
|
period_sum = 0
|
||||||
|
actual = 0
|
||||||
|
for posting in self.gle_entries:
|
||||||
|
# if period.from_date <= posting.posting_date <= period.to_date:
|
||||||
|
if period.from_date <= posting.gle_posting_date <= period.to_date:
|
||||||
|
period_sum += self.get_amount(posting)
|
||||||
|
if posting.posted == "posted":
|
||||||
|
actual += self.get_amount(posting)
|
||||||
|
|
||||||
|
self.period_total.append(
|
||||||
|
frappe._dict({"key": period.key, "total": period_sum, "actual": actual})
|
||||||
|
)
|
||||||
|
return self.period_total
|
||||||
|
|
||||||
|
|
||||||
|
class Deferred_Invoice(object):
|
||||||
|
def __init__(self, invoice, items, filters, period_list):
|
||||||
|
"""
|
||||||
|
Helper class for processing invoices with deferred revenue/expense items
|
||||||
|
invoice - string : invoice name
|
||||||
|
items - list : frappe._dict() with item details. Refer Deferred_Item for required fields
|
||||||
|
"""
|
||||||
|
self.name = invoice
|
||||||
|
self.posting_date = items[0].posting_date
|
||||||
|
self.filters = filters
|
||||||
|
self.period_list = period_list
|
||||||
|
# holds period wise total for invoice
|
||||||
|
self.period_total = []
|
||||||
|
|
||||||
|
if items[0].deferred_revenue_account:
|
||||||
|
self.type = "Sales"
|
||||||
|
elif items[0].deferred_expense_account:
|
||||||
|
self.type = "Purchase"
|
||||||
|
|
||||||
|
self.items = []
|
||||||
|
# for each uniq items
|
||||||
|
self.uniq_items = set([x.item for x in items])
|
||||||
|
for item in self.uniq_items:
|
||||||
|
self.items.append(Deferred_Item(item, self, [x for x in items if x.item == item]))
|
||||||
|
|
||||||
|
def calculate_invoice_revenue_expense_for_period(self):
|
||||||
|
"""
|
||||||
|
calculate deferred revenue/expense for all items in invoice
|
||||||
|
"""
|
||||||
|
# initialize period_total list for invoice
|
||||||
|
for period in self.period_list:
|
||||||
|
self.period_total.append(frappe._dict({"key": period.key, "total": 0, "actual": 0}))
|
||||||
|
|
||||||
|
for item in self.items:
|
||||||
|
item_total = item.calculate_item_revenue_expense_for_period()
|
||||||
|
# update invoice total
|
||||||
|
for idx, period in enumerate(self.period_list, 0):
|
||||||
|
self.period_total[idx].total += item_total[idx].total
|
||||||
|
self.period_total[idx].actual += item_total[idx].actual
|
||||||
|
return self.period_total
|
||||||
|
|
||||||
|
def estimate_future(self):
|
||||||
|
"""
|
||||||
|
create dummy GL entries for upcoming months for all items in invoice
|
||||||
|
"""
|
||||||
|
[item.simulate_future_posting() for item in self.items]
|
||||||
|
|
||||||
|
def report_data(self):
|
||||||
|
"""
|
||||||
|
generate report data for invoice, includes invoice total
|
||||||
|
"""
|
||||||
|
ret_data = []
|
||||||
|
inv_total = frappe._dict({"name": self.name})
|
||||||
|
for x in self.period_total:
|
||||||
|
inv_total[x.key] = x.total
|
||||||
|
inv_total.indent = 0
|
||||||
|
ret_data.append(inv_total)
|
||||||
|
list(map(lambda item: ret_data.append(item.report_data()), self.items))
|
||||||
|
return ret_data
|
||||||
|
|
||||||
|
|
||||||
|
class Deferred_Revenue_and_Expense_Report(object):
|
||||||
|
def __init__(self, filters=None):
|
||||||
|
"""
|
||||||
|
Initialize deferred revenue/expense report with user provided filters or system defaults, if none is provided
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If no filters are provided, get user defaults
|
||||||
|
if not filters:
|
||||||
|
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
||||||
|
self.filters = frappe._dict(
|
||||||
|
{
|
||||||
|
"company": frappe.defaults.get_user_default("Company"),
|
||||||
|
"filter_based_on": "Fiscal Year",
|
||||||
|
"period_start_date": fiscal_year.year_start_date,
|
||||||
|
"period_end_date": fiscal_year.year_end_date,
|
||||||
|
"from_fiscal_year": fiscal_year.year,
|
||||||
|
"to_fiscal_year": fiscal_year.year,
|
||||||
|
"periodicity": "Monthly",
|
||||||
|
"type": "Revenue",
|
||||||
|
"with_upcoming_postings": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.filters = frappe._dict(filters)
|
||||||
|
|
||||||
|
self.period_list = None
|
||||||
|
self.deferred_invoices = []
|
||||||
|
# holds period wise total for report
|
||||||
|
self.period_total = []
|
||||||
|
|
||||||
|
def get_period_list(self):
|
||||||
|
"""
|
||||||
|
Figure out selected period based on filters
|
||||||
|
"""
|
||||||
|
self.period_list = get_period_list(
|
||||||
|
self.filters.from_fiscal_year,
|
||||||
|
self.filters.to_fiscal_year,
|
||||||
|
self.filters.period_start_date,
|
||||||
|
self.filters.period_end_date,
|
||||||
|
self.filters.filter_based_on,
|
||||||
|
self.filters.periodicity,
|
||||||
|
company=self.filters.company,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_invoices(self):
|
||||||
|
"""
|
||||||
|
Get all sales and purchase invoices which has deferred revenue/expense items
|
||||||
|
"""
|
||||||
|
gle = qb.DocType("GL Entry")
|
||||||
|
# column doesn't have an alias option
|
||||||
|
posted = Column("posted")
|
||||||
|
|
||||||
|
if self.filters.type == "Revenue":
|
||||||
|
inv = qb.DocType("Sales Invoice")
|
||||||
|
inv_item = qb.DocType("Sales Invoice Item")
|
||||||
|
deferred_flag_field = inv_item["enable_deferred_revenue"]
|
||||||
|
deferred_account_field = inv_item["deferred_revenue_account"]
|
||||||
|
|
||||||
|
elif self.filters.type == "Expense":
|
||||||
|
inv = qb.DocType("Purchase Invoice")
|
||||||
|
inv_item = qb.DocType("Purchase Invoice Item")
|
||||||
|
deferred_flag_field = inv_item["enable_deferred_expense"]
|
||||||
|
deferred_account_field = inv_item["deferred_expense_account"]
|
||||||
|
|
||||||
|
query = (
|
||||||
|
qb.from_(inv_item)
|
||||||
|
.join(inv)
|
||||||
|
.on(inv.name == inv_item.parent)
|
||||||
|
.join(gle)
|
||||||
|
.on((inv_item.name == gle.voucher_detail_no) & (deferred_account_field == gle.account))
|
||||||
|
.select(
|
||||||
|
inv.name.as_("doc"),
|
||||||
|
inv.posting_date,
|
||||||
|
inv_item.name.as_("item"),
|
||||||
|
inv_item.item_name,
|
||||||
|
inv_item.service_start_date,
|
||||||
|
inv_item.service_end_date,
|
||||||
|
inv_item.base_net_amount,
|
||||||
|
deferred_account_field,
|
||||||
|
gle.posting_date.as_("gle_posting_date"),
|
||||||
|
functions.Sum(gle.debit).as_("debit"),
|
||||||
|
functions.Sum(gle.credit).as_("credit"),
|
||||||
|
posted,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(inv.docstatus == 1)
|
||||||
|
& (deferred_flag_field == 1)
|
||||||
|
& (
|
||||||
|
(
|
||||||
|
(self.period_list[0].from_date >= inv_item.service_start_date)
|
||||||
|
& (inv_item.service_end_date >= self.period_list[0].from_date)
|
||||||
|
)
|
||||||
|
| (
|
||||||
|
(inv_item.service_start_date >= self.period_list[0].from_date)
|
||||||
|
& (inv_item.service_start_date <= self.period_list[-1].to_date)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.groupby(inv.name, inv_item.name, gle.posting_date)
|
||||||
|
.orderby(gle.posting_date)
|
||||||
|
)
|
||||||
|
self.invoices = query.run(as_dict=True)
|
||||||
|
|
||||||
|
uniq_invoice = set([x.doc for x in self.invoices])
|
||||||
|
for inv in uniq_invoice:
|
||||||
|
self.deferred_invoices.append(
|
||||||
|
Deferred_Invoice(
|
||||||
|
inv, [x for x in self.invoices if x.doc == inv], self.filters, self.period_list
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def estimate_future(self):
|
||||||
|
"""
|
||||||
|
For all Invoices estimate upcoming postings
|
||||||
|
"""
|
||||||
|
for x in self.deferred_invoices:
|
||||||
|
x.estimate_future()
|
||||||
|
|
||||||
|
def calculate_revenue_and_expense(self):
|
||||||
|
"""
|
||||||
|
calculate the deferred revenue/expense for all invoices
|
||||||
|
"""
|
||||||
|
# initialize period_total list for report
|
||||||
|
for period in self.period_list:
|
||||||
|
self.period_total.append(frappe._dict({"key": period.key, "total": 0, "actual": 0}))
|
||||||
|
|
||||||
|
for inv in self.deferred_invoices:
|
||||||
|
inv_total = inv.calculate_invoice_revenue_expense_for_period()
|
||||||
|
# calculate total for whole report
|
||||||
|
for idx, period in enumerate(self.period_list, 0):
|
||||||
|
self.period_total[idx].total += inv_total[idx].total
|
||||||
|
self.period_total[idx].actual += inv_total[idx].actual
|
||||||
|
|
||||||
|
def get_columns(self):
|
||||||
|
columns = []
|
||||||
|
columns.append({"label": _("Name"), "fieldname": "name", "fieldtype": "Data", "read_only": 1})
|
||||||
|
for period in self.period_list:
|
||||||
|
columns.append(
|
||||||
|
{
|
||||||
|
"label": _(period.label),
|
||||||
|
"fieldname": period.key,
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"read_only": 1,
|
||||||
|
})
|
||||||
|
return columns
|
||||||
|
|
||||||
|
def generate_report_data(self):
|
||||||
|
"""
|
||||||
|
Generate report data for all invoices. Adds total rows for revenue and expense
|
||||||
|
"""
|
||||||
|
ret = []
|
||||||
|
|
||||||
|
for inv in self.deferred_invoices:
|
||||||
|
ret += inv.report_data()
|
||||||
|
|
||||||
|
# empty row for padding
|
||||||
|
ret += [{}]
|
||||||
|
|
||||||
|
# add total row
|
||||||
|
if ret is not []:
|
||||||
|
if self.filters.type == "Revenue":
|
||||||
|
total_row = frappe._dict({"name": "Total Deferred Income"})
|
||||||
|
elif self.filters.type == "Expense":
|
||||||
|
total_row = frappe._dict({"name": "Total Deferred Expense"})
|
||||||
|
|
||||||
|
for idx, period in enumerate(self.period_list, 0):
|
||||||
|
total_row[period.key] = self.period_total[idx].total
|
||||||
|
ret.append(total_row)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def prepare_chart(self):
|
||||||
|
chart = {
|
||||||
|
"data": {
|
||||||
|
"labels": [period.label for period in self.period_list],
|
||||||
|
"datasets": [
|
||||||
|
{
|
||||||
|
"name": "Actual Posting",
|
||||||
|
"chartType": "bar",
|
||||||
|
"values": [x.actual for x in self.period_total],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"type": "axis-mixed",
|
||||||
|
"height": 500,
|
||||||
|
"axisOptions": {"xAxisMode": "Tick", "xIsSeries": True},
|
||||||
|
"barOptions": {"stacked": False, "spaceRatio": 0.5},
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.filters.with_upcoming_postings:
|
||||||
|
chart["data"]["datasets"].append({
|
||||||
|
"name": "Expected",
|
||||||
|
"chartType": "line",
|
||||||
|
"values": [x.total for x in self.period_total]
|
||||||
|
})
|
||||||
|
|
||||||
|
return chart
|
||||||
|
|
||||||
|
def run(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Run report and generate data
|
||||||
|
"""
|
||||||
|
self.deferred_invoices.clear()
|
||||||
|
self.get_period_list()
|
||||||
|
self.get_invoices()
|
||||||
|
|
||||||
|
if self.filters.with_upcoming_postings:
|
||||||
|
self.estimate_future()
|
||||||
|
self.calculate_revenue_and_expense()
|
||||||
|
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
report = Deferred_Revenue_and_Expense_Report(filters=filters)
|
||||||
|
report.run()
|
||||||
|
|
||||||
|
columns = report.get_columns()
|
||||||
|
data = report.generate_report_data()
|
||||||
|
message = []
|
||||||
|
chart = report.prepare_chart()
|
||||||
|
|
||||||
|
return columns, data, message, chart
|
||||||
@ -0,0 +1,253 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe import qb
|
||||||
|
from frappe.utils import nowdate
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.account.test_account import create_account
|
||||||
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
from erpnext.accounts.report.deferred_revenue_and_expense.deferred_revenue_and_expense import (
|
||||||
|
Deferred_Revenue_and_Expense_Report,
|
||||||
|
)
|
||||||
|
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||||
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(self):
|
||||||
|
clear_old_entries()
|
||||||
|
create_company()
|
||||||
|
|
||||||
|
def test_deferred_revenue(self):
|
||||||
|
# created deferred expense accounts, if not found
|
||||||
|
deferred_revenue_account = create_account(
|
||||||
|
account_name="Deferred Revenue",
|
||||||
|
parent_account="Current Liabilities - _CD",
|
||||||
|
company="_Test Company DR",
|
||||||
|
)
|
||||||
|
|
||||||
|
acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
|
||||||
|
acc_settings.book_deferred_entries_based_on = "Months"
|
||||||
|
acc_settings.save()
|
||||||
|
|
||||||
|
customer = frappe.new_doc("Customer")
|
||||||
|
customer.customer_name = "_Test Customer DR"
|
||||||
|
customer.type = "Individual"
|
||||||
|
customer.insert()
|
||||||
|
|
||||||
|
item = create_item(
|
||||||
|
"_Test Internet Subscription",
|
||||||
|
is_stock_item=0,
|
||||||
|
warehouse="All Warehouses - _CD",
|
||||||
|
company="_Test Company DR",
|
||||||
|
)
|
||||||
|
item.enable_deferred_revenue = 1
|
||||||
|
item.deferred_revenue_account = deferred_revenue_account
|
||||||
|
item.no_of_months = 3
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
si = create_sales_invoice(
|
||||||
|
item=item.name,
|
||||||
|
company="_Test Company DR",
|
||||||
|
customer="_Test Customer DR",
|
||||||
|
debit_to="Debtors - _CD",
|
||||||
|
posting_date="2021-05-01",
|
||||||
|
parent_cost_center="Main - _CD",
|
||||||
|
cost_center="Main - _CD",
|
||||||
|
do_not_submit=True,
|
||||||
|
rate=300,
|
||||||
|
price_list_rate=300,
|
||||||
|
)
|
||||||
|
si.items[0].enable_deferred_revenue = 1
|
||||||
|
si.items[0].service_start_date = "2021-05-01"
|
||||||
|
si.items[0].service_end_date = "2021-08-01"
|
||||||
|
si.items[0].deferred_revenue_account = deferred_revenue_account
|
||||||
|
si.items[0].income_account = "Sales - _CD"
|
||||||
|
si.save()
|
||||||
|
si.submit()
|
||||||
|
|
||||||
|
pda = frappe.get_doc(
|
||||||
|
dict(
|
||||||
|
doctype="Process Deferred Accounting",
|
||||||
|
posting_date=nowdate(),
|
||||||
|
start_date="2021-05-01",
|
||||||
|
end_date="2021-08-01",
|
||||||
|
type="Income",
|
||||||
|
company="_Test Company DR",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
pda.insert()
|
||||||
|
pda.submit()
|
||||||
|
|
||||||
|
# execute report
|
||||||
|
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
||||||
|
self.filters = frappe._dict(
|
||||||
|
{
|
||||||
|
"company": frappe.defaults.get_user_default("Company"),
|
||||||
|
"filter_based_on": "Date Range",
|
||||||
|
"period_start_date": "2021-05-01",
|
||||||
|
"period_end_date": "2021-08-01",
|
||||||
|
"from_fiscal_year": fiscal_year.year,
|
||||||
|
"to_fiscal_year": fiscal_year.year,
|
||||||
|
"periodicity": "Monthly",
|
||||||
|
"type": "Revenue",
|
||||||
|
"with_upcoming_postings": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
report = Deferred_Revenue_and_Expense_Report(filters=self.filters)
|
||||||
|
report.run()
|
||||||
|
expected = [
|
||||||
|
{"key": "may_2021", "total": 100.0, "actual": 100.0},
|
||||||
|
{"key": "jun_2021", "total": 100.0, "actual": 100.0},
|
||||||
|
{"key": "jul_2021", "total": 100.0, "actual": 100.0},
|
||||||
|
{"key": "aug_2021", "total": 0, "actual": 0},
|
||||||
|
]
|
||||||
|
self.assertEqual(report.period_total, expected)
|
||||||
|
|
||||||
|
def test_deferred_expense(self):
|
||||||
|
# created deferred expense accounts, if not found
|
||||||
|
deferred_expense_account = create_account(
|
||||||
|
account_name="Deferred Expense",
|
||||||
|
parent_account="Current Assets - _CD",
|
||||||
|
company="_Test Company DR",
|
||||||
|
)
|
||||||
|
|
||||||
|
acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
|
||||||
|
acc_settings.book_deferred_entries_based_on = "Months"
|
||||||
|
acc_settings.save()
|
||||||
|
|
||||||
|
supplier = create_supplier(
|
||||||
|
supplier_name="_Test Furniture Supplier", supplier_group="Local", supplier_type="Company"
|
||||||
|
)
|
||||||
|
supplier.save()
|
||||||
|
|
||||||
|
item = create_item(
|
||||||
|
"_Test Office Desk",
|
||||||
|
is_stock_item=0,
|
||||||
|
warehouse="All Warehouses - _CD",
|
||||||
|
company="_Test Company DR",
|
||||||
|
)
|
||||||
|
item.enable_deferred_expense = 1
|
||||||
|
item.deferred_expense_account = deferred_expense_account
|
||||||
|
item.no_of_months_exp = 3
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
pi = make_purchase_invoice(
|
||||||
|
item=item.name,
|
||||||
|
company="_Test Company DR",
|
||||||
|
supplier="_Test Furniture Supplier",
|
||||||
|
is_return=False,
|
||||||
|
update_stock=False,
|
||||||
|
posting_date=frappe.utils.datetime.date(2021, 5, 1),
|
||||||
|
parent_cost_center="Main - _CD",
|
||||||
|
cost_center="Main - _CD",
|
||||||
|
do_not_save=True,
|
||||||
|
rate=300,
|
||||||
|
price_list_rate=300,
|
||||||
|
warehouse="All Warehouses - _CD",
|
||||||
|
qty=1,
|
||||||
|
)
|
||||||
|
pi.set_posting_time = True
|
||||||
|
pi.items[0].enable_deferred_expense = 1
|
||||||
|
pi.items[0].service_start_date = "2021-05-01"
|
||||||
|
pi.items[0].service_end_date = "2021-08-01"
|
||||||
|
pi.items[0].deferred_expense_account = deferred_expense_account
|
||||||
|
pi.items[0].expense_account = "Office Maintenance Expenses - _CD"
|
||||||
|
pi.save()
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
pda = frappe.get_doc(
|
||||||
|
dict(
|
||||||
|
doctype="Process Deferred Accounting",
|
||||||
|
posting_date=nowdate(),
|
||||||
|
start_date="2021-05-01",
|
||||||
|
end_date="2021-08-01",
|
||||||
|
type="Expense",
|
||||||
|
company="_Test Company DR",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
pda.insert()
|
||||||
|
pda.submit()
|
||||||
|
|
||||||
|
# execute report
|
||||||
|
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
||||||
|
self.filters = frappe._dict(
|
||||||
|
{
|
||||||
|
"company": frappe.defaults.get_user_default("Company"),
|
||||||
|
"filter_based_on": "Date Range",
|
||||||
|
"period_start_date": "2021-05-01",
|
||||||
|
"period_end_date": "2021-08-01",
|
||||||
|
"from_fiscal_year": fiscal_year.year,
|
||||||
|
"to_fiscal_year": fiscal_year.year,
|
||||||
|
"periodicity": "Monthly",
|
||||||
|
"type": "Expense",
|
||||||
|
"with_upcoming_postings": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
report = Deferred_Revenue_and_Expense_Report(filters=self.filters)
|
||||||
|
report.run()
|
||||||
|
expected = [
|
||||||
|
{"key": "may_2021", "total": -100.0, "actual": -100.0},
|
||||||
|
{"key": "jun_2021", "total": -100.0, "actual": -100.0},
|
||||||
|
{"key": "jul_2021", "total": -100.0, "actual": -100.0},
|
||||||
|
{"key": "aug_2021", "total": 0, "actual": 0},
|
||||||
|
]
|
||||||
|
self.assertEqual(report.period_total, expected)
|
||||||
|
|
||||||
|
|
||||||
|
def create_company():
|
||||||
|
company = frappe.db.exists("Company", "_Test Company DR")
|
||||||
|
if not company:
|
||||||
|
company = frappe.new_doc("Company")
|
||||||
|
company.company_name = "_Test Company DR"
|
||||||
|
company.default_currency = "INR"
|
||||||
|
company.chart_of_accounts = "Standard"
|
||||||
|
company.insert()
|
||||||
|
|
||||||
|
|
||||||
|
def clear_old_entries():
|
||||||
|
item = qb.DocType("Item")
|
||||||
|
account = qb.DocType("Account")
|
||||||
|
customer = qb.DocType("Customer")
|
||||||
|
supplier = qb.DocType("Supplier")
|
||||||
|
sinv = qb.DocType("Sales Invoice")
|
||||||
|
sinv_item = qb.DocType("Sales Invoice Item")
|
||||||
|
pinv = qb.DocType("Purchase Invoice")
|
||||||
|
pinv_item = qb.DocType("Purchase Invoice Item")
|
||||||
|
|
||||||
|
qb.from_(account).delete().where(
|
||||||
|
(account.account_name == "Deferred Revenue")
|
||||||
|
| (account.account_name == "Deferred Expense") & (account.company == "_Test Company DR")
|
||||||
|
).run()
|
||||||
|
qb.from_(item).delete().where(
|
||||||
|
(item.item_code == "_Test Internet Subscription") | (item.item_code == "_Test Office Rent")
|
||||||
|
).run()
|
||||||
|
qb.from_(customer).delete().where(customer.customer_name == "_Test Customer DR").run()
|
||||||
|
qb.from_(supplier).delete().where(supplier.supplier_name == "_Test Furniture Supplier").run()
|
||||||
|
|
||||||
|
# delete existing invoices with deferred items
|
||||||
|
deferred_invoices = (
|
||||||
|
qb.from_(sinv)
|
||||||
|
.join(sinv_item)
|
||||||
|
.on(sinv.name == sinv_item.parent)
|
||||||
|
.select(sinv.name)
|
||||||
|
.where(sinv_item.enable_deferred_revenue == 1)
|
||||||
|
.run()
|
||||||
|
)
|
||||||
|
if deferred_invoices:
|
||||||
|
qb.from_(sinv).delete().where(sinv.name.isin(deferred_invoices)).run()
|
||||||
|
|
||||||
|
deferred_invoices = (
|
||||||
|
qb.from_(pinv)
|
||||||
|
.join(pinv_item)
|
||||||
|
.on(pinv.name == pinv_item.parent)
|
||||||
|
.select(pinv.name)
|
||||||
|
.where(pinv_item.enable_deferred_expense == 1)
|
||||||
|
.run()
|
||||||
|
)
|
||||||
|
if deferred_invoices:
|
||||||
|
qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run()
|
||||||
@ -44,7 +44,7 @@ frappe.query_reports["Gross Profit"] = {
|
|||||||
"formatter": function(value, row, column, data, default_formatter) {
|
"formatter": function(value, row, column, data, default_formatter) {
|
||||||
value = default_formatter(value, row, column, data);
|
value = default_formatter(value, row, column, data);
|
||||||
|
|
||||||
if (data && data.indent == 0.0) {
|
if (data && (data.indent == 0.0 || row[1].content == "Total")) {
|
||||||
value = $(`<span>${value}</span>`);
|
value = $(`<span>${value}</span>`);
|
||||||
var $value = $(value).css("font-weight", "bold");
|
var $value = $(value).css("font-weight", "bold");
|
||||||
value = $value.wrap("<p></p>").parent().html();
|
value = $value.wrap("<p></p>").parent().html();
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
"filters": [],
|
"filters": [],
|
||||||
"idx": 3,
|
"idx": 3,
|
||||||
"is_standard": "Yes",
|
"is_standard": "Yes",
|
||||||
"modified": "2021-08-19 18:57:07.468202",
|
"modified": "2021-11-13 19:14:23.730198",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Gross Profit",
|
"name": "Gross Profit",
|
||||||
|
|||||||
@ -19,7 +19,7 @@ def execute(filters=None):
|
|||||||
data = []
|
data = []
|
||||||
|
|
||||||
group_wise_columns = frappe._dict({
|
group_wise_columns = frappe._dict({
|
||||||
"invoice": ["parent", "customer", "customer_group", "posting_date","item_code", "item_name","item_group", "brand", "description", \
|
"invoice": ["invoice_or_item", "customer", "customer_group", "posting_date","item_code", "item_name","item_group", "brand", "description",
|
||||||
"warehouse", "qty", "base_rate", "buying_rate", "base_amount",
|
"warehouse", "qty", "base_rate", "buying_rate", "base_amount",
|
||||||
"buying_amount", "gross_profit", "gross_profit_percent", "project"],
|
"buying_amount", "gross_profit", "gross_profit_percent", "project"],
|
||||||
"item_code": ["item_code", "item_name", "brand", "description", "qty", "base_rate",
|
"item_code": ["item_code", "item_name", "brand", "description", "qty", "base_rate",
|
||||||
@ -77,13 +77,15 @@ def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_
|
|||||||
|
|
||||||
row.append(filters.currency)
|
row.append(filters.currency)
|
||||||
if idx == len(gross_profit_data.grouped_data)-1:
|
if idx == len(gross_profit_data.grouped_data)-1:
|
||||||
row[0] = frappe.bold("Total")
|
row[0] = "Total"
|
||||||
|
|
||||||
data.append(row)
|
data.append(row)
|
||||||
|
|
||||||
def get_columns(group_wise_columns, filters):
|
def get_columns(group_wise_columns, filters):
|
||||||
columns = []
|
columns = []
|
||||||
column_map = frappe._dict({
|
column_map = frappe._dict({
|
||||||
"parent": _("Sales Invoice") + ":Link/Sales Invoice:120",
|
"parent": _("Sales Invoice") + ":Link/Sales Invoice:120",
|
||||||
|
"invoice_or_item": _("Sales Invoice") + ":Link/Sales Invoice:120",
|
||||||
"posting_date": _("Posting Date") + ":Date:100",
|
"posting_date": _("Posting Date") + ":Date:100",
|
||||||
"posting_time": _("Posting Time") + ":Data:100",
|
"posting_time": _("Posting Time") + ":Data:100",
|
||||||
"item_code": _("Item Code") + ":Link/Item:100",
|
"item_code": _("Item Code") + ":Link/Item:100",
|
||||||
@ -122,7 +124,7 @@ def get_columns(group_wise_columns, filters):
|
|||||||
|
|
||||||
def get_column_names():
|
def get_column_names():
|
||||||
return frappe._dict({
|
return frappe._dict({
|
||||||
'parent': 'sales_invoice',
|
'invoice_or_item': 'sales_invoice',
|
||||||
'customer': 'customer',
|
'customer': 'customer',
|
||||||
'customer_group': 'customer_group',
|
'customer_group': 'customer_group',
|
||||||
'posting_date': 'posting_date',
|
'posting_date': 'posting_date',
|
||||||
@ -245,19 +247,28 @@ class GrossProfitGenerator(object):
|
|||||||
self.add_to_totals(new_row)
|
self.add_to_totals(new_row)
|
||||||
else:
|
else:
|
||||||
for i, row in enumerate(self.grouped[key]):
|
for i, row in enumerate(self.grouped[key]):
|
||||||
if row.parent in self.returned_invoices \
|
if row.indent == 1.0:
|
||||||
and row.item_code in self.returned_invoices[row.parent]:
|
if row.parent in self.returned_invoices \
|
||||||
returned_item_rows = self.returned_invoices[row.parent][row.item_code]
|
and row.item_code in self.returned_invoices[row.parent]:
|
||||||
for returned_item_row in returned_item_rows:
|
returned_item_rows = self.returned_invoices[row.parent][row.item_code]
|
||||||
row.qty += flt(returned_item_row.qty)
|
for returned_item_row in returned_item_rows:
|
||||||
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
|
row.qty += flt(returned_item_row.qty)
|
||||||
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
|
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
|
||||||
if (flt(row.qty) or row.base_amount) and self.is_not_invoice_row(row):
|
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
|
||||||
row = self.set_average_rate(row)
|
if (flt(row.qty) or row.base_amount):
|
||||||
self.grouped_data.append(row)
|
row = self.set_average_rate(row)
|
||||||
self.add_to_totals(row)
|
self.grouped_data.append(row)
|
||||||
|
self.add_to_totals(row)
|
||||||
|
|
||||||
self.set_average_gross_profit(self.totals)
|
self.set_average_gross_profit(self.totals)
|
||||||
self.grouped_data.append(self.totals)
|
|
||||||
|
if self.filters.get("group_by") == "Invoice":
|
||||||
|
self.totals.indent = 0.0
|
||||||
|
self.totals.parent_invoice = ""
|
||||||
|
self.totals.invoice_or_item = "Total"
|
||||||
|
self.si_list.append(self.totals)
|
||||||
|
else:
|
||||||
|
self.grouped_data.append(self.totals)
|
||||||
|
|
||||||
def is_not_invoice_row(self, row):
|
def is_not_invoice_row(self, row):
|
||||||
return (self.filters.get("group_by") == "Invoice" and row.indent != 0.0) or self.filters.get("group_by") != "Invoice"
|
return (self.filters.get("group_by") == "Invoice" and row.indent != 0.0) or self.filters.get("group_by") != "Invoice"
|
||||||
@ -446,7 +457,7 @@ class GrossProfitGenerator(object):
|
|||||||
if not row.indent:
|
if not row.indent:
|
||||||
row.indent = 1.0
|
row.indent = 1.0
|
||||||
row.parent_invoice = row.parent
|
row.parent_invoice = row.parent
|
||||||
row.parent = row.item_code
|
row.invoice_or_item = row.item_code
|
||||||
|
|
||||||
if frappe.db.exists('Product Bundle', row.item_code):
|
if frappe.db.exists('Product Bundle', row.item_code):
|
||||||
self.add_bundle_items(row, index)
|
self.add_bundle_items(row, index)
|
||||||
@ -455,7 +466,8 @@ class GrossProfitGenerator(object):
|
|||||||
return frappe._dict({
|
return frappe._dict({
|
||||||
'parent_invoice': "",
|
'parent_invoice': "",
|
||||||
'indent': 0.0,
|
'indent': 0.0,
|
||||||
'parent': row.parent,
|
'invoice_or_item': row.parent,
|
||||||
|
'parent': None,
|
||||||
'posting_date': row.posting_date,
|
'posting_date': row.posting_date,
|
||||||
'posting_time': row.posting_time,
|
'posting_time': row.posting_time,
|
||||||
'project': row.project,
|
'project': row.project,
|
||||||
@ -499,7 +511,8 @@ class GrossProfitGenerator(object):
|
|||||||
return frappe._dict({
|
return frappe._dict({
|
||||||
'parent_invoice': product_bundle.item_code,
|
'parent_invoice': product_bundle.item_code,
|
||||||
'indent': product_bundle.indent + 1,
|
'indent': product_bundle.indent + 1,
|
||||||
'parent': item.item_code,
|
'parent': None,
|
||||||
|
'invoice_or_item': item.item_code,
|
||||||
'posting_date': product_bundle.posting_date,
|
'posting_date': product_bundle.posting_date,
|
||||||
'posting_time': product_bundle.posting_time,
|
'posting_time': product_bundle.posting_time,
|
||||||
'project': product_bundle.project,
|
'project': product_bundle.project,
|
||||||
|
|||||||
@ -1,18 +1,21 @@
|
|||||||
{
|
{
|
||||||
"add_total_row": 0,
|
"add_total_row": 0,
|
||||||
"apply_user_permissions": 1,
|
"columns": [],
|
||||||
"creation": "2013-05-06 12:28:23",
|
"creation": "2013-05-06 12:28:23",
|
||||||
|
"disable_prepared_report": 0,
|
||||||
"disabled": 0,
|
"disabled": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Report",
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
"idx": 3,
|
"idx": 3,
|
||||||
"is_standard": "Yes",
|
"is_standard": "Yes",
|
||||||
"modified": "2017-03-06 05:52:57.645281",
|
"modified": "2021-10-06 06:26:07.881340",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Partners Commission",
|
"name": "Sales Partners Commission",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"query": "SELECT\n sales_partner as \"Sales Partner:Link/Sales Partner:150\",\n\tsum(base_net_total) as \"Invoiced Amount (Exclusive Tax):Currency:210\",\n\tsum(total_commission) as \"Total Commission:Currency:150\",\n\tsum(total_commission)*100/sum(base_net_total) as \"Average Commission Rate:Currency:170\"\nFROM\n\t`tabSales Invoice`\nWHERE\n\tdocstatus = 1 and ifnull(base_net_total, 0) > 0 and ifnull(total_commission, 0) > 0\nGROUP BY\n\tsales_partner\nORDER BY\n\t\"Total Commission:Currency:120\"",
|
"prepared_report": 0,
|
||||||
|
"query": "SELECT\n sales_partner as \"Sales Partner:Link/Sales Partner:220\",\n\tsum(base_net_total) as \"Invoiced Amount (Excl. Tax):Currency:220\",\n\tsum(amount_eligible_for_commission) as \"Amount Eligible for Commission:Currency:220\",\n\tsum(total_commission) as \"Total Commission:Currency:170\",\n\tsum(total_commission)*100/sum(amount_eligible_for_commission) as \"Average Commission Rate:Percent:220\"\nFROM\n\t`tabSales Invoice`\nWHERE\n\tdocstatus = 1 and ifnull(base_net_total, 0) > 0 and ifnull(total_commission, 0) > 0\nGROUP BY\n\tsales_partner\nORDER BY\n\t\"Total Commission:Currency:120\"",
|
||||||
"ref_doctype": "Sales Invoice",
|
"ref_doctype": "Sales Invoice",
|
||||||
"report_name": "Sales Partners Commission",
|
"report_name": "Sales Partners Commission",
|
||||||
"report_type": "Query Report",
|
"report_type": "Query Report",
|
||||||
|
|||||||
@ -36,12 +36,16 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map):
|
|||||||
posting_date = entry.posting_date
|
posting_date = entry.posting_date
|
||||||
voucher_type = entry.voucher_type
|
voucher_type = entry.voucher_type
|
||||||
|
|
||||||
|
if not tax_withholding_category:
|
||||||
|
tax_withholding_category = supplier_map.get(supplier, {}).get('tax_withholding_category')
|
||||||
|
rate = tax_rate_map.get(tax_withholding_category)
|
||||||
|
|
||||||
if entry.account in tds_accounts:
|
if entry.account in tds_accounts:
|
||||||
tds_deducted += (entry.credit - entry.debit)
|
tds_deducted += (entry.credit - entry.debit)
|
||||||
|
|
||||||
total_amount_credited += (entry.credit - entry.debit)
|
total_amount_credited += (entry.credit - entry.debit)
|
||||||
|
|
||||||
if rate and tds_deducted:
|
if tds_deducted:
|
||||||
row = {
|
row = {
|
||||||
'pan' if frappe.db.has_column('Supplier', 'pan') else 'tax_id': supplier_map.get(supplier, {}).get('pan'),
|
'pan' if frappe.db.has_column('Supplier', 'pan') else 'tax_id': supplier_map.get(supplier, {}).get('pan'),
|
||||||
'supplier': supplier_map.get(supplier, {}).get('name')
|
'supplier': supplier_map.get(supplier, {}).get('name')
|
||||||
@ -67,7 +71,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map):
|
|||||||
|
|
||||||
def get_supplier_pan_map():
|
def get_supplier_pan_map():
|
||||||
supplier_map = frappe._dict()
|
supplier_map = frappe._dict()
|
||||||
suppliers = frappe.db.get_all('Supplier', fields=['name', 'pan', 'supplier_type', 'supplier_name'])
|
suppliers = frappe.db.get_all('Supplier', fields=['name', 'pan', 'supplier_type', 'supplier_name', 'tax_withholding_category'])
|
||||||
|
|
||||||
for d in suppliers:
|
for d in suppliers:
|
||||||
supplier_map[d.name] = d
|
supplier_map[d.name] = d
|
||||||
|
|||||||
@ -1,116 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// rename this file from _test_[name] to test_[name] to activate
|
|
||||||
// and remove above this line
|
|
||||||
|
|
||||||
QUnit.test("test: Crop", function (assert) {
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
// number of asserts
|
|
||||||
assert.expect(2);
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
// insert a new Item
|
|
||||||
() => frappe.tests.make('Item', [
|
|
||||||
// values to be set
|
|
||||||
{item_code: 'Basil Seeds'},
|
|
||||||
{item_name: 'Basil Seeds'},
|
|
||||||
{item_group: 'Seed'}
|
|
||||||
]),
|
|
||||||
// insert a new Item
|
|
||||||
() => frappe.tests.make('Item', [
|
|
||||||
// values to be set
|
|
||||||
{item_code: 'Twigs'},
|
|
||||||
{item_name: 'Twigs'},
|
|
||||||
{item_group: 'By-product'}
|
|
||||||
]),
|
|
||||||
// insert a new Item
|
|
||||||
() => frappe.tests.make('Item', [
|
|
||||||
// values to be set
|
|
||||||
{item_code: 'Basil Leaves'},
|
|
||||||
{item_name: 'Basil Leaves'},
|
|
||||||
{item_group: 'Produce'}
|
|
||||||
]),
|
|
||||||
// insert a new Crop
|
|
||||||
() => frappe.tests.make('Crop', [
|
|
||||||
// values to be set
|
|
||||||
{title: 'Basil from seed'},
|
|
||||||
{crop_name: 'Basil'},
|
|
||||||
{scientific_name: 'Ocimum basilicum'},
|
|
||||||
{materials_required: [
|
|
||||||
[
|
|
||||||
{item_code: 'Basil Seeds'},
|
|
||||||
{qty: '25'},
|
|
||||||
{uom: 'Nos'},
|
|
||||||
{rate: '1'}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{item_code: 'Urea'},
|
|
||||||
{qty: '5'},
|
|
||||||
{uom: 'Kg'},
|
|
||||||
{rate: '10'}
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
{byproducts: [
|
|
||||||
[
|
|
||||||
{item_code: 'Twigs'},
|
|
||||||
{qty: '25'},
|
|
||||||
{uom: 'Nos'},
|
|
||||||
{rate: '1'}
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
{produce: [
|
|
||||||
[
|
|
||||||
{item_code: 'Basil Leaves'},
|
|
||||||
{qty: '100'},
|
|
||||||
{uom: 'Nos'},
|
|
||||||
{rate: '1'}
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
{agriculture_task: [
|
|
||||||
[
|
|
||||||
{task_name: "Plough the field"},
|
|
||||||
{start_day: 1},
|
|
||||||
{end_day: 1},
|
|
||||||
{holiday_management: "Ignore holidays"}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{task_name: "Plant the seeds"},
|
|
||||||
{start_day: 2},
|
|
||||||
{end_day: 3},
|
|
||||||
{holiday_management: "Ignore holidays"}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{task_name: "Water the field"},
|
|
||||||
{start_day: 4},
|
|
||||||
{end_day: 4},
|
|
||||||
{holiday_management: "Ignore holidays"}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{task_name: "First harvest"},
|
|
||||||
{start_day: 8},
|
|
||||||
{end_day: 8},
|
|
||||||
{holiday_management: "Ignore holidays"}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{task_name: "Add the fertilizer"},
|
|
||||||
{start_day: 10},
|
|
||||||
{end_day: 12},
|
|
||||||
{holiday_management: "Ignore holidays"}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{task_name: "Final cut"},
|
|
||||||
{start_day: 15},
|
|
||||||
{end_day: 15},
|
|
||||||
{holiday_management: "Ignore holidays"}
|
|
||||||
]
|
|
||||||
]}
|
|
||||||
]),
|
|
||||||
// agriculture task list
|
|
||||||
() => {
|
|
||||||
assert.equal(cur_frm.doc.name, 'Basil from seed');
|
|
||||||
assert.equal(cur_frm.doc.period, 15);
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
|
|
||||||
});
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// rename this file from _test_[name] to test_[name] to activate
|
|
||||||
// and remove above this line
|
|
||||||
|
|
||||||
QUnit.test("test: Crop Cycle", function (assert) {
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
// number of asserts
|
|
||||||
assert.expect(1);
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
// insert a new Crop Cycle
|
|
||||||
() => frappe.tests.make('Crop Cycle', [
|
|
||||||
// values to be set
|
|
||||||
{title: 'Basil from seed 2017'},
|
|
||||||
{detected_disease: [
|
|
||||||
[
|
|
||||||
{start_date: '2017-11-21'},
|
|
||||||
{disease: 'Aphids'}
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
{linked_land_unit: [
|
|
||||||
[
|
|
||||||
{land_unit: 'Basil Farm'}
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
{crop: 'Basil from seed'},
|
|
||||||
{start_date: '2017-11-11'},
|
|
||||||
{cycle_type: 'Less than a year'}
|
|
||||||
]),
|
|
||||||
() => assert.equal(cur_frm.doc.name, 'Basil from seed 2017'),
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// rename this file from _test_[name] to test_[name] to activate
|
|
||||||
// and remove above this line
|
|
||||||
|
|
||||||
QUnit.test("test: Disease", function (assert) {
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
// number of asserts
|
|
||||||
assert.expect(1);
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
// insert a new Disease
|
|
||||||
() => frappe.tests.make('Disease', [
|
|
||||||
// values to be set
|
|
||||||
{common_name: 'Aphids'},
|
|
||||||
{scientific_name: 'Aphidoidea'},
|
|
||||||
{treatment_task: [
|
|
||||||
[
|
|
||||||
{task_name: "Survey and find the aphid locations"},
|
|
||||||
{start_day: 1},
|
|
||||||
{end_day: 2},
|
|
||||||
{holiday_management: "Ignore holidays"}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{task_name: "Apply Pesticides"},
|
|
||||||
{start_day: 3},
|
|
||||||
{end_day: 3},
|
|
||||||
{holiday_management: "Ignore holidays"}
|
|
||||||
]
|
|
||||||
]}
|
|
||||||
]),
|
|
||||||
() => {
|
|
||||||
assert.equal(cur_frm.doc.treatment_period, 3);
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
|
|
||||||
});
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// rename this file from _test_[name] to test_[name] to activate
|
|
||||||
// and remove above this line
|
|
||||||
|
|
||||||
QUnit.test("test: Fertilizer", function (assert) {
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
// number of asserts
|
|
||||||
assert.expect(1);
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
// insert a new Item
|
|
||||||
() => frappe.tests.make('Item', [
|
|
||||||
// values to be set
|
|
||||||
{item_code: 'Urea'},
|
|
||||||
{item_name: 'Urea'},
|
|
||||||
{item_group: 'Fertilizer'}
|
|
||||||
]),
|
|
||||||
// insert a new Fertilizer
|
|
||||||
() => frappe.tests.make('Fertilizer', [
|
|
||||||
// values to be set
|
|
||||||
{fertilizer_name: 'Urea'},
|
|
||||||
{item: 'Urea'}
|
|
||||||
]),
|
|
||||||
() => {
|
|
||||||
assert.equal(cur_frm.doc.name, 'Urea');
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
|
|
||||||
});
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// rename this file from _test_[name] to test_[name] to activate
|
|
||||||
// and remove above this line
|
|
||||||
|
|
||||||
QUnit.test("test: Soil Texture", function (assert) {
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
// number of asserts
|
|
||||||
assert.expect(2);
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
// insert a new Soil Texture
|
|
||||||
() => frappe.tests.make('Soil Texture', [
|
|
||||||
// values to be set
|
|
||||||
{location: '{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[72.882185,19.076395]}}]}'},
|
|
||||||
{collection_datetime: '2017-11-08'},
|
|
||||||
{clay_composition: 20},
|
|
||||||
{sand_composition: 30}
|
|
||||||
]),
|
|
||||||
() => {
|
|
||||||
assert.equal(cur_frm.doc.silt_composition, 50);
|
|
||||||
assert.equal(cur_frm.doc.soil_type, 'Silt Loam');
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
// rename this file from _test_[name] to test_[name] to activate
|
|
||||||
// and remove above this line
|
|
||||||
|
|
||||||
QUnit.test("test: Water Analysis", function (assert) {
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
// number of asserts
|
|
||||||
assert.expect(1);
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
// insert a new Water Analysis
|
|
||||||
() => frappe.tests.make('Water Analysis', [
|
|
||||||
// values to be set
|
|
||||||
{location: '{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[72.882185,19.076395]}}]}'},
|
|
||||||
{collection_datetime: '2017-11-08 18:43:57'},
|
|
||||||
{laboratory_testing_datetime: '2017-11-10 18:43:57'}
|
|
||||||
]),
|
|
||||||
() => {
|
|
||||||
assert.equal(cur_frm.doc.result_datetime, '2017-11-10 18:43:57');
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
|
|
||||||
});
|
|
||||||
@ -80,20 +80,20 @@ frappe.ui.form.on('Asset', {
|
|||||||
|
|
||||||
if (frm.doc.docstatus==1) {
|
if (frm.doc.docstatus==1) {
|
||||||
if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) {
|
if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) {
|
||||||
frm.add_custom_button("Transfer Asset", function() {
|
frm.add_custom_button(__("Transfer Asset"), function() {
|
||||||
erpnext.asset.transfer_asset(frm);
|
erpnext.asset.transfer_asset(frm);
|
||||||
}, __("Manage"));
|
}, __("Manage"));
|
||||||
|
|
||||||
frm.add_custom_button("Scrap Asset", function() {
|
frm.add_custom_button(__("Scrap Asset"), function() {
|
||||||
erpnext.asset.scrap_asset(frm);
|
erpnext.asset.scrap_asset(frm);
|
||||||
}, __("Manage"));
|
}, __("Manage"));
|
||||||
|
|
||||||
frm.add_custom_button("Sell Asset", function() {
|
frm.add_custom_button(__("Sell Asset"), function() {
|
||||||
frm.trigger("make_sales_invoice");
|
frm.trigger("make_sales_invoice");
|
||||||
}, __("Manage"));
|
}, __("Manage"));
|
||||||
|
|
||||||
} else if (frm.doc.status=='Scrapped') {
|
} else if (frm.doc.status=='Scrapped') {
|
||||||
frm.add_custom_button("Restore Asset", function() {
|
frm.add_custom_button(__("Restore Asset"), function() {
|
||||||
erpnext.asset.restore_asset(frm);
|
erpnext.asset.restore_asset(frm);
|
||||||
}, __("Manage"));
|
}, __("Manage"));
|
||||||
}
|
}
|
||||||
@ -110,7 +110,7 @@ frappe.ui.form.on('Asset', {
|
|||||||
|
|
||||||
if (frm.doc.status != 'Fully Depreciated') {
|
if (frm.doc.status != 'Fully Depreciated') {
|
||||||
frm.add_custom_button(__("Adjust Asset Value"), function() {
|
frm.add_custom_button(__("Adjust Asset Value"), function() {
|
||||||
frm.trigger("create_asset_adjustment");
|
frm.trigger("create_asset_value_adjustment");
|
||||||
}, __("Manage"));
|
}, __("Manage"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ frappe.ui.form.on('Asset', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.purchase_receipt || !frm.doc.is_existing_asset) {
|
if (frm.doc.purchase_receipt || !frm.doc.is_existing_asset) {
|
||||||
frm.add_custom_button("View General Ledger", function() {
|
frm.add_custom_button(__("View General Ledger"), function() {
|
||||||
frappe.route_options = {
|
frappe.route_options = {
|
||||||
"voucher_no": frm.doc.name,
|
"voucher_no": frm.doc.name,
|
||||||
"from_date": frm.doc.available_for_use_date,
|
"from_date": frm.doc.available_for_use_date,
|
||||||
@ -322,14 +322,14 @@ frappe.ui.form.on('Asset', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
create_asset_adjustment: function(frm) {
|
create_asset_value_adjustment: function(frm) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
args: {
|
args: {
|
||||||
"asset": frm.doc.name,
|
"asset": frm.doc.name,
|
||||||
"asset_category": frm.doc.asset_category,
|
"asset_category": frm.doc.asset_category,
|
||||||
"company": frm.doc.company
|
"company": frm.doc.company
|
||||||
},
|
},
|
||||||
method: "erpnext.assets.doctype.asset.asset.create_asset_adjustment",
|
method: "erpnext.assets.doctype.asset.asset.create_asset_value_adjustment",
|
||||||
freeze: 1,
|
freeze: 1,
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
var doclist = frappe.model.sync(r.message);
|
var doclist = frappe.model.sync(r.message);
|
||||||
|
|||||||
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