Merge branch 'develop' into independent-manu-entry
This commit is contained in:
commit
d209e5d6c5
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.
|
|
106
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
106
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
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: version
|
||||||
|
attributes:
|
||||||
|
label: Version
|
||||||
|
description: Affected versions.
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- v12
|
||||||
|
- v13
|
||||||
|
- v14
|
||||||
|
- develop
|
||||||
|
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: true
|
||||||
|
|
||||||
|
- 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: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: Code of Conduct
|
||||||
|
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/frappe/erpnext/blob/develop/CODE_OF_CONDUCT.md)
|
||||||
|
options:
|
||||||
|
- label: I agree to follow this project's Code of Conduct
|
||||||
|
required: true
|
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.
|
||||||
|
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'''
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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'),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
};
|
|
@ -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):
|
||||||
@ -25,6 +27,7 @@ class AccountsSettings(Document):
|
|||||||
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:
|
||||||
@ -56,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",
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
|
||||||
]);
|
|
||||||
});
|
|
@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
@ -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",
|
||||||
|
@ -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()
|
|
||||||
]);
|
|
||||||
});
|
|
@ -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",
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
@ -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()
|
|
||||||
]);
|
|
||||||
});
|
|
@ -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()
|
|
||||||
]);
|
|
||||||
});
|
|
@ -1160,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],
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1204,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
|
||||||
@ -1219,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`
|
||||||
|
@ -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",
|
||||||
@ -2086,4 +2093,4 @@
|
|||||||
"title_field": "title",
|
"title_field": "title",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_seen": 1
|
"track_seen": 1
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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()
|
|
||||||
]);
|
|
||||||
});
|
|
@ -2385,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)
|
||||||
|
@ -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()
|
@ -1,27 +1,30 @@
|
|||||||
{
|
{
|
||||||
"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",
|
||||||
"disabled": 0,
|
"disable_prepared_report": 0,
|
||||||
"docstatus": 0,
|
"disabled": 0,
|
||||||
"doctype": "Report",
|
"docstatus": 0,
|
||||||
"idx": 3,
|
"doctype": "Report",
|
||||||
"is_standard": "Yes",
|
"filters": [],
|
||||||
"modified": "2017-03-06 05:52:57.645281",
|
"idx": 3,
|
||||||
"modified_by": "Administrator",
|
"is_standard": "Yes",
|
||||||
"module": "Accounts",
|
"modified": "2021-10-06 06:26:07.881340",
|
||||||
"name": "Sales Partners Commission",
|
"modified_by": "Administrator",
|
||||||
"owner": "Administrator",
|
"module": "Accounts",
|
||||||
"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\"",
|
"name": "Sales Partners Commission",
|
||||||
"ref_doctype": "Sales Invoice",
|
"owner": "Administrator",
|
||||||
"report_name": "Sales Partners Commission",
|
"prepared_report": 0,
|
||||||
"report_type": "Query Report",
|
"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",
|
||||||
|
"report_name": "Sales Partners Commission",
|
||||||
|
"report_type": "Query Report",
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"role": "Accounts Manager"
|
"role": "Accounts Manager"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "Accounts User"
|
"role": "Accounts User"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -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);
|
||||||
|
@ -185,84 +185,84 @@ class Asset(AccountsController):
|
|||||||
if not self.available_for_use_date:
|
if not self.available_for_use_date:
|
||||||
return
|
return
|
||||||
|
|
||||||
for d in self.get('finance_books'):
|
start = self.clear_depreciation_schedule()
|
||||||
self.validate_asset_finance_books(d)
|
|
||||||
|
|
||||||
start = self.clear_depreciation_schedule()
|
for finance_book in self.get('finance_books'):
|
||||||
|
self.validate_asset_finance_books(finance_book)
|
||||||
|
|
||||||
# value_after_depreciation - current Asset value
|
# value_after_depreciation - current Asset value
|
||||||
if self.docstatus == 1 and d.value_after_depreciation:
|
if self.docstatus == 1 and finance_book.value_after_depreciation:
|
||||||
value_after_depreciation = (flt(d.value_after_depreciation) -
|
value_after_depreciation = flt(finance_book.value_after_depreciation)
|
||||||
flt(self.opening_accumulated_depreciation))
|
|
||||||
else:
|
else:
|
||||||
value_after_depreciation = (flt(self.gross_purchase_amount) -
|
value_after_depreciation = (flt(self.gross_purchase_amount) -
|
||||||
flt(self.opening_accumulated_depreciation))
|
flt(self.opening_accumulated_depreciation))
|
||||||
|
|
||||||
d.value_after_depreciation = value_after_depreciation
|
finance_book.value_after_depreciation = value_after_depreciation
|
||||||
|
|
||||||
number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \
|
number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \
|
||||||
cint(self.number_of_depreciations_booked)
|
cint(self.number_of_depreciations_booked)
|
||||||
|
|
||||||
has_pro_rata = self.check_is_pro_rata(d)
|
has_pro_rata = self.check_is_pro_rata(finance_book)
|
||||||
|
|
||||||
if has_pro_rata:
|
if has_pro_rata:
|
||||||
number_of_pending_depreciations += 1
|
number_of_pending_depreciations += 1
|
||||||
|
|
||||||
skip_row = False
|
skip_row = False
|
||||||
for n in range(start, number_of_pending_depreciations):
|
|
||||||
|
for n in range(start[finance_book.idx-1], number_of_pending_depreciations):
|
||||||
# If depreciation is already completed (for double declining balance)
|
# If depreciation is already completed (for double declining balance)
|
||||||
if skip_row: continue
|
if skip_row: continue
|
||||||
|
|
||||||
depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d)
|
depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book)
|
||||||
|
|
||||||
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
|
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
|
||||||
schedule_date = add_months(d.depreciation_start_date,
|
schedule_date = add_months(finance_book.depreciation_start_date,
|
||||||
n * cint(d.frequency_of_depreciation))
|
n * cint(finance_book.frequency_of_depreciation))
|
||||||
|
|
||||||
# schedule date will be a year later from start date
|
# schedule date will be a year later from start date
|
||||||
# so monthly schedule date is calculated by removing 11 months from it
|
# so monthly schedule date is calculated by removing 11 months from it
|
||||||
monthly_schedule_date = add_months(schedule_date, - d.frequency_of_depreciation + 1)
|
monthly_schedule_date = add_months(schedule_date, - finance_book.frequency_of_depreciation + 1)
|
||||||
|
|
||||||
# if asset is being sold
|
# if asset is being sold
|
||||||
if date_of_sale:
|
if date_of_sale:
|
||||||
from_date = self.get_from_date(d.finance_book)
|
from_date = self.get_from_date(finance_book.finance_book)
|
||||||
depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount,
|
depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
|
||||||
from_date, date_of_sale)
|
from_date, date_of_sale)
|
||||||
|
|
||||||
if depreciation_amount > 0:
|
if depreciation_amount > 0:
|
||||||
self.append("schedules", {
|
self.append("schedules", {
|
||||||
"schedule_date": date_of_sale,
|
"schedule_date": date_of_sale,
|
||||||
"depreciation_amount": depreciation_amount,
|
"depreciation_amount": depreciation_amount,
|
||||||
"depreciation_method": d.depreciation_method,
|
"depreciation_method": finance_book.depreciation_method,
|
||||||
"finance_book": d.finance_book,
|
"finance_book": finance_book.finance_book,
|
||||||
"finance_book_id": d.idx
|
"finance_book_id": finance_book.idx
|
||||||
})
|
})
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
# For first row
|
# For first row
|
||||||
if has_pro_rata and n==0:
|
if has_pro_rata and not self.opening_accumulated_depreciation and n==0:
|
||||||
depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount,
|
depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
|
||||||
self.available_for_use_date, d.depreciation_start_date)
|
self.available_for_use_date, finance_book.depreciation_start_date)
|
||||||
|
|
||||||
# For first depr schedule date will be the start date
|
# For first depr schedule date will be the start date
|
||||||
# so monthly schedule date is calculated by removing month difference between use date and start date
|
# so monthly schedule date is calculated by removing month difference between use date and start date
|
||||||
monthly_schedule_date = add_months(d.depreciation_start_date, - months + 1)
|
monthly_schedule_date = add_months(finance_book.depreciation_start_date, - months + 1)
|
||||||
|
|
||||||
# For last row
|
# For last row
|
||||||
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
|
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
|
||||||
if not self.flags.increase_in_asset_life:
|
if not self.flags.increase_in_asset_life:
|
||||||
# In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
|
# In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
|
||||||
self.to_date = add_months(self.available_for_use_date,
|
self.to_date = add_months(self.available_for_use_date,
|
||||||
n * cint(d.frequency_of_depreciation))
|
(n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation))
|
||||||
|
|
||||||
depreciation_amount_without_pro_rata = depreciation_amount
|
depreciation_amount_without_pro_rata = depreciation_amount
|
||||||
|
|
||||||
depreciation_amount, days, months = self.get_pro_rata_amt(d,
|
depreciation_amount, days, months = self.get_pro_rata_amt(finance_book,
|
||||||
depreciation_amount, schedule_date, self.to_date)
|
depreciation_amount, schedule_date, self.to_date)
|
||||||
|
|
||||||
depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata,
|
depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata,
|
||||||
depreciation_amount, d.finance_book)
|
depreciation_amount, finance_book.finance_book)
|
||||||
|
|
||||||
monthly_schedule_date = add_months(schedule_date, 1)
|
monthly_schedule_date = add_months(schedule_date, 1)
|
||||||
schedule_date = add_days(schedule_date, days)
|
schedule_date = add_days(schedule_date, days)
|
||||||
@ -273,10 +273,10 @@ class Asset(AccountsController):
|
|||||||
self.precision("gross_purchase_amount"))
|
self.precision("gross_purchase_amount"))
|
||||||
|
|
||||||
# Adjust depreciation amount in the last period based on the expected value after useful life
|
# Adjust depreciation amount in the last period based on the expected value after useful life
|
||||||
if d.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1
|
if finance_book.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1
|
||||||
and value_after_depreciation != d.expected_value_after_useful_life)
|
and value_after_depreciation != finance_book.expected_value_after_useful_life)
|
||||||
or value_after_depreciation < d.expected_value_after_useful_life):
|
or value_after_depreciation < finance_book.expected_value_after_useful_life):
|
||||||
depreciation_amount += (value_after_depreciation - d.expected_value_after_useful_life)
|
depreciation_amount += (value_after_depreciation - finance_book.expected_value_after_useful_life)
|
||||||
skip_row = True
|
skip_row = True
|
||||||
|
|
||||||
if depreciation_amount > 0:
|
if depreciation_amount > 0:
|
||||||
@ -286,7 +286,7 @@ class Asset(AccountsController):
|
|||||||
# In pro rata case, for first and last depreciation, month range would be different
|
# In pro rata case, for first and last depreciation, month range would be different
|
||||||
month_range = months \
|
month_range = months \
|
||||||
if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \
|
if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \
|
||||||
else d.frequency_of_depreciation
|
else finance_book.frequency_of_depreciation
|
||||||
|
|
||||||
for r in range(month_range):
|
for r in range(month_range):
|
||||||
if (has_pro_rata and n == 0):
|
if (has_pro_rata and n == 0):
|
||||||
@ -312,27 +312,52 @@ class Asset(AccountsController):
|
|||||||
self.append("schedules", {
|
self.append("schedules", {
|
||||||
"schedule_date": date,
|
"schedule_date": date,
|
||||||
"depreciation_amount": amount,
|
"depreciation_amount": amount,
|
||||||
"depreciation_method": d.depreciation_method,
|
"depreciation_method": finance_book.depreciation_method,
|
||||||
"finance_book": d.finance_book,
|
"finance_book": finance_book.finance_book,
|
||||||
"finance_book_id": d.idx
|
"finance_book_id": finance_book.idx
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
self.append("schedules", {
|
self.append("schedules", {
|
||||||
"schedule_date": schedule_date,
|
"schedule_date": schedule_date,
|
||||||
"depreciation_amount": depreciation_amount,
|
"depreciation_amount": depreciation_amount,
|
||||||
"depreciation_method": d.depreciation_method,
|
"depreciation_method": finance_book.depreciation_method,
|
||||||
"finance_book": d.finance_book,
|
"finance_book": finance_book.finance_book,
|
||||||
"finance_book_id": d.idx
|
"finance_book_id": finance_book.idx
|
||||||
})
|
})
|
||||||
|
|
||||||
# used when depreciation schedule needs to be modified due to increase in asset life
|
# depreciation schedules need to be cleared before modification due to increase in asset life/asset sales
|
||||||
|
# JE: Journal Entry, FB: Finance Book
|
||||||
def clear_depreciation_schedule(self):
|
def clear_depreciation_schedule(self):
|
||||||
start = 0
|
start = []
|
||||||
for n in range(len(self.schedules)):
|
num_of_depreciations_completed = 0
|
||||||
if not self.schedules[n].journal_entry:
|
depr_schedule = []
|
||||||
del self.schedules[n:]
|
|
||||||
start = n
|
for schedule in self.get('schedules'):
|
||||||
break
|
|
||||||
|
# to update start when there are JEs linked with all the schedule rows corresponding to an FB
|
||||||
|
if len(start) == (int(schedule.finance_book_id) - 2):
|
||||||
|
start.append(num_of_depreciations_completed)
|
||||||
|
num_of_depreciations_completed = 0
|
||||||
|
|
||||||
|
# to ensure that start will only be updated once for each FB
|
||||||
|
if len(start) == (int(schedule.finance_book_id) - 1):
|
||||||
|
if schedule.journal_entry:
|
||||||
|
num_of_depreciations_completed += 1
|
||||||
|
depr_schedule.append(schedule)
|
||||||
|
else:
|
||||||
|
start.append(num_of_depreciations_completed)
|
||||||
|
num_of_depreciations_completed = 0
|
||||||
|
|
||||||
|
# to update start when all the schedule rows corresponding to the last FB are linked with JEs
|
||||||
|
if len(start) == (len(self.finance_books) - 1):
|
||||||
|
start.append(num_of_depreciations_completed)
|
||||||
|
|
||||||
|
# when the Depreciation Schedule is being created for the first time
|
||||||
|
if start == []:
|
||||||
|
start = [0] * len(self.finance_books)
|
||||||
|
else:
|
||||||
|
self.schedules = depr_schedule
|
||||||
|
|
||||||
return start
|
return start
|
||||||
|
|
||||||
def get_from_date(self, finance_book):
|
def get_from_date(self, finance_book):
|
||||||
@ -354,7 +379,12 @@ class Asset(AccountsController):
|
|||||||
# if it returns True, depreciation_amount will not be equal for the first and last rows
|
# if it returns True, depreciation_amount will not be equal for the first and last rows
|
||||||
def check_is_pro_rata(self, row):
|
def check_is_pro_rata(self, row):
|
||||||
has_pro_rata = False
|
has_pro_rata = False
|
||||||
days = date_diff(row.depreciation_start_date, self.available_for_use_date) + 1
|
|
||||||
|
# if not existing asset, from_date = available_for_use_date
|
||||||
|
# otherwise, if number_of_depreciations_booked = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
|
||||||
|
# from_date = 01/01/2022
|
||||||
|
from_date = self.get_modified_available_for_use_date(row)
|
||||||
|
days = date_diff(row.depreciation_start_date, from_date) + 1
|
||||||
|
|
||||||
# if frequency_of_depreciation is 12 months, total_days = 365
|
# if frequency_of_depreciation is 12 months, total_days = 365
|
||||||
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
|
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
|
||||||
@ -364,6 +394,9 @@ class Asset(AccountsController):
|
|||||||
|
|
||||||
return has_pro_rata
|
return has_pro_rata
|
||||||
|
|
||||||
|
def get_modified_available_for_use_date(self, row):
|
||||||
|
return add_months(self.available_for_use_date, (self.number_of_depreciations_booked * row.frequency_of_depreciation))
|
||||||
|
|
||||||
def validate_asset_finance_books(self, row):
|
def validate_asset_finance_books(self, row):
|
||||||
if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
|
if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
|
||||||
frappe.throw(_("Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount")
|
frappe.throw(_("Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount")
|
||||||
@ -402,10 +435,11 @@ class Asset(AccountsController):
|
|||||||
|
|
||||||
# to ensure that final accumulated depreciation amount is accurate
|
# to ensure that final accumulated depreciation amount is accurate
|
||||||
def get_adjusted_depreciation_amount(self, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row, finance_book):
|
def get_adjusted_depreciation_amount(self, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row, finance_book):
|
||||||
depreciation_amount_for_first_row = self.get_depreciation_amount_for_first_row(finance_book)
|
if not self.opening_accumulated_depreciation:
|
||||||
|
depreciation_amount_for_first_row = self.get_depreciation_amount_for_first_row(finance_book)
|
||||||
|
|
||||||
if depreciation_amount_for_first_row + depreciation_amount_for_last_row != depreciation_amount_without_pro_rata:
|
if depreciation_amount_for_first_row + depreciation_amount_for_last_row != depreciation_amount_without_pro_rata:
|
||||||
depreciation_amount_for_last_row = depreciation_amount_without_pro_rata - depreciation_amount_for_first_row
|
depreciation_amount_for_last_row = depreciation_amount_without_pro_rata - depreciation_amount_for_first_row
|
||||||
|
|
||||||
return depreciation_amount_for_last_row
|
return depreciation_amount_for_last_row
|
||||||
|
|
||||||
@ -461,7 +495,6 @@ class Asset(AccountsController):
|
|||||||
|
|
||||||
asset_value_after_full_schedule = flt(
|
asset_value_after_full_schedule = flt(
|
||||||
flt(self.gross_purchase_amount) -
|
flt(self.gross_purchase_amount) -
|
||||||
flt(self.opening_accumulated_depreciation) -
|
|
||||||
flt(accumulated_depreciation_after_full_schedule), self.precision('gross_purchase_amount'))
|
flt(accumulated_depreciation_after_full_schedule), self.precision('gross_purchase_amount'))
|
||||||
|
|
||||||
if (row.expected_value_after_useful_life and
|
if (row.expected_value_after_useful_life and
|
||||||
@ -723,14 +756,14 @@ def create_asset_repair(asset, asset_name):
|
|||||||
return asset_repair
|
return asset_repair
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_asset_adjustment(asset, asset_category, company):
|
def create_asset_value_adjustment(asset, asset_category, company):
|
||||||
asset_maintenance = frappe.get_doc("Asset Value Adjustment")
|
asset_value_adjustment = frappe.new_doc("Asset Value Adjustment")
|
||||||
asset_maintenance.update({
|
asset_value_adjustment.update({
|
||||||
"asset": asset,
|
"asset": asset,
|
||||||
"company": company,
|
"company": company,
|
||||||
"asset_category": asset_category
|
"asset_category": asset_category
|
||||||
})
|
})
|
||||||
return asset_maintenance
|
return asset_value_adjustment
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def transfer_asset(args):
|
def transfer_asset(args):
|
||||||
@ -850,13 +883,11 @@ def get_total_days(date, frequency):
|
|||||||
|
|
||||||
@erpnext.allow_regional
|
@erpnext.allow_regional
|
||||||
def get_depreciation_amount(asset, depreciable_value, row):
|
def get_depreciation_amount(asset, depreciable_value, row):
|
||||||
depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
|
|
||||||
|
|
||||||
if row.depreciation_method in ("Straight Line", "Manual"):
|
if row.depreciation_method in ("Straight Line", "Manual"):
|
||||||
# if the Depreciation Schedule is being prepared for the first time
|
# if the Depreciation Schedule is being prepared for the first time
|
||||||
if not asset.flags.increase_in_asset_life:
|
if not asset.flags.increase_in_asset_life:
|
||||||
depreciation_amount = (flt(asset.gross_purchase_amount) - flt(asset.opening_accumulated_depreciation) -
|
depreciation_amount = (flt(asset.gross_purchase_amount) -
|
||||||
flt(row.expected_value_after_useful_life)) / depreciation_left
|
flt(row.expected_value_after_useful_life)) / flt(row.total_number_of_depreciations)
|
||||||
|
|
||||||
# if the Depreciation Schedule is being modified after Asset Repair
|
# if the Depreciation Schedule is being modified after Asset Repair
|
||||||
else:
|
else:
|
||||||
|
@ -57,8 +57,10 @@ def make_depreciation_entry(asset_name, date=None):
|
|||||||
je.finance_book = d.finance_book
|
je.finance_book = d.finance_book
|
||||||
je.remark = "Depreciation Entry against {0} worth {1}".format(asset_name, d.depreciation_amount)
|
je.remark = "Depreciation Entry against {0} worth {1}".format(asset_name, d.depreciation_amount)
|
||||||
|
|
||||||
|
credit_account, debit_account = get_credit_and_debit_accounts(accumulated_depreciation_account, depreciation_expense_account)
|
||||||
|
|
||||||
credit_entry = {
|
credit_entry = {
|
||||||
"account": accumulated_depreciation_account,
|
"account": credit_account,
|
||||||
"credit_in_account_currency": d.depreciation_amount,
|
"credit_in_account_currency": d.depreciation_amount,
|
||||||
"reference_type": "Asset",
|
"reference_type": "Asset",
|
||||||
"reference_name": asset.name,
|
"reference_name": asset.name,
|
||||||
@ -66,7 +68,7 @@ def make_depreciation_entry(asset_name, date=None):
|
|||||||
}
|
}
|
||||||
|
|
||||||
debit_entry = {
|
debit_entry = {
|
||||||
"account": depreciation_expense_account,
|
"account": debit_account,
|
||||||
"debit_in_account_currency": d.depreciation_amount,
|
"debit_in_account_currency": d.depreciation_amount,
|
||||||
"reference_type": "Asset",
|
"reference_type": "Asset",
|
||||||
"reference_name": asset.name,
|
"reference_name": asset.name,
|
||||||
@ -132,6 +134,20 @@ def get_depreciation_accounts(asset):
|
|||||||
|
|
||||||
return fixed_asset_account, accumulated_depreciation_account, depreciation_expense_account
|
return fixed_asset_account, accumulated_depreciation_account, depreciation_expense_account
|
||||||
|
|
||||||
|
def get_credit_and_debit_accounts(accumulated_depreciation_account, depreciation_expense_account):
|
||||||
|
root_type = frappe.get_value("Account", depreciation_expense_account, "root_type")
|
||||||
|
|
||||||
|
if root_type == "Expense":
|
||||||
|
credit_account = accumulated_depreciation_account
|
||||||
|
debit_account = depreciation_expense_account
|
||||||
|
elif root_type == "Income":
|
||||||
|
credit_account = depreciation_expense_account
|
||||||
|
debit_account = accumulated_depreciation_account
|
||||||
|
else:
|
||||||
|
frappe.throw(_("Depreciation Expense Account should be an Income or Expense Account."))
|
||||||
|
|
||||||
|
return credit_account, debit_account
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def scrap_asset(asset_name):
|
def scrap_asset(asset_name):
|
||||||
asset = frappe.get_doc("Asset", asset_name)
|
asset = frappe.get_doc("Asset", asset_name)
|
||||||
|
@ -409,19 +409,18 @@ class TestDepreciationMethods(AssetSetup):
|
|||||||
calculate_depreciation = 1,
|
calculate_depreciation = 1,
|
||||||
available_for_use_date = "2030-06-06",
|
available_for_use_date = "2030-06-06",
|
||||||
is_existing_asset = 1,
|
is_existing_asset = 1,
|
||||||
number_of_depreciations_booked = 1,
|
number_of_depreciations_booked = 2,
|
||||||
opening_accumulated_depreciation = 40000,
|
opening_accumulated_depreciation = 47095.89,
|
||||||
expected_value_after_useful_life = 10000,
|
expected_value_after_useful_life = 10000,
|
||||||
depreciation_start_date = "2030-12-31",
|
depreciation_start_date = "2032-12-31",
|
||||||
total_number_of_depreciations = 3,
|
total_number_of_depreciations = 3,
|
||||||
frequency_of_depreciation = 12
|
frequency_of_depreciation = 12
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(asset.status, "Draft")
|
self.assertEqual(asset.status, "Draft")
|
||||||
expected_schedules = [
|
expected_schedules = [
|
||||||
["2030-12-31", 14246.58, 54246.58],
|
["2032-12-31", 30000.0, 77095.89],
|
||||||
["2031-12-31", 25000.00, 79246.58],
|
["2033-06-06", 12904.11, 90000.0]
|
||||||
["2032-06-06", 10753.42, 90000.00]
|
|
||||||
]
|
]
|
||||||
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
|
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
|
||||||
for d in asset.get("schedules")]
|
for d in asset.get("schedules")]
|
||||||
@ -869,6 +868,72 @@ class TestDepreciationBasics(AssetSetup):
|
|||||||
self.assertFalse(asset.schedules[1].journal_entry)
|
self.assertFalse(asset.schedules[1].journal_entry)
|
||||||
self.assertFalse(asset.schedules[2].journal_entry)
|
self.assertFalse(asset.schedules[2].journal_entry)
|
||||||
|
|
||||||
|
def test_depr_entry_posting_when_depr_expense_account_is_an_expense_account(self):
|
||||||
|
"""Tests if the Depreciation Expense Account gets debited and the Accumulated Depreciation Account gets credited when the former's an Expense Account."""
|
||||||
|
|
||||||
|
asset = create_asset(
|
||||||
|
item_code = "Macbook Pro",
|
||||||
|
calculate_depreciation = 1,
|
||||||
|
available_for_use_date = "2019-12-31",
|
||||||
|
depreciation_start_date = "2020-12-31",
|
||||||
|
frequency_of_depreciation = 12,
|
||||||
|
total_number_of_depreciations = 3,
|
||||||
|
expected_value_after_useful_life = 10000,
|
||||||
|
submit = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
post_depreciation_entries(date="2021-06-01")
|
||||||
|
asset.load_from_db()
|
||||||
|
|
||||||
|
je = frappe.get_doc("Journal Entry", asset.schedules[0].journal_entry)
|
||||||
|
accounting_entries = [{"account": entry.account, "debit": entry.debit, "credit": entry.credit} for entry in je.accounts]
|
||||||
|
|
||||||
|
for entry in accounting_entries:
|
||||||
|
if entry["account"] == "_Test Depreciations - _TC":
|
||||||
|
self.assertTrue(entry["debit"])
|
||||||
|
self.assertFalse(entry["credit"])
|
||||||
|
else:
|
||||||
|
self.assertTrue(entry["credit"])
|
||||||
|
self.assertFalse(entry["debit"])
|
||||||
|
|
||||||
|
def test_depr_entry_posting_when_depr_expense_account_is_an_income_account(self):
|
||||||
|
"""Tests if the Depreciation Expense Account gets credited and the Accumulated Depreciation Account gets debited when the former's an Income Account."""
|
||||||
|
|
||||||
|
depr_expense_account = frappe.get_doc("Account", "_Test Depreciations - _TC")
|
||||||
|
depr_expense_account.root_type = "Income"
|
||||||
|
depr_expense_account.parent_account = "Income - _TC"
|
||||||
|
depr_expense_account.save()
|
||||||
|
|
||||||
|
asset = create_asset(
|
||||||
|
item_code = "Macbook Pro",
|
||||||
|
calculate_depreciation = 1,
|
||||||
|
available_for_use_date = "2019-12-31",
|
||||||
|
depreciation_start_date = "2020-12-31",
|
||||||
|
frequency_of_depreciation = 12,
|
||||||
|
total_number_of_depreciations = 3,
|
||||||
|
expected_value_after_useful_life = 10000,
|
||||||
|
submit = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
post_depreciation_entries(date="2021-06-01")
|
||||||
|
asset.load_from_db()
|
||||||
|
|
||||||
|
je = frappe.get_doc("Journal Entry", asset.schedules[0].journal_entry)
|
||||||
|
accounting_entries = [{"account": entry.account, "debit": entry.debit, "credit": entry.credit} for entry in je.accounts]
|
||||||
|
|
||||||
|
for entry in accounting_entries:
|
||||||
|
if entry["account"] == "_Test Depreciations - _TC":
|
||||||
|
self.assertTrue(entry["credit"])
|
||||||
|
self.assertFalse(entry["debit"])
|
||||||
|
else:
|
||||||
|
self.assertTrue(entry["debit"])
|
||||||
|
self.assertFalse(entry["credit"])
|
||||||
|
|
||||||
|
# resetting
|
||||||
|
depr_expense_account.root_type = "Expense"
|
||||||
|
depr_expense_account.parent_account = "Expenses - _TC"
|
||||||
|
depr_expense_account.save()
|
||||||
|
|
||||||
def test_clear_depreciation_schedule(self):
|
def test_clear_depreciation_schedule(self):
|
||||||
"""Tests if clear_depreciation_schedule() works as expected."""
|
"""Tests if clear_depreciation_schedule() works as expected."""
|
||||||
|
|
||||||
@ -890,6 +955,82 @@ class TestDepreciationBasics(AssetSetup):
|
|||||||
|
|
||||||
self.assertEqual(len(asset.schedules), 1)
|
self.assertEqual(len(asset.schedules), 1)
|
||||||
|
|
||||||
|
def test_clear_depreciation_schedule_for_multiple_finance_books(self):
|
||||||
|
asset = create_asset(
|
||||||
|
item_code = "Macbook Pro",
|
||||||
|
available_for_use_date = "2019-12-31",
|
||||||
|
do_not_save = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
asset.calculate_depreciation = 1
|
||||||
|
asset.append("finance_books", {
|
||||||
|
"depreciation_method": "Straight Line",
|
||||||
|
"frequency_of_depreciation": 1,
|
||||||
|
"total_number_of_depreciations": 3,
|
||||||
|
"expected_value_after_useful_life": 10000,
|
||||||
|
"depreciation_start_date": "2020-01-31"
|
||||||
|
})
|
||||||
|
asset.append("finance_books", {
|
||||||
|
"depreciation_method": "Straight Line",
|
||||||
|
"frequency_of_depreciation": 1,
|
||||||
|
"total_number_of_depreciations": 6,
|
||||||
|
"expected_value_after_useful_life": 10000,
|
||||||
|
"depreciation_start_date": "2020-01-31"
|
||||||
|
})
|
||||||
|
asset.append("finance_books", {
|
||||||
|
"depreciation_method": "Straight Line",
|
||||||
|
"frequency_of_depreciation": 12,
|
||||||
|
"total_number_of_depreciations": 3,
|
||||||
|
"expected_value_after_useful_life": 10000,
|
||||||
|
"depreciation_start_date": "2020-12-31"
|
||||||
|
})
|
||||||
|
asset.submit()
|
||||||
|
|
||||||
|
post_depreciation_entries(date="2020-04-01")
|
||||||
|
asset.load_from_db()
|
||||||
|
|
||||||
|
asset.clear_depreciation_schedule()
|
||||||
|
|
||||||
|
self.assertEqual(len(asset.schedules), 6)
|
||||||
|
|
||||||
|
for schedule in asset.schedules:
|
||||||
|
if schedule.idx <= 3:
|
||||||
|
self.assertEqual(schedule.finance_book_id, "1")
|
||||||
|
else:
|
||||||
|
self.assertEqual(schedule.finance_book_id, "2")
|
||||||
|
|
||||||
|
def test_depreciation_schedules_are_set_up_for_multiple_finance_books(self):
|
||||||
|
asset = create_asset(
|
||||||
|
item_code = "Macbook Pro",
|
||||||
|
available_for_use_date = "2019-12-31",
|
||||||
|
do_not_save = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
asset.calculate_depreciation = 1
|
||||||
|
asset.append("finance_books", {
|
||||||
|
"depreciation_method": "Straight Line",
|
||||||
|
"frequency_of_depreciation": 12,
|
||||||
|
"total_number_of_depreciations": 3,
|
||||||
|
"expected_value_after_useful_life": 10000,
|
||||||
|
"depreciation_start_date": "2020-12-31"
|
||||||
|
})
|
||||||
|
asset.append("finance_books", {
|
||||||
|
"depreciation_method": "Straight Line",
|
||||||
|
"frequency_of_depreciation": 12,
|
||||||
|
"total_number_of_depreciations": 6,
|
||||||
|
"expected_value_after_useful_life": 10000,
|
||||||
|
"depreciation_start_date": "2020-12-31"
|
||||||
|
})
|
||||||
|
asset.save()
|
||||||
|
|
||||||
|
self.assertEqual(len(asset.schedules), 9)
|
||||||
|
|
||||||
|
for schedule in asset.schedules:
|
||||||
|
if schedule.idx <= 3:
|
||||||
|
self.assertEqual(schedule.finance_book_id, 1)
|
||||||
|
else:
|
||||||
|
self.assertEqual(schedule.finance_book_id, 2)
|
||||||
|
|
||||||
def test_depreciation_entry_cancellation(self):
|
def test_depreciation_entry_cancellation(self):
|
||||||
asset = create_asset(
|
asset = create_asset(
|
||||||
item_code = "Macbook Pro",
|
item_code = "Macbook Pro",
|
||||||
|
@ -33,7 +33,7 @@ frappe.ui.form.on('Asset Category', {
|
|||||||
var d = locals[cdt][cdn];
|
var d = locals[cdt][cdn];
|
||||||
return {
|
return {
|
||||||
"filters": {
|
"filters": {
|
||||||
"root_type": "Expense",
|
"root_type": ["in", ["Expense", "Income"]],
|
||||||
"is_group": 0,
|
"is_group": 0,
|
||||||
"company": d.company_name
|
"company": d.company_name
|
||||||
}
|
}
|
||||||
|
@ -42,10 +42,10 @@ class AssetCategory(Document):
|
|||||||
|
|
||||||
def validate_account_types(self):
|
def validate_account_types(self):
|
||||||
account_type_map = {
|
account_type_map = {
|
||||||
'fixed_asset_account': { 'account_type': 'Fixed Asset' },
|
'fixed_asset_account': {'account_type': ['Fixed Asset']},
|
||||||
'accumulated_depreciation_account': { 'account_type': 'Accumulated Depreciation' },
|
'accumulated_depreciation_account': {'account_type': ['Accumulated Depreciation']},
|
||||||
'depreciation_expense_account': { 'root_type': 'Expense' },
|
'depreciation_expense_account': {'root_type': ['Expense', 'Income']},
|
||||||
'capital_work_in_progress_account': { 'account_type': 'Capital Work in Progress' }
|
'capital_work_in_progress_account': {'account_type': ['Capital Work in Progress']}
|
||||||
}
|
}
|
||||||
for d in self.accounts:
|
for d in self.accounts:
|
||||||
for fieldname in account_type_map.keys():
|
for fieldname in account_type_map.keys():
|
||||||
@ -53,11 +53,11 @@ class AssetCategory(Document):
|
|||||||
selected_account = d.get(fieldname)
|
selected_account = d.get(fieldname)
|
||||||
key_to_match = next(iter(account_type_map.get(fieldname))) # acount_type or root_type
|
key_to_match = next(iter(account_type_map.get(fieldname))) # acount_type or root_type
|
||||||
selected_key_type = frappe.db.get_value('Account', selected_account, key_to_match)
|
selected_key_type = frappe.db.get_value('Account', selected_account, key_to_match)
|
||||||
expected_key_type = account_type_map[fieldname][key_to_match]
|
expected_key_types = account_type_map[fieldname][key_to_match]
|
||||||
|
|
||||||
if selected_key_type != expected_key_type:
|
if selected_key_type not in expected_key_types:
|
||||||
frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.")
|
frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.")
|
||||||
.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)),
|
.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_types)),
|
||||||
title=_("Invalid Account"))
|
title=_("Invalid Account"))
|
||||||
|
|
||||||
def valide_cwip_account(self):
|
def valide_cwip_account(self):
|
||||||
|
@ -60,6 +60,10 @@ frappe.ui.form.on('Asset Repair', {
|
|||||||
if (frm.doc.repair_status == "Completed") {
|
if (frm.doc.repair_status == "Completed") {
|
||||||
frm.set_value('completion_date', frappe.datetime.now_datetime());
|
frm.set_value('completion_date', frappe.datetime.now_datetime());
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
stock_items_on_form_rendered() {
|
||||||
|
erpnext.setup_serial_or_batch_no();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -118,9 +118,10 @@ class AssetRepair(AccountsController):
|
|||||||
for stock_item in self.get('stock_items'):
|
for stock_item in self.get('stock_items'):
|
||||||
stock_entry.append('items', {
|
stock_entry.append('items', {
|
||||||
"s_warehouse": self.warehouse,
|
"s_warehouse": self.warehouse,
|
||||||
"item_code": stock_item.item,
|
"item_code": stock_item.item_code,
|
||||||
"qty": stock_item.consumed_quantity,
|
"qty": stock_item.consumed_quantity,
|
||||||
"basic_rate": stock_item.valuation_rate
|
"basic_rate": stock_item.valuation_rate,
|
||||||
|
"serial_no": stock_item.serial_no
|
||||||
})
|
})
|
||||||
|
|
||||||
stock_entry.insert()
|
stock_entry.insert()
|
||||||
|
@ -11,12 +11,15 @@ from erpnext.assets.doctype.asset.test_asset import (
|
|||||||
create_asset_data,
|
create_asset_data,
|
||||||
set_depreciation_settings_in_company,
|
set_depreciation_settings_in_company,
|
||||||
)
|
)
|
||||||
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
|
||||||
|
|
||||||
class TestAssetRepair(unittest.TestCase):
|
class TestAssetRepair(unittest.TestCase):
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
set_depreciation_settings_in_company()
|
set_depreciation_settings_in_company()
|
||||||
create_asset_data()
|
create_asset_data()
|
||||||
|
create_item("_Test Stock Item")
|
||||||
frappe.db.sql("delete from `tabTax Rule`")
|
frappe.db.sql("delete from `tabTax Rule`")
|
||||||
|
|
||||||
def test_update_status(self):
|
def test_update_status(self):
|
||||||
@ -70,9 +73,28 @@ class TestAssetRepair(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(stock_entry.stock_entry_type, "Material Issue")
|
self.assertEqual(stock_entry.stock_entry_type, "Material Issue")
|
||||||
self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.warehouse)
|
self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.warehouse)
|
||||||
self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item)
|
self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item_code)
|
||||||
self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity)
|
self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity)
|
||||||
|
|
||||||
|
def test_serialized_item_consumption(self):
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import SerialNoRequiredError
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||||
|
|
||||||
|
stock_entry = make_serialized_item()
|
||||||
|
serial_nos = stock_entry.get("items")[0].serial_no
|
||||||
|
serial_no = serial_nos.split("\n")[0]
|
||||||
|
|
||||||
|
# should not raise any error
|
||||||
|
create_asset_repair(stock_consumption = 1, item_code = stock_entry.get("items")[0].item_code,
|
||||||
|
warehouse = "_Test Warehouse - _TC", serial_no = serial_no, submit = 1)
|
||||||
|
|
||||||
|
# should raise error
|
||||||
|
asset_repair = create_asset_repair(stock_consumption = 1, warehouse = "_Test Warehouse - _TC",
|
||||||
|
item_code = stock_entry.get("items")[0].item_code)
|
||||||
|
|
||||||
|
asset_repair.repair_status = "Completed"
|
||||||
|
self.assertRaises(SerialNoRequiredError, asset_repair.submit)
|
||||||
|
|
||||||
def test_increase_in_asset_value_due_to_stock_consumption(self):
|
def test_increase_in_asset_value_due_to_stock_consumption(self):
|
||||||
asset = create_asset(calculate_depreciation = 1, submit=1)
|
asset = create_asset(calculate_depreciation = 1, submit=1)
|
||||||
initial_asset_value = get_asset_value(asset)
|
initial_asset_value = get_asset_value(asset)
|
||||||
@ -137,11 +159,12 @@ def create_asset_repair(**args):
|
|||||||
|
|
||||||
if args.stock_consumption:
|
if args.stock_consumption:
|
||||||
asset_repair.stock_consumption = 1
|
asset_repair.stock_consumption = 1
|
||||||
asset_repair.warehouse = create_warehouse("Test Warehouse", company = asset.company)
|
asset_repair.warehouse = args.warehouse or create_warehouse("Test Warehouse", company = asset.company)
|
||||||
asset_repair.append("stock_items", {
|
asset_repair.append("stock_items", {
|
||||||
"item": args.item or args.item_code or "_Test Item",
|
"item_code": args.item_code or "_Test Stock Item",
|
||||||
"valuation_rate": args.rate if args.get("rate") is not None else 100,
|
"valuation_rate": args.rate if args.get("rate") is not None else 100,
|
||||||
"consumed_quantity": args.qty or 1
|
"consumed_quantity": args.qty or 1,
|
||||||
|
"serial_no": args.serial_no
|
||||||
})
|
})
|
||||||
|
|
||||||
asset_repair.insert(ignore_if_duplicate=True)
|
asset_repair.insert(ignore_if_duplicate=True)
|
||||||
@ -158,7 +181,7 @@ def create_asset_repair(**args):
|
|||||||
})
|
})
|
||||||
stock_entry.append('items', {
|
stock_entry.append('items', {
|
||||||
"t_warehouse": asset_repair.warehouse,
|
"t_warehouse": asset_repair.warehouse,
|
||||||
"item_code": asset_repair.stock_items[0].item,
|
"item_code": asset_repair.stock_items[0].item_code,
|
||||||
"qty": asset_repair.stock_items[0].consumed_quantity
|
"qty": asset_repair.stock_items[0].consumed_quantity
|
||||||
})
|
})
|
||||||
stock_entry.submit()
|
stock_entry.submit()
|
||||||
|
@ -5,19 +5,13 @@
|
|||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"item",
|
"item_code",
|
||||||
"valuation_rate",
|
"valuation_rate",
|
||||||
"consumed_quantity",
|
"consumed_quantity",
|
||||||
"total_value"
|
"total_value",
|
||||||
|
"serial_no"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
|
||||||
"fieldname": "item",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Item",
|
|
||||||
"options": "Item"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fetch_from": "item.valuation_rate",
|
"fetch_from": "item.valuation_rate",
|
||||||
"fieldname": "valuation_rate",
|
"fieldname": "valuation_rate",
|
||||||
@ -38,12 +32,24 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Total Value",
|
"label": "Total Value",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "serial_no",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Serial No"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Item",
|
||||||
|
"options": "Item"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-05-12 03:19:55.006300",
|
"modified": "2021-11-11 18:23:00.492483",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Repair Consumed Item",
|
"name": "Asset Repair Consumed Item",
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/asset",
|
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/asset",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"modified": "2021-08-24 17:50:41.573281",
|
"modified": "2021-12-02 11:24:37.963746",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Assets",
|
"name": "Assets",
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2021-08-24 12:49:37.665239",
|
"modified": "2021-11-23 10:02:03.242127",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Asset Category",
|
"name": "Asset Category",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
{
|
{
|
||||||
"action": "Show Form Tour",
|
"action": "Create Entry",
|
||||||
"action_label": "Let's create a new Asset item",
|
"action_label": "Let's create a new Asset item",
|
||||||
"creation": "2021-08-13 14:27:07.277167",
|
"creation": "2021-08-13 14:27:07.277167",
|
||||||
"description": "# Asset Item\n\nAsset items are created based on Asset Category. You can create one or multiple items against once Asset Category. The sales and purchase transaction for Asset is done via Asset Item. ",
|
"description": "# Asset Item\n\nAsset items are created based on Asset Category. You can create one or multiple items against once Asset Category. The sales and purchase transaction for Asset is done via Asset Item. ",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Onboarding Step",
|
"doctype": "Onboarding Step",
|
||||||
|
"form_tour": "Item",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2021-08-16 13:59:18.362233",
|
"modified": "2021-12-02 11:23:48.158504",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Asset Item",
|
"name": "Asset Item",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"reference_document": "Item",
|
"reference_document": "Item",
|
||||||
"show_form_tour": 0,
|
"show_form_tour": 1,
|
||||||
"show_full_form": 0,
|
"show_full_form": 1,
|
||||||
"title": "Create an Asset Item",
|
"title": "Create an Asset Item",
|
||||||
"validate_action": 1
|
"validate_action": 1
|
||||||
}
|
}
|
@ -9,7 +9,7 @@
|
|||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2021-08-24 17:26:57.180637",
|
"modified": "2021-11-23 10:02:03.235498",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Asset Purchase",
|
"name": "Asset Purchase",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2021-08-24 17:46:37.646174",
|
"modified": "2021-11-23 10:02:03.229566",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Fixed Asset Accounts",
|
"name": "Fixed Asset Accounts",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
@ -72,6 +72,7 @@ class PurchaseOrder(BuyingController):
|
|||||||
self.create_raw_materials_supplied("supplied_items")
|
self.create_raw_materials_supplied("supplied_items")
|
||||||
self.set_received_qty_for_drop_ship_items()
|
self.set_received_qty_for_drop_ship_items()
|
||||||
validate_inter_company_party(self.doctype, self.supplier, self.company, self.inter_company_order_reference)
|
validate_inter_company_party(self.doctype, self.supplier, self.company, self.inter_company_order_reference)
|
||||||
|
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||||
|
|
||||||
def validate_with_previous_doc(self):
|
def validate_with_previous_doc(self):
|
||||||
super(PurchaseOrder, self).validate_with_previous_doc({
|
super(PurchaseOrder, self).validate_with_previous_doc({
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
QUnit.module('Buying');
|
|
||||||
|
|
||||||
QUnit.test("test: purchase order", function(assert) {
|
|
||||||
assert.expect(16);
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Purchase Order', [
|
|
||||||
{supplier: 'Test Supplier'},
|
|
||||||
{is_subcontracted: 'No'},
|
|
||||||
{currency: 'INR'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{"item_code": 'Test Product 4'},
|
|
||||||
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 2)},
|
|
||||||
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
|
|
||||||
{"qty": 5},
|
|
||||||
{"uom": 'Unit'},
|
|
||||||
{"rate": 100},
|
|
||||||
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{"item_code": 'Test Product 1'},
|
|
||||||
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
|
|
||||||
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
|
|
||||||
{"qty": 2},
|
|
||||||
{"uom": 'Unit'},
|
|
||||||
{"rate": 100},
|
|
||||||
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
|
|
||||||
{tc_name: 'Test Term 1'},
|
|
||||||
{terms: 'This is a term.'}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
|
|
||||||
() => {
|
|
||||||
// Get supplier details
|
|
||||||
assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct");
|
|
||||||
assert.ok(cur_frm.doc.schedule_date == frappe.datetime.add_days(frappe.datetime.now_date(), 1), "Schedule Date correct");
|
|
||||||
assert.ok(cur_frm.doc.contact_email == 'test@supplier.com', "Contact email correct");
|
|
||||||
// Get item details
|
|
||||||
assert.ok(cur_frm.doc.items[0].item_name == 'Test Product 4', "Item name correct");
|
|
||||||
assert.ok(cur_frm.doc.items[0].description == 'Test Product 4', "Description correct");
|
|
||||||
assert.ok(cur_frm.doc.items[0].qty == 5, "Quantity correct");
|
|
||||||
assert.ok(cur_frm.doc.items[0].schedule_date == frappe.datetime.add_days(frappe.datetime.now_date(), 2), "Schedule Date correct");
|
|
||||||
|
|
||||||
assert.ok(cur_frm.doc.items[1].item_name == 'Test Product 1', "Item name correct");
|
|
||||||
assert.ok(cur_frm.doc.items[1].description == 'Test Product 1', "Description correct");
|
|
||||||
assert.ok(cur_frm.doc.items[1].qty == 2, "Quantity correct");
|
|
||||||
assert.ok(cur_frm.doc.items[1].schedule_date == cur_frm.doc.schedule_date, "Schedule Date correct");
|
|
||||||
// Calculate total
|
|
||||||
assert.ok(cur_frm.doc.total == 700, "Total correct");
|
|
||||||
// Get terms
|
|
||||||
assert.ok(cur_frm.doc.terms == 'This is a term.', "Terms correct");
|
|
||||||
},
|
|
||||||
|
|
||||||
() => cur_frm.print_doc(),
|
|
||||||
() => frappe.timeout(2),
|
|
||||||
() => {
|
|
||||||
assert.ok($('.btn-print-print').is(':visible'), "Print Format Available");
|
|
||||||
assert.ok($('div > div:nth-child(5) > div > div > table > tbody > tr > td:nth-child(4) > div').text().includes('Test Product 4'), "Print Preview Works");
|
|
||||||
},
|
|
||||||
|
|
||||||
() => cur_frm.print_doc(),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
|
|
||||||
() => {
|
|
||||||
assert.ok(cur_frm.doc.status == 'To Receive and Bill', "Submitted successfully");
|
|
||||||
},
|
|
||||||
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
@ -1,61 +0,0 @@
|
|||||||
QUnit.module('Buying');
|
|
||||||
|
|
||||||
QUnit.test("test: purchase order with get items", function(assert) {
|
|
||||||
assert.expect(4);
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Purchase Order', [
|
|
||||||
{supplier: 'Test Supplier'},
|
|
||||||
{is_subcontracted: 'No'},
|
|
||||||
{buying_price_list: 'Test-Buying-USD'},
|
|
||||||
{currency: 'USD'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{"item_code": 'Test Product 4'},
|
|
||||||
{"qty": 5},
|
|
||||||
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
|
|
||||||
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
|
|
||||||
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
|
|
||||||
]
|
|
||||||
]}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
|
|
||||||
() => {
|
|
||||||
assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct");
|
|
||||||
},
|
|
||||||
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
() => frappe.click_button('Get items from'),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
|
|
||||||
() => frappe.click_link('Product Bundle'),
|
|
||||||
() => frappe.timeout(0.5),
|
|
||||||
|
|
||||||
() => cur_dialog.set_value('product_bundle', 'Computer'),
|
|
||||||
() => frappe.click_button('Get Items'),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
|
|
||||||
// Check if items are fetched from Product Bundle
|
|
||||||
() => {
|
|
||||||
assert.ok(cur_frm.doc.items[1].item_name == 'CPU', "Product bundle item 1 correct");
|
|
||||||
assert.ok(cur_frm.doc.items[2].item_name == 'Screen', "Product bundle item 2 correct");
|
|
||||||
assert.ok(cur_frm.doc.items[3].item_name == 'Keyboard', "Product bundle item 3 correct");
|
|
||||||
},
|
|
||||||
|
|
||||||
() => cur_frm.doc.items[1].warehouse = 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company")),
|
|
||||||
() => cur_frm.doc.items[2].warehouse = 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company")),
|
|
||||||
() => cur_frm.doc.items[3].warehouse = 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company")),
|
|
||||||
|
|
||||||
() => cur_frm.save(),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
@ -1,74 +0,0 @@
|
|||||||
QUnit.module('Buying');
|
|
||||||
|
|
||||||
QUnit.test("test: purchase order receipt", function(assert) {
|
|
||||||
assert.expect(5);
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Purchase Order', [
|
|
||||||
{supplier: 'Test Supplier'},
|
|
||||||
{is_subcontracted: 'No'},
|
|
||||||
{buying_price_list: 'Test-Buying-USD'},
|
|
||||||
{currency: 'USD'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{"item_code": 'Test Product 1'},
|
|
||||||
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
|
|
||||||
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
|
|
||||||
{"qty": 5},
|
|
||||||
{"uom": 'Unit'},
|
|
||||||
{"rate": 100},
|
|
||||||
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
|
|
||||||
() => {
|
|
||||||
|
|
||||||
// Check supplier and item details
|
|
||||||
assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct");
|
|
||||||
assert.ok(cur_frm.doc.items[0].item_name == 'Test Product 1', "Item name correct");
|
|
||||||
assert.ok(cur_frm.doc.items[0].description == 'Test Product 1', "Description correct");
|
|
||||||
assert.ok(cur_frm.doc.items[0].qty == 5, "Quantity correct");
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
|
|
||||||
() => frappe.timeout(1.5),
|
|
||||||
() => frappe.click_button('Close'),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
|
|
||||||
// Make Purchase Receipt
|
|
||||||
() => frappe.click_button('Make'),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
|
|
||||||
() => frappe.click_link('Receipt'),
|
|
||||||
() => frappe.timeout(2),
|
|
||||||
|
|
||||||
() => cur_frm.save(),
|
|
||||||
|
|
||||||
// Save and submit Purchase Receipt
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
|
|
||||||
// View Purchase order in Stock Ledger
|
|
||||||
() => frappe.click_button('View'),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
|
|
||||||
() => frappe.click_link('Stock Ledger'),
|
|
||||||
() => frappe.timeout(2),
|
|
||||||
() => {
|
|
||||||
assert.ok($('div.slick-cell.l2.r2 > a').text().includes('Test Product 1')
|
|
||||||
&& $('div.slick-cell.l9.r9 > div').text().includes(5), "Stock ledger entry correct");
|
|
||||||
},
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
@ -1,47 +0,0 @@
|
|||||||
QUnit.module('Buying');
|
|
||||||
|
|
||||||
QUnit.test("test: purchase order with discount on grand total", function(assert) {
|
|
||||||
assert.expect(4);
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Purchase Order', [
|
|
||||||
{supplier: 'Test Supplier'},
|
|
||||||
{is_subcontracted: 'No'},
|
|
||||||
{buying_price_list: 'Test-Buying-EUR'},
|
|
||||||
{currency: 'EUR'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{"item_code": 'Test Product 4'},
|
|
||||||
{"qty": 5},
|
|
||||||
{"uom": 'Unit'},
|
|
||||||
{"rate": 500 },
|
|
||||||
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
|
|
||||||
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
|
|
||||||
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
|
|
||||||
]
|
|
||||||
]},
|
|
||||||
{apply_discount_on: 'Grand Total'},
|
|
||||||
{additional_discount_percentage: 10}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
|
|
||||||
() => {
|
|
||||||
assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct");
|
|
||||||
assert.ok(cur_frm.doc.items[0].rate == 500, "Rate correct");
|
|
||||||
// Calculate total
|
|
||||||
assert.ok(cur_frm.doc.total == 2500, "Total correct");
|
|
||||||
// Calculate grand total after discount
|
|
||||||
assert.ok(cur_frm.doc.grand_total == 2250, "Grand total correct");
|
|
||||||
},
|
|
||||||
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
@ -1,44 +0,0 @@
|
|||||||
QUnit.module('Buying');
|
|
||||||
|
|
||||||
QUnit.test("test: purchase order with item wise discount", function(assert) {
|
|
||||||
assert.expect(4);
|
|
||||||
let done = assert.async();
|
|
||||||
|
|
||||||
frappe.run_serially([
|
|
||||||
() => {
|
|
||||||
return frappe.tests.make('Purchase Order', [
|
|
||||||
{supplier: 'Test Supplier'},
|
|
||||||
{is_subcontracted: 'No'},
|
|
||||||
{buying_price_list: 'Test-Buying-EUR'},
|
|
||||||
{currency: 'EUR'},
|
|
||||||
{items: [
|
|
||||||
[
|
|
||||||
{"item_code": 'Test Product 4'},
|
|
||||||
{"qty": 5},
|
|
||||||
{"uom": 'Unit'},
|
|
||||||
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
|
|
||||||
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
|
|
||||||
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))},
|
|
||||||
{"discount_percentage": 20}
|
|
||||||
]
|
|
||||||
]}
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
|
|
||||||
() => frappe.timeout(1),
|
|
||||||
|
|
||||||
() => {
|
|
||||||
assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Supplier name correct");
|
|
||||||
assert.ok(cur_frm.doc.items[0].discount_percentage == 20, "Discount correct");
|
|
||||||
// Calculate totals after discount
|
|
||||||
assert.ok(cur_frm.doc.total == 2000, "Total correct");
|
|
||||||
assert.ok(cur_frm.doc.grand_total == 2000, "Grand total correct");
|
|
||||||
},
|
|
||||||
|
|
||||||
() => frappe.tests.click_button('Submit'),
|
|
||||||
() => frappe.tests.click_button('Yes'),
|
|
||||||
() => frappe.timeout(0.3),
|
|
||||||
|
|
||||||
() => done()
|
|
||||||
]);
|
|
||||||
});
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user