mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-04 22:18:27 +00:00
feat: unlinking dynamic links on trash
This commit is contained in:
@@ -147,11 +147,14 @@
|
|||||||
"Chart": true,
|
"Chart": true,
|
||||||
"Cypress": true,
|
"Cypress": true,
|
||||||
"cy": true,
|
"cy": true,
|
||||||
|
"describe": true,
|
||||||
|
"expect": true,
|
||||||
"it": true,
|
"it": true,
|
||||||
"context": true,
|
"context": true,
|
||||||
"before": true,
|
"before": true,
|
||||||
"beforeEach": true,
|
"beforeEach": true,
|
||||||
"onScan": true,
|
"onScan": true,
|
||||||
"extend_cscript": true
|
"extend_cscript": true,
|
||||||
|
"localforage": true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,3 +10,6 @@
|
|||||||
|
|
||||||
# Replace use of Class.extend with native JS class
|
# Replace use of Class.extend with native JS class
|
||||||
1fe891b287a1b3f225d29ee3d07e7b1824aba9e7
|
1fe891b287a1b3f225d29ee3d07e7b1824aba9e7
|
||||||
|
|
||||||
|
# This commit just changes spaces to tabs for indentation in some files
|
||||||
|
5f473611bd6ed57703716244a054d3fb5ba9cd23
|
||||||
|
|||||||
3
.github/helper/install.sh
vendored
3
.github/helper/install.sh
vendored
@@ -42,5 +42,6 @@ sed -i 's/socketio:/# socketio:/g' Procfile
|
|||||||
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
|
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
|
||||||
|
|
||||||
bench get-app erpnext "${GITHUB_WORKSPACE}"
|
bench get-app erpnext "${GITHUB_WORKSPACE}"
|
||||||
bench start &
|
bench start &> bench_run_logs.txt &
|
||||||
bench --site test_site reinstall --yes
|
bench --site test_site reinstall --yes
|
||||||
|
bench build --app frappe
|
||||||
|
|||||||
@@ -98,8 +98,6 @@ rules:
|
|||||||
languages: [python]
|
languages: [python]
|
||||||
severity: WARNING
|
severity: WARNING
|
||||||
paths:
|
paths:
|
||||||
exclude:
|
|
||||||
- test_*.py
|
|
||||||
include:
|
include:
|
||||||
- "*/**/doctype/*"
|
- "*/**/doctype/*"
|
||||||
|
|
||||||
|
|||||||
15
.github/helper/semgrep_rules/security.yml
vendored
15
.github/helper/semgrep_rules/security.yml
vendored
@@ -8,18 +8,3 @@ rules:
|
|||||||
dynamic content. Avoid it or use safe_eval().
|
dynamic content. Avoid it or use safe_eval().
|
||||||
languages: [python]
|
languages: [python]
|
||||||
severity: ERROR
|
severity: ERROR
|
||||||
|
|
||||||
- id: frappe-sqli-format-strings
|
|
||||||
patterns:
|
|
||||||
- pattern-inside: |
|
|
||||||
@frappe.whitelist()
|
|
||||||
def $FUNC(...):
|
|
||||||
...
|
|
||||||
- pattern-either:
|
|
||||||
- pattern: frappe.db.sql("..." % ...)
|
|
||||||
- pattern: frappe.db.sql(f"...", ...)
|
|
||||||
- pattern: frappe.db.sql("...".format(...), ...)
|
|
||||||
message: |
|
|
||||||
Detected use of raw string formatting for SQL queries. This can lead to sql injection vulnerabilities. Refer security guidelines - https://github.com/frappe/erpnext/wiki/Code-Security-Guidelines
|
|
||||||
languages: [python]
|
|
||||||
severity: WARNING
|
|
||||||
|
|||||||
4
.github/stale.yml
vendored
4
.github/stale.yml
vendored
@@ -1,11 +1,11 @@
|
|||||||
# 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
|
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||||
daysUntilStale: 30
|
daysUntilStale: 15
|
||||||
|
|
||||||
# Number of days of inactivity before a stale Issue or Pull Request is closed.
|
# 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.
|
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||||
daysUntilClose: 7
|
daysUntilClose: 3
|
||||||
|
|
||||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||||
exemptLabels:
|
exemptLabels:
|
||||||
|
|||||||
23
.github/workflows/backport.yml
vendored
23
.github/workflows/backport.yml
vendored
@@ -1,16 +1,25 @@
|
|||||||
name: Backport
|
name: Backport
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request_target:
|
||||||
types:
|
types:
|
||||||
- closed
|
- closed
|
||||||
- labeled
|
- labeled
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
backport:
|
main:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-latest
|
||||||
name: Backport
|
|
||||||
steps:
|
steps:
|
||||||
- name: Backport
|
- name: Checkout Actions
|
||||||
uses: tibdex/backport@v1
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
repository: "ankush/backport"
|
||||||
|
path: ./actions
|
||||||
|
ref: develop
|
||||||
|
- name: Install Actions
|
||||||
|
run: npm install --production --prefix ./actions
|
||||||
|
- name: Run backport
|
||||||
|
uses: ./actions/backport
|
||||||
|
with:
|
||||||
|
token: ${{secrets.BACKPORT_BOT_TOKEN}}
|
||||||
|
labelsToAdd: "backport"
|
||||||
|
title: "{{originalTitle}}"
|
||||||
|
|||||||
36
.github/workflows/semgrep.yml
vendored
36
.github/workflows/semgrep.yml
vendored
@@ -1,34 +1,18 @@
|
|||||||
name: Semgrep
|
name: Semgrep
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request: { }
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
- version-13-hotfix
|
|
||||||
- version-13-pre-release
|
|
||||||
jobs:
|
jobs:
|
||||||
semgrep:
|
semgrep:
|
||||||
name: Frappe Linter
|
name: Frappe Linter
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Setup python3
|
- uses: returntocorp/semgrep-action@v1
|
||||||
uses: actions/setup-python@v2
|
env:
|
||||||
with:
|
SEMGREP_TIMEOUT: 120
|
||||||
python-version: 3.8
|
with:
|
||||||
|
config: >-
|
||||||
- name: Setup semgrep
|
r/python.lang.correctness
|
||||||
run: |
|
.github/helper/semgrep_rules
|
||||||
python -m pip install -q semgrep
|
|
||||||
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
|
|
||||||
|
|
||||||
- name: Semgrep errors
|
|
||||||
run: |
|
|
||||||
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
|
||||||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files
|
|
||||||
semgrep --config="r/python.lang.correctness" --quiet --error $files
|
|
||||||
|
|
||||||
- name: Semgrep warnings
|
|
||||||
run: |
|
|
||||||
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
|
||||||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files
|
|
||||||
|
|||||||
108
.github/workflows/ui-tests.yml
vendored
Normal file
108
.github/workflows/ui-tests.yml
vendored
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
name: UI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
name: UI Tests (Cypress)
|
||||||
|
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mariadb:10.3
|
||||||
|
env:
|
||||||
|
MYSQL_ALLOW_EMPTY_PASSWORD: YES
|
||||||
|
ports:
|
||||||
|
- 3306:3306
|
||||||
|
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: 3.7
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: 14
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Add to Hosts
|
||||||
|
run: |
|
||||||
|
echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
|
||||||
|
|
||||||
|
- name: Cache pip
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/.cache/pip
|
||||||
|
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pip-
|
||||||
|
${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Cache node modules
|
||||||
|
uses: actions/cache@v2
|
||||||
|
env:
|
||||||
|
cache-name: cache-node-modules
|
||||||
|
with:
|
||||||
|
path: ~/.npm
|
||||||
|
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||||
|
${{ runner.os }}-build-
|
||||||
|
${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Get yarn cache directory path
|
||||||
|
id: yarn-cache-dir-path
|
||||||
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
|
|
||||||
|
- uses: actions/cache@v2
|
||||||
|
id: yarn-cache
|
||||||
|
with:
|
||||||
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
|
- name: Cache cypress binary
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/.cache
|
||||||
|
key: ${{ runner.os }}-cypress-
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-cypress-
|
||||||
|
${{ runner.os }}-
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||||
|
env:
|
||||||
|
DB: mariadb
|
||||||
|
TYPE: ui
|
||||||
|
|
||||||
|
- name: Site Setup
|
||||||
|
run: cd ~/frappe-bench/ && bench --site test_site execute erpnext.setup.utils.before_tests
|
||||||
|
|
||||||
|
- name: cypress pre-requisites
|
||||||
|
run: cd ~/frappe-bench/apps/frappe && yarn add cypress-file-upload@^5 --no-lockfile
|
||||||
|
|
||||||
|
|
||||||
|
- name: Build Assets
|
||||||
|
run: cd ~/frappe-bench/ && bench build
|
||||||
|
|
||||||
|
- name: UI Tests
|
||||||
|
run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests erpnext --headless
|
||||||
|
env:
|
||||||
|
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||||
|
|
||||||
|
- name: Show bench console if tests failed
|
||||||
|
if: ${{ failure() }}
|
||||||
|
run: cat ~/frappe-bench/bench_run_logs.txt
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,3 +16,4 @@ __pycache__
|
|||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
.backportrc.json
|
||||||
43
CODEOWNERS
43
CODEOWNERS
@@ -3,16 +3,33 @@
|
|||||||
# These owners will be the default owners for everything in
|
# These owners will be the default owners for everything in
|
||||||
# the repo. Unless a later match takes precedence,
|
# the repo. Unless a later match takes precedence,
|
||||||
|
|
||||||
manufacturing/ @rohitwaghchaure @marination
|
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007
|
||||||
accounts/ @deepeshgarg007 @nextchamp-saqib
|
erpnext/assets/ @nextchamp-saqib @deepeshgarg007
|
||||||
loan_management/ @deepeshgarg007 @rohitwaghchaure
|
erpnext/erpnext_integrations/ @nextchamp-saqib
|
||||||
pos* @nextchamp-saqib @rohitwaghchaure
|
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
|
||||||
assets/ @nextchamp-saqib @deepeshgarg007
|
erpnext/regional @nextchamp-saqib @deepeshgarg007
|
||||||
stock/ @marination @rohitwaghchaure
|
erpnext/selling @nextchamp-saqib @deepeshgarg007
|
||||||
buying/ @marination @deepeshgarg007
|
erpnext/support/ @nextchamp-saqib @deepeshgarg007
|
||||||
hr/ @Anurag810 @rohitwaghchaure
|
pos* @nextchamp-saqib
|
||||||
projects/ @hrwX @nextchamp-saqib
|
|
||||||
support/ @hrwX @marination
|
erpnext/buying/ @marination @rohitwaghchaure @ankush
|
||||||
healthcare/ @ruchamahabal @marination
|
erpnext/e_commerce/ @marination
|
||||||
erpnext_integrations/ @Mangesh-Khairnar @nextchamp-saqib
|
erpnext/maintenance/ @marination @rohitwaghchaure
|
||||||
requirements.txt @gavindsouza
|
erpnext/manufacturing/ @marination @rohitwaghchaure @ankush
|
||||||
|
erpnext/portal/ @marination
|
||||||
|
erpnext/quality_management/ @marination @rohitwaghchaure
|
||||||
|
erpnext/shopping_cart/ @marination
|
||||||
|
erpnext/stock/ @marination @rohitwaghchaure @ankush
|
||||||
|
|
||||||
|
erpnext/crm/ @ruchamahabal @pateljannat
|
||||||
|
erpnext/education/ @ruchamahabal @pateljannat
|
||||||
|
erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand
|
||||||
|
erpnext/hr/ @ruchamahabal @pateljannat
|
||||||
|
erpnext/non_profit/ @ruchamahabal
|
||||||
|
erpnext/payroll @ruchamahabal @pateljannat
|
||||||
|
erpnext/projects/ @ruchamahabal @pateljannat
|
||||||
|
|
||||||
|
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
|
||||||
|
|
||||||
|
.github/ @surajshetty3416 @ankush
|
||||||
|
requirements.txt @gavindsouza
|
||||||
|
|||||||
11
cypress.json
Normal file
11
cypress.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"baseUrl": "http://test_site:8000/",
|
||||||
|
"projectId": "da59y9",
|
||||||
|
"adminPassword": "admin",
|
||||||
|
"defaultCommandTimeout": 20000,
|
||||||
|
"pageLoadTimeout": 15000,
|
||||||
|
"retries": {
|
||||||
|
"runMode": 2,
|
||||||
|
"openMode": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
5
cypress/fixtures/example.json
Normal file
5
cypress/fixtures/example.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "Using fixtures to represent data",
|
||||||
|
"email": "hello@cypress.io",
|
||||||
|
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||||
|
}
|
||||||
13
cypress/integration/test_customer.js
Normal file
13
cypress/integration/test_customer.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
context('Customer', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.login();
|
||||||
|
});
|
||||||
|
it('Check Customer Group', () => {
|
||||||
|
cy.visit(`app/customer/`);
|
||||||
|
cy.get('.primary-action').click();
|
||||||
|
cy.wait(500);
|
||||||
|
cy.get('.custom-actions > .btn').click();
|
||||||
|
cy.get_field('customer_group', 'Link').should('have.value', 'All Customer Groups');
|
||||||
|
});
|
||||||
|
});
|
||||||
44
cypress/integration/test_item.js
Normal file
44
cypress/integration/test_item.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
describe("Test Item Dashboard", () => {
|
||||||
|
before(() => {
|
||||||
|
cy.login();
|
||||||
|
cy.visit("/app/item");
|
||||||
|
cy.insert_doc(
|
||||||
|
"Item",
|
||||||
|
{
|
||||||
|
item_code: "e2e_test_item",
|
||||||
|
item_group: "All Item Groups",
|
||||||
|
opening_stock: 42,
|
||||||
|
valuation_rate: 100,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
cy.go_to_doc("item", "e2e_test_item");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show dashboard with correct data on first load", () => {
|
||||||
|
cy.get(".stock-levels").contains("Stock Levels").should("be.visible");
|
||||||
|
cy.get(".stock-levels").contains("e2e_test_item").should("exist");
|
||||||
|
|
||||||
|
// reserved and available qty
|
||||||
|
cy.get(".stock-levels .inline-graph-count")
|
||||||
|
.eq(0)
|
||||||
|
.contains("0")
|
||||||
|
.should("exist");
|
||||||
|
cy.get(".stock-levels .inline-graph-count")
|
||||||
|
.eq(1)
|
||||||
|
.contains("42")
|
||||||
|
.should("exist");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should persist on field change", () => {
|
||||||
|
cy.get('input[data-fieldname="disabled"]').check();
|
||||||
|
cy.wait(500);
|
||||||
|
cy.get(".stock-levels").contains("Stock Levels").should("be.visible");
|
||||||
|
cy.get(".stock-levels").should("have.length", 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should persist on reload", () => {
|
||||||
|
cy.reload();
|
||||||
|
cy.get(".stock-levels").contains("Stock Levels").should("be.visible");
|
||||||
|
});
|
||||||
|
});
|
||||||
17
cypress/plugins/index.js
Normal file
17
cypress/plugins/index.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// ***********************************************************
|
||||||
|
// This example plugins/index.js can be used to load plugins
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off loading
|
||||||
|
// the plugins file with the 'pluginsFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/plugins-guide
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// This function is called when a project is opened or re-opened (e.g. due to
|
||||||
|
// the project's config changing)
|
||||||
|
|
||||||
|
module.exports = () => {
|
||||||
|
// `on` is used to hook into various events Cypress emits
|
||||||
|
// `config` is the resolved Cypress config
|
||||||
|
};
|
||||||
31
cypress/support/commands.js
Normal file
31
cypress/support/commands.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// ***********************************************
|
||||||
|
// This example commands.js shows you how to
|
||||||
|
// create various custom commands and overwrite
|
||||||
|
// existing commands.
|
||||||
|
//
|
||||||
|
// For more comprehensive examples of custom
|
||||||
|
// commands please read more here:
|
||||||
|
// https://on.cypress.io/custom-commands
|
||||||
|
// ***********************************************
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a parent command --
|
||||||
|
// Cypress.Commands.add("login", (email, password) => { ... });
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a child command --
|
||||||
|
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... });
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a dual command --
|
||||||
|
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... });
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is will overwrite an existing command --
|
||||||
|
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... });
|
||||||
|
|
||||||
|
const slug = (name) => name.toLowerCase().replace(" ", "-");
|
||||||
|
|
||||||
|
Cypress.Commands.add("go_to_doc", (doctype, name) => {
|
||||||
|
cy.visit(`/app/${slug(doctype)}/${encodeURIComponent(name)}`);
|
||||||
|
});
|
||||||
26
cypress/support/index.js
Normal file
26
cypress/support/index.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// ***********************************************************
|
||||||
|
// This example support/index.js is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
import './commands';
|
||||||
|
import '../../../frappe/cypress/support/commands' // eslint-disable-line
|
||||||
|
|
||||||
|
|
||||||
|
// Alternatively you can use CommonJS syntax:
|
||||||
|
// require('./commands')
|
||||||
|
|
||||||
|
Cypress.Cookies.defaults({
|
||||||
|
preserve: 'sid'
|
||||||
|
});
|
||||||
12
cypress/tsconfig.json
Normal file
12
cypress/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"baseUrl": "../node_modules",
|
||||||
|
"types": [
|
||||||
|
"cypress"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.*"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import frappe
|
|||||||
from erpnext.hooks import regional_overrides
|
from erpnext.hooks import regional_overrides
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
__version__ = '13.4.1'
|
__version__ = '13.7.1'
|
||||||
|
|
||||||
def get_default_company(user=None):
|
def get_default_company(user=None):
|
||||||
'''Get default company for user'''
|
'''Get default company for user'''
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ def get_shipping_address(company, address = None):
|
|||||||
if address and frappe.db.get_value('Dynamic Link',
|
if address and frappe.db.get_value('Dynamic Link',
|
||||||
{'parent': address, 'link_name': company}):
|
{'parent': address, 'link_name': company}):
|
||||||
filters.append(["Address", "name", "=", address])
|
filters.append(["Address", "name", "=", address])
|
||||||
|
if not address:
|
||||||
|
filters.append(["Address", "is_shipping_address", "=", 1])
|
||||||
|
|
||||||
address = frappe.get_all("Address", filters=filters, fields=fields) or {}
|
address = frappe.get_all("Address", filters=filters, fields=fields) or {}
|
||||||
|
|
||||||
|
|||||||
@@ -263,6 +263,9 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
|||||||
amount, base_amount = calculate_amount(doc, item, last_gl_entry,
|
amount, base_amount = calculate_amount(doc, item, last_gl_entry,
|
||||||
total_days, total_booking_days, account_currency)
|
total_days, total_booking_days, account_currency)
|
||||||
|
|
||||||
|
if not amount:
|
||||||
|
return
|
||||||
|
|
||||||
if via_journal_entry:
|
if via_journal_entry:
|
||||||
book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount,
|
book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount,
|
||||||
base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry)
|
base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry)
|
||||||
@@ -298,17 +301,21 @@ def process_deferred_accounting(posting_date=None):
|
|||||||
start_date = add_months(today(), -1)
|
start_date = add_months(today(), -1)
|
||||||
end_date = add_days(today(), -1)
|
end_date = add_days(today(), -1)
|
||||||
|
|
||||||
for record_type in ('Income', 'Expense'):
|
companies = frappe.get_all('Company')
|
||||||
doc = frappe.get_doc(dict(
|
|
||||||
doctype='Process Deferred Accounting',
|
|
||||||
posting_date=posting_date,
|
|
||||||
start_date=start_date,
|
|
||||||
end_date=end_date,
|
|
||||||
type=record_type
|
|
||||||
))
|
|
||||||
|
|
||||||
doc.insert()
|
for company in companies:
|
||||||
doc.submit()
|
for record_type in ('Income', 'Expense'):
|
||||||
|
doc = frappe.get_doc(dict(
|
||||||
|
doctype='Process Deferred Accounting',
|
||||||
|
company=company.name,
|
||||||
|
posting_date=posting_date,
|
||||||
|
start_date=start_date,
|
||||||
|
end_date=end_date,
|
||||||
|
type=record_type
|
||||||
|
))
|
||||||
|
|
||||||
|
doc.insert()
|
||||||
|
doc.submit()
|
||||||
|
|
||||||
def make_gl_entries(doc, credit_account, debit_account, against,
|
def make_gl_entries(doc, credit_account, debit_account, against,
|
||||||
amount, base_amount, posting_date, project, account_currency, cost_center, item, deferred_process=None):
|
amount, base_amount, posting_date, project, account_currency, cost_center, item, deferred_process=None):
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class AccountingDimension(Document):
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
|
if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
|
||||||
'Cost Center', 'Accounting Dimension Detail', 'Company') :
|
'Cost Center', 'Accounting Dimension Detail', 'Company', 'Account') :
|
||||||
|
|
||||||
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
|
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
|
||||||
frappe.throw(msg)
|
frappe.throw(msg)
|
||||||
@@ -27,7 +27,7 @@ class AccountingDimension(Document):
|
|||||||
exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
|
exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
|
||||||
|
|
||||||
if exists and self.is_new():
|
if exists and self.is_new():
|
||||||
frappe.throw("Document Type already used as a dimension")
|
frappe.throw(_("Document Type already used as a dimension"))
|
||||||
|
|
||||||
if not self.is_new():
|
if not self.is_new():
|
||||||
self.validate_document_type_change()
|
self.validate_document_type_change()
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"delete_linked_ledger_entries",
|
"delete_linked_ledger_entries",
|
||||||
"book_asset_depreciation_entry_automatically",
|
"book_asset_depreciation_entry_automatically",
|
||||||
"unlink_advance_payment_on_cancelation_of_order",
|
"unlink_advance_payment_on_cancelation_of_order",
|
||||||
|
"post_change_gl_entries",
|
||||||
"tax_settings_section",
|
"tax_settings_section",
|
||||||
"determine_address_tax_category_from",
|
"determine_address_tax_category_from",
|
||||||
"column_break_19",
|
"column_break_19",
|
||||||
@@ -253,6 +254,13 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_19",
|
"fieldname": "column_break_19",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"description": "If enabled, ledger entries will be posted for change amount in POS transactions",
|
||||||
|
"fieldname": "post_change_gl_entries",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Create Ledger Entries for Change Amount"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@@ -260,7 +268,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-04-30 15:25:10.381008",
|
"modified": "2021-06-17 20:26:03.721202",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import _
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||||
@@ -24,7 +25,7 @@ class AccountsSettings(Document):
|
|||||||
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:
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
"Stale Days should start from 1.", title='Error', indicator='red',
|
_("Stale Days should start from 1."), title='Error', indicator='red',
|
||||||
raise_exception=1)
|
raise_exception=1)
|
||||||
|
|
||||||
def enable_payment_schedule_in_print(self):
|
def enable_payment_schedule_in_print(self):
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-09-12 22:26:19.594367",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"document_type": "Setup",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"add_deduct_tax",
|
||||||
|
"charge_type",
|
||||||
|
"row_id",
|
||||||
|
"account_head",
|
||||||
|
"col_break_1",
|
||||||
|
"description",
|
||||||
|
"included_in_paid_amount",
|
||||||
|
"accounting_dimensions_section",
|
||||||
|
"cost_center",
|
||||||
|
"dimension_col_break",
|
||||||
|
"section_break_8",
|
||||||
|
"rate",
|
||||||
|
"section_break_9",
|
||||||
|
"currency",
|
||||||
|
"tax_amount",
|
||||||
|
"total",
|
||||||
|
"allocated_amount",
|
||||||
|
"column_break_13",
|
||||||
|
"base_tax_amount",
|
||||||
|
"base_total",
|
||||||
|
"base_allocated_amount"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "charge_type",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Type",
|
||||||
|
"oldfieldname": "charge_type",
|
||||||
|
"oldfieldtype": "Select",
|
||||||
|
"options": "\nActual\nOn Paid Amount\nOn Previous Row Amount\nOn Previous Row Total",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:[\"On Previous Row Amount\", \"On Previous Row Total\"].indexOf(doc.charge_type)!==-1",
|
||||||
|
"fieldname": "row_id",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Reference Row #",
|
||||||
|
"oldfieldname": "row_id",
|
||||||
|
"oldfieldtype": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "account_head",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Account Head",
|
||||||
|
"oldfieldname": "account_head",
|
||||||
|
"oldfieldtype": "Link",
|
||||||
|
"options": "Account",
|
||||||
|
"reqd": 1,
|
||||||
|
"search_index": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "col_break_1",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"width": "50%"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Description",
|
||||||
|
"oldfieldname": "description",
|
||||||
|
"oldfieldtype": "Small Text",
|
||||||
|
"print_width": "300px",
|
||||||
|
"reqd": 1,
|
||||||
|
"width": "300px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "accounting_dimensions_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Accounting Dimensions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": ":Company",
|
||||||
|
"fieldname": "cost_center",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Cost Center",
|
||||||
|
"oldfieldname": "cost_center_other_charges",
|
||||||
|
"oldfieldtype": "Link",
|
||||||
|
"options": "Cost Center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "dimension_col_break",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_8",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Rate",
|
||||||
|
"oldfieldname": "rate",
|
||||||
|
"oldfieldtype": "Currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_9",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "tax_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Amount",
|
||||||
|
"options": "currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Total",
|
||||||
|
"options": "currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_13",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_tax_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Amount (Company Currency)",
|
||||||
|
"oldfieldname": "tax_amount",
|
||||||
|
"oldfieldtype": "Currency",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total (Company Currency)",
|
||||||
|
"oldfieldname": "total",
|
||||||
|
"oldfieldtype": "Currency",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "add_deduct_tax",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Add Or Deduct",
|
||||||
|
"options": "Add\nDeduct",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "included_in_paid_amount",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Considered In Paid Amount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "allocated_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Allocated Amount",
|
||||||
|
"options": "currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_allocated_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Allocated Amount (Company Currency)",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "account_head.account_currency",
|
||||||
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Account Currency",
|
||||||
|
"options": "Currency",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-06-09 11:46:58.373170",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Advance Taxes and Charges",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "ASC"
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class AdvanceTaxesandCharges(Document):
|
||||||
|
pass
|
||||||
@@ -51,7 +51,7 @@ class BankStatementImport(DataImport):
|
|||||||
self.import_file, self.google_sheets_url
|
self.import_file, self.google_sheets_url
|
||||||
)
|
)
|
||||||
|
|
||||||
if 'Bank Account' not in json.dumps(preview):
|
if 'Bank Account' not in json.dumps(preview['columns']):
|
||||||
frappe.throw(_("Please add the Bank Account column"))
|
frappe.throw(_("Please add the Bank Account column"))
|
||||||
|
|
||||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import
|
|||||||
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file, read_xls_file_from_attached_file
|
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file, read_xls_file_from_attached_file
|
||||||
|
|
||||||
class ChartofAccountsImporter(Document):
|
class ChartofAccountsImporter(Document):
|
||||||
pass
|
def validate(self):
|
||||||
|
validate_accounts(self.import_file)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def validate_company(company):
|
def validate_company(company):
|
||||||
@@ -22,7 +23,7 @@ def validate_company(company):
|
|||||||
'allow_account_creation_against_child_company'])
|
'allow_account_creation_against_child_company'])
|
||||||
|
|
||||||
if parent_company and (not allow_account_creation_against_child_company):
|
if parent_company and (not allow_account_creation_against_child_company):
|
||||||
msg = _("{} is a child company. ").format(frappe.bold(company))
|
msg = _("{} is a child company.").format(frappe.bold(company)) + " "
|
||||||
msg += _("Please import accounts against parent company or enable {} in company master.").format(
|
msg += _("Please import accounts against parent company or enable {} in company master.").format(
|
||||||
frappe.bold('Allow Account Creation Against Child Company'))
|
frappe.bold('Allow Account Creation Against Child Company'))
|
||||||
frappe.throw(msg, title=_('Wrong Company'))
|
frappe.throw(msg, title=_('Wrong Company'))
|
||||||
@@ -56,7 +57,7 @@ def get_file(file_name):
|
|||||||
extension = extension.lstrip(".")
|
extension = extension.lstrip(".")
|
||||||
|
|
||||||
if extension not in ('csv', 'xlsx', 'xls'):
|
if extension not in ('csv', 'xlsx', 'xls'):
|
||||||
frappe.throw("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload")
|
frappe.throw(_("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload"))
|
||||||
|
|
||||||
return file_doc, extension
|
return file_doc, extension
|
||||||
|
|
||||||
@@ -293,7 +294,7 @@ def validate_accounts(file_name):
|
|||||||
accounts_dict = {}
|
accounts_dict = {}
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
accounts_dict.setdefault(account["account_name"], account)
|
accounts_dict.setdefault(account["account_name"], account)
|
||||||
if not hasattr(account, "parent_account"):
|
if "parent_account" not in account:
|
||||||
msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
|
msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
|
||||||
msg += "<br><br>"
|
msg += "<br><br>"
|
||||||
msg += _("Alternatively, you can download the template and fill your data in.")
|
msg += _("Alternatively, you can download the template and fill your data in.")
|
||||||
@@ -301,28 +302,27 @@ def validate_accounts(file_name):
|
|||||||
if account["parent_account"] and accounts_dict.get(account["parent_account"]):
|
if account["parent_account"] and accounts_dict.get(account["parent_account"]):
|
||||||
accounts_dict[account["parent_account"]]["is_group"] = 1
|
accounts_dict[account["parent_account"]]["is_group"] = 1
|
||||||
|
|
||||||
message = validate_root(accounts_dict)
|
validate_root(accounts_dict)
|
||||||
if message: return message
|
|
||||||
message = validate_account_types(accounts_dict)
|
validate_account_types(accounts_dict)
|
||||||
if message: return message
|
|
||||||
|
|
||||||
return [True, len(accounts)]
|
return [True, len(accounts)]
|
||||||
|
|
||||||
def validate_root(accounts):
|
def validate_root(accounts):
|
||||||
roots = [accounts[d] for d in accounts if not accounts[d].get('parent_account')]
|
roots = [accounts[d] for d in accounts if not accounts[d].get('parent_account')]
|
||||||
if len(roots) < 4:
|
if len(roots) < 4:
|
||||||
return _("Number of root accounts cannot be less than 4")
|
frappe.throw(_("Number of root accounts cannot be less than 4"))
|
||||||
|
|
||||||
error_messages = []
|
error_messages = []
|
||||||
|
|
||||||
for account in roots:
|
for account in roots:
|
||||||
if not account.get("root_type") and account.get("account_name"):
|
if not account.get("root_type") and account.get("account_name"):
|
||||||
error_messages.append("Please enter Root Type for account- {0}".format(account.get("account_name")))
|
error_messages.append(_("Please enter Root Type for account- {0}").format(account.get("account_name")))
|
||||||
elif account.get("root_type") not in get_root_types() and account.get("account_name"):
|
elif account.get("root_type") not in get_root_types() and account.get("account_name"):
|
||||||
error_messages.append("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity".format(account.get("account_name")))
|
error_messages.append(_("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity").format(account.get("account_name")))
|
||||||
|
|
||||||
if error_messages:
|
if error_messages:
|
||||||
return "<br>".join(error_messages)
|
frappe.throw("<br>".join(error_messages))
|
||||||
|
|
||||||
def get_root_types():
|
def get_root_types():
|
||||||
return ('Asset', 'Liability', 'Expense', 'Income', 'Equity')
|
return ('Asset', 'Liability', 'Expense', 'Income', 'Equity')
|
||||||
@@ -356,7 +356,7 @@ def validate_account_types(accounts):
|
|||||||
|
|
||||||
missing = list(set(account_types_for_ledger) - set(account_types))
|
missing = list(set(account_types_for_ledger) - set(account_types))
|
||||||
if missing:
|
if missing:
|
||||||
return _("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing))
|
frappe.throw(_("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing)))
|
||||||
|
|
||||||
account_types_for_group = ["Bank", "Cash", "Stock"]
|
account_types_for_group = ["Bank", "Cash", "Stock"]
|
||||||
# fix logic bug
|
# fix logic bug
|
||||||
@@ -364,7 +364,7 @@ def validate_account_types(accounts):
|
|||||||
|
|
||||||
missing = list(set(account_types_for_group) - set(account_groups))
|
missing = list(set(account_types_for_group) - set(account_groups))
|
||||||
if missing:
|
if missing:
|
||||||
return _("Please identify/create Account (Group) for type - {0}").format(' , '.join(missing))
|
frappe.throw(_("Please identify/create Account (Group) for type - {0}").format(' , '.join(missing)))
|
||||||
|
|
||||||
def unset_existing_data(company):
|
def unset_existing_data(company):
|
||||||
linked = frappe.db.sql('''select fieldname from tabDocField
|
linked = frappe.db.sql('''select fieldname from tabDocField
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class Dunning(AccountsController):
|
|||||||
|
|
||||||
def validate_amount(self):
|
def validate_amount(self):
|
||||||
amounts = calculate_interest_and_amount(
|
amounts = calculate_interest_and_amount(
|
||||||
self.posting_date, self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days)
|
self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days)
|
||||||
if self.interest_amount != amounts.get('interest_amount'):
|
if self.interest_amount != amounts.get('interest_amount'):
|
||||||
self.interest_amount = flt(amounts.get('interest_amount'), self.precision('interest_amount'))
|
self.interest_amount = flt(amounts.get('interest_amount'), self.precision('interest_amount'))
|
||||||
if self.dunning_amount != amounts.get('dunning_amount'):
|
if self.dunning_amount != amounts.get('dunning_amount'):
|
||||||
@@ -86,18 +86,18 @@ def resolve_dunning(doc, state):
|
|||||||
for reference in doc.references:
|
for reference in doc.references:
|
||||||
if reference.reference_doctype == 'Sales Invoice' and reference.outstanding_amount <= 0:
|
if reference.reference_doctype == 'Sales Invoice' and reference.outstanding_amount <= 0:
|
||||||
dunnings = frappe.get_list('Dunning', filters={
|
dunnings = frappe.get_list('Dunning', filters={
|
||||||
'sales_invoice': reference.reference_name, 'status': ('!=', 'Resolved')})
|
'sales_invoice': reference.reference_name, 'status': ('!=', 'Resolved')}, ignore_permissions=True)
|
||||||
|
|
||||||
for dunning in dunnings:
|
for dunning in dunnings:
|
||||||
frappe.db.set_value("Dunning", dunning.name, "status", 'Resolved')
|
frappe.db.set_value("Dunning", dunning.name, "status", 'Resolved')
|
||||||
|
|
||||||
def calculate_interest_and_amount(posting_date, outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
|
def calculate_interest_and_amount(outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
|
||||||
interest_amount = 0
|
interest_amount = 0
|
||||||
grand_total = 0
|
grand_total = flt(outstanding_amount) + flt(dunning_fee)
|
||||||
if rate_of_interest:
|
if rate_of_interest:
|
||||||
interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100
|
interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100
|
||||||
interest_amount = (interest_per_year * cint(overdue_days)) / 365
|
interest_amount = (interest_per_year * cint(overdue_days)) / 365
|
||||||
grand_total = flt(outstanding_amount) + flt(interest_amount) + flt(dunning_fee)
|
grand_total += flt(interest_amount)
|
||||||
dunning_amount = flt(interest_amount) + flt(dunning_fee)
|
dunning_amount = flt(interest_amount) + flt(dunning_fee)
|
||||||
return {
|
return {
|
||||||
'interest_amount': interest_amount,
|
'interest_amount': interest_amount,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class TestDunning(unittest.TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(self):
|
||||||
create_dunning_type()
|
create_dunning_type()
|
||||||
|
create_dunning_type_with_zero_interest_rate()
|
||||||
unlink_payment_on_cancel_of_invoice()
|
unlink_payment_on_cancel_of_invoice()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -25,11 +26,19 @@ class TestDunning(unittest.TestCase):
|
|||||||
def test_dunning(self):
|
def test_dunning(self):
|
||||||
dunning = create_dunning()
|
dunning = create_dunning()
|
||||||
amounts = calculate_interest_and_amount(
|
amounts = calculate_interest_and_amount(
|
||||||
dunning.posting_date, dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days)
|
dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days)
|
||||||
self.assertEqual(round(amounts.get('interest_amount'), 2), 0.44)
|
self.assertEqual(round(amounts.get('interest_amount'), 2), 0.44)
|
||||||
self.assertEqual(round(amounts.get('dunning_amount'), 2), 20.44)
|
self.assertEqual(round(amounts.get('dunning_amount'), 2), 20.44)
|
||||||
self.assertEqual(round(amounts.get('grand_total'), 2), 120.44)
|
self.assertEqual(round(amounts.get('grand_total'), 2), 120.44)
|
||||||
|
|
||||||
|
def test_dunning_with_zero_interest_rate(self):
|
||||||
|
dunning = create_dunning_with_zero_interest_rate()
|
||||||
|
amounts = calculate_interest_and_amount(
|
||||||
|
dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days)
|
||||||
|
self.assertEqual(round(amounts.get('interest_amount'), 2), 0)
|
||||||
|
self.assertEqual(round(amounts.get('dunning_amount'), 2), 20)
|
||||||
|
self.assertEqual(round(amounts.get('grand_total'), 2), 120)
|
||||||
|
|
||||||
def test_gl_entries(self):
|
def test_gl_entries(self):
|
||||||
dunning = create_dunning()
|
dunning = create_dunning()
|
||||||
dunning.submit()
|
dunning.submit()
|
||||||
@@ -83,6 +92,27 @@ def create_dunning():
|
|||||||
dunning.save()
|
dunning.save()
|
||||||
return dunning
|
return dunning
|
||||||
|
|
||||||
|
def create_dunning_with_zero_interest_rate():
|
||||||
|
posting_date = add_days(today(), -20)
|
||||||
|
due_date = add_days(today(), -15)
|
||||||
|
sales_invoice = create_sales_invoice_against_cost_center(
|
||||||
|
posting_date=posting_date, due_date=due_date, status='Overdue')
|
||||||
|
dunning_type = frappe.get_doc("Dunning Type", 'First Notice with 0% Rate of Interest')
|
||||||
|
dunning = frappe.new_doc("Dunning")
|
||||||
|
dunning.sales_invoice = sales_invoice.name
|
||||||
|
dunning.customer_name = sales_invoice.customer_name
|
||||||
|
dunning.outstanding_amount = sales_invoice.outstanding_amount
|
||||||
|
dunning.debit_to = sales_invoice.debit_to
|
||||||
|
dunning.currency = sales_invoice.currency
|
||||||
|
dunning.company = sales_invoice.company
|
||||||
|
dunning.posting_date = nowdate()
|
||||||
|
dunning.due_date = sales_invoice.due_date
|
||||||
|
dunning.dunning_type = 'First Notice with 0% Rate of Interest'
|
||||||
|
dunning.rate_of_interest = dunning_type.rate_of_interest
|
||||||
|
dunning.dunning_fee = dunning_type.dunning_fee
|
||||||
|
dunning.save()
|
||||||
|
return dunning
|
||||||
|
|
||||||
def create_dunning_type():
|
def create_dunning_type():
|
||||||
dunning_type = frappe.new_doc("Dunning Type")
|
dunning_type = frappe.new_doc("Dunning Type")
|
||||||
dunning_type.dunning_type = 'First Notice'
|
dunning_type.dunning_type = 'First Notice'
|
||||||
@@ -98,3 +128,19 @@ def create_dunning_type():
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
dunning_type.save()
|
dunning_type.save()
|
||||||
|
|
||||||
|
def create_dunning_type_with_zero_interest_rate():
|
||||||
|
dunning_type = frappe.new_doc("Dunning Type")
|
||||||
|
dunning_type.dunning_type = 'First Notice with 0% Rate of Interest'
|
||||||
|
dunning_type.start_day = 10
|
||||||
|
dunning_type.end_day = 20
|
||||||
|
dunning_type.dunning_fee = 20
|
||||||
|
dunning_type.rate_of_interest = 0
|
||||||
|
dunning_type.append(
|
||||||
|
"dunning_letter_text", {
|
||||||
|
'language': 'en',
|
||||||
|
'body_text': 'We have still not received payment for our invoice ',
|
||||||
|
'closing_text': 'We kindly request that you pay the outstanding amount immediately, and late fees.'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
dunning_type.save()
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ class ExchangeRateRevaluation(Document):
|
|||||||
if not (self.company and self.posting_date):
|
if not (self.company and self.posting_date):
|
||||||
frappe.throw(_("Please select Company and Posting Date to getting entries"))
|
frappe.throw(_("Please select Company and Posting Date to getting entries"))
|
||||||
|
|
||||||
|
def on_cancel(self):
|
||||||
|
self.ignore_linked_doctypes = ('GL Entry')
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def check_journal_entry_condition(self):
|
def check_journal_entry_condition(self):
|
||||||
total_debit = frappe.db.get_value("Journal Entry Account", {
|
total_debit = frappe.db.get_value("Journal Entry Account", {
|
||||||
@@ -99,10 +102,12 @@ class ExchangeRateRevaluation(Document):
|
|||||||
sum(debit) - sum(credit) as balance
|
sum(debit) - sum(credit) as balance
|
||||||
from `tabGL Entry`
|
from `tabGL Entry`
|
||||||
where account in (%s)
|
where account in (%s)
|
||||||
group by account, party_type, party
|
and posting_date <= %s
|
||||||
|
and is_cancelled = 0
|
||||||
|
group by account, NULLIF(party_type,''), NULLIF(party,'')
|
||||||
having sum(debit) != sum(credit)
|
having sum(debit) != sum(credit)
|
||||||
order by account
|
order by account
|
||||||
""" % ', '.join(['%s']*len(accounts)), tuple(accounts), as_dict=1)
|
""" % (', '.join(['%s']*len(accounts)), '%s'), tuple(accounts + [self.posting_date]), as_dict=1)
|
||||||
|
|
||||||
return account_details
|
return account_details
|
||||||
|
|
||||||
@@ -143,9 +148,9 @@ class ExchangeRateRevaluation(Document):
|
|||||||
"party_type": d.get("party_type"),
|
"party_type": d.get("party_type"),
|
||||||
"party": d.get("party"),
|
"party": d.get("party"),
|
||||||
"account_currency": d.get("account_currency"),
|
"account_currency": d.get("account_currency"),
|
||||||
"balance": d.get("balance_in_account_currency"),
|
"balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
|
||||||
dr_or_cr: abs(d.get("balance_in_account_currency")),
|
dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
|
||||||
"exchange_rate":d.get("new_exchange_rate"),
|
"exchange_rate": flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")),
|
||||||
"reference_type": "Exchange Rate Revaluation",
|
"reference_type": "Exchange Rate Revaluation",
|
||||||
"reference_name": self.name,
|
"reference_name": self.name,
|
||||||
})
|
})
|
||||||
@@ -154,9 +159,9 @@ class ExchangeRateRevaluation(Document):
|
|||||||
"party_type": d.get("party_type"),
|
"party_type": d.get("party_type"),
|
||||||
"party": d.get("party"),
|
"party": d.get("party"),
|
||||||
"account_currency": d.get("account_currency"),
|
"account_currency": d.get("account_currency"),
|
||||||
"balance": d.get("balance_in_account_currency"),
|
"balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
|
||||||
reverse_dr_or_cr: abs(d.get("balance_in_account_currency")),
|
reverse_dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
|
||||||
"exchange_rate": d.get("current_exchange_rate"),
|
"exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")),
|
||||||
"reference_type": "Exchange Rate Revaluation",
|
"reference_type": "Exchange Rate Revaluation",
|
||||||
"reference_name": self.name
|
"reference_name": self.name
|
||||||
})
|
})
|
||||||
@@ -185,9 +190,9 @@ def get_account_details(account, company, posting_date, party_type=None, party=N
|
|||||||
|
|
||||||
account_details = {}
|
account_details = {}
|
||||||
company_currency = erpnext.get_company_currency(company)
|
company_currency = erpnext.get_company_currency(company)
|
||||||
balance = get_balance_on(account, party_type=party_type, party=party, in_account_currency=False)
|
balance = get_balance_on(account, date=posting_date, party_type=party_type, party=party, in_account_currency=False)
|
||||||
if balance:
|
if balance:
|
||||||
balance_in_account_currency = get_balance_on(account, party_type=party_type, party=party)
|
balance_in_account_currency = get_balance_on(account, date=posting_date, party_type=party_type, party=party)
|
||||||
current_exchange_rate = balance / balance_in_account_currency if balance_in_account_currency else 0
|
current_exchange_rate = balance / balance_in_account_currency if balance_in_account_currency else 0
|
||||||
new_exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
|
new_exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
|
||||||
new_balance_in_base_currency = balance_in_account_currency * new_exchange_rate
|
new_balance_in_base_currency = balance_in_account_currency * new_exchange_rate
|
||||||
|
|||||||
@@ -121,8 +121,7 @@ class GLEntry(Document):
|
|||||||
|
|
||||||
def check_pl_account(self):
|
def check_pl_account(self):
|
||||||
if self.is_opening=='Yes' and \
|
if self.is_opening=='Yes' and \
|
||||||
frappe.db.get_value("Account", self.account, "report_type")=="Profit and Loss" and \
|
frappe.db.get_value("Account", self.account, "report_type")=="Profit and Loss":
|
||||||
self.voucher_type not in ['Purchase Invoice', 'Sales Invoice']:
|
|
||||||
frappe.throw(_("{0} {1}: 'Profit and Loss' type account {2} not allowed in Opening Entry")
|
frappe.throw(_("{0} {1}: 'Profit and Loss' type account {2} not allowed in Opening Entry")
|
||||||
.format(self.voucher_type, self.voucher_no, self.account))
|
.format(self.voucher_type, self.voucher_no, self.account))
|
||||||
|
|
||||||
|
|||||||
@@ -49,11 +49,11 @@ class InvoiceDiscounting(AccountsController):
|
|||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.set_status()
|
self.set_status(cancel=1)
|
||||||
self.update_sales_invoice()
|
self.update_sales_invoice()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
def set_status(self, status=None):
|
def set_status(self, status=None, cancel=0):
|
||||||
if status:
|
if status:
|
||||||
self.status = status
|
self.status = status
|
||||||
self.db_set("status", status)
|
self.db_set("status", status)
|
||||||
@@ -66,6 +66,9 @@ class InvoiceDiscounting(AccountsController):
|
|||||||
elif self.docstatus == 2:
|
elif self.docstatus == 2:
|
||||||
self.status = "Cancelled"
|
self.status = "Cancelled"
|
||||||
|
|
||||||
|
if cancel:
|
||||||
|
self.db_set('status', self.status, update_modified = True)
|
||||||
|
|
||||||
def update_sales_invoice(self):
|
def update_sales_invoice(self):
|
||||||
for d in self.invoices:
|
for d in self.invoices:
|
||||||
if self.docstatus == 1:
|
if self.docstatus == 1:
|
||||||
|
|||||||
@@ -49,7 +49,15 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
|
|||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
btn: $(btn_primary),
|
btn: $(btn_primary),
|
||||||
method: "make_invoices",
|
method: "make_invoices",
|
||||||
freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type])
|
freeze: 1,
|
||||||
|
freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]),
|
||||||
|
callback: function(r) {
|
||||||
|
if (r.message.length == 1) {
|
||||||
|
frappe.msgprint(__("{0} Invoice created successfully.", [frm.doc.invoice_type]));
|
||||||
|
} else if (r.message.length < 50) {
|
||||||
|
frappe.msgprint(__("{0} Invoices created successfully.", [frm.doc.invoice_type]));
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -216,7 +216,8 @@ def start_import(invoices):
|
|||||||
return names
|
return names
|
||||||
|
|
||||||
def publish(index, total, doctype):
|
def publish(index, total, doctype):
|
||||||
if total < 5: return
|
if total < 50:
|
||||||
|
return
|
||||||
frappe.publish_realtime(
|
frappe.publish_realtime(
|
||||||
"opening_invoice_creation_progress",
|
"opening_invoice_creation_progress",
|
||||||
dict(
|
dict(
|
||||||
@@ -241,4 +242,3 @@ def get_temporary_opening_account(company=None):
|
|||||||
|
|
||||||
return accounts[0].name
|
return accounts[0].name
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,12 @@
|
|||||||
{% include "erpnext/public/js/controllers/accounts.js" %}
|
{% include "erpnext/public/js/controllers/accounts.js" %}
|
||||||
frappe.provide("erpnext.accounts.dimensions");
|
frappe.provide("erpnext.accounts.dimensions");
|
||||||
|
|
||||||
|
cur_frm.cscript.tax_table = "Advance Taxes and Charges";
|
||||||
|
|
||||||
frappe.ui.form.on('Payment Entry', {
|
frappe.ui.form.on('Payment Entry', {
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
|
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice'];
|
||||||
|
|
||||||
if(frm.doc.__islocal) {
|
if(frm.doc.__islocal) {
|
||||||
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
|
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
|
||||||
if (!frm.doc.paid_to) frm.set_value("paid_to_account_currency", null);
|
if (!frm.doc.paid_to) frm.set_value("paid_to_account_currency", null);
|
||||||
@@ -91,6 +95,16 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("advance_tax_account", function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"company": frm.doc.company,
|
||||||
|
"root_type": ["in", ["Asset", "Liability"]],
|
||||||
|
"is_group": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
frm.set_query("reference_doctype", "references", function() {
|
frm.set_query("reference_doctype", "references", function() {
|
||||||
if (frm.doc.party_type == "Customer") {
|
if (frm.doc.party_type == "Customer") {
|
||||||
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
||||||
@@ -182,6 +196,8 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency));
|
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency));
|
||||||
|
|
||||||
frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency);
|
frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency);
|
||||||
|
frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
|
||||||
|
(frm.doc.paid_from_account_currency != company_currency));
|
||||||
|
|
||||||
frm.toggle_display("base_received_amount", (
|
frm.toggle_display("base_received_amount", (
|
||||||
frm.doc.paid_to_account_currency != company_currency
|
frm.doc.paid_to_account_currency != company_currency
|
||||||
@@ -216,7 +232,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
var company_currency = frm.doc.company? frappe.get_doc(":Company", frm.doc.company).default_currency: "";
|
var company_currency = frm.doc.company? frappe.get_doc(":Company", frm.doc.company).default_currency: "";
|
||||||
|
|
||||||
frm.set_currency_labels(["base_paid_amount", "base_received_amount", "base_total_allocated_amount",
|
frm.set_currency_labels(["base_paid_amount", "base_received_amount", "base_total_allocated_amount",
|
||||||
"difference_amount"], company_currency);
|
"difference_amount", "base_paid_amount_after_tax", "base_received_amount_after_tax"], company_currency);
|
||||||
|
|
||||||
frm.set_currency_labels(["paid_amount"], frm.doc.paid_from_account_currency);
|
frm.set_currency_labels(["paid_amount"], frm.doc.paid_from_account_currency);
|
||||||
frm.set_currency_labels(["received_amount"], frm.doc.paid_to_account_currency);
|
frm.set_currency_labels(["received_amount"], frm.doc.paid_to_account_currency);
|
||||||
@@ -224,11 +240,13 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
var party_account_currency = frm.doc.payment_type=="Receive" ?
|
var party_account_currency = frm.doc.payment_type=="Receive" ?
|
||||||
frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency;
|
frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency;
|
||||||
|
|
||||||
frm.set_currency_labels(["total_allocated_amount", "unallocated_amount"], party_account_currency);
|
frm.set_currency_labels(["total_allocated_amount", "unallocated_amount",
|
||||||
|
"total_taxes_and_charges"], party_account_currency);
|
||||||
|
|
||||||
var currency_field = (frm.doc.payment_type=="Receive") ? "paid_from_account_currency" : "paid_to_account_currency"
|
var currency_field = (frm.doc.payment_type=="Receive") ? "paid_from_account_currency" : "paid_to_account_currency"
|
||||||
frm.set_df_property("total_allocated_amount", "options", currency_field);
|
frm.set_df_property("total_allocated_amount", "options", currency_field);
|
||||||
frm.set_df_property("unallocated_amount", "options", currency_field);
|
frm.set_df_property("unallocated_amount", "options", currency_field);
|
||||||
|
frm.set_df_property("total_taxes_and_charges", "options", currency_field);
|
||||||
frm.set_df_property("party_balance", "options", currency_field);
|
frm.set_df_property("party_balance", "options", currency_field);
|
||||||
|
|
||||||
frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"],
|
frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"],
|
||||||
@@ -364,6 +382,16 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
apply_tax_withholding_amount: function(frm) {
|
||||||
|
if (!frm.doc.apply_tax_withholding_amount) {
|
||||||
|
frm.set_value("tax_withholding_category", '');
|
||||||
|
} else {
|
||||||
|
frappe.db.get_value('Supplier', frm.doc.party, 'tax_withholding_category', (values) => {
|
||||||
|
frm.set_value("tax_withholding_category", values.tax_withholding_category);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
paid_from: function(frm) {
|
paid_from: function(frm) {
|
||||||
if(frm.set_party_account_based_on_party) return;
|
if(frm.set_party_account_based_on_party) return;
|
||||||
|
|
||||||
@@ -843,12 +871,12 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
if(frm.doc.payment_type == "Receive"
|
if(frm.doc.payment_type == "Receive"
|
||||||
&& frm.doc.base_total_allocated_amount < frm.doc.base_received_amount + total_deductions
|
&& frm.doc.base_total_allocated_amount < frm.doc.base_received_amount + total_deductions
|
||||||
&& frm.doc.total_allocated_amount < frm.doc.paid_amount + (total_deductions / frm.doc.source_exchange_rate)) {
|
&& frm.doc.total_allocated_amount < frm.doc.paid_amount + (total_deductions / frm.doc.source_exchange_rate)) {
|
||||||
unallocated_amount = (frm.doc.base_received_amount + total_deductions
|
unallocated_amount = (frm.doc.base_received_amount + total_deductions + frm.doc.base_total_taxes_and_charges
|
||||||
- frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate;
|
+ frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate;
|
||||||
} else if (frm.doc.payment_type == "Pay"
|
} else if (frm.doc.payment_type == "Pay"
|
||||||
&& frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions
|
&& frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions
|
||||||
&& frm.doc.total_allocated_amount < frm.doc.received_amount + (total_deductions / frm.doc.target_exchange_rate)) {
|
&& frm.doc.total_allocated_amount < frm.doc.received_amount + (total_deductions / frm.doc.target_exchange_rate)) {
|
||||||
unallocated_amount = (frm.doc.base_paid_amount - (total_deductions
|
unallocated_amount = (frm.doc.base_paid_amount + frm.doc.base_total_taxes_and_charges - (total_deductions
|
||||||
+ frm.doc.base_total_allocated_amount)) / frm.doc.target_exchange_rate;
|
+ frm.doc.base_total_allocated_amount)) / frm.doc.target_exchange_rate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -874,7 +902,8 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
|
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
|
||||||
function(d) { return flt(d.amount) }));
|
function(d) { return flt(d.amount) }));
|
||||||
|
|
||||||
frm.set_value("difference_amount", difference_amount - total_deductions);
|
frm.set_value("difference_amount", difference_amount - total_deductions +
|
||||||
|
frm.doc.base_total_taxes_and_charges);
|
||||||
|
|
||||||
frm.events.hide_unhide_fields(frm);
|
frm.events.hide_unhide_fields(frm);
|
||||||
},
|
},
|
||||||
@@ -1002,7 +1031,266 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
|
sales_taxes_and_charges_template: function(frm) {
|
||||||
|
frm.trigger('fetch_taxes_from_template');
|
||||||
|
},
|
||||||
|
|
||||||
|
purchase_taxes_and_charges_template: function(frm) {
|
||||||
|
frm.trigger('fetch_taxes_from_template');
|
||||||
|
},
|
||||||
|
|
||||||
|
fetch_taxes_from_template: function(frm) {
|
||||||
|
let master_doctype = '';
|
||||||
|
let taxes_and_charges = '';
|
||||||
|
|
||||||
|
if (frm.doc.party_type == 'Supplier') {
|
||||||
|
master_doctype = 'Purchase Taxes and Charges Template';
|
||||||
|
taxes_and_charges = frm.doc.purchase_taxes_and_charges_template;
|
||||||
|
} else if (frm.doc.party_type == 'Customer') {
|
||||||
|
master_doctype = 'Sales Taxes and Charges Template';
|
||||||
|
taxes_and_charges = frm.doc.sales_taxes_and_charges_template;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!taxes_and_charges) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.controllers.accounts_controller.get_taxes_and_charges",
|
||||||
|
args: {
|
||||||
|
"master_doctype": master_doctype,
|
||||||
|
"master_name": taxes_and_charges
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if(!r.exc && r.message) {
|
||||||
|
// set taxes table
|
||||||
|
if(r.message) {
|
||||||
|
for (let tax of r.message) {
|
||||||
|
if (tax.charge_type === 'On Net Total') {
|
||||||
|
tax.charge_type = 'On Paid Amount';
|
||||||
|
}
|
||||||
|
me.frm.add_child("taxes", tax);
|
||||||
|
}
|
||||||
|
frm.events.apply_taxes(frm);
|
||||||
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
apply_taxes: function(frm) {
|
||||||
|
frm.events.initialize_taxes(frm);
|
||||||
|
frm.events.determine_exclusive_rate(frm);
|
||||||
|
frm.events.calculate_taxes(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize_taxes: function(frm) {
|
||||||
|
$.each(frm.doc["taxes"] || [], function(i, tax) {
|
||||||
|
frm.events.validate_taxes_and_charges(tax);
|
||||||
|
frm.events.validate_inclusive_tax(tax);
|
||||||
|
tax.item_wise_tax_detail = {};
|
||||||
|
let tax_fields = ["total", "tax_fraction_for_current_item",
|
||||||
|
"grand_total_fraction_for_current_item"];
|
||||||
|
|
||||||
|
if (cstr(tax.charge_type) != "Actual") {
|
||||||
|
tax_fields.push("tax_amount");
|
||||||
|
}
|
||||||
|
|
||||||
|
$.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0; });
|
||||||
|
|
||||||
|
frm.doc.paid_amount_after_tax = frm.doc.paid_amount;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
validate_taxes_and_charges: function(d) {
|
||||||
|
let msg = "";
|
||||||
|
|
||||||
|
if (d.account_head && !d.description) {
|
||||||
|
// set description from account head
|
||||||
|
d.description = d.account_head.split(' - ').slice(0, -1).join(' - ');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!d.charge_type && (d.row_id || d.rate || d.tax_amount)) {
|
||||||
|
msg = __("Please select Charge Type first");
|
||||||
|
d.row_id = "";
|
||||||
|
d.rate = d.tax_amount = 0.0;
|
||||||
|
} else if ((d.charge_type == 'Actual' || d.charge_type == 'On Net Total' || d.charge_type == 'On Paid Amount') && d.row_id) {
|
||||||
|
msg = __("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'");
|
||||||
|
d.row_id = "";
|
||||||
|
} else if ((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id) {
|
||||||
|
if (d.idx == 1) {
|
||||||
|
msg = __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row");
|
||||||
|
d.charge_type = '';
|
||||||
|
} else if (!d.row_id) {
|
||||||
|
msg = __("Please specify a valid Row ID for row {0} in table {1}", [d.idx, __(d.doctype)]);
|
||||||
|
d.row_id = "";
|
||||||
|
} else if (d.row_id && d.row_id >= d.idx) {
|
||||||
|
msg = __("Cannot refer row number greater than or equal to current row number for this Charge type");
|
||||||
|
d.row_id = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (msg) {
|
||||||
|
frappe.validated = false;
|
||||||
|
refresh_field("taxes");
|
||||||
|
frappe.throw(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
validate_inclusive_tax: function(tax) {
|
||||||
|
let actual_type_error = function() {
|
||||||
|
let msg = __("Actual type tax cannot be included in Item rate in row {0}", [tax.idx])
|
||||||
|
frappe.throw(msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
let on_previous_row_error = function(row_range) {
|
||||||
|
let msg = __("For row {0} in {1}. To include {2} in Item rate, rows {3} must also be included",
|
||||||
|
[tax.idx, __(tax.doctype), tax.charge_type, row_range])
|
||||||
|
frappe.throw(msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
if(cint(tax.included_in_paid_amount)) {
|
||||||
|
if(tax.charge_type == "Actual") {
|
||||||
|
// inclusive tax cannot be of type Actual
|
||||||
|
actual_type_error();
|
||||||
|
} else if(tax.charge_type == "On Previous Row Amount" &&
|
||||||
|
!cint(this.frm.doc["taxes"][tax.row_id - 1].included_in_paid_amount)
|
||||||
|
) {
|
||||||
|
// referred row should also be an inclusive tax
|
||||||
|
on_previous_row_error(tax.row_id);
|
||||||
|
} else if(tax.charge_type == "On Previous Row Total") {
|
||||||
|
let taxes_not_included = $.map(this.frm.doc["taxes"].slice(0, tax.row_id),
|
||||||
|
function(t) { return cint(t.included_in_paid_amount) ? null : t; });
|
||||||
|
if(taxes_not_included.length > 0) {
|
||||||
|
// all rows above this tax should be inclusive
|
||||||
|
on_previous_row_error(tax.row_id == 1 ? "1" : "1 - " + tax.row_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
determine_exclusive_rate: function(frm) {
|
||||||
|
let has_inclusive_tax = false;
|
||||||
|
$.each(frm.doc["taxes"] || [], function(i, row) {
|
||||||
|
if(cint(row.included_in_paid_amount)) has_inclusive_tax = true;
|
||||||
|
});
|
||||||
|
if(has_inclusive_tax==false) return;
|
||||||
|
|
||||||
|
let cumulated_tax_fraction = 0.0;
|
||||||
|
$.each(frm.doc["taxes"] || [], function(i, tax) {
|
||||||
|
tax.tax_fraction_for_current_item = frm.events.get_current_tax_fraction(frm, tax);
|
||||||
|
|
||||||
|
if(i==0) {
|
||||||
|
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item;
|
||||||
|
} else {
|
||||||
|
tax.grand_total_fraction_for_current_item =
|
||||||
|
me.frm.doc["taxes"][i-1].grand_total_fraction_for_current_item +
|
||||||
|
tax.tax_fraction_for_current_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
cumulated_tax_fraction += tax.tax_fraction_for_current_item;
|
||||||
|
frm.doc.paid_amount_after_tax = flt(frm.doc.paid_amount/(1+cumulated_tax_fraction))
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get_current_tax_fraction: function(frm, tax) {
|
||||||
|
let current_tax_fraction = 0.0;
|
||||||
|
|
||||||
|
if(cint(tax.included_in_paid_amount)) {
|
||||||
|
let tax_rate = tax.rate;
|
||||||
|
|
||||||
|
if(tax.charge_type == "On Paid Amount") {
|
||||||
|
current_tax_fraction = (tax_rate / 100.0);
|
||||||
|
} else if(tax.charge_type == "On Previous Row Amount") {
|
||||||
|
current_tax_fraction = (tax_rate / 100.0) *
|
||||||
|
frm.doc["taxes"][cint(tax.row_id) - 1].tax_fraction_for_current_item;
|
||||||
|
} else if(tax.charge_type == "On Previous Row Total") {
|
||||||
|
current_tax_fraction = (tax_rate / 100.0) *
|
||||||
|
frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_fraction_for_current_item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") {
|
||||||
|
current_tax_fraction *= -1;
|
||||||
|
}
|
||||||
|
return current_tax_fraction;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
calculate_taxes: function(frm) {
|
||||||
|
frm.doc.total_taxes_and_charges = 0.0;
|
||||||
|
frm.doc.base_total_taxes_and_charges = 0.0;
|
||||||
|
|
||||||
|
let actual_tax_dict = {};
|
||||||
|
|
||||||
|
// maintain actual tax rate based on idx
|
||||||
|
$.each(frm.doc["taxes"] || [], function(i, tax) {
|
||||||
|
if (tax.charge_type == "Actual") {
|
||||||
|
actual_tax_dict[tax.idx] = flt(tax.tax_amount, precision("tax_amount", tax));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$.each(me.frm.doc["taxes"] || [], function(i, tax) {
|
||||||
|
let current_tax_amount = frm.events.get_current_tax_amount(frm, tax);
|
||||||
|
|
||||||
|
// Adjust divisional loss to the last item
|
||||||
|
if (tax.charge_type == "Actual") {
|
||||||
|
actual_tax_dict[tax.idx] -= current_tax_amount;
|
||||||
|
if (i == frm.doc["taxes"].length - 1) {
|
||||||
|
current_tax_amount += actual_tax_dict[tax.idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tax.tax_amount = current_tax_amount;
|
||||||
|
tax.base_tax_amount = tax.tax_amount * frm.doc.source_exchange_rate;
|
||||||
|
current_tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
|
||||||
|
|
||||||
|
if(i==0) {
|
||||||
|
tax.total = flt(frm.doc.paid_amount_after_tax + current_tax_amount, precision("total", tax));
|
||||||
|
} else {
|
||||||
|
tax.total = flt(frm.doc["taxes"][i-1].total + current_tax_amount, precision("total", tax));
|
||||||
|
}
|
||||||
|
|
||||||
|
tax.base_total = tax.total * frm.doc.source_exchange_rate;
|
||||||
|
frm.doc.total_taxes_and_charges += current_tax_amount;
|
||||||
|
frm.doc.base_total_taxes_and_charges += current_tax_amount * frm.doc.source_exchange_rate;
|
||||||
|
|
||||||
|
frm.refresh_field('taxes');
|
||||||
|
frm.refresh_field('total_taxes_and_charges');
|
||||||
|
frm.refresh_field('base_total_taxes_and_charges');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get_current_tax_amount: function(frm, tax) {
|
||||||
|
let tax_rate = tax.rate;
|
||||||
|
let current_tax_amount = 0.0;
|
||||||
|
|
||||||
|
// To set row_id by default as previous row.
|
||||||
|
if(["On Previous Row Amount", "On Previous Row Total"].includes(tax.charge_type)) {
|
||||||
|
if (tax.idx === 1) {
|
||||||
|
frappe.throw(
|
||||||
|
__("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tax.charge_type == "Actual") {
|
||||||
|
current_tax_amount = flt(tax.tax_amount, precision("tax_amount", tax))
|
||||||
|
} else if(tax.charge_type == "On Paid Amount") {
|
||||||
|
current_tax_amount = flt((tax_rate / 100.0) * frm.doc.paid_amount_after_tax);
|
||||||
|
} else if(tax.charge_type == "On Previous Row Amount") {
|
||||||
|
current_tax_amount = flt((tax_rate / 100.0) *
|
||||||
|
frm.doc["taxes"][cint(tax.row_id) - 1].tax_amount);
|
||||||
|
|
||||||
|
} else if(tax.charge_type == "On Previous Row Total") {
|
||||||
|
current_tax_amount = flt((tax_rate / 100.0) *
|
||||||
|
frm.doc["taxes"][cint(tax.row_id) - 1].total);
|
||||||
|
}
|
||||||
|
|
||||||
|
return current_tax_amount;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -1049,6 +1337,38 @@ frappe.ui.form.on('Payment Entry Reference', {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
frappe.ui.form.on('Advance Taxes and Charges', {
|
||||||
|
rate: function(frm) {
|
||||||
|
frm.events.apply_taxes(frm);
|
||||||
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
tax_amount : function(frm) {
|
||||||
|
frm.events.apply_taxes(frm);
|
||||||
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
row_id: function(frm) {
|
||||||
|
frm.events.apply_taxes(frm);
|
||||||
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
taxes_remove: function(frm) {
|
||||||
|
frm.events.apply_taxes(frm);
|
||||||
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
included_in_paid_amount: function(frm) {
|
||||||
|
frm.events.apply_taxes(frm);
|
||||||
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
charge_type: function(frm) {
|
||||||
|
frm.events.apply_taxes(frm);
|
||||||
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
frappe.ui.form.on('Payment Entry Deduction', {
|
frappe.ui.form.on('Payment Entry Deduction', {
|
||||||
amount: function(frm) {
|
amount: function(frm) {
|
||||||
frm.events.set_unallocated_amount(frm);
|
frm.events.set_unallocated_amount(frm);
|
||||||
|
|||||||
@@ -35,12 +35,16 @@
|
|||||||
"paid_to_account_balance",
|
"paid_to_account_balance",
|
||||||
"payment_amounts_section",
|
"payment_amounts_section",
|
||||||
"paid_amount",
|
"paid_amount",
|
||||||
|
"paid_amount_after_tax",
|
||||||
"source_exchange_rate",
|
"source_exchange_rate",
|
||||||
"base_paid_amount",
|
"base_paid_amount",
|
||||||
|
"base_paid_amount_after_tax",
|
||||||
"column_break_21",
|
"column_break_21",
|
||||||
"received_amount",
|
"received_amount",
|
||||||
|
"received_amount_after_tax",
|
||||||
"target_exchange_rate",
|
"target_exchange_rate",
|
||||||
"base_received_amount",
|
"base_received_amount",
|
||||||
|
"base_received_amount_after_tax",
|
||||||
"section_break_14",
|
"section_break_14",
|
||||||
"get_outstanding_invoice",
|
"get_outstanding_invoice",
|
||||||
"references",
|
"references",
|
||||||
@@ -52,6 +56,17 @@
|
|||||||
"unallocated_amount",
|
"unallocated_amount",
|
||||||
"difference_amount",
|
"difference_amount",
|
||||||
"write_off_difference_amount",
|
"write_off_difference_amount",
|
||||||
|
"taxes_and_charges_section",
|
||||||
|
"purchase_taxes_and_charges_template",
|
||||||
|
"sales_taxes_and_charges_template",
|
||||||
|
"advance_tax_account",
|
||||||
|
"column_break_55",
|
||||||
|
"apply_tax_withholding_amount",
|
||||||
|
"tax_withholding_category",
|
||||||
|
"section_break_56",
|
||||||
|
"taxes",
|
||||||
|
"base_total_taxes_and_charges",
|
||||||
|
"total_taxes_and_charges",
|
||||||
"deductions_or_loss_section",
|
"deductions_or_loss_section",
|
||||||
"deductions",
|
"deductions",
|
||||||
"transaction_references",
|
"transaction_references",
|
||||||
@@ -320,6 +335,7 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "doc.received_amount",
|
||||||
"fieldname": "base_received_amount",
|
"fieldname": "base_received_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Received Amount (Company Currency)",
|
"label": "Received Amount (Company Currency)",
|
||||||
@@ -584,12 +600,119 @@
|
|||||||
"fieldname": "custom_remarks",
|
"fieldname": "custom_remarks",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Custom Remarks"
|
"label": "Custom Remarks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.apply_tax_withholding_amount",
|
||||||
|
"fieldname": "tax_withholding_category",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Tax Withholding Category",
|
||||||
|
"mandatory_depends_on": "eval:doc.apply_tax_withholding_amount",
|
||||||
|
"options": "Tax Withholding Category"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.party_type == 'Supplier'",
|
||||||
|
"fieldname": "apply_tax_withholding_amount",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Apply Tax Withholding Amount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "taxes_and_charges_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Taxes and Charges"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.party_type == 'Supplier'",
|
||||||
|
"fieldname": "purchase_taxes_and_charges_template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Taxes and Charges Template",
|
||||||
|
"options": "Purchase Taxes and Charges Template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.party_type == 'Customer'",
|
||||||
|
"fieldname": "sales_taxes_and_charges_template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Taxes and Charges Template",
|
||||||
|
"options": "Sales Taxes and Charges Template"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.party_type == 'Supplier' || doc.party_type == 'Customer'",
|
||||||
|
"fieldname": "taxes",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Advance Taxes and Charges",
|
||||||
|
"options": "Advance Taxes and Charges"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_total_taxes_and_charges",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Taxes and Charges (Company Currency)",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total_taxes_and_charges",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Taxes and Charges",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "paid_amount_after_tax",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Paid Amount After Tax",
|
||||||
|
"options": "paid_from_account_currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_paid_amount_after_tax",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Paid Amount After Tax (Company Currency)",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_55",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_56",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"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'",
|
||||||
|
"fieldname": "received_amount_after_tax",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Received Amount After Tax",
|
||||||
|
"options": "paid_to_account_currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "doc.received_amount",
|
||||||
|
"fieldname": "base_received_amount_after_tax",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Received Amount After Tax (Company Currency)",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-08 13:05:16.958866",
|
"modified": "2021-07-09 08:58:15.008761",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry",
|
"name": "Payment Entry",
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe, erpnext, json
|
import frappe, erpnext, json
|
||||||
from frappe import _, scrub, ValidationError
|
from frappe import _, scrub, ValidationError, throw
|
||||||
from frappe.utils import flt, comma_or, nowdate, getdate
|
from frappe.utils import flt, comma_or, nowdate, getdate, cint
|
||||||
from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on
|
from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on
|
||||||
from erpnext.accounts.party import get_party_account
|
from erpnext.accounts.party import get_party_account
|
||||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
||||||
@@ -15,9 +15,11 @@ from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amo
|
|||||||
from erpnext.accounts.doctype.bank_account.bank_account import get_party_bank_account, get_bank_account_details
|
from erpnext.accounts.doctype.bank_account.bank_account import get_party_bank_account, get_bank_account_details
|
||||||
from erpnext.controllers.accounts_controller import AccountsController, get_supplier_block_status
|
from erpnext.controllers.accounts_controller import AccountsController, get_supplier_block_status
|
||||||
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting
|
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting
|
||||||
|
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
|
||||||
from six import string_types, iteritems
|
from six import string_types, iteritems
|
||||||
|
|
||||||
|
from erpnext.controllers.accounts_controller import validate_taxes_and_charges
|
||||||
|
|
||||||
class InvalidPaymentEntry(ValidationError):
|
class InvalidPaymentEntry(ValidationError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -52,6 +54,8 @@ class PaymentEntry(AccountsController):
|
|||||||
self.set_exchange_rate()
|
self.set_exchange_rate()
|
||||||
self.validate_mandatory()
|
self.validate_mandatory()
|
||||||
self.validate_reference_documents()
|
self.validate_reference_documents()
|
||||||
|
self.set_tax_withholding()
|
||||||
|
self.apply_taxes()
|
||||||
self.set_amounts()
|
self.set_amounts()
|
||||||
self.clear_unallocated_reference_document_rows()
|
self.clear_unallocated_reference_document_rows()
|
||||||
self.validate_payment_against_negative_invoice()
|
self.validate_payment_against_negative_invoice()
|
||||||
@@ -65,7 +69,6 @@ class PaymentEntry(AccountsController):
|
|||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.setup_party_account_field()
|
|
||||||
if self.difference_amount:
|
if self.difference_amount:
|
||||||
frappe.throw(_("Difference Amount must be zero"))
|
frappe.throw(_("Difference Amount must be zero"))
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
@@ -78,7 +81,6 @@ class PaymentEntry(AccountsController):
|
|||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
|
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
|
||||||
self.setup_party_account_field()
|
|
||||||
self.make_gl_entries(cancel=1)
|
self.make_gl_entries(cancel=1)
|
||||||
self.update_outstanding_amounts()
|
self.update_outstanding_amounts()
|
||||||
self.update_advance_paid()
|
self.update_advance_paid()
|
||||||
@@ -122,6 +124,11 @@ class PaymentEntry(AccountsController):
|
|||||||
if flt(d.allocated_amount) > flt(d.outstanding_amount):
|
if flt(d.allocated_amount) > flt(d.outstanding_amount):
|
||||||
frappe.throw(_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx))
|
frappe.throw(_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx))
|
||||||
|
|
||||||
|
# Check for negative outstanding invoices as well
|
||||||
|
if flt(d.allocated_amount) < 0:
|
||||||
|
if flt(d.allocated_amount) < flt(d.outstanding_amount):
|
||||||
|
frappe.throw(_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx))
|
||||||
|
|
||||||
def delink_advance_entry_references(self):
|
def delink_advance_entry_references(self):
|
||||||
for reference in self.references:
|
for reference in self.references:
|
||||||
if reference.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
if reference.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||||
@@ -176,8 +183,15 @@ class PaymentEntry(AccountsController):
|
|||||||
d.reference_name, self.party_account_currency)
|
d.reference_name, self.party_account_currency)
|
||||||
|
|
||||||
for field, value in iteritems(ref_details):
|
for field, value in iteritems(ref_details):
|
||||||
|
if d.exchange_gain_loss:
|
||||||
|
# for cases where gain/loss is booked into invoice
|
||||||
|
# exchange_gain_loss is calculated from invoice & populated
|
||||||
|
# and row.exchange_rate is already set to payment entry's exchange rate
|
||||||
|
# refer -> `update_reference_in_payment_entry()` in utils.py
|
||||||
|
continue
|
||||||
|
|
||||||
if field == 'exchange_rate' or not d.get(field) or force:
|
if field == 'exchange_rate' or not d.get(field) or force:
|
||||||
d.set(field, value)
|
d.db_set(field, value)
|
||||||
|
|
||||||
def validate_payment_type(self):
|
def validate_payment_type(self):
|
||||||
if self.payment_type not in ("Receive", "Pay", "Internal Transfer"):
|
if self.payment_type not in ("Receive", "Pay", "Internal Transfer"):
|
||||||
@@ -307,7 +321,6 @@ class PaymentEntry(AccountsController):
|
|||||||
+ "<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")
|
||||||
|
|
||||||
|
|
||||||
def validate_journal_entry(self):
|
def validate_journal_entry(self):
|
||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
if d.allocated_amount and d.reference_doctype == "Journal Entry":
|
if d.allocated_amount and d.reference_doctype == "Journal Entry":
|
||||||
@@ -386,12 +399,104 @@ class PaymentEntry(AccountsController):
|
|||||||
else:
|
else:
|
||||||
self.status = 'Draft'
|
self.status = 'Draft'
|
||||||
|
|
||||||
|
self.db_set('status', self.status, update_modified = True)
|
||||||
|
|
||||||
|
def set_tax_withholding(self):
|
||||||
|
if not self.party_type == 'Supplier':
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.apply_tax_withholding_amount:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.advance_tax_account:
|
||||||
|
frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction"))
|
||||||
|
|
||||||
|
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
|
||||||
|
args = frappe._dict({
|
||||||
|
'company': self.company,
|
||||||
|
'doctype': 'Purchase Invoice',
|
||||||
|
'supplier': self.party,
|
||||||
|
'posting_date': self.posting_date,
|
||||||
|
'net_total': net_total
|
||||||
|
})
|
||||||
|
|
||||||
|
tax_withholding_details = get_party_tax_withholding_details(args, self.tax_withholding_category)
|
||||||
|
|
||||||
|
if not tax_withholding_details:
|
||||||
|
return
|
||||||
|
|
||||||
|
tax_withholding_details.update({
|
||||||
|
'add_deduct_tax': 'Add',
|
||||||
|
'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company)
|
||||||
|
})
|
||||||
|
|
||||||
|
accounts = []
|
||||||
|
for d in self.taxes:
|
||||||
|
if d.account_head == tax_withholding_details.get("account_head"):
|
||||||
|
|
||||||
|
# Preserve user updated included in paid amount
|
||||||
|
if d.included_in_paid_amount:
|
||||||
|
tax_withholding_details.update({'included_in_paid_amount': d.included_in_paid_amount})
|
||||||
|
|
||||||
|
d.update(tax_withholding_details)
|
||||||
|
accounts.append(d.account_head)
|
||||||
|
|
||||||
|
if not accounts or tax_withholding_details.get("account_head") not in accounts:
|
||||||
|
self.append("taxes", tax_withholding_details)
|
||||||
|
|
||||||
|
to_remove = [d for d in self.taxes
|
||||||
|
if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")]
|
||||||
|
|
||||||
|
for d in to_remove:
|
||||||
|
self.remove(d)
|
||||||
|
|
||||||
|
def apply_taxes(self):
|
||||||
|
self.initialize_taxes()
|
||||||
|
self.determine_exclusive_rate()
|
||||||
|
self.calculate_taxes()
|
||||||
|
|
||||||
def set_amounts(self):
|
def set_amounts(self):
|
||||||
|
self.set_received_amount()
|
||||||
self.set_amounts_in_company_currency()
|
self.set_amounts_in_company_currency()
|
||||||
|
self.set_amounts_after_tax()
|
||||||
self.set_total_allocated_amount()
|
self.set_total_allocated_amount()
|
||||||
self.set_unallocated_amount()
|
self.set_unallocated_amount()
|
||||||
self.set_difference_amount()
|
self.set_difference_amount()
|
||||||
|
|
||||||
|
def set_received_amount(self):
|
||||||
|
self.base_received_amount = self.base_paid_amount
|
||||||
|
|
||||||
|
def set_amounts_after_tax(self):
|
||||||
|
applicable_tax = 0
|
||||||
|
base_applicable_tax = 0
|
||||||
|
for tax in self.get('taxes'):
|
||||||
|
if not tax.included_in_paid_amount:
|
||||||
|
amount = -1 * tax.tax_amount if tax.add_deduct_tax == 'Deduct' else tax.tax_amount
|
||||||
|
base_amount = -1 * tax.base_tax_amount if tax.add_deduct_tax == 'Deduct' else tax.base_tax_amount
|
||||||
|
|
||||||
|
applicable_tax += amount
|
||||||
|
base_applicable_tax += base_amount
|
||||||
|
|
||||||
|
self.paid_amount_after_tax = flt(flt(self.paid_amount) + flt(applicable_tax),
|
||||||
|
self.precision("paid_amount_after_tax"))
|
||||||
|
self.base_paid_amount_after_tax = flt(flt(self.paid_amount_after_tax) * flt(self.source_exchange_rate),
|
||||||
|
self.precision("base_paid_amount_after_tax"))
|
||||||
|
|
||||||
|
self.received_amount_after_tax = flt(flt(self.received_amount) + flt(applicable_tax),
|
||||||
|
self.precision("paid_amount_after_tax"))
|
||||||
|
self.base_received_amount_after_tax = flt(flt(self.received_amount_after_tax) * flt(self.target_exchange_rate),
|
||||||
|
self.precision("base_paid_amount_after_tax"))
|
||||||
|
|
||||||
def set_amounts_in_company_currency(self):
|
def set_amounts_in_company_currency(self):
|
||||||
self.base_paid_amount, self.base_received_amount, self.difference_amount = 0, 0, 0
|
self.base_paid_amount, self.base_received_amount, self.difference_amount = 0, 0, 0
|
||||||
if self.paid_amount:
|
if self.paid_amount:
|
||||||
@@ -420,16 +525,19 @@ class PaymentEntry(AccountsController):
|
|||||||
self.unallocated_amount = 0
|
self.unallocated_amount = 0
|
||||||
if self.party:
|
if self.party:
|
||||||
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
|
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
|
||||||
|
included_taxes = self.get_included_taxes()
|
||||||
if self.payment_type == "Receive" \
|
if self.payment_type == "Receive" \
|
||||||
and self.base_total_allocated_amount < self.base_received_amount + total_deductions \
|
and self.base_total_allocated_amount < self.base_received_amount + total_deductions \
|
||||||
and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate):
|
and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate):
|
||||||
self.unallocated_amount = (self.base_received_amount + total_deductions -
|
self.unallocated_amount = (self.received_amount + total_deductions -
|
||||||
self.base_total_allocated_amount) / self.source_exchange_rate
|
self.base_total_allocated_amount) / self.source_exchange_rate
|
||||||
|
self.unallocated_amount -= included_taxes
|
||||||
elif self.payment_type == "Pay" \
|
elif self.payment_type == "Pay" \
|
||||||
and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \
|
and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \
|
||||||
and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate):
|
and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate):
|
||||||
self.unallocated_amount = (self.base_paid_amount - (total_deductions +
|
self.unallocated_amount = (self.base_paid_amount - (total_deductions +
|
||||||
self.base_total_allocated_amount)) / self.target_exchange_rate
|
self.base_total_allocated_amount)) / self.target_exchange_rate
|
||||||
|
self.unallocated_amount -= included_taxes
|
||||||
|
|
||||||
def set_difference_amount(self):
|
def set_difference_amount(self):
|
||||||
base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate)
|
base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate)
|
||||||
@@ -445,10 +553,22 @@ class PaymentEntry(AccountsController):
|
|||||||
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
|
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
|
||||||
|
|
||||||
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
|
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
|
||||||
|
included_taxes = self.get_included_taxes()
|
||||||
|
|
||||||
self.difference_amount = flt(self.difference_amount - total_deductions,
|
self.difference_amount = flt(self.difference_amount - total_deductions - included_taxes,
|
||||||
self.precision("difference_amount"))
|
self.precision("difference_amount"))
|
||||||
|
|
||||||
|
def get_included_taxes(self):
|
||||||
|
included_taxes = 0
|
||||||
|
for tax in self.get('taxes'):
|
||||||
|
if tax.included_in_paid_amount:
|
||||||
|
if tax.add_deduct_tax == 'Add':
|
||||||
|
included_taxes += tax.base_tax_amount
|
||||||
|
else:
|
||||||
|
included_taxes -= tax.base_tax_amount
|
||||||
|
|
||||||
|
return included_taxes
|
||||||
|
|
||||||
# Paid amount is auto allocated in the reference document by default.
|
# Paid amount is auto allocated in the reference document by default.
|
||||||
# Clear the reference document which doesn't have allocated amount on validate so that form can be loaded fast
|
# Clear the reference document which doesn't have allocated amount on validate so that form can be loaded fast
|
||||||
def clear_unallocated_reference_document_rows(self):
|
def clear_unallocated_reference_document_rows(self):
|
||||||
@@ -532,6 +652,7 @@ class PaymentEntry(AccountsController):
|
|||||||
self.add_party_gl_entries(gl_entries)
|
self.add_party_gl_entries(gl_entries)
|
||||||
self.add_bank_gl_entries(gl_entries)
|
self.add_bank_gl_entries(gl_entries)
|
||||||
self.add_deductions_gl_entries(gl_entries)
|
self.add_deductions_gl_entries(gl_entries)
|
||||||
|
self.add_tax_gl_entries(gl_entries)
|
||||||
|
|
||||||
make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj)
|
make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj)
|
||||||
|
|
||||||
@@ -571,8 +692,8 @@ class PaymentEntry(AccountsController):
|
|||||||
gl_entries.append(gle)
|
gl_entries.append(gle)
|
||||||
|
|
||||||
if self.unallocated_amount:
|
if self.unallocated_amount:
|
||||||
base_unallocated_amount = base_unallocated_amount = self.unallocated_amount * \
|
exchange_rate = self.get_exchange_rate()
|
||||||
(self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate)
|
base_unallocated_amount = (self.unallocated_amount * exchange_rate)
|
||||||
|
|
||||||
gle = party_gl_dict.copy()
|
gle = party_gl_dict.copy()
|
||||||
|
|
||||||
@@ -607,6 +728,51 @@ class PaymentEntry(AccountsController):
|
|||||||
}, item=self)
|
}, item=self)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def add_tax_gl_entries(self, gl_entries):
|
||||||
|
for d in self.get('taxes'):
|
||||||
|
account_currency = get_account_currency(d.account_head)
|
||||||
|
if account_currency != self.company_currency:
|
||||||
|
frappe.throw(_("Currency for {0} must be {1}").format(d.account_head, self.company_currency))
|
||||||
|
|
||||||
|
if self.payment_type in ('Pay', 'Internal Transfer'):
|
||||||
|
dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit"
|
||||||
|
against = self.party or self.paid_from
|
||||||
|
elif self.payment_type == 'Receive':
|
||||||
|
dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit"
|
||||||
|
against = self.party or self.paid_to
|
||||||
|
|
||||||
|
payment_or_advance_account = self.get_party_account_for_taxes()
|
||||||
|
tax_amount = d.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(
|
||||||
|
self.get_gl_dict({
|
||||||
|
"account": d.account_head,
|
||||||
|
"against": against,
|
||||||
|
dr_or_cr: tax_amount,
|
||||||
|
dr_or_cr + "_in_account_currency": base_tax_amount
|
||||||
|
if account_currency==self.company_currency
|
||||||
|
else d.tax_amount,
|
||||||
|
"cost_center": d.cost_center
|
||||||
|
}, account_currency, item=d))
|
||||||
|
|
||||||
|
#Intentionally use -1 to get net values in party account
|
||||||
|
if not d.included_in_paid_amount or self.advance_tax_account:
|
||||||
|
gl_entries.append(
|
||||||
|
self.get_gl_dict({
|
||||||
|
"account": payment_or_advance_account,
|
||||||
|
"against": against,
|
||||||
|
dr_or_cr: -1 * tax_amount,
|
||||||
|
dr_or_cr + "_in_account_currency": -1 * base_tax_amount
|
||||||
|
if account_currency==self.company_currency
|
||||||
|
else d.tax_amount,
|
||||||
|
"cost_center": self.cost_center,
|
||||||
|
}, account_currency, item=d))
|
||||||
|
|
||||||
def add_deductions_gl_entries(self, gl_entries):
|
def add_deductions_gl_entries(self, gl_entries):
|
||||||
for d in self.get("deductions"):
|
for d in self.get("deductions"):
|
||||||
if d.amount:
|
if d.amount:
|
||||||
@@ -625,6 +791,14 @@ class PaymentEntry(AccountsController):
|
|||||||
}, item=d)
|
}, item=d)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_party_account_for_taxes(self):
|
||||||
|
if self.advance_tax_account:
|
||||||
|
return self.advance_tax_account
|
||||||
|
elif self.payment_type == 'Receive':
|
||||||
|
return self.paid_to
|
||||||
|
elif self.payment_type in ('Pay', 'Internal Transfer'):
|
||||||
|
return self.paid_from
|
||||||
|
|
||||||
def update_advance_paid(self):
|
def update_advance_paid(self):
|
||||||
if self.payment_type in ("Receive", "Pay") and self.party:
|
if self.payment_type in ("Receive", "Pay") and self.party:
|
||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
@@ -668,9 +842,149 @@ class PaymentEntry(AccountsController):
|
|||||||
if account_details:
|
if account_details:
|
||||||
row.update(account_details)
|
row.update(account_details)
|
||||||
|
|
||||||
|
if not row.get('amount'):
|
||||||
|
# if no difference amount
|
||||||
|
return
|
||||||
|
|
||||||
self.append('deductions', row)
|
self.append('deductions', row)
|
||||||
self.set_unallocated_amount()
|
self.set_unallocated_amount()
|
||||||
|
|
||||||
|
def get_exchange_rate(self):
|
||||||
|
return self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate
|
||||||
|
|
||||||
|
def initialize_taxes(self):
|
||||||
|
for tax in self.get("taxes"):
|
||||||
|
validate_taxes_and_charges(tax)
|
||||||
|
validate_inclusive_tax(tax, self)
|
||||||
|
|
||||||
|
tax_fields = ["total", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
|
||||||
|
|
||||||
|
if tax.charge_type != "Actual":
|
||||||
|
tax_fields.append("tax_amount")
|
||||||
|
|
||||||
|
for fieldname in tax_fields:
|
||||||
|
tax.set(fieldname, 0.0)
|
||||||
|
|
||||||
|
self.paid_amount_after_tax = self.paid_amount
|
||||||
|
|
||||||
|
def determine_exclusive_rate(self):
|
||||||
|
if not any((cint(tax.included_in_paid_amount) for tax in self.get("taxes"))):
|
||||||
|
return
|
||||||
|
|
||||||
|
cumulated_tax_fraction = 0
|
||||||
|
for i, tax in enumerate(self.get("taxes")):
|
||||||
|
tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax)
|
||||||
|
if i==0:
|
||||||
|
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
|
||||||
|
else:
|
||||||
|
tax.grand_total_fraction_for_current_item = \
|
||||||
|
self.get("taxes")[i-1].grand_total_fraction_for_current_item \
|
||||||
|
+ tax.tax_fraction_for_current_item
|
||||||
|
|
||||||
|
cumulated_tax_fraction += tax.tax_fraction_for_current_item
|
||||||
|
|
||||||
|
self.paid_amount_after_tax = flt(self.paid_amount/(1+cumulated_tax_fraction))
|
||||||
|
|
||||||
|
def calculate_taxes(self):
|
||||||
|
self.total_taxes_and_charges = 0.0
|
||||||
|
self.base_total_taxes_and_charges = 0.0
|
||||||
|
|
||||||
|
actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
|
||||||
|
for tax in self.get("taxes") if tax.charge_type == "Actual"])
|
||||||
|
|
||||||
|
for i, tax in enumerate(self.get('taxes')):
|
||||||
|
current_tax_amount = self.get_current_tax_amount(tax)
|
||||||
|
|
||||||
|
if tax.charge_type == "Actual":
|
||||||
|
actual_tax_dict[tax.idx] -= current_tax_amount
|
||||||
|
if i == len(self.get("taxes")) - 1:
|
||||||
|
current_tax_amount += actual_tax_dict[tax.idx]
|
||||||
|
|
||||||
|
tax.tax_amount = current_tax_amount
|
||||||
|
tax.base_tax_amount = tax.tax_amount * self.source_exchange_rate
|
||||||
|
|
||||||
|
if tax.add_deduct_tax == "Deduct":
|
||||||
|
current_tax_amount *= -1.0
|
||||||
|
else:
|
||||||
|
current_tax_amount *= 1.0
|
||||||
|
|
||||||
|
if i == 0:
|
||||||
|
tax.total = flt(self.paid_amount_after_tax + current_tax_amount, self.precision("total", tax))
|
||||||
|
else:
|
||||||
|
tax.total = flt(self.get('taxes')[i-1].total + current_tax_amount, self.precision("total", tax))
|
||||||
|
|
||||||
|
tax.base_total = tax.total * self.source_exchange_rate
|
||||||
|
|
||||||
|
self.total_taxes_and_charges += current_tax_amount
|
||||||
|
self.base_total_taxes_and_charges += current_tax_amount * self.source_exchange_rate
|
||||||
|
|
||||||
|
if self.get('taxes'):
|
||||||
|
self.paid_amount_after_tax = self.get('taxes')[-1].base_total
|
||||||
|
|
||||||
|
def get_current_tax_amount(self, tax):
|
||||||
|
tax_rate = tax.rate
|
||||||
|
|
||||||
|
# To set row_id by default as previous row.
|
||||||
|
if tax.charge_type in ["On Previous Row Amount", "On Previous Row Total"]:
|
||||||
|
if tax.idx == 1:
|
||||||
|
frappe.throw(_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"))
|
||||||
|
|
||||||
|
if not tax.row_id:
|
||||||
|
tax.row_id = tax.idx - 1
|
||||||
|
|
||||||
|
if tax.charge_type == "Actual":
|
||||||
|
current_tax_amount = flt(tax.tax_amount, self.precision("tax_amount", tax))
|
||||||
|
elif tax.charge_type == "On Paid Amount":
|
||||||
|
current_tax_amount = (tax_rate / 100.0) * self.paid_amount_after_tax
|
||||||
|
elif tax.charge_type == "On Previous Row Amount":
|
||||||
|
current_tax_amount = (tax_rate / 100.0) * \
|
||||||
|
self.get('taxes')[cint(tax.row_id) - 1].tax_amount
|
||||||
|
|
||||||
|
elif tax.charge_type == "On Previous Row Total":
|
||||||
|
current_tax_amount = (tax_rate / 100.0) * \
|
||||||
|
self.get('taxes')[cint(tax.row_id) - 1].total
|
||||||
|
|
||||||
|
return current_tax_amount
|
||||||
|
|
||||||
|
def get_current_tax_fraction(self, tax):
|
||||||
|
current_tax_fraction = 0
|
||||||
|
|
||||||
|
if cint(tax.included_in_paid_amount):
|
||||||
|
tax_rate = tax.rate
|
||||||
|
|
||||||
|
if tax.charge_type == "On Paid Amount":
|
||||||
|
current_tax_fraction = tax_rate / 100.0
|
||||||
|
elif tax.charge_type == "On Previous Row Amount":
|
||||||
|
current_tax_fraction = (tax_rate / 100.0) * \
|
||||||
|
self.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item
|
||||||
|
elif tax.charge_type == "On Previous Row Total":
|
||||||
|
current_tax_fraction = (tax_rate / 100.0) * \
|
||||||
|
self.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
|
||||||
|
|
||||||
|
if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
|
||||||
|
current_tax_fraction *= -1.0
|
||||||
|
|
||||||
|
return current_tax_fraction
|
||||||
|
|
||||||
|
def validate_inclusive_tax(tax, doc):
|
||||||
|
def _on_previous_row_error(row_range):
|
||||||
|
throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, row_range))
|
||||||
|
|
||||||
|
if cint(getattr(tax, "included_in_paid_amount", None)):
|
||||||
|
if tax.charge_type == "Actual":
|
||||||
|
# inclusive tax cannot be of type Actual
|
||||||
|
throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate or Paid Amount").format(tax.idx))
|
||||||
|
elif tax.charge_type == "On Previous Row Amount" and \
|
||||||
|
not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_paid_amount):
|
||||||
|
# referred row should also be inclusive
|
||||||
|
_on_previous_row_error(tax.row_id)
|
||||||
|
elif tax.charge_type == "On Previous Row Total" and \
|
||||||
|
not all([cint(t.included_in_paid_amount for t in doc.get("taxes")[:cint(tax.row_id) - 1])]):
|
||||||
|
# all rows about the referred tax should be inclusive
|
||||||
|
_on_previous_row_error("1 - %d" % (cint(tax.row_id),))
|
||||||
|
elif tax.get("category") == "Valuation":
|
||||||
|
frappe.throw(_("Valuation type charges can not be marked as Inclusive"))
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_outstanding_reference_documents(args):
|
def get_outstanding_reference_documents(args):
|
||||||
|
|
||||||
@@ -989,6 +1303,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
|||||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||||
elif reference_doctype == "Donation":
|
elif reference_doctype == "Donation":
|
||||||
total_amount = ref_doc.get("amount")
|
total_amount = ref_doc.get("amount")
|
||||||
|
outstanding_amount = total_amount
|
||||||
exchange_rate = 1
|
exchange_rate = 1
|
||||||
elif reference_doctype == "Dunning":
|
elif reference_doctype == "Dunning":
|
||||||
total_amount = ref_doc.get("dunning_amount")
|
total_amount = ref_doc.get("dunning_amount")
|
||||||
@@ -1045,9 +1360,9 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
|||||||
|
|
||||||
return frappe._dict({
|
return frappe._dict({
|
||||||
"due_date": ref_doc.get("due_date"),
|
"due_date": ref_doc.get("due_date"),
|
||||||
"total_amount": total_amount,
|
"total_amount": flt(total_amount),
|
||||||
"outstanding_amount": outstanding_amount,
|
"outstanding_amount": flt(outstanding_amount),
|
||||||
"exchange_rate": exchange_rate,
|
"exchange_rate": flt(exchange_rate),
|
||||||
"bill_no": bill_no
|
"bill_no": bill_no
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1235,6 +1550,13 @@ 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):
|
||||||
@@ -1353,6 +1675,7 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta
|
|||||||
paid_amount = received_amount * doc.get('conversion_rate', 1)
|
paid_amount = received_amount * doc.get('conversion_rate', 1)
|
||||||
if dt == "Employee Advance":
|
if dt == "Employee Advance":
|
||||||
paid_amount = received_amount * doc.get('exchange_rate', 1)
|
paid_amount = received_amount * doc.get('exchange_rate', 1)
|
||||||
|
|
||||||
return paid_amount, received_amount
|
return paid_amount, received_amount
|
||||||
|
|
||||||
def apply_early_payment_discount(paid_amount, received_amount, doc):
|
def apply_early_payment_discount(paid_amount, received_amount, doc):
|
||||||
|
|||||||
@@ -589,9 +589,9 @@ class TestPaymentEntry(unittest.TestCase):
|
|||||||
party_account_balance = get_balance_on(account=pe.paid_from, cost_center=pe.cost_center)
|
party_account_balance = get_balance_on(account=pe.paid_from, cost_center=pe.cost_center)
|
||||||
|
|
||||||
self.assertEqual(pe.cost_center, si.cost_center)
|
self.assertEqual(pe.cost_center, si.cost_center)
|
||||||
self.assertEqual(expected_account_balance, account_balance)
|
self.assertEqual(flt(expected_account_balance), account_balance)
|
||||||
self.assertEqual(expected_party_balance, party_balance)
|
self.assertEqual(flt(expected_party_balance), party_balance)
|
||||||
self.assertEqual(expected_party_account_balance, party_account_balance)
|
self.assertEqual(flt(expected_party_account_balance), party_account_balance)
|
||||||
|
|
||||||
def create_payment_terms_template():
|
def create_payment_terms_template():
|
||||||
|
|
||||||
|
|||||||
@@ -1,140 +1,70 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_events_in_timeline": 0,
|
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2016-06-15 15:56:30.815503",
|
"creation": "2016-06-15 15:56:30.815503",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "",
|
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
|
"field_order": [
|
||||||
|
"account",
|
||||||
|
"cost_center",
|
||||||
|
"amount",
|
||||||
|
"column_break_2",
|
||||||
|
"description"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "account",
|
"fieldname": "account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Account",
|
"label": "Account",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Account",
|
"options": "Account",
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"show_days": 1,
|
||||||
"set_only_once": 0,
|
"show_seconds": 1
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "cost_center",
|
"fieldname": "cost_center",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Cost Center",
|
"label": "Cost Center",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"options": "Cost Center",
|
"options": "Cost Center",
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"show_days": 1,
|
||||||
"set_only_once": 0,
|
"show_seconds": 1
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "amount",
|
"fieldname": "amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Amount",
|
"label": "Amount",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
"search_index": 0,
|
"show_days": 1,
|
||||||
"set_only_once": 0,
|
"show_seconds": 1
|
||||||
"translatable": 0,
|
},
|
||||||
"unique": 0
|
{
|
||||||
|
"fieldname": "column_break_2",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "description",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Description",
|
||||||
|
"show_days": 1,
|
||||||
|
"show_seconds": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
"index_web_pages_for_search": 1,
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 0,
|
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"links": [],
|
||||||
"modified": "2019-01-07 16:52:07.040146",
|
"modified": "2020-09-12 20:38:08.110674",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry Deduction",
|
"name": "Payment Entry Deduction",
|
||||||
"name_case": "",
|
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC"
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
"total_amount",
|
"total_amount",
|
||||||
"outstanding_amount",
|
"outstanding_amount",
|
||||||
"allocated_amount",
|
"allocated_amount",
|
||||||
"exchange_rate"
|
"exchange_rate",
|
||||||
|
"exchange_gain_loss"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -90,12 +91,19 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Payment Term",
|
"label": "Payment Term",
|
||||||
"options": "Payment Term"
|
"options": "Payment Term"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "exchange_gain_loss",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Exchange Gain/Loss",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-02-10 11:25:47.144392",
|
"modified": "2021-04-21 13:30:11.605388",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry Reference",
|
"name": "Payment Entry Reference",
|
||||||
|
|||||||
@@ -492,7 +492,6 @@ def update_payment_req_status(doc, method):
|
|||||||
status = 'Requested'
|
status = 'Requested'
|
||||||
|
|
||||||
pay_req_doc.db_set('status', status)
|
pay_req_doc.db_set('status', status)
|
||||||
frappe.db.commit()
|
|
||||||
|
|
||||||
def get_dummy_message(doc):
|
def get_dummy_message(doc):
|
||||||
return frappe.render_template("""{% if doc.contact_person -%}
|
return frappe.render_template("""{% if doc.contact_person -%}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class PaymentTermsTemplate(Document):
|
|||||||
def check_duplicate_terms(self):
|
def check_duplicate_terms(self):
|
||||||
terms = []
|
terms = []
|
||||||
for term in self.terms:
|
for term in self.terms:
|
||||||
term_info = (term.credit_days, term.credit_months, term.due_date_based_on)
|
term_info = (term.payment_term, term.credit_days, term.credit_months, term.due_date_based_on)
|
||||||
if term_info in terms:
|
if term_info in terms:
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_('The Payment Term at row {0} is possibly a duplicate.').format(term.idx),
|
_('The Payment Term at row {0} is possibly a duplicate.').format(term.idx),
|
||||||
|
|||||||
@@ -1,297 +1,102 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_guest_to_view": 0,
|
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"autoname": "ACC-PCV-.YYYY.-.#####",
|
"autoname": "ACC-PCV-.YYYY.-.#####",
|
||||||
"beta": 0,
|
|
||||||
"creation": "2013-01-10 16:34:07",
|
"creation": "2013-01-10 16:34:07",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 0,
|
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"transaction_date",
|
||||||
|
"posting_date",
|
||||||
|
"fiscal_year",
|
||||||
|
"amended_from",
|
||||||
|
"company",
|
||||||
|
"cost_center_wise_pnl",
|
||||||
|
"column_break1",
|
||||||
|
"closing_account_head",
|
||||||
|
"remarks"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "transaction_date",
|
"fieldname": "transaction_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Transaction Date",
|
"label": "Transaction Date",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "transaction_date",
|
"oldfieldname": "transaction_date",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date"
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "posting_date",
|
"fieldname": "posting_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Posting Date",
|
"label": "Posting Date",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "posting_date",
|
"oldfieldname": "posting_date",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "fiscal_year",
|
"fieldname": "fiscal_year",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Closing Fiscal Year",
|
"label": "Closing Fiscal Year",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "fiscal_year",
|
"oldfieldname": "fiscal_year",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "Fiscal Year",
|
"options": "Fiscal Year",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "amended_from",
|
"fieldname": "amended_from",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 1,
|
"ignore_user_permissions": 1,
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Amended From",
|
"label": "Amended From",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "amended_from",
|
"oldfieldname": "amended_from",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"options": "Period Closing Voucher",
|
"options": "Period Closing Voucher",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "company",
|
"oldfieldname": "company",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "Company",
|
"options": "Company",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "column_break1",
|
"fieldname": "column_break1",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break",
|
||||||
"hidden": 0,
|
"oldfieldtype": "Column Break"
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldtype": "Column Break",
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"description": "The account head under Liability or Equity, in which Profit/Loss will be booked",
|
"description": "The account head under Liability or Equity, in which Profit/Loss will be booked",
|
||||||
"fieldname": "closing_account_head",
|
"fieldname": "closing_account_head",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Closing Account Head",
|
"label": "Closing Account Head",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "closing_account_head",
|
"oldfieldname": "closing_account_head",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "Account",
|
"options": "Account",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"remember_last_selected_value": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 1,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_bulk_edit": 0,
|
|
||||||
"allow_in_quick_entry": 0,
|
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "remarks",
|
"fieldname": "remarks",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_global_search": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"in_standard_filter": 0,
|
|
||||||
"label": "Remarks",
|
"label": "Remarks",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"oldfieldname": "remarks",
|
"oldfieldname": "remarks",
|
||||||
"oldfieldtype": "Small Text",
|
"oldfieldtype": "Small Text",
|
||||||
"permlevel": 0,
|
"reqd": 1
|
||||||
"print_hide": 0,
|
},
|
||||||
"print_hide_if_no_value": 0,
|
{
|
||||||
"read_only": 0,
|
"default": "0",
|
||||||
"remember_last_selected_value": 0,
|
"fieldname": "cost_center_wise_pnl",
|
||||||
"report_hide": 0,
|
"fieldtype": "Check",
|
||||||
"reqd": 1,
|
"label": "Book Cost Center Wise Profit/Loss"
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"translatable": 0,
|
|
||||||
"unique": 0
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"has_web_view": 0,
|
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_view": 0,
|
|
||||||
"in_create": 0,
|
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"issingle": 0,
|
"links": [],
|
||||||
"istable": 0,
|
"modified": "2021-05-20 15:27:37.210458",
|
||||||
"max_attachments": 0,
|
|
||||||
"modified": "2020-09-18 17:26:09.703215",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Period Closing Voucher",
|
"name": "Period Closing Voucher",
|
||||||
@@ -303,15 +108,10 @@
|
|||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 1,
|
"submit": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
@@ -322,29 +122,17 @@
|
|||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
"export": 0,
|
|
||||||
"if_owner": 0,
|
|
||||||
"import": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Accounts Manager",
|
"role": "Accounts Manager",
|
||||||
"set_user_permissions": 0,
|
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 1,
|
"submit": 1,
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"read_only_onload": 0,
|
|
||||||
"search_fields": "posting_date, fiscal_year",
|
"search_fields": "posting_date, fiscal_year",
|
||||||
"show_name_in_global_search": 0,
|
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"title_field": "closing_account_head",
|
"title_field": "closing_account_head"
|
||||||
"track_changes": 0,
|
|
||||||
"track_seen": 0,
|
|
||||||
"track_views": 0
|
|
||||||
}
|
}
|
||||||
@@ -52,62 +52,95 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
def make_gl_entries(self):
|
def make_gl_entries(self):
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
net_pl_balance = 0
|
net_pl_balance = 0
|
||||||
dimension_fields = ['t1.cost_center']
|
|
||||||
|
|
||||||
accounting_dimensions = get_accounting_dimensions()
|
pl_accounts = self.get_pl_balances()
|
||||||
for dimension in accounting_dimensions:
|
|
||||||
dimension_fields.append('t1.{0}'.format(dimension))
|
|
||||||
|
|
||||||
dimension_filters, default_dimensions = get_dimensions()
|
|
||||||
|
|
||||||
pl_accounts = self.get_pl_balances(dimension_fields)
|
|
||||||
|
|
||||||
for acc in pl_accounts:
|
for acc in pl_accounts:
|
||||||
if flt(acc.balance_in_company_currency):
|
if flt(acc.bal_in_company_currency):
|
||||||
gl_entries.append(self.get_gl_dict({
|
gl_entries.append(self.get_gl_dict({
|
||||||
"account": acc.account,
|
"account": acc.account,
|
||||||
"cost_center": acc.cost_center,
|
"cost_center": acc.cost_center,
|
||||||
"account_currency": acc.account_currency,
|
"account_currency": acc.account_currency,
|
||||||
"debit_in_account_currency": abs(flt(acc.balance_in_account_currency)) \
|
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) < 0 else 0,
|
||||||
if flt(acc.balance_in_account_currency) < 0 else 0,
|
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
|
||||||
"debit": abs(flt(acc.balance_in_company_currency)) \
|
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,
|
||||||
if flt(acc.balance_in_company_currency) < 0 else 0,
|
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0
|
||||||
"credit_in_account_currency": abs(flt(acc.balance_in_account_currency)) \
|
|
||||||
if flt(acc.balance_in_account_currency) > 0 else 0,
|
|
||||||
"credit": abs(flt(acc.balance_in_company_currency)) \
|
|
||||||
if flt(acc.balance_in_company_currency) > 0 else 0
|
|
||||||
}, item=acc))
|
}, item=acc))
|
||||||
|
|
||||||
net_pl_balance += flt(acc.balance_in_company_currency)
|
net_pl_balance += flt(acc.bal_in_company_currency)
|
||||||
|
|
||||||
if net_pl_balance:
|
if net_pl_balance:
|
||||||
cost_center = frappe.db.get_value("Company", self.company, "cost_center")
|
if self.cost_center_wise_pnl:
|
||||||
gl_entry = self.get_gl_dict({
|
costcenter_wise_gl_entries = self.get_costcenter_wise_pnl_gl_entries(pl_accounts)
|
||||||
"account": self.closing_account_head,
|
gl_entries += costcenter_wise_gl_entries
|
||||||
"debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0,
|
else:
|
||||||
"debit": abs(net_pl_balance) if net_pl_balance > 0 else 0,
|
gl_entry = self.get_pnl_gl_entry(net_pl_balance)
|
||||||
"credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0,
|
gl_entries.append(gl_entry)
|
||||||
"credit": abs(net_pl_balance) if net_pl_balance < 0 else 0,
|
|
||||||
"cost_center": cost_center
|
|
||||||
})
|
|
||||||
|
|
||||||
for dimension in accounting_dimensions:
|
|
||||||
gl_entry.update({
|
|
||||||
dimension: default_dimensions.get(self.company, {}).get(dimension)
|
|
||||||
})
|
|
||||||
|
|
||||||
gl_entries.append(gl_entry)
|
|
||||||
|
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
make_gl_entries(gl_entries)
|
make_gl_entries(gl_entries)
|
||||||
|
|
||||||
def get_pl_balances(self, dimension_fields):
|
def get_pnl_gl_entry(self, net_pl_balance):
|
||||||
"""Get balance for Profit and Loss accounts, only including valid transactions (not cancelled)"""
|
cost_center = frappe.db.get_value("Company", self.company, "cost_center")
|
||||||
|
gl_entry = self.get_gl_dict({
|
||||||
|
"account": self.closing_account_head,
|
||||||
|
"debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0,
|
||||||
|
"debit": abs(net_pl_balance) if net_pl_balance > 0 else 0,
|
||||||
|
"credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0,
|
||||||
|
"credit": abs(net_pl_balance) if net_pl_balance < 0 else 0,
|
||||||
|
"cost_center": cost_center
|
||||||
|
})
|
||||||
|
|
||||||
|
self.update_default_dimensions(gl_entry)
|
||||||
|
|
||||||
|
return gl_entry
|
||||||
|
|
||||||
|
def get_costcenter_wise_pnl_gl_entries(self, pl_accounts):
|
||||||
|
company_cost_center = frappe.db.get_value("Company", self.company, "cost_center")
|
||||||
|
gl_entries = []
|
||||||
|
|
||||||
|
for acc in pl_accounts:
|
||||||
|
if flt(acc.bal_in_company_currency):
|
||||||
|
gl_entry = self.get_gl_dict({
|
||||||
|
"account": self.closing_account_head,
|
||||||
|
"cost_center": acc.cost_center or company_cost_center,
|
||||||
|
"account_currency": acc.account_currency,
|
||||||
|
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,
|
||||||
|
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
|
||||||
|
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) < 0 else 0,
|
||||||
|
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0
|
||||||
|
}, item=acc)
|
||||||
|
|
||||||
|
self.update_default_dimensions(gl_entry)
|
||||||
|
|
||||||
|
gl_entries.append(gl_entry)
|
||||||
|
|
||||||
|
return gl_entries
|
||||||
|
|
||||||
|
def update_default_dimensions(self, gl_entry):
|
||||||
|
if not self.accounting_dimensions:
|
||||||
|
self.accounting_dimensions = get_accounting_dimensions()
|
||||||
|
|
||||||
|
_, default_dimensions = get_dimensions()
|
||||||
|
for dimension in self.accounting_dimensions:
|
||||||
|
gl_entry.update({
|
||||||
|
dimension: default_dimensions.get(self.company, {}).get(dimension)
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_pl_balances(self):
|
||||||
|
"""Get balance for dimension-wise pl accounts"""
|
||||||
|
|
||||||
|
dimension_fields = ['t1.cost_center']
|
||||||
|
|
||||||
|
self.accounting_dimensions = get_accounting_dimensions()
|
||||||
|
for dimension in self.accounting_dimensions:
|
||||||
|
dimension_fields.append('t1.{0}'.format(dimension))
|
||||||
|
|
||||||
return frappe.db.sql("""
|
return frappe.db.sql("""
|
||||||
select
|
select
|
||||||
t1.account, t2.account_currency, {dimension_fields},
|
t1.account, t2.account_currency, {dimension_fields},
|
||||||
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as balance_in_account_currency,
|
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
|
||||||
sum(t1.debit) - sum(t1.credit) as balance_in_company_currency
|
sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
|
||||||
from `tabGL Entry` t1, `tabAccount` t2
|
from `tabGL Entry` t1, `tabAccount` t2
|
||||||
where t1.is_cancelled = 0 and t1.account = t2.name and t2.report_type = 'Profit and Loss'
|
where t1.is_cancelled = 0 and t1.account = t2.name and t2.report_type = 'Profit and Loss'
|
||||||
and t2.docstatus < 2 and t2.company = %s
|
and t2.docstatus < 2 and t2.company = %s
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import frappe
|
|||||||
from frappe.utils import flt, today
|
from frappe.utils import flt, today
|
||||||
from erpnext.accounts.utils import get_fiscal_year, now
|
from erpnext.accounts.utils import get_fiscal_year, now
|
||||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
|
||||||
class TestPeriodClosingVoucher(unittest.TestCase):
|
class TestPeriodClosingVoucher(unittest.TestCase):
|
||||||
def test_closing_entry(self):
|
def test_closing_entry(self):
|
||||||
@@ -65,6 +66,58 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
|||||||
self.assertEqual(gle_for_random_expense_account[0].amount_in_account_currency,
|
self.assertEqual(gle_for_random_expense_account[0].amount_in_account_currency,
|
||||||
-1*random_expense_account[0].balance_in_account_currency)
|
-1*random_expense_account[0].balance_in_account_currency)
|
||||||
|
|
||||||
|
def test_cost_center_wise_posting(self):
|
||||||
|
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
|
||||||
|
|
||||||
|
company = create_company()
|
||||||
|
surplus_account = create_account()
|
||||||
|
|
||||||
|
cost_center1 = create_cost_center("Test Cost Center 1")
|
||||||
|
cost_center2 = create_cost_center("Test Cost Center 2")
|
||||||
|
|
||||||
|
create_sales_invoice(
|
||||||
|
company=company,
|
||||||
|
cost_center=cost_center1,
|
||||||
|
income_account="Sales - TPC",
|
||||||
|
expense_account="Cost of Goods Sold - TPC",
|
||||||
|
rate=400,
|
||||||
|
debit_to="Debtors - TPC"
|
||||||
|
)
|
||||||
|
create_sales_invoice(
|
||||||
|
company=company,
|
||||||
|
cost_center=cost_center2,
|
||||||
|
income_account="Sales - TPC",
|
||||||
|
expense_account="Cost of Goods Sold - TPC",
|
||||||
|
rate=200,
|
||||||
|
debit_to="Debtors - TPC"
|
||||||
|
)
|
||||||
|
|
||||||
|
pcv = frappe.get_doc({
|
||||||
|
"transaction_date": today(),
|
||||||
|
"posting_date": today(),
|
||||||
|
"fiscal_year": get_fiscal_year(today())[0],
|
||||||
|
"company": "Test PCV Company",
|
||||||
|
"cost_center_wise_pnl": 1,
|
||||||
|
"closing_account_head": surplus_account,
|
||||||
|
"remarks": "Test",
|
||||||
|
"doctype": "Period Closing Voucher"
|
||||||
|
})
|
||||||
|
pcv.insert()
|
||||||
|
pcv.submit()
|
||||||
|
|
||||||
|
expected_gle = (
|
||||||
|
('Sales - TPC', 200.0, 0.0, cost_center2),
|
||||||
|
(surplus_account, 0.0, 200.0, cost_center2),
|
||||||
|
('Sales - TPC', 400.0, 0.0, cost_center1),
|
||||||
|
(surplus_account, 0.0, 400.0, cost_center1)
|
||||||
|
)
|
||||||
|
|
||||||
|
pcv_gle = frappe.db.sql("""
|
||||||
|
select account, debit, credit, cost_center from `tabGL Entry` where voucher_no=%s
|
||||||
|
""", (pcv.name))
|
||||||
|
|
||||||
|
self.assertTrue(pcv_gle, expected_gle)
|
||||||
|
|
||||||
def make_period_closing_voucher(self):
|
def make_period_closing_voucher(self):
|
||||||
pcv = frappe.get_doc({
|
pcv = frappe.get_doc({
|
||||||
"doctype": "Period Closing Voucher",
|
"doctype": "Period Closing Voucher",
|
||||||
@@ -80,6 +133,38 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
|||||||
|
|
||||||
return pcv
|
return pcv
|
||||||
|
|
||||||
|
def create_company():
|
||||||
|
company = frappe.get_doc({
|
||||||
|
'doctype': 'Company',
|
||||||
|
'company_name': "Test PCV Company",
|
||||||
|
'country': 'United States',
|
||||||
|
'default_currency': 'USD'
|
||||||
|
})
|
||||||
|
company.insert(ignore_if_duplicate = True)
|
||||||
|
return company.name
|
||||||
|
|
||||||
|
def create_account():
|
||||||
|
account = frappe.get_doc({
|
||||||
|
"account_name": "Reserve and Surplus",
|
||||||
|
"is_group": 0,
|
||||||
|
"company": "Test PCV Company",
|
||||||
|
"root_type": "Liability",
|
||||||
|
"report_type": "Balance Sheet",
|
||||||
|
"account_currency": "USD",
|
||||||
|
"parent_account": "Current Liabilities - TPC",
|
||||||
|
"doctype": "Account"
|
||||||
|
}).insert(ignore_if_duplicate = True)
|
||||||
|
return account.name
|
||||||
|
|
||||||
|
def create_cost_center(cc_name):
|
||||||
|
costcenter = frappe.get_doc({
|
||||||
|
"company": "Test PCV Company",
|
||||||
|
"cost_center_name": cc_name,
|
||||||
|
"doctype": "Cost Center",
|
||||||
|
"parent_cost_center": "Test PCV Company - TPC"
|
||||||
|
})
|
||||||
|
costcenter.insert(ignore_if_duplicate = True)
|
||||||
|
return costcenter.name
|
||||||
|
|
||||||
test_dependencies = ["Customer", "Cost Center"]
|
test_dependencies = ["Customer", "Cost Center"]
|
||||||
test_records = frappe.get_test_records("Period Closing Voucher")
|
test_records = frappe.get_test_records("Period Closing Voucher")
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ frappe.ui.form.on('POS Closing Entry', {
|
|||||||
frm.set_value("taxes", []);
|
frm.set_value("taxes", []);
|
||||||
|
|
||||||
for (let row of frm.doc.payment_reconciliation) {
|
for (let row of frm.doc.payment_reconciliation) {
|
||||||
row.expected_amount = 0;
|
row.expected_amount = row.opening_amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let row of frm.doc.pos_transactions) {
|
for (let row of frm.doc.pos_transactions) {
|
||||||
@@ -154,6 +154,9 @@ function add_to_pos_transaction(d, frm) {
|
|||||||
function refresh_payments(d, frm) {
|
function refresh_payments(d, frm) {
|
||||||
d.payments.forEach(p => {
|
d.payments.forEach(p => {
|
||||||
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment);
|
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment);
|
||||||
|
if (p.account == d.account_for_change_amount) {
|
||||||
|
p.amount -= flt(d.change_amount);
|
||||||
|
}
|
||||||
if (payment) {
|
if (payment) {
|
||||||
payment.expected_amount += flt(p.amount);
|
payment.expected_amount += flt(p.amount);
|
||||||
payment.difference = payment.closing_amount - payment.expected_amount;
|
payment.difference = payment.closing_amount - payment.expected_amount;
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
return
|
return
|
||||||
|
|
||||||
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
||||||
|
|
||||||
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
|
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
|
||||||
if flt(available_stock) <= 0:
|
if flt(available_stock) <= 0:
|
||||||
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.')
|
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.')
|
||||||
@@ -213,8 +214,9 @@ class POSInvoice(SalesInvoice):
|
|||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
|
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
|
||||||
if not is_stock_item:
|
if not is_stock_item:
|
||||||
frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ")
|
if not frappe.db.exists('Product Bundle', d.item_code):
|
||||||
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice.")
|
||||||
|
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
||||||
|
|
||||||
def validate_mode_of_payment(self):
|
def validate_mode_of_payment(self):
|
||||||
if len(self.payments) == 0:
|
if len(self.payments) == 0:
|
||||||
@@ -455,15 +457,36 @@ class POSInvoice(SalesInvoice):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_stock_availability(item_code, warehouse):
|
def get_stock_availability(item_code, warehouse):
|
||||||
|
if frappe.db.get_value('Item', item_code, 'is_stock_item'):
|
||||||
|
bin_qty = get_bin_qty(item_code, warehouse)
|
||||||
|
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
|
||||||
|
return bin_qty - pos_sales_qty
|
||||||
|
else:
|
||||||
|
if frappe.db.exists('Product Bundle', item_code):
|
||||||
|
return get_bundle_availability(item_code, warehouse)
|
||||||
|
|
||||||
|
def get_bundle_availability(bundle_item_code, warehouse):
|
||||||
|
product_bundle = frappe.get_doc('Product Bundle', bundle_item_code)
|
||||||
|
|
||||||
|
bundle_bin_qty = 1000000
|
||||||
|
for item in product_bundle.items:
|
||||||
|
item_bin_qty = get_bin_qty(item.item_code, warehouse)
|
||||||
|
item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse)
|
||||||
|
available_qty = item_bin_qty - item_pos_reserved_qty
|
||||||
|
|
||||||
|
max_available_bundles = available_qty / item.qty
|
||||||
|
if bundle_bin_qty > max_available_bundles:
|
||||||
|
bundle_bin_qty = max_available_bundles
|
||||||
|
|
||||||
|
pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse)
|
||||||
|
return bundle_bin_qty - pos_sales_qty
|
||||||
|
|
||||||
|
def get_bin_qty(item_code, warehouse):
|
||||||
bin_qty = frappe.db.sql("""select actual_qty from `tabBin`
|
bin_qty = frappe.db.sql("""select actual_qty from `tabBin`
|
||||||
where item_code = %s and warehouse = %s
|
where item_code = %s and warehouse = %s
|
||||||
limit 1""", (item_code, warehouse), as_dict=1)
|
limit 1""", (item_code, warehouse), as_dict=1)
|
||||||
|
|
||||||
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
|
return bin_qty[0].actual_qty or 0 if bin_qty else 0
|
||||||
|
|
||||||
bin_qty = bin_qty[0].actual_qty or 0 if bin_qty else 0
|
|
||||||
|
|
||||||
return bin_qty - pos_sales_qty
|
|
||||||
|
|
||||||
def get_pos_reserved_qty(item_code, warehouse):
|
def get_pos_reserved_qty(item_code, warehouse):
|
||||||
reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty
|
reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
|||||||
frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'});
|
frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'});
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
frappe.msgprint('No Records for these settings.')
|
frappe.msgprint(__('No Records for these settings.'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -33,7 +33,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
|||||||
type: 'GET',
|
type: 'GET',
|
||||||
success: function(result) {
|
success: function(result) {
|
||||||
if(jQuery.isEmptyObject(result)){
|
if(jQuery.isEmptyObject(result)){
|
||||||
frappe.msgprint('No Records for these settings.');
|
frappe.msgprint(__('No Records for these settings.'));
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
window.location = url;
|
window.location = url;
|
||||||
@@ -92,7 +92,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
|||||||
frm.refresh_field('customers');
|
frm.refresh_field('customers');
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
frappe.throw('No Customers found with selected options.');
|
frappe.throw(__('No Customers found with selected options.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,10 +207,9 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory):
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True):
|
def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True):
|
||||||
billing_email = frappe.db.sql("""
|
billing_email = frappe.db.sql("""
|
||||||
SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent \
|
SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent
|
||||||
WHERE l.link_doctype='Customer' and l.link_name='""" + customer_name + """' and \
|
WHERE l.link_doctype='Customer' and l.link_name=%s and c.is_billing_contact=1
|
||||||
c.is_billing_contact=1 \
|
order by c.creation desc""", customer_name)
|
||||||
order by c.creation desc""")
|
|
||||||
|
|
||||||
if len(billing_email) == 0 or (billing_email[0][0] is None):
|
if len(billing_email) == 0 or (billing_email[0][0] is None):
|
||||||
if billing_and_primary:
|
if billing_and_primary:
|
||||||
|
|||||||
@@ -27,10 +27,6 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
company() {
|
|
||||||
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
|
||||||
}
|
|
||||||
|
|
||||||
onload() {
|
onload() {
|
||||||
super.onload();
|
super.onload();
|
||||||
|
|
||||||
@@ -569,5 +565,9 @@ frappe.ui.form.on("Purchase Invoice", {
|
|||||||
frm: frm,
|
frm: frm,
|
||||||
freeze_message: __("Creating Purchase Receipt ...")
|
freeze_message: __("Creating Purchase Receipt ...")
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
|
|
||||||
|
company: function(frm) {
|
||||||
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -592,11 +592,12 @@
|
|||||||
"label": "Raw Materials Supplied"
|
"label": "Raw Materials Supplied"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "update_stock",
|
||||||
"fieldname": "supplied_items",
|
"fieldname": "supplied_items",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Supplied Items",
|
"label": "Supplied Items",
|
||||||
"options": "Purchase Receipt Item Supplied",
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"options": "Purchase Receipt Item Supplied"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_26",
|
"fieldname": "section_break_26",
|
||||||
@@ -837,6 +838,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:!doc.disable_rounded_total",
|
||||||
"fieldname": "base_rounding_adjustment",
|
"fieldname": "base_rounding_adjustment",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Rounding Adjustment (Company Currency)",
|
"label": "Rounding Adjustment (Company Currency)",
|
||||||
@@ -883,6 +885,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "eval:!doc.disable_rounded_total",
|
||||||
"fieldname": "rounding_adjustment",
|
"fieldname": "rounding_adjustment",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Rounding Adjustment",
|
"label": "Rounding Adjustment",
|
||||||
@@ -1380,7 +1383,7 @@
|
|||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-04-30 22:45:58.334107",
|
"modified": "2021-06-15 18:20:56.806195",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
|||||||
@@ -68,9 +68,6 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
super(PurchaseInvoice, self).validate()
|
super(PurchaseInvoice, self).validate()
|
||||||
|
|
||||||
# apply tax withholding only if checked and applicable
|
|
||||||
self.set_tax_withholding()
|
|
||||||
|
|
||||||
if not self.is_return:
|
if not self.is_return:
|
||||||
self.po_required()
|
self.po_required()
|
||||||
self.pr_required()
|
self.pr_required()
|
||||||
@@ -251,11 +248,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
if self.update_stock and (not item.from_warehouse):
|
if self.update_stock and (not item.from_warehouse):
|
||||||
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
|
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
|
||||||
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]))
|
msg = _("Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account").format(
|
||||||
msg += _("because account {} is not linked to warehouse {} ").format(frappe.bold(item.expense_account), frappe.bold(item.warehouse))
|
item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]), frappe.bold(item.expense_account), frappe.bold(item.warehouse))
|
||||||
msg += _("or it is not the default inventory account")
|
|
||||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
|
|
||||||
item.expense_account = warehouse_account[item.warehouse]["account"]
|
item.expense_account = warehouse_account[item.warehouse]["account"]
|
||||||
else:
|
else:
|
||||||
# check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not
|
# check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not
|
||||||
@@ -266,8 +261,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
if negative_expense_booked_in_pr:
|
if negative_expense_booked_in_pr:
|
||||||
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
||||||
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
|
msg = _("Row {0}: Expense Head changed to {1} because expense is booked against this account in Purchase Receipt {2}").format(
|
||||||
msg += _("because expense is booked against this account in Purchase Receipt {}").format(frappe.bold(item.purchase_receipt))
|
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.purchase_receipt))
|
||||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
|
|
||||||
item.expense_account = stock_not_billed_account
|
item.expense_account = stock_not_billed_account
|
||||||
@@ -275,8 +270,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
# If no purchase receipt present then book expense in 'Stock Received But Not Billed'
|
# If no purchase receipt present then book expense in 'Stock Received But Not Billed'
|
||||||
# This is done in cases when Purchase Invoice is created before Purchase Receipt
|
# This is done in cases when Purchase Invoice is created before Purchase Receipt
|
||||||
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
||||||
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
|
msg = _("Row {0}: Expense Head changed to {1} as no Purchase Receipt is created against Item {2}.").format(
|
||||||
msg += _("as no Purchase Receipt is created against Item {}. ").format(frappe.bold(item.item_code))
|
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.item_code))
|
||||||
|
msg += "<br>"
|
||||||
msg += _("This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice")
|
msg += _("This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice")
|
||||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||||
|
|
||||||
@@ -308,8 +304,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if not d.purchase_order:
|
if not d.purchase_order:
|
||||||
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
|
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
|
||||||
msg += "<br><br>"
|
msg += "<br><br>"
|
||||||
msg += _("To submit the invoice without purchase order please set {} ").format(frappe.bold(_('Purchase Order Required')))
|
msg += _("To submit the invoice without purchase order please set {0} as {1} in {2}").format(
|
||||||
msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
frappe.bold(_('Purchase Order Required')), frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
||||||
throw(msg, title=_("Mandatory Purchase Order"))
|
throw(msg, title=_("Mandatory Purchase Order"))
|
||||||
|
|
||||||
def pr_required(self):
|
def pr_required(self):
|
||||||
@@ -323,8 +319,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if not d.purchase_receipt and d.item_code in stock_items:
|
if not d.purchase_receipt and d.item_code in stock_items:
|
||||||
msg = _("Purchase Receipt Required for item {}").format(frappe.bold(d.item_code))
|
msg = _("Purchase Receipt Required for item {}").format(frappe.bold(d.item_code))
|
||||||
msg += "<br><br>"
|
msg += "<br><br>"
|
||||||
msg += _("To submit the invoice without purchase receipt please set {} ").format(frappe.bold(_('Purchase Receipt Required')))
|
msg += _("To submit the invoice without purchase receipt please set {0} as {1} in {2}").format(
|
||||||
msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
frappe.bold(_('Purchase Receipt Required')), frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
||||||
throw(msg, title=_("Mandatory Purchase Receipt"))
|
throw(msg, title=_("Mandatory Purchase Receipt"))
|
||||||
|
|
||||||
def validate_write_off_account(self):
|
def validate_write_off_account(self):
|
||||||
@@ -404,6 +400,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
# because updating ordered qty in bin depends upon updated ordered qty in PO
|
# because updating ordered qty in bin depends upon updated ordered qty in PO
|
||||||
if self.update_stock == 1:
|
if self.update_stock == 1:
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
|
self.set_consumed_qty_in_po()
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
|
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
|
||||||
update_serial_nos_after_submit(self, "items")
|
update_serial_nos_after_submit(self, "items")
|
||||||
|
|
||||||
@@ -454,8 +451,11 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.get_asset_gl_entry(gl_entries)
|
self.get_asset_gl_entry(gl_entries)
|
||||||
|
|
||||||
self.make_tax_gl_entries(gl_entries)
|
self.make_tax_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)
|
||||||
@@ -518,6 +518,8 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if d.category in ('Valuation', 'Total and Valuation')
|
if d.category in ('Valuation', 'Total and Valuation')
|
||||||
and flt(d.base_tax_amount_after_discount_amount)]
|
and flt(d.base_tax_amount_after_discount_amount)]
|
||||||
|
|
||||||
|
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
|
||||||
|
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if flt(item.base_net_amount):
|
if flt(item.base_net_amount):
|
||||||
account_currency = get_account_currency(item.expense_account)
|
account_currency = get_account_currency(item.expense_account)
|
||||||
@@ -635,6 +637,34 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"project": item.project or self.project
|
"project": item.project or self.project
|
||||||
}, account_currency, item=item))
|
}, account_currency, item=item))
|
||||||
|
|
||||||
|
# check if the exchange rate has changed
|
||||||
|
if item.get('purchase_receipt'):
|
||||||
|
if exchange_rate_map[item.purchase_receipt] and \
|
||||||
|
self.conversion_rate != exchange_rate_map[item.purchase_receipt] and \
|
||||||
|
item.net_rate == net_rate_map[item.pr_detail]:
|
||||||
|
|
||||||
|
discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * \
|
||||||
|
(exchange_rate_map[item.purchase_receipt] - self.conversion_rate)
|
||||||
|
|
||||||
|
gl_entries.append(
|
||||||
|
self.get_gl_dict({
|
||||||
|
"account": expense_account,
|
||||||
|
"against": self.supplier,
|
||||||
|
"debit": discrepancy_caused_by_exchange_rate_difference,
|
||||||
|
"cost_center": item.cost_center,
|
||||||
|
"project": item.project or self.project
|
||||||
|
}, account_currency, item=item)
|
||||||
|
)
|
||||||
|
gl_entries.append(
|
||||||
|
self.get_gl_dict({
|
||||||
|
"account": self.get_company_default("exchange_gain_loss_account"),
|
||||||
|
"against": self.supplier,
|
||||||
|
"credit": discrepancy_caused_by_exchange_rate_difference,
|
||||||
|
"cost_center": item.cost_center,
|
||||||
|
"project": item.project or self.project
|
||||||
|
}, account_currency, item=item)
|
||||||
|
)
|
||||||
|
|
||||||
# If asset is bought through this document and not linked to PR
|
# If asset is bought through this document and not linked to PR
|
||||||
if self.update_stock and item.landed_cost_voucher_amount:
|
if self.update_stock and item.landed_cost_voucher_amount:
|
||||||
expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation")
|
expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation")
|
||||||
@@ -1000,6 +1030,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if self.update_stock == 1:
|
if self.update_stock == 1:
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
self.delete_auto_created_batches()
|
self.delete_auto_created_batches()
|
||||||
|
self.set_consumed_qty_in_po()
|
||||||
|
|
||||||
self.make_gl_entries_on_cancel()
|
self.make_gl_entries_on_cancel()
|
||||||
|
|
||||||
@@ -1090,6 +1121,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
for d in self.taxes:
|
for d in self.taxes:
|
||||||
if d.account_head == tax_withholding_details.get("account_head"):
|
if d.account_head == tax_withholding_details.get("account_head"):
|
||||||
d.update(tax_withholding_details)
|
d.update(tax_withholding_details)
|
||||||
|
|
||||||
accounts.append(d.account_head)
|
accounts.append(d.account_head)
|
||||||
|
|
||||||
if not accounts or tax_withholding_details.get("account_head") not in accounts:
|
if not accounts or tax_withholding_details.get("account_head") not in accounts:
|
||||||
@@ -1140,6 +1172,36 @@ class PurchaseInvoice(BuyingController):
|
|||||||
if update:
|
if update:
|
||||||
self.db_set('status', self.status, update_modified = update_modified)
|
self.db_set('status', self.status, update_modified = update_modified)
|
||||||
|
|
||||||
|
# to get details of purchase invoice/receipt from which this doc was created for exchange rate difference handling
|
||||||
|
def get_purchase_document_details(doc):
|
||||||
|
if doc.doctype == 'Purchase Invoice':
|
||||||
|
doc_reference = 'purchase_receipt'
|
||||||
|
items_reference = 'pr_detail'
|
||||||
|
parent_doctype = 'Purchase Receipt'
|
||||||
|
child_doctype = 'Purchase Receipt Item'
|
||||||
|
else:
|
||||||
|
doc_reference = 'purchase_invoice'
|
||||||
|
items_reference = 'purchase_invoice_item'
|
||||||
|
parent_doctype = 'Purchase Invoice'
|
||||||
|
child_doctype = 'Purchase Invoice Item'
|
||||||
|
|
||||||
|
purchase_receipts_or_invoices = []
|
||||||
|
items = []
|
||||||
|
|
||||||
|
for item in doc.get('items'):
|
||||||
|
if item.get(doc_reference):
|
||||||
|
purchase_receipts_or_invoices.append(item.get(doc_reference))
|
||||||
|
if item.get(items_reference):
|
||||||
|
items.append(item.get(items_reference))
|
||||||
|
|
||||||
|
exchange_rate_map = frappe._dict(frappe.get_all(parent_doctype, filters={'name': ('in',
|
||||||
|
purchase_receipts_or_invoices)}, fields=['name', 'conversion_rate'], as_list=1))
|
||||||
|
|
||||||
|
net_rate_map = frappe._dict(frappe.get_all(child_doctype, filters={'name': ('in',
|
||||||
|
items)}, fields=['name', 'net_rate'], as_list=1))
|
||||||
|
|
||||||
|
return exchange_rate_map, net_rate_map
|
||||||
|
|
||||||
def get_list_context(context=None):
|
def get_list_context(context=None):
|
||||||
from erpnext.controllers.website_list_for_contact import get_list_context
|
from erpnext.controllers.website_list_for_contact import get_list_context
|
||||||
list_context = get_list_context(context)
|
list_context = get_list_context(context)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_tra
|
|||||||
from erpnext.projects.doctype.project.test_project import make_project
|
from erpnext.projects.doctype.project.test_project import make_project
|
||||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
|
from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
|
||||||
from erpnext.stock.doctype.item.test_item import create_item
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||||
|
|
||||||
test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"]
|
test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"]
|
||||||
test_ignore = ["Serial No"]
|
test_ignore = ["Serial No"]
|
||||||
@@ -229,6 +230,27 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(expected_values[gle.account][1], gle.debit)
|
self.assertEqual(expected_values[gle.account][1], gle.debit)
|
||||||
self.assertEqual(expected_values[gle.account][2], gle.credit)
|
self.assertEqual(expected_values[gle.account][2], gle.credit)
|
||||||
|
|
||||||
|
def test_purchase_invoice_with_exchange_rate_difference(self):
|
||||||
|
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as create_purchase_invoice
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse='Stores - TCP1',
|
||||||
|
currency = "USD", conversion_rate = 70)
|
||||||
|
|
||||||
|
pi = create_purchase_invoice(pr.name)
|
||||||
|
pi.conversion_rate = 80
|
||||||
|
|
||||||
|
pi.insert()
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
# Get exchnage gain and loss account
|
||||||
|
exchange_gain_loss_account = frappe.db.get_value('Company', pi.company, 'exchange_gain_loss_account')
|
||||||
|
|
||||||
|
# fetching the latest GL Entry with exchange gain and loss account account
|
||||||
|
amount = frappe.db.get_value('GL Entry', {'account': exchange_gain_loss_account, 'voucher_no': pi.name}, 'debit')
|
||||||
|
discrepancy_caused_by_exchange_rate_diff = abs(pi.items[0].base_net_amount - pr.items[0].base_net_amount)
|
||||||
|
|
||||||
|
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
|
||||||
|
|
||||||
def test_purchase_invoice_change_naming_series(self):
|
def test_purchase_invoice_change_naming_series(self):
|
||||||
pi = frappe.copy_doc(test_records[1])
|
pi = frappe.copy_doc(test_records[1])
|
||||||
pi.insert()
|
pi.insert()
|
||||||
@@ -620,8 +642,10 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(actual_qty_0, get_qty_after_transaction())
|
self.assertEqual(actual_qty_0, get_qty_after_transaction())
|
||||||
|
|
||||||
def test_subcontracting_via_purchase_invoice(self):
|
def test_subcontracting_via_purchase_invoice(self):
|
||||||
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import update_backflush_based_on
|
||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
|
||||||
|
update_backflush_based_on('BOM')
|
||||||
make_stock_entry(item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100)
|
make_stock_entry(item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100)
|
||||||
make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse 1 - _TC",
|
make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse 1 - _TC",
|
||||||
qty=100, basic_rate=100)
|
qty=100, basic_rate=100)
|
||||||
@@ -950,6 +974,217 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
acc_settings.submit_journal_entriessubmit_journal_entries = 0
|
acc_settings.submit_journal_entriessubmit_journal_entries = 0
|
||||||
acc_settings.save()
|
acc_settings.save()
|
||||||
|
|
||||||
|
def test_gain_loss_with_advance_entry(self):
|
||||||
|
unlink_enabled = frappe.db.get_value(
|
||||||
|
"Accounts Settings", "Accounts Settings",
|
||||||
|
"unlink_payment_on_cancel_of_invoice")
|
||||||
|
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Accounts Settings", "Accounts Settings",
|
||||||
|
"unlink_payment_on_cancel_of_invoice", 1)
|
||||||
|
|
||||||
|
original_account = frappe.db.get_value("Company", "_Test Company", "exchange_gain_loss_account")
|
||||||
|
frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", "Exchange Gain/Loss - _TC")
|
||||||
|
|
||||||
|
pay = frappe.get_doc({
|
||||||
|
'doctype': 'Payment Entry',
|
||||||
|
'company': '_Test Company',
|
||||||
|
'payment_type': 'Pay',
|
||||||
|
'party_type': 'Supplier',
|
||||||
|
'party': '_Test Supplier USD',
|
||||||
|
'paid_to': '_Test Payable USD - _TC',
|
||||||
|
'paid_from': 'Cash - _TC',
|
||||||
|
'paid_amount': 70000,
|
||||||
|
'target_exchange_rate': 70,
|
||||||
|
'received_amount': 1000,
|
||||||
|
})
|
||||||
|
pay.insert()
|
||||||
|
pay.submit()
|
||||||
|
|
||||||
|
pi = make_purchase_invoice(supplier='_Test Supplier USD', currency="USD",
|
||||||
|
conversion_rate=75, rate=500, do_not_save=1, qty=1)
|
||||||
|
pi.cost_center = "_Test Cost Center - _TC"
|
||||||
|
pi.advances = []
|
||||||
|
pi.append("advances", {
|
||||||
|
"reference_type": "Payment Entry",
|
||||||
|
"reference_name": pay.name,
|
||||||
|
"advance_amount": 1000,
|
||||||
|
"remarks": pay.remarks,
|
||||||
|
"allocated_amount": 500,
|
||||||
|
"ref_exchange_rate": 70
|
||||||
|
})
|
||||||
|
pi.save()
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
expected_gle = [
|
||||||
|
["_Test Account Cost for Goods Sold - _TC", 37500.0],
|
||||||
|
["_Test Payable USD - _TC", -40000.0],
|
||||||
|
["Exchange Gain/Loss - _TC", 2500.0]
|
||||||
|
]
|
||||||
|
|
||||||
|
gl_entries = frappe.db.sql("""
|
||||||
|
select account, sum(debit - credit) as balance from `tabGL Entry`
|
||||||
|
where voucher_no=%s
|
||||||
|
group by account
|
||||||
|
order by account asc""", (pi.name), as_dict=1)
|
||||||
|
|
||||||
|
for i, gle in enumerate(gl_entries):
|
||||||
|
self.assertEqual(expected_gle[i][0], gle.account)
|
||||||
|
self.assertEqual(expected_gle[i][1], gle.balance)
|
||||||
|
|
||||||
|
pi_2 = make_purchase_invoice(supplier='_Test Supplier USD', currency="USD",
|
||||||
|
conversion_rate=73, rate=500, do_not_save=1, qty=1)
|
||||||
|
pi_2.cost_center = "_Test Cost Center - _TC"
|
||||||
|
pi_2.advances = []
|
||||||
|
pi_2.append("advances", {
|
||||||
|
"reference_type": "Payment Entry",
|
||||||
|
"reference_name": pay.name,
|
||||||
|
"advance_amount": 500,
|
||||||
|
"remarks": pay.remarks,
|
||||||
|
"allocated_amount": 500,
|
||||||
|
"ref_exchange_rate": 70
|
||||||
|
})
|
||||||
|
pi_2.save()
|
||||||
|
pi_2.submit()
|
||||||
|
|
||||||
|
expected_gle = [
|
||||||
|
["_Test Account Cost for Goods Sold - _TC", 36500.0],
|
||||||
|
["_Test Payable USD - _TC", -38000.0],
|
||||||
|
["Exchange Gain/Loss - _TC", 1500.0]
|
||||||
|
]
|
||||||
|
|
||||||
|
gl_entries = frappe.db.sql("""
|
||||||
|
select account, sum(debit - credit) as balance from `tabGL Entry`
|
||||||
|
where voucher_no=%s
|
||||||
|
group by account order by account asc""", (pi_2.name), as_dict=1)
|
||||||
|
|
||||||
|
for i, gle in enumerate(gl_entries):
|
||||||
|
self.assertEqual(expected_gle[i][0], gle.account)
|
||||||
|
self.assertEqual(expected_gle[i][1], gle.balance)
|
||||||
|
|
||||||
|
expected_gle = [
|
||||||
|
["_Test Payable USD - _TC", 70000.0],
|
||||||
|
["Cash - _TC", -70000.0]
|
||||||
|
]
|
||||||
|
|
||||||
|
gl_entries = frappe.db.sql("""
|
||||||
|
select account, sum(debit - credit) as balance from `tabGL Entry`
|
||||||
|
where voucher_no=%s and is_cancelled=0
|
||||||
|
group by account order by account asc""", (pay.name), as_dict=1)
|
||||||
|
|
||||||
|
for i, gle in enumerate(gl_entries):
|
||||||
|
self.assertEqual(expected_gle[i][0], gle.account)
|
||||||
|
self.assertEqual(expected_gle[i][1], gle.balance)
|
||||||
|
|
||||||
|
pi.reload()
|
||||||
|
pi.cancel()
|
||||||
|
|
||||||
|
pi_2.reload()
|
||||||
|
pi_2.cancel()
|
||||||
|
|
||||||
|
pay.reload()
|
||||||
|
pay.cancel()
|
||||||
|
|
||||||
|
frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled)
|
||||||
|
frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account)
|
||||||
|
|
||||||
|
def test_purchase_invoice_advance_taxes(self):
|
||||||
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||||
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||||
|
from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice
|
||||||
|
|
||||||
|
# create a new supplier to test
|
||||||
|
supplier = create_supplier(supplier_name = '_Test TDS Advance Supplier',
|
||||||
|
tax_withholding_category = 'TDS - 194 - Dividends - Individual')
|
||||||
|
|
||||||
|
# Update tax withholding category with current fiscal year and rate details
|
||||||
|
update_tax_witholding_category('_Test Company', 'TDS Payable - _TC', nowdate())
|
||||||
|
|
||||||
|
# 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.apply_tds = 1
|
||||||
|
po.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
|
||||||
|
po.save()
|
||||||
|
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
|
||||||
|
payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name)
|
||||||
|
payment_entry.paid_from = 'Cash - _TC'
|
||||||
|
payment_entry.save()
|
||||||
|
payment_entry.submit()
|
||||||
|
|
||||||
|
# Check GLE for Payment Entry
|
||||||
|
expected_gle = [
|
||||||
|
['_Test Account Excise Duty - _TC', 3000, 0],
|
||||||
|
['Cash - _TC', 0, 27000],
|
||||||
|
['Creditors - _TC', 27000, 0],
|
||||||
|
['TDS Payable - _TC', 0, 3000],
|
||||||
|
]
|
||||||
|
|
||||||
|
gl_entries = frappe.db.sql("""select account, debit, credit
|
||||||
|
from `tabGL Entry`
|
||||||
|
where voucher_type='Payment Entry' and voucher_no=%s
|
||||||
|
order by account asc""", (payment_entry.name), as_dict=1)
|
||||||
|
|
||||||
|
for i, gle in enumerate(gl_entries):
|
||||||
|
self.assertEqual(expected_gle[i][0], gle.account)
|
||||||
|
self.assertEqual(expected_gle[i][1], gle.debit)
|
||||||
|
self.assertEqual(expected_gle[i][2], gle.credit)
|
||||||
|
|
||||||
|
# Create Purchase Invoice against Purchase Order
|
||||||
|
purchase_invoice = get_mapped_purchase_invoice(po.name)
|
||||||
|
purchase_invoice.allocate_advances_automatically = 1
|
||||||
|
purchase_invoice.items[0].item_code = '_Test Non Stock Item'
|
||||||
|
purchase_invoice.items[0].expense_account = '_Test Account Cost for Goods Sold - _TC'
|
||||||
|
purchase_invoice.save()
|
||||||
|
purchase_invoice.submit()
|
||||||
|
|
||||||
|
# Check GLE for Purchase Invoice
|
||||||
|
# Zero net effect on final TDS Payable on invoice
|
||||||
|
expected_gle = [
|
||||||
|
['_Test Account Cost for Goods Sold - _TC', 30000],
|
||||||
|
['_Test Account Excise Duty - _TC', -3000],
|
||||||
|
['Creditors - _TC', -27000],
|
||||||
|
['TDS Payable - _TC', 0]
|
||||||
|
]
|
||||||
|
|
||||||
|
gl_entries = frappe.db.sql("""select account, sum(debit - credit) as amount
|
||||||
|
from `tabGL Entry`
|
||||||
|
where voucher_type='Purchase Invoice' and voucher_no=%s
|
||||||
|
group by account
|
||||||
|
order by account asc""", (purchase_invoice.name), as_dict=1)
|
||||||
|
|
||||||
|
for i, gle in enumerate(gl_entries):
|
||||||
|
self.assertEqual(expected_gle[i][0], gle.account)
|
||||||
|
self.assertEqual(expected_gle[i][1], gle.amount)
|
||||||
|
|
||||||
|
def update_tax_witholding_category(company, account, date):
|
||||||
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
|
||||||
|
fiscal_year = get_fiscal_year(date=date, company=company)
|
||||||
|
|
||||||
|
if not frappe.db.get_value('Tax Withholding Rate',
|
||||||
|
{'parent': 'TDS - 194 - Dividends - Individual', 'fiscal_year': fiscal_year[0]}):
|
||||||
|
tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual')
|
||||||
|
tds_category.append('rates', {
|
||||||
|
'fiscal_year': fiscal_year[0],
|
||||||
|
'tax_withholding_rate': 10,
|
||||||
|
'single_threshold': 2500,
|
||||||
|
'cumulative_threshold': 0
|
||||||
|
})
|
||||||
|
tds_category.save()
|
||||||
|
|
||||||
|
if not frappe.db.get_value('Tax Withholding Account',
|
||||||
|
{'parent': 'TDS - 194 - Dividends - Individual', 'account': account}):
|
||||||
|
tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual')
|
||||||
|
tds_category.append('accounts', {
|
||||||
|
'company': company,
|
||||||
|
'account': account
|
||||||
|
})
|
||||||
|
tds_category.save()
|
||||||
|
|
||||||
def unlink_payment_on_cancel_of_invoice(enable=1):
|
def unlink_payment_on_cancel_of_invoice(enable=1):
|
||||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||||
|
|||||||
@@ -1,235 +1,127 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2013-03-08 15:36:46",
|
"creation": "2013-03-08 15:36:46",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Document",
|
"document_type": "Document",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"reference_type",
|
||||||
|
"reference_name",
|
||||||
|
"remarks",
|
||||||
|
"reference_row",
|
||||||
|
"col_break1",
|
||||||
|
"advance_amount",
|
||||||
|
"allocated_amount",
|
||||||
|
"exchange_gain_loss",
|
||||||
|
"ref_exchange_rate"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "reference_type",
|
"fieldname": "reference_type",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"label": "Reference Type",
|
"label": "Reference Type",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "journal_voucher",
|
"oldfieldname": "journal_voucher",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "DocType",
|
"options": "DocType",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "180px",
|
"print_width": "180px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "180px"
|
"width": "180px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 3,
|
"columns": 3,
|
||||||
"fieldname": "reference_name",
|
"fieldname": "reference_name",
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Reference Name",
|
"label": "Reference Name",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "reference_type",
|
"options": "reference_type",
|
||||||
"permlevel": 0,
|
"read_only": 1
|
||||||
"precision": "",
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 1,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 3,
|
"columns": 3,
|
||||||
"fieldname": "remarks",
|
"fieldname": "remarks",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Remarks",
|
"label": "Remarks",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "remarks",
|
"oldfieldname": "remarks",
|
||||||
"oldfieldtype": "Small Text",
|
"oldfieldtype": "Small Text",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "150px",
|
"print_width": "150px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "150px"
|
"width": "150px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "reference_row",
|
"fieldname": "reference_row",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"label": "Reference Row",
|
"label": "Reference Row",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "jv_detail_no",
|
"oldfieldname": "jv_detail_no",
|
||||||
"oldfieldtype": "Date",
|
"oldfieldtype": "Date",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "80px",
|
"print_width": "80px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "80px"
|
"width": "80px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "col_break1",
|
"fieldname": "col_break1",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break"
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
"columns": 2,
|
||||||
"fieldname": "advance_amount",
|
"fieldname": "advance_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Advance Amount",
|
"label": "Advance Amount",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "advance_amount",
|
"oldfieldname": "advance_amount",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "party_account_currency",
|
"options": "party_account_currency",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "100px",
|
"print_width": "100px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
"columns": 2,
|
||||||
"fieldname": "allocated_amount",
|
"fieldname": "allocated_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Allocated Amount",
|
"label": "Allocated Amount",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "allocated_amount",
|
"oldfieldname": "allocated_amount",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "party_account_currency",
|
"options": "party_account_currency",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "100px",
|
"print_width": "100px",
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "exchange_gain_loss",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Exchange Gain/Loss",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "ref_exchange_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Reference Exchange Rate",
|
||||||
|
"non_negative": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_view": 0,
|
"index_web_pages_for_search": 1,
|
||||||
"in_create": 0,
|
|
||||||
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"links": [],
|
||||||
"menu_index": 0,
|
"modified": "2021-04-20 16:26:53.820530",
|
||||||
"modified": "2016-08-26 02:30:54.407138",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Advance",
|
"name": "Purchase Invoice Advance",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
"sort_field": "modified",
|
||||||
"read_only_onload": 0,
|
"sort_order": "DESC"
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
||||||
@@ -272,7 +272,7 @@
|
|||||||
"fieldname": "rate",
|
"fieldname": "rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Rate ",
|
"label": "Rate",
|
||||||
"oldfieldname": "import_rate",
|
"oldfieldname": "import_rate",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "currency",
|
"options": "currency",
|
||||||
@@ -854,7 +854,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-30 09:02:39.256602",
|
"modified": "2021-06-16 19:43:51.099386",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Item",
|
"name": "Purchase Invoice Item",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"charge_type",
|
"charge_type",
|
||||||
"row_id",
|
"row_id",
|
||||||
"included_in_print_rate",
|
"included_in_print_rate",
|
||||||
|
"included_in_paid_amount",
|
||||||
"col_break1",
|
"col_break1",
|
||||||
"account_head",
|
"account_head",
|
||||||
"description",
|
"description",
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
"section_break_9",
|
"section_break_9",
|
||||||
|
"currency",
|
||||||
"tax_amount",
|
"tax_amount",
|
||||||
"tax_amount_after_discount_amount",
|
"tax_amount_after_discount_amount",
|
||||||
"total",
|
"total",
|
||||||
@@ -205,12 +207,28 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "account_head.account_currency",
|
||||||
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Account Currency",
|
||||||
|
"options": "Currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:['Purchase Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)",
|
||||||
|
"description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry",
|
||||||
|
"fieldname": "included_in_paid_amount",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Considered In Paid Amount"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-09-18 17:26:09.703215",
|
"modified": "2021-06-14 01:43:50.750455",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Taxes and Charges",
|
"name": "Purchase Taxes and Charges",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
|||||||
var me = this;
|
var me = this;
|
||||||
super.onload();
|
super.onload();
|
||||||
|
|
||||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet'];
|
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log', 'POS Closing Entry'];
|
||||||
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
||||||
// show debit_to in print format
|
// show debit_to in print format
|
||||||
this.frm.set_df_property("debit_to", "print_hide", 0);
|
this.frm.set_df_property("debit_to", "print_hide", 0);
|
||||||
|
|||||||
@@ -48,6 +48,8 @@
|
|||||||
"shipping_address",
|
"shipping_address",
|
||||||
"company_address",
|
"company_address",
|
||||||
"company_address_display",
|
"company_address_display",
|
||||||
|
"dispatch_address_name",
|
||||||
|
"dispatch_address",
|
||||||
"currency_and_price_list",
|
"currency_and_price_list",
|
||||||
"currency",
|
"currency",
|
||||||
"conversion_rate",
|
"conversion_rate",
|
||||||
@@ -1966,6 +1968,21 @@
|
|||||||
"fieldname": "disable_rounded_total",
|
"fieldname": "disable_rounded_total",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Disable Rounded Total"
|
"label": "Disable Rounded Total"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"fieldname": "dispatch_address_name",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Dispatch Address Name",
|
||||||
|
"options": "Address",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 1,
|
||||||
|
"fieldname": "dispatch_address",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Dispatch Address",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
@@ -1978,7 +1995,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2021-05-20 22:48:33.988881",
|
"modified": "2021-07-08 14:03:55.502522",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from erpnext.accounts.utils import get_account_currency
|
|||||||
from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so
|
from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so
|
||||||
from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
|
from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
|
||||||
from erpnext.assets.doctype.asset.depreciation \
|
from erpnext.assets.doctype.asset.depreciation \
|
||||||
import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal
|
import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain
|
||||||
from erpnext.stock.doctype.batch.batch import set_batch_nos
|
from erpnext.stock.doctype.batch.batch import set_batch_nos
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no
|
||||||
from erpnext.setup.doctype.company.company import update_company_current_month_sales
|
from erpnext.setup.doctype.company.company import update_company_current_month_sales
|
||||||
@@ -149,7 +149,7 @@ class SalesInvoice(SellingController):
|
|||||||
if self.update_stock:
|
if self.update_stock:
|
||||||
frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
|
frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
|
||||||
|
|
||||||
elif asset.status in ("Scrapped", "Cancelled", "Sold"):
|
elif asset.status in ("Scrapped", "Cancelled") or (asset.status == "Sold" and not self.is_return):
|
||||||
frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(d.idx, d.asset, asset.status))
|
frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(d.idx, d.asset, asset.status))
|
||||||
|
|
||||||
def validate_item_cost_centers(self):
|
def validate_item_cost_centers(self):
|
||||||
@@ -531,7 +531,7 @@ class SalesInvoice(SellingController):
|
|||||||
# set pos values in items
|
# set pos values in items
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item.get('item_code'):
|
if item.get('item_code'):
|
||||||
profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos)
|
profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos, update_data=True)
|
||||||
for fname, val in iteritems(profile_details):
|
for fname, val in iteritems(profile_details):
|
||||||
if (not for_validate) or (for_validate and not item.get(fname)):
|
if (not for_validate) or (for_validate and not item.get(fname)):
|
||||||
item.set(fname, val)
|
item.set(fname, val)
|
||||||
@@ -840,8 +840,11 @@ class SalesInvoice(SellingController):
|
|||||||
self.make_customer_gl_entry(gl_entries)
|
self.make_customer_gl_entry(gl_entries)
|
||||||
|
|
||||||
self.make_tax_gl_entries(gl_entries)
|
self.make_tax_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)
|
||||||
|
|
||||||
# merge gl entries before adding pos entries
|
# merge gl entries before adding pos entries
|
||||||
@@ -849,7 +852,6 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
self.make_loyalty_point_redemption_gle(gl_entries)
|
self.make_loyalty_point_redemption_gle(gl_entries)
|
||||||
self.make_pos_gl_entries(gl_entries)
|
self.make_pos_gl_entries(gl_entries)
|
||||||
self.make_gle_for_change_amount(gl_entries)
|
|
||||||
|
|
||||||
self.make_write_off_gl_entry(gl_entries)
|
self.make_write_off_gl_entry(gl_entries)
|
||||||
self.make_gle_for_rounding_adjustment(gl_entries)
|
self.make_gle_for_rounding_adjustment(gl_entries)
|
||||||
@@ -916,22 +918,33 @@ class SalesInvoice(SellingController):
|
|||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if flt(item.base_net_amount, item.precision("base_net_amount")):
|
if flt(item.base_net_amount, item.precision("base_net_amount")):
|
||||||
if item.is_fixed_asset:
|
if item.is_fixed_asset:
|
||||||
asset = frappe.get_doc("Asset", item.asset)
|
if item.get('asset'):
|
||||||
|
asset = frappe.get_doc("Asset", item.asset)
|
||||||
|
else:
|
||||||
|
frappe.throw(_(
|
||||||
|
"Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name),
|
||||||
|
title=_("Missing Asset")
|
||||||
|
)
|
||||||
if (len(asset.finance_books) > 1 and not item.finance_book
|
if (len(asset.finance_books) > 1 and not item.finance_book
|
||||||
and asset.finance_books[0].finance_book):
|
and asset.finance_books[0].finance_book):
|
||||||
frappe.throw(_("Select finance book for the item {0} at row {1}")
|
frappe.throw(_("Select finance book for the item {0} at row {1}")
|
||||||
.format(item.item_code, item.idx))
|
.format(item.item_code, item.idx))
|
||||||
|
|
||||||
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(asset,
|
if self.is_return:
|
||||||
item.base_net_amount, item.finance_book)
|
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(asset,
|
||||||
|
item.base_net_amount, item.finance_book)
|
||||||
|
asset.db_set("disposal_date", None)
|
||||||
|
else:
|
||||||
|
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(asset,
|
||||||
|
item.base_net_amount, item.finance_book)
|
||||||
|
asset.db_set("disposal_date", self.posting_date)
|
||||||
|
|
||||||
for gle in fixed_asset_gl_entries:
|
for gle in fixed_asset_gl_entries:
|
||||||
gle["against"] = self.customer
|
gle["against"] = self.customer
|
||||||
gl_entries.append(self.get_gl_dict(gle, item=item))
|
gl_entries.append(self.get_gl_dict(gle, item=item))
|
||||||
|
|
||||||
asset.db_set("disposal_date", self.posting_date)
|
self.set_asset_status(asset)
|
||||||
asset.set_status("Sold" if self.docstatus==1 else None)
|
|
||||||
else:
|
else:
|
||||||
# Do not book income for transfer within same company
|
# Do not book income for transfer within same company
|
||||||
if not self.is_internal_transfer():
|
if not self.is_internal_transfer():
|
||||||
@@ -957,6 +970,12 @@ class SalesInvoice(SellingController):
|
|||||||
erpnext.is_perpetual_inventory_enabled(self.company):
|
erpnext.is_perpetual_inventory_enabled(self.company):
|
||||||
gl_entries += super(SalesInvoice, self).get_gl_entries()
|
gl_entries += super(SalesInvoice, self).get_gl_entries()
|
||||||
|
|
||||||
|
def set_asset_status(self, asset):
|
||||||
|
if self.is_return:
|
||||||
|
asset.set_status()
|
||||||
|
else:
|
||||||
|
asset.set_status("Sold" if self.docstatus==1 else None)
|
||||||
|
|
||||||
def make_loyalty_point_redemption_gle(self, gl_entries):
|
def make_loyalty_point_redemption_gle(self, gl_entries):
|
||||||
if cint(self.redeem_loyalty_points):
|
if cint(self.redeem_loyalty_points):
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
@@ -983,7 +1002,13 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
def make_pos_gl_entries(self, gl_entries):
|
def make_pos_gl_entries(self, gl_entries):
|
||||||
if cint(self.is_pos):
|
if cint(self.is_pos):
|
||||||
|
|
||||||
|
skip_change_gl_entries = not cint(frappe.db.get_single_value('Accounts Settings', 'post_change_gl_entries'))
|
||||||
|
|
||||||
for payment_mode in self.payments:
|
for payment_mode in self.payments:
|
||||||
|
if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
|
||||||
|
payment_mode.base_amount -= flt(self.change_amount)
|
||||||
|
|
||||||
if payment_mode.amount:
|
if payment_mode.amount:
|
||||||
# POS, make payment entries
|
# POS, make payment entries
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
@@ -1015,8 +1040,11 @@ class SalesInvoice(SellingController):
|
|||||||
}, payment_mode_account_currency, item=self)
|
}, payment_mode_account_currency, item=self)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not skip_change_gl_entries:
|
||||||
|
self.make_gle_for_change_amount(gl_entries)
|
||||||
|
|
||||||
def make_gle_for_change_amount(self, gl_entries):
|
def make_gle_for_change_amount(self, gl_entries):
|
||||||
if cint(self.is_pos) and self.change_amount:
|
if self.change_amount:
|
||||||
if self.account_for_change_amount:
|
if self.account_for_change_amount:
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
self.get_gl_dict({
|
self.get_gl_dict({
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from frappe.model.dynamic_links import get_dynamic_link_map
|
|||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
|
||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
|
||||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||||
|
from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data
|
||||||
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
|
from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
|
||||||
from frappe.model.naming import make_autoname
|
from frappe.model.naming import make_autoname
|
||||||
@@ -713,7 +714,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
si.submit()
|
si.submit()
|
||||||
self.assertEqual(si.paid_amount, 100.0)
|
self.assertEqual(si.paid_amount, 100.0)
|
||||||
|
|
||||||
self.pos_gl_entry(si, pos, 50)
|
self.validate_pos_gl_entry(si, pos, 50)
|
||||||
|
|
||||||
def test_pos_returns_with_repayment(self):
|
def test_pos_returns_with_repayment(self):
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
|
||||||
@@ -749,7 +750,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||||
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||||
|
|
||||||
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
|
make_purchase_receipt(company= "_Test Company with perpetual inventory",
|
||||||
item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
|
item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
|
||||||
|
|
||||||
pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
|
pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
|
||||||
@@ -770,7 +771,45 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(pos.grand_total, 100.0)
|
self.assertEqual(pos.grand_total, 100.0)
|
||||||
self.assertEqual(pos.write_off_amount, -5)
|
self.assertEqual(pos.write_off_amount, -5)
|
||||||
|
|
||||||
def pos_gl_entry(self, si, pos, cash_amount):
|
def test_pos_with_no_gl_entry_for_change_amount(self):
|
||||||
|
frappe.db.set_value('Accounts Settings', None, 'post_change_gl_entries', 0)
|
||||||
|
|
||||||
|
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||||
|
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||||
|
|
||||||
|
make_purchase_receipt(company= "_Test Company with perpetual inventory",
|
||||||
|
item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
|
||||||
|
|
||||||
|
pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
|
||||||
|
debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1",
|
||||||
|
income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1",
|
||||||
|
cost_center = "Main - TCP1", do_not_save=True)
|
||||||
|
|
||||||
|
pos.is_pos = 1
|
||||||
|
pos.update_stock = 1
|
||||||
|
|
||||||
|
taxes = get_taxes_and_charges()
|
||||||
|
pos.taxes = []
|
||||||
|
for tax in taxes:
|
||||||
|
pos.append("taxes", tax)
|
||||||
|
|
||||||
|
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50})
|
||||||
|
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 60})
|
||||||
|
|
||||||
|
pos.insert()
|
||||||
|
pos.submit()
|
||||||
|
|
||||||
|
self.assertEqual(pos.grand_total, 100.0)
|
||||||
|
self.assertEqual(pos.change_amount, 10)
|
||||||
|
|
||||||
|
self.validate_pos_gl_entry(pos, pos, 60, validate_without_change_gle=True)
|
||||||
|
|
||||||
|
frappe.db.set_value('Accounts Settings', None, 'post_change_gl_entries', 1)
|
||||||
|
|
||||||
|
def validate_pos_gl_entry(self, si, pos, cash_amount, validate_without_change_gle=False):
|
||||||
|
if validate_without_change_gle:
|
||||||
|
cash_amount -= pos.change_amount
|
||||||
|
|
||||||
# check stock ledger entries
|
# check stock ledger entries
|
||||||
sle = frappe.db.sql("""select * from `tabStock Ledger Entry`
|
sle = frappe.db.sql("""select * from `tabStock Ledger Entry`
|
||||||
where voucher_type = 'Sales Invoice' and voucher_no = %s""",
|
where voucher_type = 'Sales Invoice' and voucher_no = %s""",
|
||||||
@@ -1031,6 +1070,36 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertFalse(si1.outstanding_amount)
|
self.assertFalse(si1.outstanding_amount)
|
||||||
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 1500)
|
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 1500)
|
||||||
|
|
||||||
|
def test_gle_made_when_asset_is_returned(self):
|
||||||
|
create_asset_data()
|
||||||
|
asset = create_asset(item_code="Macbook Pro")
|
||||||
|
|
||||||
|
si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000)
|
||||||
|
return_si = create_sales_invoice(is_return=1, return_against=si.name, item_code="Macbook Pro", asset=asset.name, qty=-1, rate=90000)
|
||||||
|
|
||||||
|
disposal_account = frappe.get_cached_value("Company", "_Test Company", "disposal_account")
|
||||||
|
|
||||||
|
# Asset value is 100,000 but it was sold for 90,000, so there should be a loss of 10,000
|
||||||
|
loss_for_si = frappe.get_all(
|
||||||
|
"GL Entry",
|
||||||
|
filters = {
|
||||||
|
"voucher_no": si.name,
|
||||||
|
"account": disposal_account
|
||||||
|
},
|
||||||
|
fields = ["credit", "debit"]
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
loss_for_return_si = frappe.get_all(
|
||||||
|
"GL Entry",
|
||||||
|
filters = {
|
||||||
|
"voucher_no": return_si.name,
|
||||||
|
"account": disposal_account
|
||||||
|
},
|
||||||
|
fields = ["credit", "debit"]
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
self.assertEqual(loss_for_si['credit'], loss_for_return_si['debit'])
|
||||||
|
self.assertEqual(loss_for_si['debit'], loss_for_return_si['credit'])
|
||||||
|
|
||||||
def test_discount_on_net_total(self):
|
def test_discount_on_net_total(self):
|
||||||
si = frappe.copy_doc(test_records[2])
|
si = frappe.copy_doc(test_records[2])
|
||||||
@@ -1870,6 +1939,8 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
|
self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
|
||||||
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
|
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
|
||||||
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
|
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
|
||||||
|
self.assertEqual(data['billLists'][0]['actualFromStateCode'],7)
|
||||||
|
self.assertEqual(data['billLists'][0]['fromStateCode'],27)
|
||||||
|
|
||||||
def test_einvoice_submission_without_irn(self):
|
def test_einvoice_submission_without_irn(self):
|
||||||
# init
|
# init
|
||||||
@@ -1899,69 +1970,80 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
frappe.flags.country = country
|
frappe.flags.country = country
|
||||||
|
|
||||||
def test_einvoice_json(self):
|
def test_einvoice_json(self):
|
||||||
from erpnext.regional.india.e_invoice.utils import make_einvoice
|
from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals
|
||||||
|
|
||||||
si = make_sales_invoice_for_ewaybill()
|
si = get_sales_invoice_for_e_invoice()
|
||||||
si.naming_series = 'INV-2020-.#####'
|
|
||||||
si.items = []
|
|
||||||
si.append("items", {
|
|
||||||
"item_code": "_Test Item",
|
|
||||||
"uom": "Nos",
|
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
|
||||||
"qty": 2000,
|
|
||||||
"rate": 12,
|
|
||||||
"income_account": "Sales - _TC",
|
|
||||||
"expense_account": "Cost of Goods Sold - _TC",
|
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
|
||||||
})
|
|
||||||
si.append("items", {
|
|
||||||
"item_code": "_Test Item 2",
|
|
||||||
"uom": "Nos",
|
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
|
||||||
"qty": 420,
|
|
||||||
"rate": 15,
|
|
||||||
"income_account": "Sales - _TC",
|
|
||||||
"expense_account": "Cost of Goods Sold - _TC",
|
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
|
||||||
})
|
|
||||||
si.discount_amount = 100
|
si.discount_amount = 100
|
||||||
si.save()
|
si.save()
|
||||||
|
|
||||||
einvoice = make_einvoice(si)
|
einvoice = make_einvoice(si)
|
||||||
|
|
||||||
total_item_ass_value = 0
|
|
||||||
total_item_cgst_value = 0
|
|
||||||
total_item_sgst_value = 0
|
|
||||||
total_item_igst_value = 0
|
|
||||||
total_item_value = 0
|
|
||||||
|
|
||||||
for item in einvoice['ItemList']:
|
|
||||||
total_item_ass_value += item['AssAmt']
|
|
||||||
total_item_cgst_value += item['CgstAmt']
|
|
||||||
total_item_sgst_value += item['SgstAmt']
|
|
||||||
total_item_igst_value += item['IgstAmt']
|
|
||||||
total_item_value += item['TotItemVal']
|
|
||||||
|
|
||||||
self.assertTrue(item['AssAmt'], item['TotAmt'] - item['Discount'])
|
|
||||||
self.assertTrue(item['TotItemVal'], item['AssAmt'] + item['CgstAmt'] + item['SgstAmt'] + item['IgstAmt'])
|
|
||||||
|
|
||||||
value_details = einvoice['ValDtls']
|
|
||||||
|
|
||||||
self.assertEqual(einvoice['Version'], '1.1')
|
|
||||||
self.assertEqual(value_details['AssVal'], total_item_ass_value)
|
|
||||||
self.assertEqual(value_details['CgstVal'], total_item_cgst_value)
|
|
||||||
self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
|
|
||||||
self.assertEqual(value_details['IgstVal'], total_item_igst_value)
|
|
||||||
|
|
||||||
calculated_invoice_value = \
|
|
||||||
value_details['AssVal'] + value_details['CgstVal'] \
|
|
||||||
+ value_details['SgstVal'] + value_details['IgstVal'] \
|
|
||||||
+ value_details['OthChrg'] - value_details['Discount']
|
|
||||||
|
|
||||||
self.assertTrue(value_details['TotInvVal'] - calculated_invoice_value < 0.1)
|
|
||||||
|
|
||||||
self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
|
|
||||||
self.assertTrue(einvoice['EwbDtls'])
|
self.assertTrue(einvoice['EwbDtls'])
|
||||||
|
validate_totals(einvoice)
|
||||||
|
|
||||||
|
si.apply_discount_on = 'Net Total'
|
||||||
|
si.save()
|
||||||
|
einvoice = make_einvoice(si)
|
||||||
|
validate_totals(einvoice)
|
||||||
|
|
||||||
|
[d.set('included_in_print_rate', 1) for d in si.taxes]
|
||||||
|
si.save()
|
||||||
|
einvoice = make_einvoice(si)
|
||||||
|
validate_totals(einvoice)
|
||||||
|
|
||||||
|
def test_item_tax_net_range(self):
|
||||||
|
item = create_item("T Shirt")
|
||||||
|
|
||||||
|
item.set('taxes', [])
|
||||||
|
item.append("taxes", {
|
||||||
|
"item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
|
||||||
|
"minimum_net_rate": 0,
|
||||||
|
"maximum_net_rate": 500
|
||||||
|
})
|
||||||
|
|
||||||
|
item.append("taxes", {
|
||||||
|
"item_tax_template": "_Test Account Excise Duty @ 12 - _TC",
|
||||||
|
"minimum_net_rate": 501,
|
||||||
|
"maximum_net_rate": 1000
|
||||||
|
})
|
||||||
|
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
sales_invoice = create_sales_invoice(item = "T Shirt", rate=700, do_not_submit=True)
|
||||||
|
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC")
|
||||||
|
|
||||||
|
# Apply discount
|
||||||
|
sales_invoice.apply_discount_on = 'Net Total'
|
||||||
|
sales_invoice.discount_amount = 300
|
||||||
|
sales_invoice.save()
|
||||||
|
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
|
||||||
|
|
||||||
|
def get_sales_invoice_for_e_invoice():
|
||||||
|
si = make_sales_invoice_for_ewaybill()
|
||||||
|
si.naming_series = 'INV-2020-.#####'
|
||||||
|
si.items = []
|
||||||
|
si.append("items", {
|
||||||
|
"item_code": "_Test Item",
|
||||||
|
"uom": "Nos",
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"qty": 2000,
|
||||||
|
"rate": 12,
|
||||||
|
"income_account": "Sales - _TC",
|
||||||
|
"expense_account": "Cost of Goods Sold - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
})
|
||||||
|
|
||||||
|
si.append("items", {
|
||||||
|
"item_code": "_Test Item 2",
|
||||||
|
"uom": "Nos",
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"qty": 420,
|
||||||
|
"rate": 15,
|
||||||
|
"income_account": "Sales - _TC",
|
||||||
|
"expense_account": "Cost of Goods Sold - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
})
|
||||||
|
|
||||||
|
return si
|
||||||
|
|
||||||
def make_test_address_for_ewaybill():
|
def make_test_address_for_ewaybill():
|
||||||
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
|
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
|
||||||
@@ -2012,6 +2094,30 @@ def make_test_address_for_ewaybill():
|
|||||||
|
|
||||||
address.save()
|
address.save()
|
||||||
|
|
||||||
|
if not frappe.db.exists('Address', '_Test Dispatch-Address for Eway bill-Shipping'):
|
||||||
|
address = frappe.get_doc({
|
||||||
|
"address_line1": "_Test Dispatch Address Line 1",
|
||||||
|
"address_title": "_Test Dispatch-Address for Eway bill",
|
||||||
|
"address_type": "Shipping",
|
||||||
|
"city": "_Test City",
|
||||||
|
"state": "Test State",
|
||||||
|
"country": "India",
|
||||||
|
"doctype": "Address",
|
||||||
|
"is_primary_address": 0,
|
||||||
|
"phone": "+910000000000",
|
||||||
|
"gstin": "07AAACC1206D1ZI",
|
||||||
|
"gst_state": "Delhi",
|
||||||
|
"gst_state_number": "07",
|
||||||
|
"pincode": "1100101"
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
address.append("links", {
|
||||||
|
"link_doctype": "Company",
|
||||||
|
"link_name": "_Test Company"
|
||||||
|
})
|
||||||
|
|
||||||
|
address.save()
|
||||||
|
|
||||||
def make_test_transporter_for_ewaybill():
|
def make_test_transporter_for_ewaybill():
|
||||||
if not frappe.db.exists('Supplier', '_Test Transporter'):
|
if not frappe.db.exists('Supplier', '_Test Transporter'):
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
@@ -2038,9 +2144,9 @@ def make_sales_invoice_for_ewaybill():
|
|||||||
if not gst_account:
|
if not gst_account:
|
||||||
gst_settings.append("gst_accounts", {
|
gst_settings.append("gst_accounts", {
|
||||||
"company": "_Test Company",
|
"company": "_Test Company",
|
||||||
"cgst_account": "CGST - _TC",
|
"cgst_account": "Output Tax CGST - _TC",
|
||||||
"sgst_account": "SGST - _TC",
|
"sgst_account": "Output Tax SGST - _TC",
|
||||||
"igst_account": "IGST - _TC",
|
"igst_account": "Output Tax IGST - _TC",
|
||||||
})
|
})
|
||||||
|
|
||||||
gst_settings.save()
|
gst_settings.save()
|
||||||
@@ -2050,6 +2156,7 @@ def make_sales_invoice_for_ewaybill():
|
|||||||
si.distance = 2000
|
si.distance = 2000
|
||||||
si.company_address = "_Test Address for Eway bill-Billing"
|
si.company_address = "_Test Address for Eway bill-Billing"
|
||||||
si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
|
si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
|
||||||
|
si.dispatch_address_name = "_Test Dispatch-Address for Eway bill-Shipping"
|
||||||
si.vehicle_no = "KA12KA1234"
|
si.vehicle_no = "KA12KA1234"
|
||||||
si.gst_category = "Registered Regular"
|
si.gst_category = "Registered Regular"
|
||||||
si.mode_of_transport = 'Road'
|
si.mode_of_transport = 'Road'
|
||||||
@@ -2057,7 +2164,7 @@ def make_sales_invoice_for_ewaybill():
|
|||||||
|
|
||||||
si.append("taxes", {
|
si.append("taxes", {
|
||||||
"charge_type": "On Net Total",
|
"charge_type": "On Net Total",
|
||||||
"account_head": "CGST - _TC",
|
"account_head": "Output Tax CGST - _TC",
|
||||||
"cost_center": "Main - _TC",
|
"cost_center": "Main - _TC",
|
||||||
"description": "CGST @ 9.0",
|
"description": "CGST @ 9.0",
|
||||||
"rate": 9
|
"rate": 9
|
||||||
@@ -2065,7 +2172,7 @@ def make_sales_invoice_for_ewaybill():
|
|||||||
|
|
||||||
si.append("taxes", {
|
si.append("taxes", {
|
||||||
"charge_type": "On Net Total",
|
"charge_type": "On Net Total",
|
||||||
"account_head": "SGST - _TC",
|
"account_head": "Output Tax SGST - _TC",
|
||||||
"cost_center": "Main - _TC",
|
"cost_center": "Main - _TC",
|
||||||
"description": "SGST @ 9.0",
|
"description": "SGST @ 9.0",
|
||||||
"rate": 9
|
"rate": 9
|
||||||
@@ -2085,27 +2192,6 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
|||||||
doc.assertEqual(expected_gle[i][2], gle.credit)
|
doc.assertEqual(expected_gle[i][2], gle.credit)
|
||||||
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
|
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
|
||||||
|
|
||||||
def test_item_tax_validity(self):
|
|
||||||
item = frappe.get_doc("Item", "_Test Item 2")
|
|
||||||
|
|
||||||
if item.taxes:
|
|
||||||
item.taxes = []
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
item.append("taxes", {
|
|
||||||
"item_tax_template": "_Test Item Tax Template 1 - _TC",
|
|
||||||
"valid_from": add_days(nowdate(), 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
sales_invoice = create_sales_invoice(item = "_Test Item 2", do_not_save=1)
|
|
||||||
sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1 - _TC"
|
|
||||||
self.assertRaises(frappe.ValidationError, sales_invoice.save)
|
|
||||||
|
|
||||||
item.taxes = []
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
def create_sales_invoice(**args):
|
def create_sales_invoice(**args):
|
||||||
si = frappe.new_doc("Sales Invoice")
|
si = frappe.new_doc("Sales Invoice")
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
@@ -2136,6 +2222,7 @@ def create_sales_invoice(**args):
|
|||||||
"rate": args.rate if args.get("rate") is not None else 100,
|
"rate": args.rate if args.get("rate") is not None else 100,
|
||||||
"income_account": args.income_account or "Sales - _TC",
|
"income_account": args.income_account or "Sales - _TC",
|
||||||
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
||||||
|
"asset": args.asset or None,
|
||||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||||
"serial_no": args.serial_no,
|
"serial_no": args.serial_no,
|
||||||
"conversion_factor": 1
|
"conversion_factor": 1
|
||||||
|
|||||||
@@ -1,235 +1,128 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"actions": [],
|
||||||
"allow_import": 0,
|
|
||||||
"allow_rename": 0,
|
|
||||||
"beta": 0,
|
|
||||||
"creation": "2013-02-22 01:27:41",
|
"creation": "2013-02-22 01:27:41",
|
||||||
"custom": 0,
|
|
||||||
"docstatus": 0,
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Document",
|
"document_type": "Document",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"reference_type",
|
||||||
|
"reference_name",
|
||||||
|
"remarks",
|
||||||
|
"reference_row",
|
||||||
|
"col_break1",
|
||||||
|
"advance_amount",
|
||||||
|
"allocated_amount",
|
||||||
|
"exchange_gain_loss",
|
||||||
|
"ref_exchange_rate"
|
||||||
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "reference_type",
|
"fieldname": "reference_type",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"label": "Reference Type",
|
"label": "Reference Type",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "journal_voucher",
|
"oldfieldname": "journal_voucher",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"options": "DocType",
|
"options": "DocType",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "250px",
|
"print_width": "250px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "250px"
|
"width": "250px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 3,
|
"columns": 3,
|
||||||
"fieldname": "reference_name",
|
"fieldname": "reference_name",
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "Dynamic Link",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Reference Name",
|
"label": "Reference Name",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "reference_type",
|
"options": "reference_type",
|
||||||
"permlevel": 0,
|
|
||||||
"precision": "",
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
"read_only": 1
|
||||||
"read_only": 1,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 3,
|
"columns": 3,
|
||||||
"fieldname": "remarks",
|
"fieldname": "remarks",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Remarks",
|
"label": "Remarks",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "remarks",
|
"oldfieldname": "remarks",
|
||||||
"oldfieldtype": "Small Text",
|
"oldfieldtype": "Small Text",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "150px",
|
"print_width": "150px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "150px"
|
"width": "150px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "reference_row",
|
"fieldname": "reference_row",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"label": "Reference Row",
|
"label": "Reference Row",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "jv_detail_no",
|
"oldfieldname": "jv_detail_no",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "120px",
|
"print_width": "120px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "120px"
|
"width": "120px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 0,
|
|
||||||
"fieldname": "col_break1",
|
"fieldname": "col_break1",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break"
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 0,
|
|
||||||
"length": 0,
|
|
||||||
"no_copy": 0,
|
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
"columns": 2,
|
||||||
"fieldname": "advance_amount",
|
"fieldname": "advance_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Advance amount",
|
"label": "Advance amount",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "advance_amount",
|
"oldfieldname": "advance_amount",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "party_account_currency",
|
"options": "party_account_currency",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "120px",
|
"print_width": "120px",
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "120px"
|
"width": "120px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 0,
|
|
||||||
"bold": 0,
|
|
||||||
"collapsible": 0,
|
|
||||||
"columns": 2,
|
"columns": 2,
|
||||||
"fieldname": "allocated_amount",
|
"fieldname": "allocated_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
|
||||||
"ignore_user_permissions": 0,
|
|
||||||
"ignore_xss_filter": 0,
|
|
||||||
"in_filter": 0,
|
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Allocated amount",
|
"label": "Allocated amount",
|
||||||
"length": 0,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "allocated_amount",
|
"oldfieldname": "allocated_amount",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "party_account_currency",
|
"options": "party_account_currency",
|
||||||
"permlevel": 0,
|
|
||||||
"print_hide": 0,
|
|
||||||
"print_hide_if_no_value": 0,
|
|
||||||
"print_width": "120px",
|
"print_width": "120px",
|
||||||
"read_only": 0,
|
|
||||||
"report_hide": 0,
|
|
||||||
"reqd": 0,
|
|
||||||
"search_index": 0,
|
|
||||||
"set_only_once": 0,
|
|
||||||
"unique": 0,
|
|
||||||
"width": "120px"
|
"width": "120px"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "exchange_gain_loss",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Exchange Gain/Loss",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "ref_exchange_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Reference Exchange Rate",
|
||||||
|
"non_negative": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hide_heading": 0,
|
|
||||||
"hide_toolbar": 0,
|
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_view": 0,
|
"index_web_pages_for_search": 1,
|
||||||
"in_create": 0,
|
|
||||||
|
|
||||||
"is_submittable": 0,
|
|
||||||
"issingle": 0,
|
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"links": [],
|
||||||
"menu_index": 0,
|
"modified": "2021-06-04 20:25:49.832052",
|
||||||
"modified": "2016-08-26 02:36:10.718057",
|
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Advance",
|
"name": "Sales Invoice Advance",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"read_only": 0,
|
"sort_field": "modified",
|
||||||
"read_only_onload": 0,
|
"sort_order": "DESC"
|
||||||
"sort_order": "DESC",
|
|
||||||
"track_seen": 0
|
|
||||||
}
|
}
|
||||||
@@ -743,7 +743,6 @@
|
|||||||
"fieldname": "asset",
|
"fieldname": "asset",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Asset",
|
"label": "Asset",
|
||||||
"no_copy": 1,
|
|
||||||
"options": "Asset"
|
"options": "Asset"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -826,7 +825,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-02-23 01:05:22.123527",
|
"modified": "2021-06-21 23:03:11.599901",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Item",
|
"name": "Sales Invoice Item",
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"creation": "2013-04-24 11:39:32",
|
"creation": "2013-04-24 11:39:32",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"charge_type",
|
"charge_type",
|
||||||
"row_id",
|
"row_id",
|
||||||
@@ -10,12 +12,14 @@
|
|||||||
"col_break_1",
|
"col_break_1",
|
||||||
"description",
|
"description",
|
||||||
"included_in_print_rate",
|
"included_in_print_rate",
|
||||||
|
"included_in_paid_amount",
|
||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
"section_break_8",
|
"section_break_8",
|
||||||
"rate",
|
"rate",
|
||||||
"section_break_9",
|
"section_break_9",
|
||||||
|
"currency",
|
||||||
"tax_amount",
|
"tax_amount",
|
||||||
"total",
|
"total",
|
||||||
"tax_amount_after_discount_amount",
|
"tax_amount_after_discount_amount",
|
||||||
@@ -23,8 +27,7 @@
|
|||||||
"base_tax_amount",
|
"base_tax_amount",
|
||||||
"base_total",
|
"base_total",
|
||||||
"base_tax_amount_after_discount_amount",
|
"base_tax_amount_after_discount_amount",
|
||||||
"item_wise_tax_detail",
|
"item_wise_tax_detail"
|
||||||
"parenttype"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -173,17 +176,6 @@
|
|||||||
"oldfieldtype": "Small Text",
|
"oldfieldtype": "Small Text",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "parenttype",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"hidden": 1,
|
|
||||||
"in_filter": 1,
|
|
||||||
"label": "Parenttype",
|
|
||||||
"oldfieldname": "parenttype",
|
|
||||||
"oldfieldtype": "Data",
|
|
||||||
"print_hide": 1,
|
|
||||||
"search_index": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "accounting_dimensions_section",
|
"fieldname": "accounting_dimensions_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
@@ -192,15 +184,34 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "dimension_col_break",
|
"fieldname": "dimension_col_break",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "account_head.account_currency",
|
||||||
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Account Currency",
|
||||||
|
"options": "Currency",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:['Sales Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)",
|
||||||
|
"description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry",
|
||||||
|
"fieldname": "included_in_paid_amount",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Considered In Paid Amount"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2019-05-25 22:59:38.740883",
|
"links": [],
|
||||||
|
"modified": "2021-06-14 01:44:36.899147",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Taxes and Charges",
|
"name": "Sales Taxes and Charges",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
"sort_order": "ASC"
|
"sort_order": "ASC"
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,6 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
cur_frm.add_fetch("customer", "customer_group", "customer_group" );
|
|
||||||
cur_frm.add_fetch("supplier", "supplier_group_name", "supplier_group" );
|
|
||||||
|
|
||||||
frappe.ui.form.on("Tax Rule", "tax_type", function(frm) {
|
|
||||||
frm.toggle_reqd("sales_tax_template", frm.doc.tax_type=="Sales");
|
|
||||||
frm.toggle_reqd("purchase_tax_template", frm.doc.tax_type=="Purchase");
|
|
||||||
})
|
|
||||||
|
|
||||||
frappe.ui.form.on("Tax Rule", "onload", function(frm) {
|
|
||||||
if(frm.doc.__islocal) {
|
|
||||||
frm.set_value("use_for_shopping_cart", 1);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
frappe.ui.form.on("Tax Rule", "refresh", function(frm) {
|
|
||||||
frappe.ui.form.trigger("Tax Rule", "tax_type");
|
|
||||||
})
|
|
||||||
|
|
||||||
frappe.ui.form.on("Tax Rule", "customer", function(frm) {
|
frappe.ui.form.on("Tax Rule", "customer", function(frm) {
|
||||||
if(frm.doc.customer) {
|
if(frm.doc.customer) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -50,7 +50,7 @@ class TestTaxRule(unittest.TestCase):
|
|||||||
tax_rule1 = make_tax_rule(customer_group= "All Customer Groups",
|
tax_rule1 = make_tax_rule(customer_group= "All Customer Groups",
|
||||||
sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", priority = 1, from_date = "2015-01-01")
|
sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", priority = 1, from_date = "2015-01-01")
|
||||||
tax_rule1.save()
|
tax_rule1.save()
|
||||||
self.assertEqual(get_tax_template("2015-01-01", {"customer_group" : "Commercial", "use_for_shopping_cart":0}),
|
self.assertEqual(get_tax_template("2015-01-01", {"customer_group" : "Commercial", "use_for_shopping_cart":1}),
|
||||||
"_Test Sales Taxes and Charges Template - _TC")
|
"_Test Sales Taxes and Charges Template - _TC")
|
||||||
|
|
||||||
def test_conflict_with_overlapping_dates(self):
|
def test_conflict_with_overlapping_dates(self):
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
|||||||
if not parties:
|
if not parties:
|
||||||
parties.append(party)
|
parties.append(party)
|
||||||
|
|
||||||
fiscal_year = get_fiscal_year(inv.posting_date, company=inv.company)
|
fiscal_year = get_fiscal_year(inv.get('posting_date') or inv.get('transaction_date'), company=inv.company)
|
||||||
tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company)
|
tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company)
|
||||||
|
|
||||||
if not tax_details:
|
if not tax_details:
|
||||||
@@ -154,7 +154,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, p
|
|||||||
tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details)
|
tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details)
|
||||||
|
|
||||||
tax_amount = 0
|
tax_amount = 0
|
||||||
posting_date = inv.posting_date
|
posting_date = inv.get('posting_date') or inv.get('transaction_date')
|
||||||
if party_type == 'Supplier':
|
if party_type == 'Supplier':
|
||||||
ldc = get_lower_deduction_certificate(fiscal_year, pan_no)
|
ldc = get_lower_deduction_certificate(fiscal_year, pan_no)
|
||||||
if tax_deducted:
|
if tax_deducted:
|
||||||
@@ -257,7 +257,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
|
|||||||
if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
|
if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
|
||||||
if ldc and is_valid_certificate(
|
if ldc and is_valid_certificate(
|
||||||
ldc.valid_from, ldc.valid_upto,
|
ldc.valid_from, ldc.valid_upto,
|
||||||
inv.posting_date, tax_deducted,
|
inv.get('posting_date') or inv.get('transaction_date'), tax_deducted,
|
||||||
inv.net_total, ldc.certificate_limit
|
inv.net_total, ldc.certificate_limit
|
||||||
):
|
):
|
||||||
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
|
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ def merge_similar_entries(gl_map, precision=None):
|
|||||||
|
|
||||||
def check_if_in_list(gle, gl_map, dimensions=None):
|
def check_if_in_list(gle, gl_map, dimensions=None):
|
||||||
account_head_fieldnames = ['party_type', 'party', 'against_voucher', 'against_voucher_type',
|
account_head_fieldnames = ['party_type', 'party', 'against_voucher', 'against_voucher_type',
|
||||||
'cost_center', 'project']
|
'cost_center', 'project', 'voucher_detail_no']
|
||||||
|
|
||||||
if dimensions:
|
if dimensions:
|
||||||
account_head_fieldnames = account_head_fieldnames + dimensions
|
account_head_fieldnames = account_head_fieldnames + dimensions
|
||||||
@@ -185,10 +185,10 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
|
|||||||
for d in gl_map:
|
for d in gl_map:
|
||||||
if d.account == round_off_account:
|
if d.account == round_off_account:
|
||||||
round_off_gle = d
|
round_off_gle = d
|
||||||
if d.debit_in_account_currency:
|
if d.debit:
|
||||||
debit_credit_diff -= flt(d.debit_in_account_currency)
|
debit_credit_diff -= flt(d.debit)
|
||||||
else:
|
else:
|
||||||
debit_credit_diff += flt(d.credit_in_account_currency)
|
debit_credit_diff += flt(d.credit)
|
||||||
round_off_account_exists = True
|
round_off_account_exists = True
|
||||||
|
|
||||||
if round_off_account_exists and abs(debit_credit_diff) <= (1.0 / (10**precision)):
|
if round_off_account_exists and abs(debit_credit_diff) <= (1.0 / (10**precision)):
|
||||||
|
|||||||
@@ -542,6 +542,7 @@ def get_dashboard_info(party_type, party, loyalty_program=None):
|
|||||||
select company, sum(debit_in_account_currency) - sum(credit_in_account_currency)
|
select company, sum(debit_in_account_currency) - sum(credit_in_account_currency)
|
||||||
from `tabGL Entry`
|
from `tabGL Entry`
|
||||||
where party_type = %s and party=%s
|
where party_type = %s and party=%s
|
||||||
|
and is_cancelled = 0
|
||||||
group by company""", (party_type, party)))
|
group by company""", (party_type, party)))
|
||||||
|
|
||||||
for d in companies:
|
for d in companies:
|
||||||
|
|||||||
@@ -99,7 +99,6 @@ class ReceivablePayableReport(object):
|
|||||||
voucher_no = gle.voucher_no,
|
voucher_no = gle.voucher_no,
|
||||||
party = gle.party,
|
party = gle.party,
|
||||||
posting_date = gle.posting_date,
|
posting_date = gle.posting_date,
|
||||||
remarks = gle.remarks,
|
|
||||||
account_currency = gle.account_currency,
|
account_currency = gle.account_currency,
|
||||||
invoiced = 0.0,
|
invoiced = 0.0,
|
||||||
paid = 0.0,
|
paid = 0.0,
|
||||||
@@ -579,11 +578,12 @@ class ReceivablePayableReport(object):
|
|||||||
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, remarks, {0}
|
against_voucher_type, against_voucher, account_currency, {0}
|
||||||
from
|
from
|
||||||
`tabGL Entry`
|
`tabGL Entry`
|
||||||
where
|
where
|
||||||
docstatus < 2
|
docstatus < 2
|
||||||
|
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}"""
|
{1} {2} {3}"""
|
||||||
@@ -791,8 +791,6 @@ class ReceivablePayableReport(object):
|
|||||||
self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link',
|
self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link',
|
||||||
options='Supplier Group')
|
options='Supplier Group')
|
||||||
|
|
||||||
self.add_column(label=_('Remarks'), fieldname='remarks', fieldtype='Text', width=200)
|
|
||||||
|
|
||||||
def add_column(self, label, fieldname=None, fieldtype='Currency', options=None, width=120):
|
def add_column(self, label, fieldname=None, fieldtype='Currency', options=None, width=120):
|
||||||
if not fieldname: fieldname = scrub(label)
|
if not fieldname: fieldname = scrub(label)
|
||||||
if fieldtype=='Currency': options='currency'
|
if fieldtype=='Currency': options='currency'
|
||||||
|
|||||||
@@ -397,6 +397,7 @@ def get_chart_data(filters, columns, data):
|
|||||||
{'name': 'Budget', 'chartType': 'bar', 'values': budget_values},
|
{'name': 'Budget', 'chartType': 'bar', 'values': budget_values},
|
||||||
{'name': 'Actual Expense', 'chartType': 'bar', 'values': actual_values}
|
{'name': 'Actual Expense', 'chartType': 'bar', 'values': actual_values}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
'type' : 'bar'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -380,7 +380,7 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g
|
|||||||
gl_entries = frappe.db.sql("""select gl.posting_date, gl.account, gl.debit, gl.credit, gl.is_opening, gl.company,
|
gl_entries = frappe.db.sql("""select gl.posting_date, gl.account, gl.debit, gl.credit, gl.is_opening, gl.company,
|
||||||
gl.fiscal_year, gl.debit_in_account_currency, gl.credit_in_account_currency, gl.account_currency,
|
gl.fiscal_year, gl.debit_in_account_currency, gl.credit_in_account_currency, gl.account_currency,
|
||||||
acc.account_name, acc.account_number
|
acc.account_name, acc.account_number
|
||||||
from `tabGL Entry` gl, `tabAccount` acc where acc.name = gl.account and gl.company = %(company)s
|
from `tabGL Entry` gl, `tabAccount` acc where acc.name = gl.account and gl.company = %(company)s and gl.is_cancelled = 0
|
||||||
{additional_conditions} and gl.posting_date <= %(to_date)s and acc.lft >= %(lft)s and acc.rgt <= %(rgt)s
|
{additional_conditions} and gl.posting_date <= %(to_date)s and acc.lft >= %(lft)s and acc.rgt <= %(rgt)s
|
||||||
order by gl.account, gl.posting_date""".format(additional_conditions=additional_conditions),
|
order by gl.account, gl.posting_date""".format(additional_conditions=additional_conditions),
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,16 +36,12 @@ frappe.query_reports["General Ledger"] = {
|
|||||||
{
|
{
|
||||||
"fieldname":"account",
|
"fieldname":"account",
|
||||||
"label": __("Account"),
|
"label": __("Account"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "MultiSelectList",
|
||||||
"options": "Account",
|
"options": "Account",
|
||||||
"get_query": function() {
|
get_data: function(txt) {
|
||||||
var company = frappe.query_report.get_filter_value('company');
|
return frappe.db.get_link_options('Account', txt, {
|
||||||
return {
|
company: frappe.query_report.get_filter_value("company")
|
||||||
"doctype": "Account",
|
});
|
||||||
"filters": {
|
|
||||||
"company": company,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -135,7 +131,9 @@ frappe.query_reports["General Ledger"] = {
|
|||||||
"label": __("Cost Center"),
|
"label": __("Cost Center"),
|
||||||
"fieldtype": "MultiSelectList",
|
"fieldtype": "MultiSelectList",
|
||||||
get_data: function(txt) {
|
get_data: function(txt) {
|
||||||
return frappe.db.get_link_options('Cost Center', txt);
|
return frappe.db.get_link_options('Cost Center', txt, {
|
||||||
|
company: frappe.query_report.get_filter_value("company")
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -143,7 +141,9 @@ frappe.query_reports["General Ledger"] = {
|
|||||||
"label": __("Project"),
|
"label": __("Project"),
|
||||||
"fieldtype": "MultiSelectList",
|
"fieldtype": "MultiSelectList",
|
||||||
get_data: function(txt) {
|
get_data: function(txt) {
|
||||||
return frappe.db.get_link_options('Project', txt);
|
return frappe.db.get_link_options('Project', txt, {
|
||||||
|
company: frappe.query_report.get_filter_value("company")
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -49,12 +49,17 @@ def validate_filters(filters, account_details):
|
|||||||
if not filters.get("from_date") and not filters.get("to_date"):
|
if not filters.get("from_date") and not filters.get("to_date"):
|
||||||
frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date"))))
|
frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date"))))
|
||||||
|
|
||||||
if filters.get("account") and not account_details.get(filters.account):
|
if filters.get('account'):
|
||||||
frappe.throw(_("Account {0} does not exists").format(filters.account))
|
filters.account = frappe.parse_json(filters.get('account'))
|
||||||
|
for account in filters.account:
|
||||||
|
if not account_details.get(account):
|
||||||
|
frappe.throw(_("Account {0} does not exists").format(account))
|
||||||
|
|
||||||
if (filters.get("account") and filters.get("group_by") == _('Group by Account')
|
if (filters.get("account") and filters.get("group_by") == _('Group by Account')):
|
||||||
and account_details[filters.account].is_group == 0):
|
filters.account = frappe.parse_json(filters.get('account'))
|
||||||
frappe.throw(_("Can not filter based on Account, if grouped by Account"))
|
for account in filters.account:
|
||||||
|
if account_details[account].is_group == 0:
|
||||||
|
frappe.throw(_("Can not filter based on Child Account, if grouped by Account"))
|
||||||
|
|
||||||
if (filters.get("voucher_no")
|
if (filters.get("voucher_no")
|
||||||
and filters.get("group_by") in [_('Group by Voucher')]):
|
and filters.get("group_by") in [_('Group by Voucher')]):
|
||||||
@@ -87,7 +92,19 @@ def set_account_currency(filters):
|
|||||||
account_currency = None
|
account_currency = None
|
||||||
|
|
||||||
if filters.get("account"):
|
if filters.get("account"):
|
||||||
account_currency = get_account_currency(filters.account)
|
if len(filters.get("account")) == 1:
|
||||||
|
account_currency = get_account_currency(filters.account[0])
|
||||||
|
else:
|
||||||
|
currency = get_account_currency(filters.account[0])
|
||||||
|
is_same_account_currency = True
|
||||||
|
for account in filters.get("account"):
|
||||||
|
if get_account_currency(account) != currency:
|
||||||
|
is_same_account_currency = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if is_same_account_currency:
|
||||||
|
account_currency = currency
|
||||||
|
|
||||||
elif filters.get("party"):
|
elif filters.get("party"):
|
||||||
gle_currency = frappe.db.get_value(
|
gle_currency = frappe.db.get_value(
|
||||||
"GL Entry", {
|
"GL Entry", {
|
||||||
@@ -205,10 +222,10 @@ def get_gl_entries(filters, accounting_dimensions):
|
|||||||
|
|
||||||
def get_conditions(filters):
|
def get_conditions(filters):
|
||||||
conditions = []
|
conditions = []
|
||||||
if filters.get("account") and not filters.get("include_dimensions"):
|
|
||||||
lft, rgt = frappe.db.get_value("Account", filters["account"], ["lft", "rgt"])
|
if filters.get("account"):
|
||||||
conditions.append("""account in (select name from tabAccount
|
filters.account = get_accounts_with_children(filters.account)
|
||||||
where lft>=%s and rgt<=%s and docstatus<2)""" % (lft, rgt))
|
conditions.append("account in %(account)s")
|
||||||
|
|
||||||
if filters.get("cost_center"):
|
if filters.get("cost_center"):
|
||||||
filters.cost_center = get_cost_centers_with_children(filters.cost_center)
|
filters.cost_center = get_cost_centers_with_children(filters.cost_center)
|
||||||
@@ -266,6 +283,20 @@ def get_conditions(filters):
|
|||||||
|
|
||||||
return "and {}".format(" and ".join(conditions)) if conditions else ""
|
return "and {}".format(" and ".join(conditions)) if conditions else ""
|
||||||
|
|
||||||
|
def get_accounts_with_children(accounts):
|
||||||
|
if not isinstance(accounts, list):
|
||||||
|
accounts = [d.strip() for d in accounts.strip().split(',') if d]
|
||||||
|
|
||||||
|
all_accounts = []
|
||||||
|
for d in accounts:
|
||||||
|
if frappe.db.exists("Account", d):
|
||||||
|
lft, rgt = frappe.db.get_value("Account", d, ["lft", "rgt"])
|
||||||
|
children = frappe.get_all("Account", filters={"lft": [">=", lft], "rgt": ["<=", rgt]})
|
||||||
|
all_accounts += [c.name for c in children]
|
||||||
|
else:
|
||||||
|
frappe.throw(_("Account: {0} does not exist").format(d))
|
||||||
|
|
||||||
|
return list(set(all_accounts))
|
||||||
|
|
||||||
def get_data_with_opening_closing(filters, account_details, accounting_dimensions, gl_entries):
|
def get_data_with_opening_closing(filters, account_details, accounting_dimensions, gl_entries):
|
||||||
data = []
|
data = []
|
||||||
|
|||||||
@@ -168,21 +168,24 @@ def get_columns(filters):
|
|||||||
"label": _("Income"),
|
"label": _("Income"),
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"options": "currency",
|
"options": "currency",
|
||||||
"width": 120
|
"width": 305
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "expense",
|
"fieldname": "expense",
|
||||||
"label": _("Expense"),
|
"label": _("Expense"),
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"options": "currency",
|
"options": "currency",
|
||||||
"width": 120
|
"width": 305
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "gross_profit_loss",
|
"fieldname": "gross_profit_loss",
|
||||||
"label": _("Gross Profit / Loss"),
|
"label": _("Gross Profit / Loss"),
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"options": "currency",
|
"options": "currency",
|
||||||
"width": 120
|
"width": 307
|
||||||
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date, f
|
|||||||
select voucher_no, credit
|
select voucher_no, credit
|
||||||
from `tabGL Entry`
|
from `tabGL Entry`
|
||||||
where party in (%s) and credit > 0
|
where party in (%s) and credit > 0
|
||||||
and company=%s and posting_date between %s and %s
|
and company=%s and is_cancelled = 0
|
||||||
|
and posting_date between %s and %s
|
||||||
""", (supplier, company, from_date, to_date), as_dict=1)
|
""", (supplier, company, from_date, to_date), as_dict=1)
|
||||||
|
|
||||||
supplier_credit_amount = flt(sum(d.credit for d in entries))
|
supplier_credit_amount = flt(sum(d.credit for d in entries))
|
||||||
|
|||||||
@@ -54,6 +54,32 @@ frappe.query_reports["TDS Payable Monthly"] = {
|
|||||||
frappe.query_report.refresh();
|
frappe.query_report.refresh();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname":"purchase_order",
|
||||||
|
"label": __("Purchase Order"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Purchase Order",
|
||||||
|
"get_query": function() {
|
||||||
|
return {
|
||||||
|
"filters": {
|
||||||
|
"name": ["in", frappe.query_report.invoices]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
on_change: function() {
|
||||||
|
let supplier = frappe.query_report.get_filter_value('supplier');
|
||||||
|
if(!supplier) return; // return if no supplier selected
|
||||||
|
|
||||||
|
// filter invoices based on selected supplier
|
||||||
|
let invoices = [];
|
||||||
|
frappe.query_report.invoice_data.map(d => {
|
||||||
|
if(d.supplier==supplier)
|
||||||
|
invoices.push(d.name)
|
||||||
|
});
|
||||||
|
frappe.query_report.invoices = invoices;
|
||||||
|
frappe.query_report.refresh();
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"from_date",
|
"fieldname":"from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
@@ -75,15 +101,17 @@ frappe.query_reports["TDS Payable Monthly"] = {
|
|||||||
onload: function(report) {
|
onload: function(report) {
|
||||||
// fetch all tds applied invoices
|
// fetch all tds applied invoices
|
||||||
frappe.call({
|
frappe.call({
|
||||||
"method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices",
|
"method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices_and_orders",
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
let invoices = [];
|
let invoices = [];
|
||||||
|
|
||||||
r.message.map(d => {
|
r.message.map(d => {
|
||||||
invoices.push(d.name);
|
invoices.push(d.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
report["invoice_data"] = r.message;
|
report["invoice_data"] = r.message.invoices;
|
||||||
report["invoices"] = invoices;
|
report["invoices"] = invoices;
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,14 @@ def execute(filters=None):
|
|||||||
validate_filters(filters)
|
validate_filters(filters)
|
||||||
set_filters(filters)
|
set_filters(filters)
|
||||||
|
|
||||||
|
# TDS payment entries
|
||||||
|
payment_entries = get_payment_entires(filters)
|
||||||
|
|
||||||
columns = get_columns(filters)
|
columns = get_columns(filters)
|
||||||
if not filters["invoices"]:
|
if not filters.get("invoices"):
|
||||||
return columns, []
|
return columns, []
|
||||||
|
|
||||||
res = get_result(filters)
|
res = get_result(filters, payment_entries)
|
||||||
|
|
||||||
return columns, res
|
return columns, res
|
||||||
|
|
||||||
@@ -27,8 +30,9 @@ def validate_filters(filters):
|
|||||||
def set_filters(filters):
|
def set_filters(filters):
|
||||||
invoices = []
|
invoices = []
|
||||||
|
|
||||||
if not filters["invoices"]:
|
if not filters.get("invoices"):
|
||||||
filters["invoices"] = get_tds_invoices()
|
filters["invoices"] = get_tds_invoices_and_orders()
|
||||||
|
|
||||||
if filters.supplier and filters.purchase_invoice:
|
if filters.supplier and filters.purchase_invoice:
|
||||||
for d in filters["invoices"]:
|
for d in filters["invoices"]:
|
||||||
if d.name == filters.purchase_invoice and d.supplier == filters.supplier:
|
if d.name == filters.purchase_invoice and d.supplier == filters.supplier:
|
||||||
@@ -41,13 +45,29 @@ def set_filters(filters):
|
|||||||
for d in filters["invoices"]:
|
for d in filters["invoices"]:
|
||||||
if d.name == filters.purchase_invoice:
|
if d.name == filters.purchase_invoice:
|
||||||
invoices.append(d)
|
invoices.append(d)
|
||||||
|
elif filters.supplier and filters.purchase_order:
|
||||||
|
for d in filters.get("invoices"):
|
||||||
|
if d.name == filters.purchase_order and d.supplier == filters.supplier:
|
||||||
|
invoices.append(d)
|
||||||
|
elif filters.supplier and not filters.purchase_order:
|
||||||
|
for d in filters.get("invoices"):
|
||||||
|
if d.supplier == filters.supplier:
|
||||||
|
invoices.append(d)
|
||||||
|
elif filters.purchase_order and not filters.supplier:
|
||||||
|
for d in filters.get("invoices"):
|
||||||
|
if d.name == filters.purchase_order:
|
||||||
|
invoices.append(d)
|
||||||
|
|
||||||
filters["invoices"] = invoices if invoices else filters["invoices"]
|
filters["invoices"] = invoices if invoices else filters["invoices"]
|
||||||
filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name')
|
filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name')
|
||||||
|
|
||||||
def get_result(filters):
|
#print(filters.get('invoices'))
|
||||||
supplier_map, tds_docs = get_supplier_map(filters)
|
|
||||||
gle_map = get_gle_map(filters)
|
def get_result(filters, payment_entries):
|
||||||
|
supplier_map, tds_docs = get_supplier_map(filters, payment_entries)
|
||||||
|
documents = [d.get('name') for d in filters.get('invoices')] + [d.get('name') for d in payment_entries]
|
||||||
|
|
||||||
|
gle_map = get_gle_map(filters, documents)
|
||||||
|
|
||||||
out = []
|
out = []
|
||||||
for d in gle_map:
|
for d in gle_map:
|
||||||
@@ -62,10 +82,11 @@ def get_result(filters):
|
|||||||
|
|
||||||
for k in gle_map[d]:
|
for k in gle_map[d]:
|
||||||
if k.party == supplier_map[d] and k.credit > 0:
|
if k.party == supplier_map[d] and k.credit > 0:
|
||||||
total_amount_credited += k.credit
|
total_amount_credited += (k.credit - k.debit)
|
||||||
elif account_list and k.account == account and k.credit > 0:
|
elif account_list and k.account == account and (k.credit - k.debit) > 0:
|
||||||
tds_deducted = k.credit
|
tds_deducted = (k.credit - k.debit)
|
||||||
total_amount_credited += k.credit
|
total_amount_credited += (k.credit - k.debit)
|
||||||
|
voucher_type = k.voucher_type
|
||||||
|
|
||||||
rate = [i.tax_withholding_rate for i in tds_doc.rates
|
rate = [i.tax_withholding_rate for i in tds_doc.rates
|
||||||
if i.fiscal_year == gle_map[d][0].fiscal_year]
|
if i.fiscal_year == gle_map[d][0].fiscal_year]
|
||||||
@@ -73,32 +94,36 @@ def get_result(filters):
|
|||||||
if rate and len(rate) > 0 and tds_deducted:
|
if rate and len(rate) > 0 and tds_deducted:
|
||||||
rate = rate[0]
|
rate = rate[0]
|
||||||
|
|
||||||
if getdate(filters.from_date) <= gle_map[d][0].posting_date \
|
row = [supplier.pan, supplier.name]
|
||||||
and getdate(filters.to_date) >= gle_map[d][0].posting_date:
|
|
||||||
row = [supplier.pan, supplier.name]
|
|
||||||
|
|
||||||
if filters.naming_series == 'Naming Series':
|
if filters.naming_series == 'Naming Series':
|
||||||
row.append(supplier.supplier_name)
|
row.append(supplier.supplier_name)
|
||||||
|
|
||||||
row.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited,
|
row.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited,
|
||||||
tds_deducted, gle_map[d][0].posting_date, "Purchase Invoice", d])
|
tds_deducted, gle_map[d][0].posting_date, voucher_type, d])
|
||||||
out.append(row)
|
out.append(row)
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def get_supplier_map(filters):
|
def get_supplier_map(filters, payment_entries):
|
||||||
# create a supplier_map of the form {"purchase_invoice": {supplier_name, pan, tds_name}}
|
# create a supplier_map of the form {"purchase_invoice": {supplier_name, pan, tds_name}}
|
||||||
# pre-fetch all distinct applicable tds docs
|
# pre-fetch all distinct applicable tds docs
|
||||||
supplier_map, tds_docs = {}, {}
|
supplier_map, tds_docs = {}, {}
|
||||||
pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id"
|
pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id"
|
||||||
|
supplier_list = [d.supplier for d in filters["invoices"]]
|
||||||
|
|
||||||
supplier_detail = frappe.db.get_all('Supplier',
|
supplier_detail = frappe.db.get_all('Supplier',
|
||||||
{"name": ["in", [d.supplier for d in filters["invoices"]]]},
|
{"name": ["in", supplier_list]},
|
||||||
["tax_withholding_category", "name", pan+" as pan", "supplier_type", "supplier_name"])
|
["tax_withholding_category", "name", pan+" as pan", "supplier_type", "supplier_name"])
|
||||||
|
|
||||||
for d in filters["invoices"]:
|
for d in filters["invoices"]:
|
||||||
supplier_map[d.get("name")] = [k for k in supplier_detail
|
supplier_map[d.get("name")] = [k for k in supplier_detail
|
||||||
if k.name == d.get("supplier")][0]
|
if k.name == d.get("supplier")][0]
|
||||||
|
|
||||||
|
for d in payment_entries:
|
||||||
|
supplier_map[d.get("name")] = [k for k in supplier_detail
|
||||||
|
if k.name == d.get("supplier")][0]
|
||||||
|
|
||||||
for d in supplier_detail:
|
for d in supplier_detail:
|
||||||
if d.get("tax_withholding_category") not in tds_docs:
|
if d.get("tax_withholding_category") not in tds_docs:
|
||||||
tds_docs[d.get("tax_withholding_category")] = \
|
tds_docs[d.get("tax_withholding_category")] = \
|
||||||
@@ -106,13 +131,19 @@ def get_supplier_map(filters):
|
|||||||
|
|
||||||
return supplier_map, tds_docs
|
return supplier_map, tds_docs
|
||||||
|
|
||||||
def get_gle_map(filters):
|
def get_gle_map(filters, documents):
|
||||||
# create gle_map of the form
|
# create gle_map of the form
|
||||||
# {"purchase_invoice": list of dict of all gle created for this invoice}
|
# {"purchase_invoice": list of dict of all gle created for this invoice}
|
||||||
gle_map = {}
|
gle_map = {}
|
||||||
gle = frappe.db.get_all('GL Entry',\
|
|
||||||
{"voucher_no": ["in", [d.get("name") for d in filters["invoices"]]], 'is_cancelled': 0},
|
gle = frappe.db.get_all('GL Entry',
|
||||||
["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date"])
|
{
|
||||||
|
"voucher_no": ["in", documents],
|
||||||
|
'is_cancelled': 0,
|
||||||
|
'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]),
|
||||||
|
},
|
||||||
|
["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date", "voucher_type"],
|
||||||
|
)
|
||||||
|
|
||||||
for d in gle:
|
for d in gle:
|
||||||
if not d.voucher_no in gle_map:
|
if not d.voucher_no in gle_map:
|
||||||
@@ -201,8 +232,26 @@ def get_columns(filters):
|
|||||||
|
|
||||||
return columns
|
return columns
|
||||||
|
|
||||||
|
def get_payment_entires(filters):
|
||||||
|
filter_dict = {
|
||||||
|
'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]),
|
||||||
|
'party_type': 'Supplier',
|
||||||
|
'apply_tax_withholding_amount': 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if filters.get('purchase_invoice') or filters.get('purchase_order'):
|
||||||
|
parent = frappe.db.get_all('Payment Entry Reference',
|
||||||
|
{'reference_name': ('in', [d.get('name') for d in filters.get('invoices')])}, ['parent'])
|
||||||
|
|
||||||
|
filter_dict.update({'name': ('in', [d.get('parent') for d in parent])})
|
||||||
|
|
||||||
|
payment_entries = frappe.get_all('Payment Entry', fields=['name', 'party_name as supplier'],
|
||||||
|
filters=filter_dict)
|
||||||
|
|
||||||
|
return payment_entries
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_tds_invoices():
|
def get_tds_invoices_and_orders():
|
||||||
# fetch tds applicable supplier and fetch invoices for these suppliers
|
# fetch tds applicable supplier and fetch invoices for these suppliers
|
||||||
suppliers = [d.name for d in frappe.db.get_list("Supplier",
|
suppliers = [d.name for d in frappe.db.get_list("Supplier",
|
||||||
{"tax_withholding_category": ["!=", ""]}, ["name"])]
|
{"tax_withholding_category": ["!=", ""]}, ["name"])]
|
||||||
@@ -210,7 +259,12 @@ def get_tds_invoices():
|
|||||||
invoices = frappe.db.get_list("Purchase Invoice",
|
invoices = frappe.db.get_list("Purchase Invoice",
|
||||||
{"supplier": ["in", suppliers]}, ["name", "supplier"])
|
{"supplier": ["in", suppliers]}, ["name", "supplier"])
|
||||||
|
|
||||||
|
orders = frappe.db.get_list("Purchase Order",
|
||||||
|
{"supplier": ["in", suppliers]}, ["name", "supplier"])
|
||||||
|
|
||||||
|
invoices = invoices + orders
|
||||||
invoices = [d for d in invoices if d.supplier]
|
invoices = [d for d in invoices if d.supplier]
|
||||||
|
|
||||||
frappe.cache().hset("invoices", frappe.session.user, invoices)
|
frappe.cache().hset("invoices", frappe.session.user, invoices)
|
||||||
|
|
||||||
return invoices
|
return invoices
|
||||||
|
|||||||
@@ -472,7 +472,8 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False):
|
|||||||
"total_amount": d.grand_total,
|
"total_amount": d.grand_total,
|
||||||
"outstanding_amount": d.outstanding_amount,
|
"outstanding_amount": d.outstanding_amount,
|
||||||
"allocated_amount": d.allocated_amount,
|
"allocated_amount": d.allocated_amount,
|
||||||
"exchange_rate": d.exchange_rate
|
"exchange_rate": d.exchange_rate if not d.exchange_gain_loss else payment_entry.get_exchange_rate(),
|
||||||
|
"exchange_gain_loss": d.exchange_gain_loss # only populated from invoice in case of advance allocation
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.voucher_detail_no:
|
if d.voucher_detail_no:
|
||||||
@@ -498,12 +499,15 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False):
|
|||||||
payment_entry.set_amounts()
|
payment_entry.set_amounts()
|
||||||
|
|
||||||
if d.difference_amount and d.difference_account:
|
if d.difference_amount and d.difference_account:
|
||||||
payment_entry.set_gain_or_loss(account_details={
|
account_details = {
|
||||||
'account': d.difference_account,
|
'account': d.difference_account,
|
||||||
'cost_center': payment_entry.cost_center or frappe.get_cached_value('Company',
|
'cost_center': payment_entry.cost_center or frappe.get_cached_value('Company',
|
||||||
payment_entry.company, "cost_center"),
|
payment_entry.company, "cost_center")
|
||||||
'amount': d.difference_amount
|
}
|
||||||
})
|
if d.difference_amount:
|
||||||
|
account_details['amount'] = d.difference_amount
|
||||||
|
|
||||||
|
payment_entry.set_gain_or_loss(account_details=account_details)
|
||||||
|
|
||||||
if not do_not_save:
|
if not do_not_save:
|
||||||
payment_entry.save(ignore_permissions=True)
|
payment_entry.save(ignore_permissions=True)
|
||||||
@@ -784,7 +788,7 @@ def get_children(doctype, parent, company, is_root=False):
|
|||||||
return acc
|
return acc
|
||||||
|
|
||||||
def create_payment_gateway_account(gateway, payment_channel="Email"):
|
def create_payment_gateway_account(gateway, payment_channel="Email"):
|
||||||
from erpnext.setup.setup_wizard.operations.company_setup import create_bank_account
|
from erpnext.setup.setup_wizard.operations.install_fixtures import create_bank_account
|
||||||
|
|
||||||
company = frappe.db.get_value("Global Defaults", None, "default_company")
|
company = frappe.db.get_value("Global Defaults", None, "default_company")
|
||||||
if not company:
|
if not company:
|
||||||
|
|||||||
@@ -707,6 +707,7 @@
|
|||||||
"link_to": "GST Settings",
|
"link_to": "GST Settings",
|
||||||
"link_type": "DocType",
|
"link_type": "DocType",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -717,6 +718,7 @@
|
|||||||
"link_to": "GST HSN Code",
|
"link_to": "GST HSN Code",
|
||||||
"link_type": "DocType",
|
"link_type": "DocType",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -727,6 +729,7 @@
|
|||||||
"link_to": "GSTR-1",
|
"link_to": "GSTR-1",
|
||||||
"link_type": "Report",
|
"link_type": "Report",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -737,6 +740,7 @@
|
|||||||
"link_to": "GSTR-2",
|
"link_to": "GSTR-2",
|
||||||
"link_type": "Report",
|
"link_type": "Report",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -747,6 +751,7 @@
|
|||||||
"link_to": "GSTR 3B Report",
|
"link_to": "GSTR 3B Report",
|
||||||
"link_type": "DocType",
|
"link_type": "DocType",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -757,6 +762,7 @@
|
|||||||
"link_to": "GST Sales Register",
|
"link_to": "GST Sales Register",
|
||||||
"link_type": "Report",
|
"link_type": "Report",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -767,6 +773,7 @@
|
|||||||
"link_to": "GST Purchase Register",
|
"link_to": "GST Purchase Register",
|
||||||
"link_type": "Report",
|
"link_type": "Report",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -777,6 +784,7 @@
|
|||||||
"link_to": "GST Itemised Sales Register",
|
"link_to": "GST Itemised Sales Register",
|
||||||
"link_type": "Report",
|
"link_type": "Report",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -787,6 +795,7 @@
|
|||||||
"link_to": "GST Itemised Purchase Register",
|
"link_to": "GST Itemised Purchase Register",
|
||||||
"link_type": "Report",
|
"link_type": "Report",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -797,6 +806,7 @@
|
|||||||
"link_to": "C-Form",
|
"link_to": "C-Form",
|
||||||
"link_type": "DocType",
|
"link_type": "DocType",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -807,6 +817,7 @@
|
|||||||
"link_to": "Lower Deduction Certificate",
|
"link_to": "Lower Deduction Certificate",
|
||||||
"link_type": "DocType",
|
"link_type": "DocType",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
|
"only_for": "India",
|
||||||
"type": "Link"
|
"type": "Link"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1065,7 +1076,7 @@
|
|||||||
"type": "Link"
|
"type": "Link"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2021-05-13 13:44:56.249888",
|
"modified": "2021-06-10 03:17:31.427945",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounting",
|
"name": "Accounting",
|
||||||
|
|||||||
@@ -82,24 +82,46 @@ frappe.ui.form.on('Asset', {
|
|||||||
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"));
|
||||||
|
|
||||||
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"));
|
||||||
|
|
||||||
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"));
|
||||||
|
|
||||||
} 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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) {
|
||||||
|
frm.add_custom_button(__("Maintain Asset"), function() {
|
||||||
|
frm.trigger("create_asset_maintenance");
|
||||||
|
}, __("Manage"));
|
||||||
|
}
|
||||||
|
|
||||||
|
frm.add_custom_button(__("Repair Asset"), function() {
|
||||||
|
frm.trigger("create_asset_repair");
|
||||||
|
}, __("Manage"));
|
||||||
|
|
||||||
|
if (frm.doc.status != 'Fully Depreciated') {
|
||||||
|
frm.add_custom_button(__("Adjust Asset Value"), function() {
|
||||||
|
frm.trigger("create_asset_adjustment");
|
||||||
|
}, __("Manage"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!frm.doc.calculate_depreciation) {
|
||||||
|
frm.add_custom_button(__("Create Depreciation Entry"), function() {
|
||||||
|
frm.trigger("make_journal_entry");
|
||||||
|
}, __("Manage"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.purchase_receipt || !frm.doc.is_existing_asset) {
|
if (frm.doc.purchase_receipt || !frm.doc.is_existing_asset) {
|
||||||
frm.add_custom_button("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,
|
||||||
@@ -107,27 +129,9 @@ frappe.ui.form.on('Asset', {
|
|||||||
"company": frm.doc.company
|
"company": frm.doc.company
|
||||||
};
|
};
|
||||||
frappe.set_route("query-report", "General Ledger");
|
frappe.set_route("query-report", "General Ledger");
|
||||||
});
|
}, __("Manage"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) {
|
|
||||||
frm.add_custom_button(__("Asset Maintenance"), function() {
|
|
||||||
frm.trigger("create_asset_maintenance");
|
|
||||||
}, __('Create'));
|
|
||||||
}
|
|
||||||
if (frm.doc.status != 'Fully Depreciated') {
|
|
||||||
frm.add_custom_button(__("Asset Value Adjustment"), function() {
|
|
||||||
frm.trigger("create_asset_adjustment");
|
|
||||||
}, __('Create'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!frm.doc.calculate_depreciation) {
|
|
||||||
frm.add_custom_button(__("Depreciation Entry"), function() {
|
|
||||||
frm.trigger("make_journal_entry");
|
|
||||||
}, __('Create'));
|
|
||||||
}
|
|
||||||
|
|
||||||
frm.page.set_inner_btn_group_as_primary(__('Create'));
|
|
||||||
frm.trigger("setup_chart");
|
frm.trigger("setup_chart");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,6 +308,20 @@ frappe.ui.form.on('Asset', {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
create_asset_repair: function(frm) {
|
||||||
|
frappe.call({
|
||||||
|
args: {
|
||||||
|
"asset": frm.doc.name,
|
||||||
|
"asset_name": frm.doc.asset_name
|
||||||
|
},
|
||||||
|
method: "erpnext.assets.doctype.asset.asset.create_asset_repair",
|
||||||
|
callback: function(r) {
|
||||||
|
var doclist = frappe.model.sync(r.message);
|
||||||
|
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
create_asset_adjustment: function(frm) {
|
create_asset_adjustment: function(frm) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
args: {
|
args: {
|
||||||
|
|||||||
@@ -502,7 +502,7 @@
|
|||||||
"link_fieldname": "asset"
|
"link_fieldname": "asset"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2021-01-22 12:38:59.091510",
|
"modified": "2021-06-24 14:58:51.097908",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset",
|
"name": "Asset",
|
||||||
|
|||||||
@@ -168,17 +168,24 @@ class Asset(AccountsController):
|
|||||||
d.precision("rate_of_depreciation"))
|
d.precision("rate_of_depreciation"))
|
||||||
|
|
||||||
def make_depreciation_schedule(self):
|
def make_depreciation_schedule(self):
|
||||||
if 'Manual' not in [d.depreciation_method for d in self.finance_books]:
|
if 'Manual' not in [d.depreciation_method for d in self.finance_books] and not self.schedules:
|
||||||
self.schedules = []
|
self.schedules = []
|
||||||
|
|
||||||
if self.get("schedules") or not self.available_for_use_date:
|
if not self.available_for_use_date:
|
||||||
return
|
return
|
||||||
|
|
||||||
for d in self.get('finance_books'):
|
for d in self.get('finance_books'):
|
||||||
self.validate_asset_finance_books(d)
|
self.validate_asset_finance_books(d)
|
||||||
|
|
||||||
value_after_depreciation = (flt(self.gross_purchase_amount) -
|
start = self.clear_depreciation_schedule()
|
||||||
flt(self.opening_accumulated_depreciation))
|
|
||||||
|
# value_after_depreciation - current Asset value
|
||||||
|
if d.value_after_depreciation:
|
||||||
|
value_after_depreciation = (flt(d.value_after_depreciation) -
|
||||||
|
flt(self.opening_accumulated_depreciation))
|
||||||
|
else:
|
||||||
|
value_after_depreciation = (flt(self.gross_purchase_amount) -
|
||||||
|
flt(self.opening_accumulated_depreciation))
|
||||||
|
|
||||||
d.value_after_depreciation = value_after_depreciation
|
d.value_after_depreciation = value_after_depreciation
|
||||||
|
|
||||||
@@ -191,7 +198,7 @@ class Asset(AccountsController):
|
|||||||
number_of_pending_depreciations += 1
|
number_of_pending_depreciations += 1
|
||||||
|
|
||||||
skip_row = False
|
skip_row = False
|
||||||
for n in range(number_of_pending_depreciations):
|
for n in range(start, 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
|
||||||
|
|
||||||
@@ -216,11 +223,13 @@ class Asset(AccountsController):
|
|||||||
|
|
||||||
# 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:
|
||||||
to_date = add_months(self.available_for_use_date,
|
if not self.flags.increase_in_asset_life:
|
||||||
n * cint(d.frequency_of_depreciation))
|
# In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
|
||||||
|
self.to_date = add_months(self.available_for_use_date,
|
||||||
|
n * cint(d.frequency_of_depreciation))
|
||||||
|
|
||||||
depreciation_amount, days, months = self.get_pro_rata_amt(d,
|
depreciation_amount, days, months = self.get_pro_rata_amt(d,
|
||||||
depreciation_amount, schedule_date, to_date)
|
depreciation_amount, schedule_date, self.to_date)
|
||||||
|
|
||||||
monthly_schedule_date = add_months(schedule_date, 1)
|
monthly_schedule_date = add_months(schedule_date, 1)
|
||||||
|
|
||||||
@@ -284,10 +293,23 @@ class Asset(AccountsController):
|
|||||||
"finance_book_id": d.idx
|
"finance_book_id": d.idx
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# used when depreciation schedule needs to be modified due to increase in asset life
|
||||||
|
def clear_depreciation_schedule(self):
|
||||||
|
start = 0
|
||||||
|
for n in range(len(self.schedules)):
|
||||||
|
if not self.schedules[n].journal_entry:
|
||||||
|
del self.schedules[n:]
|
||||||
|
start = n
|
||||||
|
break
|
||||||
|
return start
|
||||||
|
|
||||||
|
|
||||||
|
# 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
|
days = date_diff(row.depreciation_start_date, self.available_for_use_date) + 1
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
if days < total_days:
|
if days < total_days:
|
||||||
@@ -346,11 +368,12 @@ class Asset(AccountsController):
|
|||||||
if d.finance_book_id not in finance_books:
|
if d.finance_book_id not in finance_books:
|
||||||
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
|
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
|
||||||
value_after_depreciation = flt(self.get_value_after_depreciation(d.finance_book_id))
|
value_after_depreciation = flt(self.get_value_after_depreciation(d.finance_book_id))
|
||||||
finance_books.append(d.finance_book_id)
|
finance_books.append(int(d.finance_book_id))
|
||||||
|
|
||||||
depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
|
depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
|
||||||
value_after_depreciation -= flt(depreciation_amount)
|
value_after_depreciation -= flt(depreciation_amount)
|
||||||
|
|
||||||
|
# for the last row, if depreciation method = Straight Line
|
||||||
if straight_line_idx and i == max(straight_line_idx) - 1:
|
if straight_line_idx and i == max(straight_line_idx) - 1:
|
||||||
book = self.get('finance_books')[cint(d.finance_book_id) - 1]
|
book = self.get('finance_books')[cint(d.finance_book_id) - 1]
|
||||||
depreciation_amount += flt(value_after_depreciation -
|
depreciation_amount += flt(value_after_depreciation -
|
||||||
@@ -625,9 +648,18 @@ def create_asset_maintenance(asset, item_code, item_name, asset_category, compan
|
|||||||
})
|
})
|
||||||
return asset_maintenance
|
return asset_maintenance
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def create_asset_repair(asset, asset_name):
|
||||||
|
asset_repair = frappe.new_doc("Asset Repair")
|
||||||
|
asset_repair.update({
|
||||||
|
"asset": asset,
|
||||||
|
"asset_name": asset_name
|
||||||
|
})
|
||||||
|
return asset_repair
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_asset_adjustment(asset, asset_category, company):
|
def create_asset_adjustment(asset, asset_category, company):
|
||||||
asset_maintenance = frappe.new_doc("Asset Value Adjustment")
|
asset_maintenance = frappe.get_doc("Asset Value Adjustment")
|
||||||
asset_maintenance.update({
|
asset_maintenance.update({
|
||||||
"asset": asset,
|
"asset": asset,
|
||||||
"company": company,
|
"company": company,
|
||||||
@@ -757,8 +789,15 @@ def get_depreciation_amount(asset, depreciable_value, row):
|
|||||||
depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
|
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"):
|
||||||
depreciation_amount = (flt(row.value_after_depreciation) -
|
# if the Depreciation Schedule is being prepared for the first time
|
||||||
flt(row.expected_value_after_useful_life)) / depreciation_left
|
if not asset.flags.increase_in_asset_life:
|
||||||
|
depreciation_amount = (flt(row.value_after_depreciation) -
|
||||||
|
flt(row.expected_value_after_useful_life)) / depreciation_left
|
||||||
|
|
||||||
|
# if the Depreciation Schedule is being modified after Asset Repair
|
||||||
|
else:
|
||||||
|
depreciation_amount = (flt(row.value_after_depreciation) -
|
||||||
|
flt(row.expected_value_after_useful_life)) / (date_diff(asset.to_date, asset.available_for_use_date) / 365)
|
||||||
else:
|
else:
|
||||||
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
|
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
|
||||||
|
|
||||||
|
|||||||
@@ -176,22 +176,34 @@ def restore_asset(asset_name):
|
|||||||
|
|
||||||
asset.set_status()
|
asset.set_status()
|
||||||
|
|
||||||
@frappe.whitelist()
|
def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
|
||||||
|
fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount, disposal_account, value_after_depreciation = \
|
||||||
|
get_asset_details(asset, finance_book)
|
||||||
|
|
||||||
|
gl_entries = [
|
||||||
|
{
|
||||||
|
"account": fixed_asset_account,
|
||||||
|
"debit_in_account_currency": asset.gross_purchase_amount,
|
||||||
|
"debit": asset.gross_purchase_amount,
|
||||||
|
"cost_center": depreciation_cost_center
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"account": accumulated_depr_account,
|
||||||
|
"credit_in_account_currency": accumulated_depr_amount,
|
||||||
|
"credit": accumulated_depr_amount,
|
||||||
|
"cost_center": depreciation_cost_center
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount))
|
||||||
|
if profit_amount:
|
||||||
|
get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
|
||||||
|
|
||||||
|
return gl_entries
|
||||||
|
|
||||||
def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None):
|
def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None):
|
||||||
fixed_asset_account, accumulated_depr_account, depr_expense_account = get_depreciation_accounts(asset)
|
fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount, disposal_account, value_after_depreciation = \
|
||||||
disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company)
|
get_asset_details(asset, finance_book)
|
||||||
depreciation_cost_center = asset.cost_center or depreciation_cost_center
|
|
||||||
|
|
||||||
idx = 1
|
|
||||||
if finance_book:
|
|
||||||
for d in asset.finance_books:
|
|
||||||
if d.finance_book == finance_book:
|
|
||||||
idx = d.idx
|
|
||||||
break
|
|
||||||
|
|
||||||
value_after_depreciation = (asset.finance_books[idx - 1].value_after_depreciation
|
|
||||||
if asset.calculate_depreciation else asset.value_after_depreciation)
|
|
||||||
accumulated_depr_amount = flt(asset.gross_purchase_amount) - flt(value_after_depreciation)
|
|
||||||
|
|
||||||
gl_entries = [
|
gl_entries = [
|
||||||
{
|
{
|
||||||
@@ -210,16 +222,37 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None)
|
|||||||
|
|
||||||
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
|
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
|
||||||
if profit_amount:
|
if profit_amount:
|
||||||
debit_or_credit = "debit" if profit_amount < 0 else "credit"
|
get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
|
||||||
gl_entries.append({
|
|
||||||
"account": disposal_account,
|
|
||||||
"cost_center": depreciation_cost_center,
|
|
||||||
debit_or_credit: abs(profit_amount),
|
|
||||||
debit_or_credit + "_in_account_currency": abs(profit_amount)
|
|
||||||
})
|
|
||||||
|
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
|
def get_asset_details(asset, finance_book=None):
|
||||||
|
fixed_asset_account, accumulated_depr_account, depr_expense_account = get_depreciation_accounts(asset)
|
||||||
|
disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company)
|
||||||
|
depreciation_cost_center = asset.cost_center or depreciation_cost_center
|
||||||
|
|
||||||
|
idx = 1
|
||||||
|
if finance_book:
|
||||||
|
for d in asset.finance_books:
|
||||||
|
if d.finance_book == finance_book:
|
||||||
|
idx = d.idx
|
||||||
|
break
|
||||||
|
|
||||||
|
value_after_depreciation = (asset.finance_books[idx - 1].value_after_depreciation
|
||||||
|
if asset.calculate_depreciation else asset.value_after_depreciation)
|
||||||
|
accumulated_depr_amount = flt(asset.gross_purchase_amount) - flt(value_after_depreciation)
|
||||||
|
|
||||||
|
return fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount, disposal_account, value_after_depreciation
|
||||||
|
|
||||||
|
def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center):
|
||||||
|
debit_or_credit = "debit" if profit_amount < 0 else "credit"
|
||||||
|
gl_entries.append({
|
||||||
|
"account": disposal_account,
|
||||||
|
"cost_center": depreciation_cost_center,
|
||||||
|
debit_or_credit: abs(profit_amount),
|
||||||
|
debit_or_credit + "_in_account_currency": abs(profit_amount)
|
||||||
|
})
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_disposal_account_and_cost_center(company):
|
def get_disposal_account_and_cost_center(company):
|
||||||
disposal_account, depreciation_cost_center = frappe.get_cached_value('Company', company,
|
disposal_account, depreciation_cost_center = frappe.get_cached_value('Company', company,
|
||||||
|
|||||||
@@ -125,7 +125,6 @@ class TestAsset(unittest.TestCase):
|
|||||||
"frequency_of_depreciation": 12,
|
"frequency_of_depreciation": 12,
|
||||||
"depreciation_start_date": "2030-12-31"
|
"depreciation_start_date": "2030-12-31"
|
||||||
})
|
})
|
||||||
asset.insert()
|
|
||||||
self.assertEqual(asset.status, "Draft")
|
self.assertEqual(asset.status, "Draft")
|
||||||
asset.save()
|
asset.save()
|
||||||
expected_schedules = [
|
expected_schedules = [
|
||||||
@@ -154,9 +153,8 @@ class TestAsset(unittest.TestCase):
|
|||||||
"frequency_of_depreciation": 12,
|
"frequency_of_depreciation": 12,
|
||||||
"depreciation_start_date": '2030-12-31'
|
"depreciation_start_date": '2030-12-31'
|
||||||
})
|
})
|
||||||
asset.insert()
|
|
||||||
self.assertEqual(asset.status, "Draft")
|
|
||||||
asset.save()
|
asset.save()
|
||||||
|
self.assertEqual(asset.status, "Draft")
|
||||||
|
|
||||||
expected_schedules = [
|
expected_schedules = [
|
||||||
['2030-12-31', 66667.00, 66667.00],
|
['2030-12-31', 66667.00, 66667.00],
|
||||||
@@ -185,7 +183,7 @@ class TestAsset(unittest.TestCase):
|
|||||||
"frequency_of_depreciation": 12,
|
"frequency_of_depreciation": 12,
|
||||||
"depreciation_start_date": "2030-12-31"
|
"depreciation_start_date": "2030-12-31"
|
||||||
})
|
})
|
||||||
asset.insert()
|
asset.save()
|
||||||
self.assertEqual(asset.status, "Draft")
|
self.assertEqual(asset.status, "Draft")
|
||||||
|
|
||||||
expected_schedules = [
|
expected_schedules = [
|
||||||
@@ -216,7 +214,6 @@ class TestAsset(unittest.TestCase):
|
|||||||
"depreciation_start_date": "2030-12-31"
|
"depreciation_start_date": "2030-12-31"
|
||||||
})
|
})
|
||||||
|
|
||||||
asset.insert()
|
|
||||||
asset.save()
|
asset.save()
|
||||||
|
|
||||||
expected_schedules = [
|
expected_schedules = [
|
||||||
@@ -247,7 +244,6 @@ class TestAsset(unittest.TestCase):
|
|||||||
"frequency_of_depreciation": 10,
|
"frequency_of_depreciation": 10,
|
||||||
"depreciation_start_date": "2020-12-31"
|
"depreciation_start_date": "2020-12-31"
|
||||||
})
|
})
|
||||||
asset.insert()
|
|
||||||
asset.submit()
|
asset.submit()
|
||||||
asset.load_from_db()
|
asset.load_from_db()
|
||||||
self.assertEqual(asset.status, "Submitted")
|
self.assertEqual(asset.status, "Submitted")
|
||||||
@@ -350,7 +346,6 @@ class TestAsset(unittest.TestCase):
|
|||||||
"frequency_of_depreciation": 10,
|
"frequency_of_depreciation": 10,
|
||||||
"depreciation_start_date": "2020-12-31"
|
"depreciation_start_date": "2020-12-31"
|
||||||
})
|
})
|
||||||
asset.insert()
|
|
||||||
asset.submit()
|
asset.submit()
|
||||||
post_depreciation_entries(date="2021-01-01")
|
post_depreciation_entries(date="2021-01-01")
|
||||||
|
|
||||||
@@ -380,7 +375,6 @@ class TestAsset(unittest.TestCase):
|
|||||||
"total_number_of_depreciations": 10,
|
"total_number_of_depreciations": 10,
|
||||||
"frequency_of_depreciation": 1
|
"frequency_of_depreciation": 1
|
||||||
})
|
})
|
||||||
asset.insert()
|
|
||||||
asset.submit()
|
asset.submit()
|
||||||
|
|
||||||
post_depreciation_entries(date=add_months('2020-01-01', 4))
|
post_depreciation_entries(date=add_months('2020-01-01', 4))
|
||||||
@@ -424,7 +418,6 @@ class TestAsset(unittest.TestCase):
|
|||||||
"frequency_of_depreciation": 10,
|
"frequency_of_depreciation": 10,
|
||||||
"depreciation_start_date": "2020-12-31"
|
"depreciation_start_date": "2020-12-31"
|
||||||
})
|
})
|
||||||
asset.insert()
|
|
||||||
asset.submit()
|
asset.submit()
|
||||||
post_depreciation_entries(date="2021-01-01")
|
post_depreciation_entries(date="2021-01-01")
|
||||||
|
|
||||||
@@ -468,7 +461,7 @@ class TestAsset(unittest.TestCase):
|
|||||||
"total_number_of_depreciations": 3,
|
"total_number_of_depreciations": 3,
|
||||||
"frequency_of_depreciation": 10
|
"frequency_of_depreciation": 10
|
||||||
})
|
})
|
||||||
asset.insert()
|
asset.save()
|
||||||
accumulated_depreciation_after_full_schedule = \
|
accumulated_depreciation_after_full_schedule = \
|
||||||
max(d.accumulated_depreciation_amount for d in asset.get("schedules"))
|
max(d.accumulated_depreciation_amount for d in asset.get("schedules"))
|
||||||
|
|
||||||
@@ -699,7 +692,7 @@ def create_asset(**args):
|
|||||||
"item_code": args.item_code or "Macbook Pro",
|
"item_code": args.item_code or "Macbook Pro",
|
||||||
"company": args.company or"_Test Company",
|
"company": args.company or"_Test Company",
|
||||||
"purchase_date": "2015-01-01",
|
"purchase_date": "2015-01-01",
|
||||||
"calculate_depreciation": 0,
|
"calculate_depreciation": args.calculate_depreciation or 0,
|
||||||
"gross_purchase_amount": 100000,
|
"gross_purchase_amount": 100000,
|
||||||
"purchase_receipt_amount": 100000,
|
"purchase_receipt_amount": 100000,
|
||||||
"expected_value_after_useful_life": 10000,
|
"expected_value_after_useful_life": 10000,
|
||||||
@@ -707,9 +700,16 @@ def create_asset(**args):
|
|||||||
"available_for_use_date": "2020-06-06",
|
"available_for_use_date": "2020-06-06",
|
||||||
"location": "Test Location",
|
"location": "Test Location",
|
||||||
"asset_owner": "Company",
|
"asset_owner": "Company",
|
||||||
"is_existing_asset": args.is_existing_asset or 0
|
"is_existing_asset": 1
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if asset.calculate_depreciation:
|
||||||
|
asset.append("finance_books", {
|
||||||
|
"depreciation_method": "Straight Line",
|
||||||
|
"frequency_of_depreciation": 12,
|
||||||
|
"total_number_of_depreciations": 5
|
||||||
|
})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
asset.save()
|
asset.save()
|
||||||
except frappe.DuplicateEntryError:
|
except frappe.DuplicateEntryError:
|
||||||
|
|||||||
@@ -67,7 +67,6 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "value_after_depreciation",
|
"fieldname": "value_after_depreciation",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 1,
|
|
||||||
"label": "Value After Depreciation",
|
"label": "Value After Depreciation",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Company:company:default_currency",
|
"options": "Company:company:default_currency",
|
||||||
@@ -85,7 +84,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-11-05 16:30:09.213479",
|
"modified": "2021-06-17 12:59:05.743683",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Finance Book",
|
"name": "Asset Finance Book",
|
||||||
|
|||||||
@@ -2,6 +2,45 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Asset Repair', {
|
frappe.ui.form.on('Asset Repair', {
|
||||||
|
setup: function(frm) {
|
||||||
|
frm.fields_dict.cost_center.get_query = function(doc) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'is_group': 0,
|
||||||
|
'company': doc.company
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
frm.fields_dict.project.get_query = function(doc) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'company': doc.company
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
frm.fields_dict.warehouse.get_query = function(doc) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'is_group': 0,
|
||||||
|
'company': doc.company
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh: function(frm) {
|
||||||
|
if (frm.doc.docstatus) {
|
||||||
|
frm.add_custom_button("View General Ledger", function() {
|
||||||
|
frappe.route_options = {
|
||||||
|
"voucher_no": frm.doc.name
|
||||||
|
};
|
||||||
|
frappe.set_route("query-report", "General Ledger");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
repair_status: (frm) => {
|
repair_status: (frm) => {
|
||||||
if (frm.doc.completion_date && frm.doc.repair_status == "Completed") {
|
if (frm.doc.completion_date && frm.doc.repair_status == "Completed") {
|
||||||
frappe.call ({
|
frappe.call ({
|
||||||
@@ -17,5 +56,16 @@ frappe.ui.form.on('Asset Repair', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (frm.doc.repair_status == "Completed") {
|
||||||
|
frm.set_value('completion_date', frappe.datetime.now_datetime());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on('Asset Repair Consumed Item', {
|
||||||
|
consumed_quantity: function(frm, cdt, cdn) {
|
||||||
|
var row = locals[cdt][cdn];
|
||||||
|
frappe.model.set_value(cdt, cdn, 'total_value', row.consumed_quantity * row.valuation_rate);
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -7,38 +7,43 @@
|
|||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"naming_series",
|
"asset",
|
||||||
"asset_name",
|
"company",
|
||||||
"column_break_2",
|
"column_break_2",
|
||||||
"item_code",
|
"asset_name",
|
||||||
"item_name",
|
"naming_series",
|
||||||
"section_break_5",
|
"section_break_5",
|
||||||
"failure_date",
|
"failure_date",
|
||||||
"assign_to",
|
"repair_status",
|
||||||
"assign_to_name",
|
|
||||||
"column_break_6",
|
"column_break_6",
|
||||||
"completion_date",
|
"completion_date",
|
||||||
"repair_status",
|
"accounting_dimensions_section",
|
||||||
|
"cost_center",
|
||||||
|
"column_break_14",
|
||||||
|
"project",
|
||||||
|
"accounting_details",
|
||||||
"repair_cost",
|
"repair_cost",
|
||||||
|
"capitalize_repair_cost",
|
||||||
|
"stock_consumption",
|
||||||
|
"column_break_8",
|
||||||
|
"purchase_invoice",
|
||||||
|
"stock_consumption_details_section",
|
||||||
|
"warehouse",
|
||||||
|
"stock_items",
|
||||||
|
"total_repair_cost",
|
||||||
|
"stock_entry",
|
||||||
|
"asset_depreciation_details_section",
|
||||||
|
"increase_in_asset_life",
|
||||||
"section_break_9",
|
"section_break_9",
|
||||||
"description",
|
"description",
|
||||||
"column_break_9",
|
"column_break_9",
|
||||||
"actions_performed",
|
"actions_performed",
|
||||||
"section_break_17",
|
"section_break_23",
|
||||||
"downtime",
|
"downtime",
|
||||||
"column_break_19",
|
"column_break_19",
|
||||||
"amended_from"
|
"amended_from"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
|
||||||
"columns": 1,
|
|
||||||
"fieldname": "asset_name",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Asset",
|
|
||||||
"options": "Asset",
|
|
||||||
"reqd": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "naming_series",
|
"fieldname": "naming_series",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
@@ -50,18 +55,6 @@
|
|||||||
"fieldname": "column_break_2",
|
"fieldname": "column_break_2",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fetch_from": "asset_name.item_code",
|
|
||||||
"fieldname": "item_code",
|
|
||||||
"fieldtype": "Read Only",
|
|
||||||
"label": "Item Code"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fetch_from": "asset_name.item_name",
|
|
||||||
"fieldname": "item_name",
|
|
||||||
"fieldtype": "Read Only",
|
|
||||||
"label": "Item Name"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_5",
|
"fieldname": "section_break_5",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
@@ -74,33 +67,20 @@
|
|||||||
"label": "Failure Date",
|
"label": "Failure Date",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"allow_on_submit": 1,
|
|
||||||
"fieldname": "assign_to",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Assign To",
|
|
||||||
"options": "User"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 1,
|
|
||||||
"fetch_from": "assign_to.full_name",
|
|
||||||
"fieldname": "assign_to_name",
|
|
||||||
"fieldtype": "Read Only",
|
|
||||||
"label": "Assign To Name"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_6",
|
"fieldname": "column_break_6",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"depends_on": "eval:!doc.__islocal",
|
||||||
"fieldname": "completion_date",
|
"fieldname": "completion_date",
|
||||||
"fieldtype": "Datetime",
|
"fieldtype": "Datetime",
|
||||||
"label": "Completion Date"
|
"label": "Completion Date",
|
||||||
|
"no_copy": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
|
||||||
"default": "Pending",
|
"default": "Pending",
|
||||||
|
"depends_on": "eval:!doc.__islocal",
|
||||||
"fieldname": "repair_status",
|
"fieldname": "repair_status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Repair Status",
|
"label": "Repair Status",
|
||||||
@@ -116,25 +96,18 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"fieldtype": "Long Text",
|
"fieldtype": "Long Text",
|
||||||
"label": "Error Description",
|
"label": "Error Description"
|
||||||
"reqd": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_9",
|
"fieldname": "column_break_9",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
|
||||||
"fieldname": "actions_performed",
|
"fieldname": "actions_performed",
|
||||||
"fieldtype": "Long Text",
|
"fieldtype": "Long Text",
|
||||||
"label": "Actions performed"
|
"label": "Actions performed"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_17",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"allow_on_submit": 1,
|
|
||||||
"fieldname": "downtime",
|
"fieldname": "downtime",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -146,7 +119,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"default": "0",
|
||||||
"fieldname": "repair_cost",
|
"fieldname": "repair_cost",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Repair Cost"
|
"label": "Repair Cost"
|
||||||
@@ -159,12 +132,139 @@
|
|||||||
"options": "Asset Repair",
|
"options": "Asset Repair",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 1,
|
||||||
|
"fieldname": "asset",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Asset",
|
||||||
|
"options": "Asset",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "asset.asset_name",
|
||||||
|
"fieldname": "asset_name",
|
||||||
|
"fieldtype": "Read Only",
|
||||||
|
"label": "Asset Name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_8",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:!doc.__islocal",
|
||||||
|
"fieldname": "capitalize_repair_cost",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Capitalize Repair Cost"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "accounting_details",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Accounting Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "stock_items",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Stock Items",
|
||||||
|
"mandatory_depends_on": "stock_consumption",
|
||||||
|
"options": "Asset Repair Consumed Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_23",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"fieldname": "accounting_dimensions_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Accounting Dimensions"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cost_center",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Cost Center",
|
||||||
|
"options": "Cost Center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Project",
|
||||||
|
"options": "Project"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_14",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:!doc.__islocal",
|
||||||
|
"fieldname": "stock_consumption",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Stock Consumed During Repair"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "stock_consumption",
|
||||||
|
"fieldname": "stock_consumption_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Stock Consumption Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.stock_consumption && doc.total_repair_cost > 0",
|
||||||
|
"description": "Sum of Repair Cost and Value of Consumed Stock Items.",
|
||||||
|
"fieldname": "total_repair_cost",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Total Repair Cost",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "stock_consumption",
|
||||||
|
"fieldname": "warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Warehouse",
|
||||||
|
"options": "Warehouse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "capitalize_repair_cost",
|
||||||
|
"fieldname": "asset_depreciation_details_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Asset Depreciation Details"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "increase_in_asset_life",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Increase In Asset Life(Months)",
|
||||||
|
"no_copy": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:!doc.__islocal",
|
||||||
|
"fieldname": "purchase_invoice",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Purchase Invoice",
|
||||||
|
"mandatory_depends_on": "eval: doc.repair_status == 'Completed' && doc.repair_cost > 0",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Purchase Invoice"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "asset.company",
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "stock_entry",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Stock Entry",
|
||||||
|
"options": "Stock Entry",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-01-22 15:08:12.495850",
|
"modified": "2021-06-25 13:14:38.307723",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Repair",
|
"name": "Asset Repair",
|
||||||
@@ -203,6 +303,7 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"title_field": "asset_name",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_seen": 1
|
"track_seen": 1
|
||||||
}
|
}
|
||||||
@@ -5,14 +5,250 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import time_diff_in_hours
|
from frappe.utils import time_diff_in_hours, getdate, add_months, flt, cint
|
||||||
from frappe.model.document import Document
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
|
from erpnext.assets.doctype.asset.asset import get_asset_account
|
||||||
|
from erpnext.controllers.accounts_controller import AccountsController
|
||||||
|
|
||||||
class AssetRepair(Document):
|
class AssetRepair(AccountsController):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if self.repair_status == "Completed" and not self.completion_date:
|
self.asset_doc = frappe.get_doc('Asset', self.asset)
|
||||||
frappe.throw(_("Please select Completion Date for Completed Repair"))
|
self.update_status()
|
||||||
|
|
||||||
|
if self.get('stock_items'):
|
||||||
|
self.set_total_value()
|
||||||
|
self.calculate_total_repair_cost()
|
||||||
|
|
||||||
|
def update_status(self):
|
||||||
|
if self.repair_status == 'Pending':
|
||||||
|
frappe.db.set_value('Asset', self.asset, 'status', 'Out of Order')
|
||||||
|
else:
|
||||||
|
self.asset_doc.set_status()
|
||||||
|
|
||||||
|
def set_total_value(self):
|
||||||
|
for item in self.get('stock_items'):
|
||||||
|
item.total_value = flt(item.valuation_rate) * flt(item.consumed_quantity)
|
||||||
|
|
||||||
|
def calculate_total_repair_cost(self):
|
||||||
|
self.total_repair_cost = flt(self.repair_cost)
|
||||||
|
|
||||||
|
total_value_of_stock_consumed = self.get_total_value_of_stock_consumed()
|
||||||
|
self.total_repair_cost += total_value_of_stock_consumed
|
||||||
|
|
||||||
|
def before_submit(self):
|
||||||
|
self.check_repair_status()
|
||||||
|
|
||||||
|
if self.get('stock_consumption') or self.get('capitalize_repair_cost'):
|
||||||
|
self.increase_asset_value()
|
||||||
|
if self.get('stock_consumption'):
|
||||||
|
self.check_for_stock_items_and_warehouse()
|
||||||
|
self.decrease_stock_quantity()
|
||||||
|
if self.get('capitalize_repair_cost'):
|
||||||
|
self.make_gl_entries()
|
||||||
|
if frappe.db.get_value('Asset', self.asset, 'calculate_depreciation') and self.increase_in_asset_life:
|
||||||
|
self.modify_depreciation_schedule()
|
||||||
|
|
||||||
|
self.asset_doc.flags.ignore_validate_update_after_submit = True
|
||||||
|
self.asset_doc.prepare_depreciation_data()
|
||||||
|
self.asset_doc.save()
|
||||||
|
|
||||||
|
def before_cancel(self):
|
||||||
|
self.asset_doc = frappe.get_doc('Asset', self.asset)
|
||||||
|
|
||||||
|
if self.get('stock_consumption') or self.get('capitalize_repair_cost'):
|
||||||
|
self.decrease_asset_value()
|
||||||
|
if self.get('stock_consumption'):
|
||||||
|
self.increase_stock_quantity()
|
||||||
|
if self.get('capitalize_repair_cost'):
|
||||||
|
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
|
||||||
|
self.make_gl_entries(cancel=True)
|
||||||
|
if frappe.db.get_value('Asset', self.asset, 'calculate_depreciation') and self.increase_in_asset_life:
|
||||||
|
self.revert_depreciation_schedule_on_cancellation()
|
||||||
|
|
||||||
|
self.asset_doc.flags.ignore_validate_update_after_submit = True
|
||||||
|
self.asset_doc.prepare_depreciation_data()
|
||||||
|
self.asset_doc.save()
|
||||||
|
|
||||||
|
def check_repair_status(self):
|
||||||
|
if self.repair_status == "Pending":
|
||||||
|
frappe.throw(_("Please update Repair Status."))
|
||||||
|
|
||||||
|
def check_for_stock_items_and_warehouse(self):
|
||||||
|
if not self.get('stock_items'):
|
||||||
|
frappe.throw(_("Please enter Stock Items consumed during the Repair."), title=_("Missing Items"))
|
||||||
|
if not self.warehouse:
|
||||||
|
frappe.throw(_("Please enter Warehouse from which Stock Items consumed during the Repair were taken."), title=_("Missing Warehouse"))
|
||||||
|
|
||||||
|
def increase_asset_value(self):
|
||||||
|
total_value_of_stock_consumed = self.get_total_value_of_stock_consumed()
|
||||||
|
|
||||||
|
if self.asset_doc.calculate_depreciation:
|
||||||
|
for row in self.asset_doc.finance_books:
|
||||||
|
row.value_after_depreciation += total_value_of_stock_consumed
|
||||||
|
|
||||||
|
if self.capitalize_repair_cost:
|
||||||
|
row.value_after_depreciation += self.repair_cost
|
||||||
|
|
||||||
|
def decrease_asset_value(self):
|
||||||
|
total_value_of_stock_consumed = self.get_total_value_of_stock_consumed()
|
||||||
|
|
||||||
|
if self.asset_doc.calculate_depreciation:
|
||||||
|
for row in self.asset_doc.finance_books:
|
||||||
|
row.value_after_depreciation -= total_value_of_stock_consumed
|
||||||
|
|
||||||
|
if self.capitalize_repair_cost:
|
||||||
|
row.value_after_depreciation -= self.repair_cost
|
||||||
|
|
||||||
|
def get_total_value_of_stock_consumed(self):
|
||||||
|
total_value_of_stock_consumed = 0
|
||||||
|
if self.get('stock_consumption'):
|
||||||
|
for item in self.get('stock_items'):
|
||||||
|
total_value_of_stock_consumed += item.total_value
|
||||||
|
|
||||||
|
return total_value_of_stock_consumed
|
||||||
|
|
||||||
|
def decrease_stock_quantity(self):
|
||||||
|
stock_entry = frappe.get_doc({
|
||||||
|
"doctype": "Stock Entry",
|
||||||
|
"stock_entry_type": "Material Issue",
|
||||||
|
"company": self.company
|
||||||
|
})
|
||||||
|
|
||||||
|
for stock_item in self.get('stock_items'):
|
||||||
|
stock_entry.append('items', {
|
||||||
|
"s_warehouse": self.warehouse,
|
||||||
|
"item_code": stock_item.item,
|
||||||
|
"qty": stock_item.consumed_quantity,
|
||||||
|
"basic_rate": stock_item.valuation_rate
|
||||||
|
})
|
||||||
|
|
||||||
|
stock_entry.insert()
|
||||||
|
stock_entry.submit()
|
||||||
|
|
||||||
|
self.db_set('stock_entry', stock_entry.name)
|
||||||
|
|
||||||
|
def increase_stock_quantity(self):
|
||||||
|
stock_entry = frappe.get_doc('Stock Entry', self.stock_entry)
|
||||||
|
stock_entry.flags.ignore_links = True
|
||||||
|
stock_entry.cancel()
|
||||||
|
|
||||||
|
def make_gl_entries(self, cancel=False):
|
||||||
|
if flt(self.repair_cost) > 0:
|
||||||
|
gl_entries = self.get_gl_entries()
|
||||||
|
make_gl_entries(gl_entries, cancel)
|
||||||
|
|
||||||
|
def get_gl_entries(self):
|
||||||
|
gl_entries = []
|
||||||
|
repair_and_maintenance_account = frappe.db.get_value('Company', self.company, 'repair_and_maintenance_account')
|
||||||
|
fixed_asset_account = get_asset_account("fixed_asset_account", asset=self.asset, company=self.company)
|
||||||
|
expense_account = frappe.get_doc('Purchase Invoice', self.purchase_invoice).items[0].expense_account
|
||||||
|
|
||||||
|
gl_entries.append(
|
||||||
|
self.get_gl_dict({
|
||||||
|
"account": expense_account,
|
||||||
|
"credit": self.repair_cost,
|
||||||
|
"credit_in_account_currency": self.repair_cost,
|
||||||
|
"against": repair_and_maintenance_account,
|
||||||
|
"voucher_type": self.doctype,
|
||||||
|
"voucher_no": self.name,
|
||||||
|
"cost_center": self.cost_center,
|
||||||
|
"posting_date": getdate(),
|
||||||
|
"company": self.company
|
||||||
|
}, item=self)
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.get('stock_consumption'):
|
||||||
|
# creating GL Entries for each row in Stock Items based on the Stock Entry created for it
|
||||||
|
stock_entry = frappe.get_doc('Stock Entry', self.stock_entry)
|
||||||
|
for item in stock_entry.items:
|
||||||
|
gl_entries.append(
|
||||||
|
self.get_gl_dict({
|
||||||
|
"account": item.expense_account,
|
||||||
|
"credit": item.amount,
|
||||||
|
"credit_in_account_currency": item.amount,
|
||||||
|
"against": repair_and_maintenance_account,
|
||||||
|
"voucher_type": self.doctype,
|
||||||
|
"voucher_no": self.name,
|
||||||
|
"cost_center": self.cost_center,
|
||||||
|
"posting_date": getdate(),
|
||||||
|
"company": self.company
|
||||||
|
}, item=self)
|
||||||
|
)
|
||||||
|
|
||||||
|
gl_entries.append(
|
||||||
|
self.get_gl_dict({
|
||||||
|
"account": fixed_asset_account,
|
||||||
|
"debit": self.total_repair_cost,
|
||||||
|
"debit_in_account_currency": self.total_repair_cost,
|
||||||
|
"against": expense_account,
|
||||||
|
"voucher_type": self.doctype,
|
||||||
|
"voucher_no": self.name,
|
||||||
|
"cost_center": self.cost_center,
|
||||||
|
"posting_date": getdate(),
|
||||||
|
"against_voucher_type": "Purchase Invoice",
|
||||||
|
"against_voucher": self.purchase_invoice,
|
||||||
|
"company": self.company
|
||||||
|
}, item=self)
|
||||||
|
)
|
||||||
|
|
||||||
|
return gl_entries
|
||||||
|
|
||||||
|
def modify_depreciation_schedule(self):
|
||||||
|
for row in self.asset_doc.finance_books:
|
||||||
|
row.total_number_of_depreciations += self.increase_in_asset_life/row.frequency_of_depreciation
|
||||||
|
|
||||||
|
self.asset_doc.flags.increase_in_asset_life = False
|
||||||
|
extra_months = self.increase_in_asset_life % row.frequency_of_depreciation
|
||||||
|
if extra_months != 0:
|
||||||
|
self.calculate_last_schedule_date(self.asset_doc, row, extra_months)
|
||||||
|
|
||||||
|
# to help modify depreciation schedule when increase_in_asset_life is not a multiple of frequency_of_depreciation
|
||||||
|
def calculate_last_schedule_date(self, asset, row, extra_months):
|
||||||
|
asset.flags.increase_in_asset_life = True
|
||||||
|
number_of_pending_depreciations = cint(row.total_number_of_depreciations) - \
|
||||||
|
cint(asset.number_of_depreciations_booked)
|
||||||
|
|
||||||
|
# the Schedule Date in the final row of the old Depreciation Schedule
|
||||||
|
last_schedule_date = asset.schedules[len(asset.schedules)-1].schedule_date
|
||||||
|
|
||||||
|
# the Schedule Date in the final row of the new Depreciation Schedule
|
||||||
|
asset.to_date = add_months(last_schedule_date, extra_months)
|
||||||
|
|
||||||
|
# the latest possible date at which the depreciation can occur, without increasing the Total Number of Depreciations
|
||||||
|
# if depreciations happen yearly and the Depreciation Posting Date is 01-01-2020, this could be 01-01-2021, 01-01-2022...
|
||||||
|
schedule_date = add_months(row.depreciation_start_date,
|
||||||
|
number_of_pending_depreciations * cint(row.frequency_of_depreciation))
|
||||||
|
|
||||||
|
if asset.to_date > schedule_date:
|
||||||
|
row.total_number_of_depreciations += 1
|
||||||
|
|
||||||
|
def revert_depreciation_schedule_on_cancellation(self):
|
||||||
|
for row in self.asset_doc.finance_books:
|
||||||
|
row.total_number_of_depreciations -= self.increase_in_asset_life/row.frequency_of_depreciation
|
||||||
|
|
||||||
|
self.asset_doc.flags.increase_in_asset_life = False
|
||||||
|
extra_months = self.increase_in_asset_life % row.frequency_of_depreciation
|
||||||
|
if extra_months != 0:
|
||||||
|
self.calculate_last_schedule_date_before_modification(self.asset_doc, row, extra_months)
|
||||||
|
|
||||||
|
def calculate_last_schedule_date_before_modification(self, asset, row, extra_months):
|
||||||
|
asset.flags.increase_in_asset_life = True
|
||||||
|
number_of_pending_depreciations = cint(row.total_number_of_depreciations) - \
|
||||||
|
cint(asset.number_of_depreciations_booked)
|
||||||
|
|
||||||
|
# the Schedule Date in the final row of the modified Depreciation Schedule
|
||||||
|
last_schedule_date = asset.schedules[len(asset.schedules)-1].schedule_date
|
||||||
|
|
||||||
|
# the Schedule Date in the final row of the original Depreciation Schedule
|
||||||
|
asset.to_date = add_months(last_schedule_date, -extra_months)
|
||||||
|
|
||||||
|
# the latest possible date at which the depreciation can occur, without decreasing the Total Number of Depreciations
|
||||||
|
# if depreciations happen yearly and the Depreciation Posting Date is 01-01-2020, this could be 01-01-2021, 01-01-2022...
|
||||||
|
schedule_date = add_months(row.depreciation_start_date,
|
||||||
|
(number_of_pending_depreciations - 1) * cint(row.frequency_of_depreciation))
|
||||||
|
|
||||||
|
if asset.to_date < schedule_date:
|
||||||
|
row.total_number_of_depreciations -= 1
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_downtime(failure_date, completion_date):
|
def get_downtime(failure_date, completion_date):
|
||||||
|
|||||||
@@ -2,8 +2,167 @@
|
|||||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.utils import nowdate, flt
|
||||||
import unittest
|
import unittest
|
||||||
|
from erpnext.assets.doctype.asset.test_asset import create_asset_data, create_asset, set_depreciation_settings_in_company
|
||||||
|
|
||||||
class TestAssetRepair(unittest.TestCase):
|
class TestAssetRepair(unittest.TestCase):
|
||||||
pass
|
def setUp(self):
|
||||||
|
set_depreciation_settings_in_company()
|
||||||
|
create_asset_data()
|
||||||
|
frappe.db.sql("delete from `tabTax Rule`")
|
||||||
|
|
||||||
|
def test_update_status(self):
|
||||||
|
asset = create_asset()
|
||||||
|
initial_status = asset.status
|
||||||
|
asset_repair = create_asset_repair(asset = asset)
|
||||||
|
|
||||||
|
if asset_repair.repair_status == "Pending":
|
||||||
|
asset.reload()
|
||||||
|
self.assertEqual(asset.status, "Out of Order")
|
||||||
|
|
||||||
|
asset_repair.repair_status = "Completed"
|
||||||
|
asset_repair.save()
|
||||||
|
asset_status = frappe.db.get_value("Asset", asset_repair.asset, "status")
|
||||||
|
self.assertEqual(asset_status, initial_status)
|
||||||
|
|
||||||
|
def test_stock_item_total_value(self):
|
||||||
|
asset_repair = create_asset_repair(stock_consumption = 1)
|
||||||
|
|
||||||
|
for item in asset_repair.stock_items:
|
||||||
|
total_value = flt(item.valuation_rate) * flt(item.consumed_quantity)
|
||||||
|
self.assertEqual(item.total_value, total_value)
|
||||||
|
|
||||||
|
def test_total_repair_cost(self):
|
||||||
|
asset_repair = create_asset_repair(stock_consumption = 1)
|
||||||
|
|
||||||
|
total_repair_cost = asset_repair.repair_cost
|
||||||
|
self.assertEqual(total_repair_cost, asset_repair.repair_cost)
|
||||||
|
for item in asset_repair.stock_items:
|
||||||
|
total_repair_cost += item.total_value
|
||||||
|
|
||||||
|
self.assertEqual(total_repair_cost, asset_repair.total_repair_cost)
|
||||||
|
|
||||||
|
def test_repair_status_after_submit(self):
|
||||||
|
asset_repair = create_asset_repair(submit = 1)
|
||||||
|
self.assertNotEqual(asset_repair.repair_status, "Pending")
|
||||||
|
|
||||||
|
def test_stock_items(self):
|
||||||
|
asset_repair = create_asset_repair(stock_consumption = 1)
|
||||||
|
self.assertTrue(asset_repair.stock_consumption)
|
||||||
|
self.assertTrue(asset_repair.stock_items)
|
||||||
|
|
||||||
|
def test_warehouse(self):
|
||||||
|
asset_repair = create_asset_repair(stock_consumption = 1)
|
||||||
|
self.assertTrue(asset_repair.stock_consumption)
|
||||||
|
self.assertTrue(asset_repair.warehouse)
|
||||||
|
|
||||||
|
def test_decrease_stock_quantity(self):
|
||||||
|
asset_repair = create_asset_repair(stock_consumption = 1, submit = 1)
|
||||||
|
stock_entry = frappe.get_last_doc('Stock Entry')
|
||||||
|
|
||||||
|
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].item_code, asset_repair.stock_items[0].item)
|
||||||
|
self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity)
|
||||||
|
|
||||||
|
def test_increase_in_asset_value_due_to_stock_consumption(self):
|
||||||
|
asset = create_asset(calculate_depreciation = 1)
|
||||||
|
initial_asset_value = get_asset_value(asset)
|
||||||
|
asset_repair = create_asset_repair(asset= asset, stock_consumption = 1, submit = 1)
|
||||||
|
asset.reload()
|
||||||
|
|
||||||
|
increase_in_asset_value = get_asset_value(asset) - initial_asset_value
|
||||||
|
self.assertEqual(asset_repair.stock_items[0].total_value, increase_in_asset_value)
|
||||||
|
|
||||||
|
def test_increase_in_asset_value_due_to_repair_cost_capitalisation(self):
|
||||||
|
asset = create_asset(calculate_depreciation = 1)
|
||||||
|
initial_asset_value = get_asset_value(asset)
|
||||||
|
asset_repair = create_asset_repair(asset= asset, capitalize_repair_cost = 1, submit = 1)
|
||||||
|
asset.reload()
|
||||||
|
|
||||||
|
increase_in_asset_value = get_asset_value(asset) - initial_asset_value
|
||||||
|
self.assertEqual(asset_repair.repair_cost, increase_in_asset_value)
|
||||||
|
|
||||||
|
def test_purchase_invoice(self):
|
||||||
|
asset_repair = create_asset_repair(capitalize_repair_cost = 1, submit = 1)
|
||||||
|
self.assertTrue(asset_repair.purchase_invoice)
|
||||||
|
|
||||||
|
def test_gl_entries(self):
|
||||||
|
asset_repair = create_asset_repair(capitalize_repair_cost = 1, submit = 1)
|
||||||
|
gl_entry = frappe.get_last_doc('GL Entry')
|
||||||
|
self.assertEqual(asset_repair.name, gl_entry.voucher_no)
|
||||||
|
|
||||||
|
def test_increase_in_asset_life(self):
|
||||||
|
asset = create_asset(calculate_depreciation = 1)
|
||||||
|
initial_num_of_depreciations = num_of_depreciations(asset)
|
||||||
|
create_asset_repair(asset= asset, capitalize_repair_cost = 1, submit = 1)
|
||||||
|
asset.reload()
|
||||||
|
|
||||||
|
self.assertEqual((initial_num_of_depreciations + 1), num_of_depreciations(asset))
|
||||||
|
self.assertEqual(asset.schedules[-1].accumulated_depreciation_amount, asset.finance_books[0].value_after_depreciation)
|
||||||
|
|
||||||
|
def get_asset_value(asset):
|
||||||
|
return asset.finance_books[0].value_after_depreciation
|
||||||
|
|
||||||
|
def num_of_depreciations(asset):
|
||||||
|
return asset.finance_books[0].total_number_of_depreciations
|
||||||
|
|
||||||
|
def create_asset_repair(**args):
|
||||||
|
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||||
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
|
|
||||||
|
args = frappe._dict(args)
|
||||||
|
|
||||||
|
if args.asset:
|
||||||
|
asset = args.asset
|
||||||
|
else:
|
||||||
|
asset = create_asset(is_existing_asset = 1)
|
||||||
|
asset_repair = frappe.new_doc("Asset Repair")
|
||||||
|
asset_repair.update({
|
||||||
|
"asset": asset.name,
|
||||||
|
"asset_name": asset.asset_name,
|
||||||
|
"failure_date": nowdate(),
|
||||||
|
"description": "Test Description",
|
||||||
|
"repair_cost": 0,
|
||||||
|
"company": asset.company
|
||||||
|
})
|
||||||
|
|
||||||
|
if args.stock_consumption:
|
||||||
|
asset_repair.stock_consumption = 1
|
||||||
|
asset_repair.warehouse = create_warehouse("Test Warehouse", company = asset.company)
|
||||||
|
asset_repair.append("stock_items", {
|
||||||
|
"item": args.item or args.item_code or "_Test Item",
|
||||||
|
"valuation_rate": args.rate if args.get("rate") is not None else 100,
|
||||||
|
"consumed_quantity": args.qty or 1
|
||||||
|
})
|
||||||
|
|
||||||
|
asset_repair.insert(ignore_if_duplicate=True)
|
||||||
|
|
||||||
|
if args.submit:
|
||||||
|
asset_repair.repair_status = "Completed"
|
||||||
|
asset_repair.cost_center = "_Test Cost Center - _TC"
|
||||||
|
|
||||||
|
if args.stock_consumption:
|
||||||
|
stock_entry = frappe.get_doc({
|
||||||
|
"doctype": "Stock Entry",
|
||||||
|
"stock_entry_type": "Material Receipt",
|
||||||
|
"company": asset.company
|
||||||
|
})
|
||||||
|
stock_entry.append('items', {
|
||||||
|
"t_warehouse": asset_repair.warehouse,
|
||||||
|
"item_code": asset_repair.stock_items[0].item,
|
||||||
|
"qty": asset_repair.stock_items[0].consumed_quantity
|
||||||
|
})
|
||||||
|
stock_entry.submit()
|
||||||
|
|
||||||
|
if args.capitalize_repair_cost:
|
||||||
|
asset_repair.capitalize_repair_cost = 1
|
||||||
|
asset_repair.repair_cost = 1000
|
||||||
|
if asset.calculate_depreciation:
|
||||||
|
asset_repair.increase_in_asset_life = 12
|
||||||
|
asset_repair.purchase_invoice = make_purchase_invoice().name
|
||||||
|
|
||||||
|
asset_repair.submit()
|
||||||
|
return asset_repair
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2021-05-12 02:41:54.161024",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"item",
|
||||||
|
"valuation_rate",
|
||||||
|
"consumed_quantity",
|
||||||
|
"total_value"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "item",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Item",
|
||||||
|
"options": "Item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "item.valuation_rate",
|
||||||
|
"fieldname": "valuation_rate",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Valuation Rate",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "consumed_quantity",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Consumed Quantity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "total_value",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Total Value",
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-05-12 03:19:55.006300",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Assets",
|
||||||
|
"name": "Asset Repair Consumed Item",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# 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 AssetRepairConsumedItem(Document):
|
||||||
|
pass
|
||||||
@@ -9,13 +9,14 @@
|
|||||||
"supp_master_name",
|
"supp_master_name",
|
||||||
"supplier_group",
|
"supplier_group",
|
||||||
"buying_price_list",
|
"buying_price_list",
|
||||||
|
"maintain_same_rate_action",
|
||||||
|
"role_to_override_stop_action",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
"po_required",
|
"po_required",
|
||||||
"pr_required",
|
"pr_required",
|
||||||
"maintain_same_rate",
|
"maintain_same_rate",
|
||||||
"maintain_same_rate_action",
|
|
||||||
"role_to_override_stop_action",
|
|
||||||
"allow_multiple_items",
|
"allow_multiple_items",
|
||||||
|
"bill_for_rejected_quantity_in_purchase_invoice",
|
||||||
"subcontract",
|
"subcontract",
|
||||||
"backflush_raw_materials_of_subcontract_based_on",
|
"backflush_raw_materials_of_subcontract_based_on",
|
||||||
"column_break_11",
|
"column_break_11",
|
||||||
@@ -108,6 +109,13 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Role Allowed to Override Stop Action",
|
"label": "Role Allowed to Override Stop Action",
|
||||||
"options": "Role"
|
"options": "Role"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"description": "If checked, Rejected Quantity will be included while making Purchase Invoice from Purchase Receipt.",
|
||||||
|
"fieldname": "bill_for_rejected_quantity_in_purchase_invoice",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Bill for Rejected Quantity in Purchase Invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-cog",
|
"icon": "fa fa-cog",
|
||||||
@@ -115,7 +123,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-04-04 20:01:44.087066",
|
"modified": "2021-06-24 10:38:28.934525",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Buying Settings",
|
"name": "Buying Settings",
|
||||||
|
|||||||
@@ -45,6 +45,47 @@ frappe.ui.form.on("Purchase Order", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||||
|
},
|
||||||
|
|
||||||
|
apply_tds: function(frm) {
|
||||||
|
if (!frm.doc.apply_tds) {
|
||||||
|
frm.set_value("tax_withholding_category", '');
|
||||||
|
} else {
|
||||||
|
frm.set_value("tax_withholding_category", frm.supplier_tds);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh: function(frm) {
|
||||||
|
frm.trigger('get_materials_from_supplier');
|
||||||
|
},
|
||||||
|
|
||||||
|
get_materials_from_supplier: function(frm) {
|
||||||
|
let po_details = [];
|
||||||
|
|
||||||
|
if (frm.doc.supplied_items && (frm.doc.per_received == 100 || frm.doc.status === 'Closed')) {
|
||||||
|
frm.doc.supplied_items.forEach(d => {
|
||||||
|
if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) {
|
||||||
|
po_details.push(d.name)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (po_details && po_details.length) {
|
||||||
|
frm.add_custom_button(__('Return of Components'), () => {
|
||||||
|
frm.call({
|
||||||
|
method: 'erpnext.buying.doctype.purchase_order.purchase_order.get_materials_from_supplier',
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __('Creating Stock Entry'),
|
||||||
|
args: { purchase_order: frm.doc.name, po_details: po_details },
|
||||||
|
callback: function(r) {
|
||||||
|
if (r && r.message) {
|
||||||
|
const doc = frappe.model.sync(r.message);
|
||||||
|
frappe.set_route("Form", doc[0].doctype, doc[0].name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, __('Create'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -209,7 +250,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
|||||||
}
|
}
|
||||||
|
|
||||||
has_unsupplied_items() {
|
has_unsupplied_items() {
|
||||||
return this.frm.doc['supplied_items'].some(item => item.required_qty != item.supplied_qty)
|
return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty);
|
||||||
}
|
}
|
||||||
|
|
||||||
make_stock_entry() {
|
make_stock_entry() {
|
||||||
@@ -313,7 +354,8 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
|||||||
if(me.values) {
|
if(me.values) {
|
||||||
me.values.sub_con_rm_items.map((row,i) => {
|
me.values.sub_con_rm_items.map((row,i) => {
|
||||||
if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) {
|
if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) {
|
||||||
frappe.throw(__("Item Code, warehouse, quantity are required on row {0}", [i+1]));
|
let row_id = i+1;
|
||||||
|
frappe.throw(__("Item Code, warehouse and quantity are required on row {0}", [row_id]));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children())
|
me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children())
|
||||||
@@ -504,12 +546,14 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
|||||||
],
|
],
|
||||||
primary_action: function() {
|
primary_action: function() {
|
||||||
var data = d.get_values();
|
var data = d.get_values();
|
||||||
|
let reason_for_hold = 'Reason for hold: ' + data.reason_for_hold;
|
||||||
|
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "frappe.desk.form.utils.add_comment",
|
method: "frappe.desk.form.utils.add_comment",
|
||||||
args: {
|
args: {
|
||||||
reference_doctype: me.frm.doctype,
|
reference_doctype: me.frm.doctype,
|
||||||
reference_name: me.frm.docname,
|
reference_name: me.frm.docname,
|
||||||
content: __('Reason for hold: ')+data.reason_for_hold,
|
content: __(reason_for_hold),
|
||||||
comment_email: frappe.session.user,
|
comment_email: frappe.session.user,
|
||||||
comment_by: frappe.session.user_fullname
|
comment_by: frappe.session.user_fullname
|
||||||
},
|
},
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -14,11 +14,11 @@ from frappe.desk.notifications import clear_doctype_notifications
|
|||||||
from erpnext.buying.utils import validate_for_items, check_on_hold_or_closed_status
|
from erpnext.buying.utils import validate_for_items, check_on_hold_or_closed_status
|
||||||
from erpnext.stock.utils import get_bin
|
from erpnext.stock.utils import get_bin
|
||||||
from erpnext.accounts.party import get_party_account_currency
|
from erpnext.accounts.party import get_party_account_currency
|
||||||
from six import string_types
|
|
||||||
from erpnext.stock.doctype.item.item import get_item_defaults
|
from erpnext.stock.doctype.item.item import get_item_defaults
|
||||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\
|
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
|
||||||
unlink_inter_company_doc
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (validate_inter_company_party,
|
||||||
|
update_linked_doc, unlink_inter_company_doc)
|
||||||
|
|
||||||
form_grid_templates = {
|
form_grid_templates = {
|
||||||
"items": "templates/form_grid/item_grid.html"
|
"items": "templates/form_grid/item_grid.html"
|
||||||
@@ -39,11 +39,18 @@ class PurchaseOrder(BuyingController):
|
|||||||
'percent_join_field': 'material_request'
|
'percent_join_field': 'material_request'
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
def onload(self):
|
||||||
|
supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
|
||||||
|
self.set_onload("supplier_tds", supplier_tds)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
super(PurchaseOrder, self).validate()
|
super(PurchaseOrder, self).validate()
|
||||||
|
|
||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
|
# apply tax withholding only if checked and applicable
|
||||||
|
self.set_tax_withholding()
|
||||||
|
|
||||||
self.validate_supplier()
|
self.validate_supplier()
|
||||||
self.validate_schedule_date()
|
self.validate_schedule_date()
|
||||||
validate_for_items(self)
|
validate_for_items(self)
|
||||||
@@ -87,6 +94,33 @@ class PurchaseOrder(BuyingController):
|
|||||||
if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')):
|
if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')):
|
||||||
self.validate_rate_with_reference_doc([["Supplier Quotation", "supplier_quotation", "supplier_quotation_item"]])
|
self.validate_rate_with_reference_doc([["Supplier Quotation", "supplier_quotation", "supplier_quotation_item"]])
|
||||||
|
|
||||||
|
def set_tax_withholding(self):
|
||||||
|
if not self.apply_tds:
|
||||||
|
return
|
||||||
|
|
||||||
|
tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category)
|
||||||
|
|
||||||
|
if not tax_withholding_details:
|
||||||
|
return
|
||||||
|
|
||||||
|
accounts = []
|
||||||
|
for d in self.taxes:
|
||||||
|
if d.account_head == tax_withholding_details.get("account_head"):
|
||||||
|
d.update(tax_withholding_details)
|
||||||
|
accounts.append(d.account_head)
|
||||||
|
|
||||||
|
if not accounts or tax_withholding_details.get("account_head") not in accounts:
|
||||||
|
self.append("taxes", tax_withholding_details)
|
||||||
|
|
||||||
|
to_remove = [d for d in self.taxes
|
||||||
|
if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")]
|
||||||
|
|
||||||
|
for d in to_remove:
|
||||||
|
self.remove(d)
|
||||||
|
|
||||||
|
# calculate totals again after applying TDS
|
||||||
|
self.calculate_taxes_and_totals()
|
||||||
|
|
||||||
def validate_supplier(self):
|
def validate_supplier(self):
|
||||||
prevent_po = frappe.db.get_value("Supplier", self.supplier, 'prevent_pos')
|
prevent_po = frappe.db.get_value("Supplier", self.supplier, 'prevent_pos')
|
||||||
if prevent_po:
|
if prevent_po:
|
||||||
@@ -468,9 +502,11 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_rm_stock_entry(purchase_order, rm_items):
|
def make_rm_stock_entry(purchase_order, rm_items):
|
||||||
if isinstance(rm_items, string_types):
|
rm_items_list = rm_items
|
||||||
|
|
||||||
|
if isinstance(rm_items, str):
|
||||||
rm_items_list = json.loads(rm_items)
|
rm_items_list = json.loads(rm_items)
|
||||||
else:
|
elif not rm_items:
|
||||||
frappe.throw(_("No Items available for transfer"))
|
frappe.throw(_("No Items available for transfer"))
|
||||||
|
|
||||||
if rm_items_list:
|
if rm_items_list:
|
||||||
@@ -508,6 +544,8 @@ def make_rm_stock_entry(purchase_order, rm_items):
|
|||||||
'qty': rm_item_data["qty"],
|
'qty': rm_item_data["qty"],
|
||||||
'from_warehouse': rm_item_data["warehouse"],
|
'from_warehouse': rm_item_data["warehouse"],
|
||||||
'stock_uom': rm_item_data["stock_uom"],
|
'stock_uom': rm_item_data["stock_uom"],
|
||||||
|
'serial_no': rm_item_data.get('serial_no'),
|
||||||
|
'batch_no': rm_item_data.get('batch_no'),
|
||||||
'main_item_code': rm_item_data["item_code"],
|
'main_item_code': rm_item_data["item_code"],
|
||||||
'allow_alternative_item': item_wh.get(rm_item_code, {}).get('allow_alternative_item')
|
'allow_alternative_item': item_wh.get(rm_item_code, {}).get('allow_alternative_item')
|
||||||
}
|
}
|
||||||
@@ -547,3 +585,58 @@ def update_status(status, name):
|
|||||||
def make_inter_company_sales_order(source_name, target_doc=None):
|
def make_inter_company_sales_order(source_name, target_doc=None):
|
||||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
|
||||||
return make_inter_company_transaction("Purchase Order", source_name, target_doc)
|
return make_inter_company_transaction("Purchase Order", source_name, target_doc)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_materials_from_supplier(purchase_order, po_details):
|
||||||
|
if isinstance(po_details, str):
|
||||||
|
po_details = json.loads(po_details)
|
||||||
|
|
||||||
|
doc = frappe.get_cached_doc('Purchase Order', purchase_order)
|
||||||
|
doc.initialized_fields()
|
||||||
|
doc.purchase_orders = [doc.name]
|
||||||
|
doc.get_available_materials()
|
||||||
|
|
||||||
|
if not doc.available_materials:
|
||||||
|
frappe.throw(_('Materials are already received against the purchase order {0}')
|
||||||
|
.format(purchase_order))
|
||||||
|
|
||||||
|
return make_return_stock_entry_for_subcontract(doc.available_materials, doc, po_details)
|
||||||
|
|
||||||
|
def make_return_stock_entry_for_subcontract(available_materials, po_doc, po_details):
|
||||||
|
ste_doc = frappe.new_doc('Stock Entry')
|
||||||
|
ste_doc.purpose = 'Material Transfer'
|
||||||
|
ste_doc.purchase_order = po_doc.name
|
||||||
|
ste_doc.company = po_doc.company
|
||||||
|
ste_doc.is_return = 1
|
||||||
|
|
||||||
|
for key, value in available_materials.items():
|
||||||
|
if not value.qty:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if value.batch_no:
|
||||||
|
for batch_no, qty in value.batch_no.items():
|
||||||
|
if qty > 0:
|
||||||
|
add_items_in_ste(ste_doc, value, value.qty, po_details, batch_no)
|
||||||
|
else:
|
||||||
|
add_items_in_ste(ste_doc, value, value.qty, po_details)
|
||||||
|
|
||||||
|
ste_doc.set_stock_entry_type()
|
||||||
|
ste_doc.calculate_rate_and_amount()
|
||||||
|
|
||||||
|
return ste_doc
|
||||||
|
|
||||||
|
def add_items_in_ste(ste_doc, row, qty, po_details, batch_no=None):
|
||||||
|
item = ste_doc.append('items', row.item_details)
|
||||||
|
|
||||||
|
po_detail = list(set(row.po_details).intersection(po_details))
|
||||||
|
item.update({
|
||||||
|
'qty': qty,
|
||||||
|
'batch_no': batch_no,
|
||||||
|
'basic_rate': row.item_details['rate'],
|
||||||
|
'po_detail': po_detail[0] if po_detail else '',
|
||||||
|
's_warehouse': row.item_details['t_warehouse'],
|
||||||
|
't_warehouse': row.item_details['s_warehouse'],
|
||||||
|
'item_code': row.item_details['rm_item_code'],
|
||||||
|
'subcontracted_item': row.item_details['main_item_code'],
|
||||||
|
'serial_no': '\n'.join(row.serial_no) if row.serial_no else ''
|
||||||
|
})
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user