Compare commits
301 Commits
overtime-f
...
enterprise
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ca433fd56 | ||
|
|
80d0f432a3 | ||
|
|
4c6398665b | ||
|
|
8880d13227 | ||
|
|
8cb560c753 | ||
|
|
b86213c890 | ||
|
|
6fffc90b46 | ||
|
|
64267faa8f | ||
|
|
6eded547f5 | ||
|
|
d4ae1febe3 | ||
|
|
50b4b70589 | ||
|
|
9c7a9f3a13 | ||
|
|
57cd273f7c | ||
|
|
020b8ae110 | ||
|
|
a6d276a06f | ||
|
|
f8343890b9 | ||
|
|
c6fe601efa | ||
|
|
bfd824c56e | ||
|
|
6ac68f3bc7 | ||
|
|
8c7d9efa9d | ||
|
|
8ed7a21cd5 | ||
|
|
1cd2f36b23 | ||
|
|
868a6cb26d | ||
|
|
441adf763f | ||
|
|
5a7fad8a6a | ||
|
|
82d0cfc68e | ||
|
|
940356d28a | ||
|
|
a07423ebb5 | ||
|
|
7a97b6d6a8 | ||
|
|
9d8efbe1db | ||
|
|
831ce88d1c | ||
|
|
af84ce9216 | ||
|
|
61d014d249 | ||
|
|
93dd238577 | ||
|
|
3fcc5e3134 | ||
|
|
19a0ca1980 | ||
|
|
356a55258e | ||
|
|
cba847b051 | ||
|
|
a661667e2a | ||
|
|
5fe7d80a64 | ||
|
|
c468e4a93d | ||
|
|
50b76d04bf | ||
|
|
ed68f11a46 | ||
|
|
f3ae7584c5 | ||
|
|
64454a5dc8 | ||
|
|
7aa802a073 | ||
|
|
60a0b7fe77 | ||
|
|
338f436a1c | ||
|
|
7551bcf421 | ||
|
|
13466bba90 | ||
|
|
f46f25ba53 | ||
|
|
21d918c194 | ||
|
|
bad69f6c39 | ||
|
|
71642c3172 | ||
|
|
bf91fe985e | ||
|
|
f229e62703 | ||
|
|
da72b9b840 | ||
|
|
94c492c085 | ||
|
|
bc187c4b84 | ||
|
|
8e108473c2 | ||
|
|
b965bcc6e8 | ||
|
|
8387613fdf | ||
|
|
c03f8c9e3f | ||
|
|
20691f0ba2 | ||
|
|
3c83691353 | ||
|
|
f1ebe4ef8b | ||
|
|
86c2cf5ab9 | ||
|
|
2725e61087 | ||
|
|
d1f1345a7f | ||
|
|
d82fd8ded1 | ||
|
|
def7183979 | ||
|
|
d2541caded | ||
|
|
fe515321f2 | ||
|
|
02e7de0e45 | ||
|
|
4a294bead2 | ||
|
|
f7c73a3069 | ||
|
|
e042ed69a1 | ||
|
|
8635d39865 | ||
|
|
beeaf0b04a | ||
|
|
96812893f1 | ||
|
|
a2595a0931 | ||
|
|
509d507d6e | ||
|
|
e33be287b9 | ||
|
|
31256698a9 | ||
|
|
78f7c92549 | ||
|
|
9e1de9c206 | ||
|
|
6fab603a23 | ||
|
|
fa4d314f7b | ||
|
|
55690c8165 | ||
|
|
cb65c6ce64 | ||
|
|
9871e124d0 | ||
|
|
08fddac884 | ||
|
|
d1b396637c | ||
|
|
27bbb7002d | ||
|
|
2d7f04dd1a | ||
|
|
9ab18b5341 | ||
|
|
9fa92c912b | ||
|
|
72eb72f66f | ||
|
|
50b188214d | ||
|
|
f9da88cb15 | ||
|
|
d99d56b0e8 | ||
|
|
4e6f49209f | ||
|
|
e079a1bb33 | ||
|
|
e8abb201c1 | ||
|
|
0c6ca09e06 | ||
|
|
f6d6897f34 | ||
|
|
a0df79ee8c | ||
|
|
958edc7b83 | ||
|
|
2ac0a1a1a0 | ||
|
|
2f775ee53c | ||
|
|
a51e230f4a | ||
|
|
9997cce1ea | ||
|
|
cc73e7d6fa | ||
|
|
f225196028 | ||
|
|
f40da4ac4c | ||
|
|
fd197a3aab | ||
|
|
683d18dd95 | ||
|
|
9ea5072c52 | ||
|
|
b24c0bfbf9 | ||
|
|
03f4db0606 | ||
|
|
72715956f1 | ||
|
|
59bf2df28e | ||
|
|
7ff6865817 | ||
|
|
c00d851a88 | ||
|
|
740d5c6c53 | ||
|
|
01c89eaad9 | ||
|
|
2f350bf450 | ||
|
|
9513d61a50 | ||
|
|
51ae46f0de | ||
|
|
2f89b5b38e | ||
|
|
41c5357602 | ||
|
|
bd8699b8a9 | ||
|
|
fea29ae8cb | ||
|
|
7be9f8dab1 | ||
|
|
0bad696faf | ||
|
|
07d9f3f74b | ||
|
|
4acbeecbbe | ||
|
|
ec5f6c78d0 | ||
|
|
621927d9f9 | ||
|
|
c76b4430cf | ||
|
|
d9b6fe0d0f | ||
|
|
53fb5f778d | ||
|
|
fad426fd5c | ||
|
|
74b3fc1e1c | ||
|
|
1e8f598ba5 | ||
|
|
dd0a8f20e2 | ||
|
|
fedee0e8da | ||
|
|
f07f7e9305 | ||
|
|
1cb2af00a8 | ||
|
|
9965af166e | ||
|
|
1ef1daf383 | ||
|
|
0003938f2b | ||
|
|
01aada6c90 | ||
|
|
ae41b53cee | ||
|
|
470c7e773f | ||
|
|
24a25cd834 | ||
|
|
14a9ec0592 | ||
|
|
034ad28387 | ||
|
|
904b8481cf | ||
|
|
ea2470d174 | ||
|
|
a01264dae7 | ||
|
|
c2b5562a3f | ||
|
|
eee03fcbab | ||
|
|
d7c4818a5d | ||
|
|
9a4926bb0d | ||
|
|
f99f872946 | ||
|
|
87b4e6ea32 | ||
|
|
cf4e29a604 | ||
|
|
4df54cc62c | ||
|
|
8492bf040d | ||
|
|
1f10a99910 | ||
|
|
9bcc402f41 | ||
|
|
5abf905ff7 | ||
|
|
478360397d | ||
|
|
5d5dc56f94 | ||
|
|
6588a936d5 | ||
|
|
9af3f12411 | ||
|
|
b4e7ee0e45 | ||
|
|
bb0b1e61d6 | ||
|
|
6eb8d19cc9 | ||
|
|
cd36ba7e64 | ||
|
|
532a224c44 | ||
|
|
67768faaef | ||
|
|
a57976660a | ||
|
|
4704003c94 | ||
|
|
b5d1a7731c | ||
|
|
7a8a449ed4 | ||
|
|
bf3e191570 | ||
|
|
b5ffaff6a8 | ||
|
|
f79a72dbf3 | ||
|
|
820a579051 | ||
|
|
4f0e6cd911 | ||
|
|
f913838373 | ||
|
|
9f305e983c | ||
|
|
ea2408744a | ||
|
|
5884f1aeb0 | ||
|
|
26bec9d7b4 | ||
|
|
6495a4d2ed | ||
|
|
94484d7766 | ||
|
|
9cab26b7bf | ||
|
|
99531a35e0 | ||
|
|
a97556fa8d | ||
|
|
24a88f6cf6 | ||
|
|
1e2df2c109 | ||
|
|
b2f2d0e749 | ||
|
|
95bc141531 | ||
|
|
433815daba | ||
|
|
4afda3c89c | ||
|
|
a717b5ad6e | ||
|
|
bbf6121bb5 | ||
|
|
ac52daa14f | ||
|
|
5ef9a62917 | ||
|
|
302855e160 | ||
|
|
c8ed863454 | ||
|
|
8230569f23 | ||
|
|
098a833424 | ||
|
|
efd7d584b2 | ||
|
|
42d72b55b8 | ||
|
|
27299cfcc5 | ||
|
|
dad6a596eb | ||
|
|
bcbb1c68b4 | ||
|
|
1e898c0f5b | ||
|
|
88c0cd6f7a | ||
|
|
2924ff830e | ||
|
|
bc490e246e | ||
|
|
60de73b904 | ||
|
|
af13348817 | ||
|
|
16b309e66e | ||
|
|
12fb02ed2a | ||
|
|
223efec26e | ||
|
|
10e8316240 | ||
|
|
8106d70e70 | ||
|
|
c368c5b20b | ||
|
|
b45d90153c | ||
|
|
cd503ce01d | ||
|
|
cee0ddea78 | ||
|
|
8449a47048 | ||
|
|
4e10ce1632 | ||
|
|
0385ae2b43 | ||
|
|
8821d71094 | ||
|
|
830e87a4dd | ||
|
|
5dd92934ae | ||
|
|
d0ba0217e4 | ||
|
|
c63e233bc7 | ||
|
|
4c94ccc8d8 | ||
|
|
2df7f474fa | ||
|
|
d4dc76c3ee | ||
|
|
b084f1d320 | ||
|
|
5269f1537d | ||
|
|
753e5894de | ||
|
|
bb22972708 | ||
|
|
6ac69672f2 | ||
|
|
34997ae7c5 | ||
|
|
3e29361549 | ||
|
|
39a7a4f0ea | ||
|
|
f9656ed790 | ||
|
|
c5a43a59e0 | ||
|
|
f5f883b6a0 | ||
|
|
390bafef08 | ||
|
|
342d0d1bbb | ||
|
|
e6be20a94d | ||
|
|
25b3507809 | ||
|
|
0a9a4d5f88 | ||
|
|
d66559b6e4 | ||
|
|
e16a632edb | ||
|
|
ccd0237ae8 | ||
|
|
e93ed7d324 | ||
|
|
ec2d4c41d6 | ||
|
|
15a89f83c3 | ||
|
|
de03bc2cb3 | ||
|
|
e5df60287e | ||
|
|
db95db892c | ||
|
|
4367f1c4b6 | ||
|
|
8f057b4bac | ||
|
|
10085580c8 | ||
|
|
6368c976c7 | ||
|
|
6bae78f410 | ||
|
|
bc92ecb10f | ||
|
|
9ec0f11800 | ||
|
|
2f403f1bcd | ||
|
|
ad0b8fdd1e | ||
|
|
e9f6c8cdb1 | ||
|
|
0048418c46 | ||
|
|
a0a88a710e | ||
|
|
7c6de1a8ac | ||
|
|
6084baa9ae | ||
|
|
8e748f8451 | ||
|
|
c29c6ff9a7 | ||
|
|
fe68a0ff80 | ||
|
|
6578c045ca | ||
|
|
4ec0656f64 | ||
|
|
aaca8335f0 | ||
|
|
dd1822ef58 | ||
|
|
e38192cb6d | ||
|
|
ec2ba6bc1f | ||
|
|
ae18efaa0a | ||
|
|
0e41295c0e | ||
|
|
92cefd5655 | ||
|
|
daf6c124a9 | ||
|
|
be66ee9723 | ||
|
|
a0ed517c85 |
@@ -147,15 +147,10 @@
|
||||
"Chart": true,
|
||||
"Cypress": true,
|
||||
"cy": true,
|
||||
"describe": true,
|
||||
"expect": true,
|
||||
"it": true,
|
||||
"context": true,
|
||||
"before": true,
|
||||
"beforeEach": true,
|
||||
"onScan": true,
|
||||
"html2canvas": true,
|
||||
"extend_cscript": true,
|
||||
"localforage": true
|
||||
"onScan": true
|
||||
}
|
||||
}
|
||||
|
||||
2
.github/helper/install.sh
vendored
2
.github/helper/install.sh
vendored
@@ -42,5 +42,5 @@ sed -i 's/socketio:/# socketio:/g' Procfile
|
||||
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
|
||||
|
||||
bench get-app erpnext "${GITHUB_WORKSPACE}"
|
||||
bench start &> bench_run_logs.txt &
|
||||
bench start &
|
||||
bench --site test_site reinstall --yes
|
||||
|
||||
23
.github/workflows/backport.yml
vendored
23
.github/workflows/backport.yml
vendored
@@ -1,25 +1,16 @@
|
||||
name: Backport
|
||||
on:
|
||||
pull_request_target:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
backport:
|
||||
runs-on: ubuntu-18.04
|
||||
name: Backport
|
||||
steps:
|
||||
- name: Checkout Actions
|
||||
uses: actions/checkout@v2
|
||||
- name: Backport
|
||||
uses: tibdex/backport@v1
|
||||
with:
|
||||
repository: "frappe/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}}"
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
108
.github/workflows/ui-tests.yml
vendored
108
.github/workflows/ui-tests.yml
vendored
@@ -1,108 +0,0 @@
|
||||
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: 60a8e3bf-08f5-45b1-9269-2b207d7d30cd
|
||||
|
||||
- name: Show bench console if tests failed
|
||||
if: ${{ failure() }}
|
||||
run: cat ~/frappe-bench/bench_run_logs.txt
|
||||
12
CODEOWNERS
12
CODEOWNERS
@@ -21,13 +21,13 @@ 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/crm/ @ruchamahabal
|
||||
erpnext/education/ @ruchamahabal
|
||||
erpnext/healthcare/ @ruchamahabal
|
||||
erpnext/hr/ @ruchamahabal
|
||||
erpnext/non_profit/ @ruchamahabal
|
||||
erpnext/payroll @ruchamahabal @pateljannat
|
||||
erpnext/projects/ @ruchamahabal @pateljannat
|
||||
erpnext/payroll @ruchamahabal
|
||||
erpnext/projects/ @ruchamahabal
|
||||
|
||||
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
|
||||
|
||||
|
||||
11
cypress.json
11
cypress.json
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"baseUrl": "http://test_site:8000",
|
||||
"projectId": "da59y9",
|
||||
"adminPassword": "admin",
|
||||
"defaultCommandTimeout": 20000,
|
||||
"pageLoadTimeout": 15000,
|
||||
"retries": {
|
||||
"runMode": 2,
|
||||
"openMode": 2
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
@@ -1,111 +0,0 @@
|
||||
context('Organizational Chart', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.visit('/app/website');
|
||||
cy.awesomebar('Organizational Chart');
|
||||
|
||||
cy.window().its('frappe.csrf_token').then(csrf_token => {
|
||||
return cy.request({
|
||||
url: `/api/method/erpnext.tests.ui_test_helpers.create_employee_records`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Frappe-CSRF-Token': csrf_token
|
||||
},
|
||||
timeout: 60000
|
||||
}).then(res => {
|
||||
expect(res.status).eq(200);
|
||||
cy.get('.frappe-control[data-fieldname=company] input').focus().as('input');
|
||||
cy.get('@input')
|
||||
.clear({ force: true })
|
||||
.type('Test Org Chart{enter}', { force: true })
|
||||
.blur({ force: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('renders root nodes and loads children for the first expandable node', () => {
|
||||
// check rendered root nodes and the node name, title, connections
|
||||
cy.get('.hierarchy').find('.root-level ul.node-children').children()
|
||||
.should('have.length', 2)
|
||||
.first()
|
||||
.as('first-child');
|
||||
|
||||
cy.get('@first-child').get('.node-name').contains('Test Employee 1');
|
||||
cy.get('@first-child').get('.node-info').find('.node-title').contains('CEO');
|
||||
cy.get('@first-child').get('.node-info').find('.node-connections').contains('· 2 Connections');
|
||||
|
||||
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
|
||||
// children of 1st root visible
|
||||
cy.get(`div[data-parent="${employee_records.message[0]}"]`).as('child-node');
|
||||
cy.get('@child-node')
|
||||
.should('have.length', 1)
|
||||
.should('be.visible');
|
||||
cy.get('@child-node').get('.node-name').contains('Test Employee 3');
|
||||
|
||||
// connectors between first root node and immediate child
|
||||
cy.get(`path[data-parent="${employee_records.message[0]}"]`)
|
||||
.should('be.visible')
|
||||
.invoke('attr', 'data-child')
|
||||
.should('equal', employee_records.message[2]);
|
||||
});
|
||||
});
|
||||
|
||||
it('hides active nodes children and connectors on expanding sibling node', () => {
|
||||
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
|
||||
// click sibling
|
||||
cy.get(`#${employee_records.message[1]}`)
|
||||
.click()
|
||||
.should('have.class', 'active');
|
||||
|
||||
// child nodes and connectors hidden
|
||||
cy.get(`[data-parent="${employee_records.message[0]}"]`).should('not.be.visible');
|
||||
cy.get(`path[data-parent="${employee_records.message[0]}"]`).should('not.be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('collapses previous level nodes and refreshes connectors on expanding child node', () => {
|
||||
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
|
||||
// click child node
|
||||
cy.get(`#${employee_records.message[3]}`)
|
||||
.click()
|
||||
.should('have.class', 'active');
|
||||
|
||||
// previous level nodes: parent should be on active-path; other nodes should be collapsed
|
||||
cy.get(`#${employee_records.message[0]}`).should('have.class', 'collapsed');
|
||||
cy.get(`#${employee_records.message[1]}`).should('have.class', 'active-path');
|
||||
|
||||
// previous level connectors refreshed
|
||||
cy.get(`path[data-parent="${employee_records.message[1]}"]`)
|
||||
.should('have.class', 'collapsed-connector');
|
||||
|
||||
// child node's children and connectors rendered
|
||||
cy.get(`[data-parent="${employee_records.message[3]}"]`).should('be.visible');
|
||||
cy.get(`path[data-parent="${employee_records.message[3]}"]`).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
it('expands previous level nodes', () => {
|
||||
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
|
||||
cy.get(`#${employee_records.message[0]}`)
|
||||
.click()
|
||||
.should('have.class', 'active');
|
||||
|
||||
cy.get(`[data-parent="${employee_records.message[0]}"]`)
|
||||
.should('be.visible');
|
||||
|
||||
cy.get('ul.hierarchy').children().should('have.length', 2);
|
||||
cy.get(`#connectors`).children().should('have.length', 1);
|
||||
});
|
||||
});
|
||||
|
||||
it('edit node navigates to employee master', () => {
|
||||
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
|
||||
cy.get(`#${employee_records.message[0]}`).find('.btn-edit-node')
|
||||
.click();
|
||||
|
||||
cy.url().should('include', `/employee/${employee_records.message[0]}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,190 +0,0 @@
|
||||
context('Organizational Chart Mobile', () => {
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.viewport(375, 667);
|
||||
cy.visit('/app/website');
|
||||
cy.awesomebar('Organizational Chart');
|
||||
|
||||
cy.window().its('frappe.csrf_token').then(csrf_token => {
|
||||
return cy.request({
|
||||
url: `/api/method/erpnext.tests.ui_test_helpers.create_employee_records`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'X-Frappe-CSRF-Token': csrf_token
|
||||
},
|
||||
timeout: 60000
|
||||
}).then(res => {
|
||||
expect(res.status).eq(200);
|
||||
cy.get('.frappe-control[data-fieldname=company] input').focus().as('input');
|
||||
cy.get('@input')
|
||||
.clear({ force: true })
|
||||
.type('Test Org Chart{enter}', { force: true })
|
||||
.blur({ force: true });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('renders root nodes', () => {
|
||||
// check rendered root nodes and the node name, title, connections
|
||||
cy.get('.hierarchy-mobile').find('.root-level').children()
|
||||
.should('have.length', 2)
|
||||
.first()
|
||||
.as('first-child');
|
||||
|
||||
cy.get('@first-child').get('.node-name').contains('Test Employee 1');
|
||||
cy.get('@first-child').get('.node-info').find('.node-title').contains('CEO');
|
||||
cy.get('@first-child').get('.node-info').find('.node-connections').contains('· 2');
|
||||
});
|
||||
|
||||
it('expands root node', () => {
|
||||
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
|
||||
cy.get(`#${employee_records.message[1]}`)
|
||||
.click()
|
||||
.should('have.class', 'active');
|
||||
|
||||
// other root node removed
|
||||
cy.get(`#${employee_records.message[0]}`).should('not.exist');
|
||||
|
||||
// children of active root node
|
||||
cy.get('.hierarchy-mobile').find('.level').first().find('ul.node-children').children()
|
||||
.should('have.length', 2);
|
||||
|
||||
cy.get(`div[data-parent="${employee_records.message[1]}"]`).first().as('child-node');
|
||||
cy.get('@child-node').should('be.visible');
|
||||
|
||||
cy.get('@child-node')
|
||||
.get('.node-name')
|
||||
.contains('Test Employee 4');
|
||||
|
||||
// connectors between root node and immediate children
|
||||
cy.get(`path[data-parent="${employee_records.message[1]}"]`).as('connectors');
|
||||
cy.get('@connectors')
|
||||
.should('have.length', 2)
|
||||
.should('be.visible');
|
||||
|
||||
cy.get('@connectors')
|
||||
.first()
|
||||
.invoke('attr', 'data-child')
|
||||
.should('eq', employee_records.message[3]);
|
||||
});
|
||||
});
|
||||
|
||||
it('expands child node', () => {
|
||||
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
|
||||
cy.get(`#${employee_records.message[3]}`)
|
||||
.click()
|
||||
.should('have.class', 'active')
|
||||
.as('expanded_node');
|
||||
|
||||
// 2 levels on screen; 1 on active path; 1 collapsed
|
||||
cy.get('.hierarchy-mobile').children().should('have.length', 2);
|
||||
cy.get(`#${employee_records.message[1]}`).should('have.class', 'active-path');
|
||||
|
||||
// children of expanded node visible
|
||||
cy.get('@expanded_node')
|
||||
.next()
|
||||
.should('have.class', 'node-children')
|
||||
.as('node-children');
|
||||
|
||||
cy.get('@node-children').children().should('have.length', 1);
|
||||
cy.get('@node-children')
|
||||
.first()
|
||||
.get('.node-card')
|
||||
.should('have.class', 'active-child')
|
||||
.contains('Test Employee 7');
|
||||
|
||||
// orphan connectors removed
|
||||
cy.get(`#connectors`).children().should('have.length', 2);
|
||||
});
|
||||
});
|
||||
|
||||
it('renders sibling group', () => {
|
||||
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
|
||||
// sibling group visible for parent
|
||||
cy.get(`#${employee_records.message[1]}`)
|
||||
.next()
|
||||
.as('sibling_group');
|
||||
|
||||
cy.get('@sibling_group')
|
||||
.should('have.attr', 'data-parent', 'undefined')
|
||||
.should('have.class', 'node-group')
|
||||
.and('have.class', 'collapsed');
|
||||
|
||||
cy.get('@sibling_group').get('.avatar-group').children().as('siblings');
|
||||
cy.get('@siblings').should('have.length', 1);
|
||||
cy.get('@siblings')
|
||||
.first()
|
||||
.should('have.attr', 'title', 'Test Employee 1');
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('expands previous level nodes', () => {
|
||||
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
|
||||
cy.get(`#${employee_records.message[6]}`)
|
||||
.click()
|
||||
.should('have.class', 'active');
|
||||
|
||||
// clicking on previous level node should remove all the nodes ahead
|
||||
// and expand that node
|
||||
cy.get(`#${employee_records.message[3]}`).click();
|
||||
cy.get(`#${employee_records.message[3]}`)
|
||||
.should('have.class', 'active')
|
||||
.should('not.have.class', 'active-path');
|
||||
|
||||
cy.get(`#${employee_records.message[6]}`).should('have.class', 'active-child');
|
||||
cy.get('.hierarchy-mobile').children().should('have.length', 2);
|
||||
cy.get(`#connectors`).children().should('have.length', 2);
|
||||
});
|
||||
});
|
||||
|
||||
it('expands sibling group', () => {
|
||||
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
|
||||
// sibling group visible for parent
|
||||
cy.get(`#${employee_records.message[6]}`).click();
|
||||
|
||||
cy.get(`#${employee_records.message[3]}`)
|
||||
.next()
|
||||
.click();
|
||||
|
||||
// siblings of parent should be visible
|
||||
cy.get('.hierarchy-mobile').prev().as('sibling_group');
|
||||
cy.get('@sibling_group')
|
||||
.should('exist')
|
||||
.should('have.class', 'sibling-group')
|
||||
.should('not.have.class', 'collapsed');
|
||||
|
||||
cy.get(`#${employee_records.message[1]}`)
|
||||
.should('be.visible')
|
||||
.should('have.class', 'active');
|
||||
|
||||
cy.get(`[data-parent="${employee_records.message[1]}"]`)
|
||||
.should('be.visible')
|
||||
.should('have.length', 2)
|
||||
.should('have.class', 'active-child');
|
||||
});
|
||||
});
|
||||
|
||||
it('goes to the respective level after clicking on non-collapsed sibling group', () => {
|
||||
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(() => {
|
||||
// click on non-collapsed sibling group
|
||||
cy.get('.hierarchy-mobile')
|
||||
.prev()
|
||||
.click();
|
||||
|
||||
// should take you to that level
|
||||
cy.get('.hierarchy-mobile').find('li.level .node-card').should('have.length', 2);
|
||||
});
|
||||
});
|
||||
|
||||
it('edit node navigates to employee master', () => {
|
||||
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
|
||||
cy.get(`#${employee_records.message[0]}`).find('.btn-edit-node')
|
||||
.click();
|
||||
|
||||
cy.url().should('include', `/employee/${employee_records.message[0]}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
// ***********************************************************
|
||||
// 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
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
// ***********************************************
|
||||
// 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)}`);
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
// ***********************************************************
|
||||
// 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'
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"baseUrl": "../node_modules",
|
||||
"types": [
|
||||
"cypress"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.*"
|
||||
]
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '13.2.0'
|
||||
__version__ = '13.8.0'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
||||
@@ -230,7 +230,7 @@ class Account(NestedSet):
|
||||
if self.check_gle_exists():
|
||||
throw(_("Account with existing transaction can not be converted to group."))
|
||||
elif self.account_type and not self.flags.exclude_account_type_check:
|
||||
throw(_("Cannot convert to Group because Account Type is selected."))
|
||||
throw(_("Cannot covert to Group because Account Type is selected."))
|
||||
else:
|
||||
self.is_group = 1
|
||||
self.save()
|
||||
|
||||
@@ -249,7 +249,7 @@ class TestBudget(unittest.TestCase):
|
||||
|
||||
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
|
||||
if budget_against_field == "project":
|
||||
budget_against = frappe.db.get_value("Project", {"project_name": "_Test Project"})
|
||||
budget_against = "_Test Project"
|
||||
else:
|
||||
budget_against = budget_against_CC or "_Test Cost Center - _TC"
|
||||
|
||||
@@ -275,7 +275,7 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again
|
||||
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date=nowdate(), submit=True)
|
||||
elif budget_against_field == "project":
|
||||
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project=budget_against, posting_date=nowdate())
|
||||
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date=nowdate())
|
||||
|
||||
def make_budget(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-05-06 16:18:25.410476",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"campaign"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "campaign",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Campaign",
|
||||
"options": "Campaign"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-07 10:43:49.717633",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Campaign Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
# 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 CampaignItem(Document):
|
||||
pass
|
||||
@@ -57,7 +57,7 @@ def test_create_test_data():
|
||||
})
|
||||
item_price.insert()
|
||||
# create test item pricing rule
|
||||
if not frappe.db.exists("Pricing Rule", {"title": "_Test Pricing Rule for _Test Item"}):
|
||||
if not frappe.db.exists("Pricing Rule","_Test Pricing Rule for _Test Item"):
|
||||
item_pricing_rule = frappe.get_doc({
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Pricing Rule for _Test Item",
|
||||
@@ -86,15 +86,14 @@ def test_create_test_data():
|
||||
sales_partner.insert()
|
||||
# create test item coupon code
|
||||
if not frappe.db.exists("Coupon Code", "SAVE30"):
|
||||
pricing_rule = frappe.db.get_value("Pricing Rule", {"title": "_Test Pricing Rule for _Test Item"}, ['name'])
|
||||
coupon_code = frappe.get_doc({
|
||||
"doctype": "Coupon Code",
|
||||
"coupon_name":"SAVE30",
|
||||
"coupon_code":"SAVE30",
|
||||
"pricing_rule": pricing_rule,
|
||||
"valid_from": "2014-01-01",
|
||||
"maximum_use":1,
|
||||
"used":0
|
||||
"doctype": "Coupon Code",
|
||||
"coupon_name":"SAVE30",
|
||||
"coupon_code":"SAVE30",
|
||||
"pricing_rule": "_Test Pricing Rule for _Test Item",
|
||||
"valid_from": "2014-01-01",
|
||||
"maximum_use":1,
|
||||
"used":0
|
||||
})
|
||||
coupon_code.insert()
|
||||
|
||||
@@ -103,7 +102,7 @@ class TestCouponCode(unittest.TestCase):
|
||||
test_create_test_data()
|
||||
|
||||
def tearDown(self):
|
||||
frappe.set_user("Administrator")
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
def test_sales_order_with_coupon_code(self):
|
||||
frappe.db.set_value("Coupon Code", "SAVE30", "used", 0)
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-05-06 16:12:42.558878",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"customer_group"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "customer_group",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Customer Group",
|
||||
"options": "Customer Group"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-07 10:39:21.563506",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Customer Group Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
# 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 CustomerGroupItem(Document):
|
||||
pass
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-05-05 14:04:54.266353",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"customer"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Customer ",
|
||||
"options": "Customer"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-06 10:02:32.967841",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Customer Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
# 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 CustomerItem(Document):
|
||||
pass
|
||||
@@ -58,8 +58,8 @@ class GLEntry(Document):
|
||||
if not self.get(k):
|
||||
frappe.throw(_("{0} is required").format(_(self.meta.get_label(k))))
|
||||
|
||||
account_type = frappe.get_cached_value("Account", self.account, "account_type")
|
||||
if not (self.party_type and self.party):
|
||||
account_type = frappe.get_cached_value("Account", self.account, "account_type")
|
||||
if account_type == "Receivable":
|
||||
frappe.throw(_("{0} {1}: Customer is required against Receivable account {2}")
|
||||
.format(self.voucher_type, self.voucher_no, self.account))
|
||||
@@ -73,19 +73,15 @@ class GLEntry(Document):
|
||||
.format(self.voucher_type, self.voucher_no, self.account))
|
||||
|
||||
def pl_must_have_cost_center(self):
|
||||
"""Validate that profit and loss type account GL entries have a cost center."""
|
||||
|
||||
if self.cost_center or self.voucher_type == 'Period Closing Voucher':
|
||||
return
|
||||
|
||||
if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss":
|
||||
msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format(
|
||||
self.voucher_type, self.voucher_no, self.account)
|
||||
msg += " "
|
||||
msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format(
|
||||
self.voucher_type)
|
||||
if not self.cost_center and self.voucher_type != 'Period Closing Voucher':
|
||||
msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format(
|
||||
self.voucher_type, self.voucher_no, self.account)
|
||||
msg += " "
|
||||
msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format(
|
||||
self.voucher_type)
|
||||
|
||||
frappe.throw(msg, title=_("Missing Cost Center"))
|
||||
frappe.throw(msg, title=_("Missing Cost Center"))
|
||||
|
||||
def validate_dimensions_for_pl_and_bs(self):
|
||||
account_type = frappe.db.get_value("Account", self.account, "report_type")
|
||||
|
||||
@@ -7,8 +7,6 @@ cur_frm.cscript.tax_table = "Advance Taxes and Charges";
|
||||
|
||||
frappe.ui.form.on('Payment Entry', {
|
||||
onload: function(frm) {
|
||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice'];
|
||||
|
||||
if(frm.doc.__islocal) {
|
||||
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);
|
||||
@@ -533,8 +531,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
source_exchange_rate: function(frm) {
|
||||
if (frm.doc.paid_amount) {
|
||||
frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate));
|
||||
if(!frm.set_paid_amount_based_on_received_amount &&
|
||||
(frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency)) {
|
||||
// target exchange rate should always be same as source if both account currencies is same
|
||||
if(frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
|
||||
frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate);
|
||||
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
|
||||
}
|
||||
|
||||
@@ -691,7 +691,7 @@
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'",
|
||||
"depends_on": "eval:doc.received_amount",
|
||||
"fieldname": "received_amount_after_tax",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
|
||||
@@ -45,7 +45,7 @@ class PaymentEntry(AccountsController):
|
||||
self.party_account = self.paid_to
|
||||
self.party_account_currency = self.paid_to_account_currency
|
||||
|
||||
def validate(self):
|
||||
def validate(self, on_reference_unlink=False):
|
||||
self.setup_party_account_field()
|
||||
self.set_missing_values()
|
||||
self.validate_payment_type()
|
||||
@@ -55,16 +55,18 @@ class PaymentEntry(AccountsController):
|
||||
self.validate_mandatory()
|
||||
self.validate_reference_documents()
|
||||
self.set_tax_withholding()
|
||||
self.apply_taxes()
|
||||
self.set_amounts()
|
||||
self.validate_amounts()
|
||||
self.apply_taxes()
|
||||
self.clear_unallocated_reference_document_rows()
|
||||
self.validate_payment_against_negative_invoice()
|
||||
self.validate_transaction_reference()
|
||||
self.set_title()
|
||||
self.set_remarks()
|
||||
self.validate_duplicate_entry()
|
||||
self.validate_allocated_amount()
|
||||
self.validate_paid_invoices()
|
||||
if not on_reference_unlink:
|
||||
self.validate_allocated_amount()
|
||||
self.validate_paid_invoices()
|
||||
self.ensure_supplier_is_not_blocked()
|
||||
self.set_status()
|
||||
|
||||
@@ -236,7 +238,9 @@ class PaymentEntry(AccountsController):
|
||||
self.company_currency, self.posting_date)
|
||||
|
||||
def set_target_exchange_rate(self, ref_doc=None):
|
||||
if self.paid_to and not self.target_exchange_rate:
|
||||
if self.paid_from_account_currency == self.paid_to_account_currency:
|
||||
self.target_exchange_rate = self.source_exchange_rate
|
||||
elif self.paid_to and not self.target_exchange_rate:
|
||||
if ref_doc:
|
||||
if self.paid_to_account_currency == ref_doc.currency:
|
||||
self.target_exchange_rate = ref_doc.get("exchange_rate")
|
||||
@@ -473,6 +477,14 @@ class PaymentEntry(AccountsController):
|
||||
self.set_unallocated_amount()
|
||||
self.set_difference_amount()
|
||||
|
||||
def validate_amounts(self):
|
||||
self.validate_received_amount()
|
||||
|
||||
def validate_received_amount(self):
|
||||
if self.paid_from_account_currency == self.paid_to_account_currency:
|
||||
if self.paid_amount != self.received_amount:
|
||||
frappe.throw(_("Received Amount cannot be greater than Paid Amount"))
|
||||
|
||||
def set_received_amount(self):
|
||||
self.base_received_amount = self.base_paid_amount
|
||||
|
||||
@@ -518,8 +530,10 @@ class PaymentEntry(AccountsController):
|
||||
base_total_allocated_amount += flt(flt(d.allocated_amount) * flt(d.exchange_rate),
|
||||
self.precision("base_paid_amount"))
|
||||
|
||||
self.total_allocated_amount = abs(total_allocated_amount)
|
||||
self.base_total_allocated_amount = abs(base_total_allocated_amount)
|
||||
# Do not use absolute values as only credit notes could be allocated
|
||||
# and total allocated should be negative in that scenario
|
||||
self.total_allocated_amount = total_allocated_amount
|
||||
self.base_total_allocated_amount = base_total_allocated_amount
|
||||
|
||||
def set_unallocated_amount(self):
|
||||
self.unallocated_amount = 0
|
||||
|
||||
@@ -1545,7 +1545,6 @@
|
||||
"fieldname": "consolidated_invoice",
|
||||
"fieldtype": "Link",
|
||||
"label": "Consolidated Sales Invoice",
|
||||
"no_copy": 1,
|
||||
"options": "Sales Invoice",
|
||||
"read_only": 1
|
||||
}
|
||||
@@ -1553,7 +1552,7 @@
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-29 13:37:20.636171",
|
||||
"modified": "2021-02-01 15:03:33.800707",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "naming_series:",
|
||||
"autoname": "field:title",
|
||||
"creation": "2014-02-21 15:02:51",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"applicability_section",
|
||||
"naming_series",
|
||||
"title",
|
||||
"disable",
|
||||
"apply_on",
|
||||
@@ -96,7 +95,8 @@
|
||||
"fieldtype": "Data",
|
||||
"label": "Title",
|
||||
"no_copy": 1,
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -558,8 +558,7 @@
|
||||
"description": "Simple Python Expression, Example: territory != 'All Territories'",
|
||||
"fieldname": "condition",
|
||||
"fieldtype": "Code",
|
||||
"label": "Condition",
|
||||
"options": "PythonExpression"
|
||||
"label": "Condition"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_42",
|
||||
@@ -571,19 +570,12 @@
|
||||
"fieldname": "is_recursive",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Recursive"
|
||||
},
|
||||
{
|
||||
"default": "PRLE-.####",
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"label": "Naming Series",
|
||||
"options": "PRLE-.####"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-06 15:10:04.219321",
|
||||
"modified": "2021-03-06 22:01:24.840422",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule",
|
||||
@@ -641,6 +633,5 @@
|
||||
],
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "title"
|
||||
}
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,31 +25,22 @@ product_discount_fields = ['free_item', 'free_qty', 'free_item_uom',
|
||||
|
||||
class PromotionalScheme(Document):
|
||||
def validate(self):
|
||||
if not self.selling and not self.buying:
|
||||
frappe.throw(_("Either 'Selling' or 'Buying' must be selected"), title=_("Mandatory"))
|
||||
if not (self.price_discount_slabs
|
||||
or self.product_discount_slabs):
|
||||
frappe.throw(_("Price or product discount slabs are required"))
|
||||
|
||||
def on_update(self):
|
||||
pricing_rules = frappe.get_all(
|
||||
'Pricing Rule',
|
||||
fields = ["promotional_scheme_id", "name", "creation"],
|
||||
filters = {
|
||||
'promotional_scheme': self.name,
|
||||
'applicable_for': self.applicable_for
|
||||
},
|
||||
order_by = 'creation asc',
|
||||
) or {}
|
||||
self.update_pricing_rules(pricing_rules)
|
||||
data = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name"],
|
||||
filters = {'promotional_scheme': self.name}) or {}
|
||||
|
||||
def update_pricing_rules(self, pricing_rules):
|
||||
self.update_pricing_rules(data)
|
||||
|
||||
def update_pricing_rules(self, data):
|
||||
rules = {}
|
||||
count = 0
|
||||
names = []
|
||||
for rule in pricing_rules:
|
||||
names.append(rule.name)
|
||||
rules[rule.get('promotional_scheme_id')] = names
|
||||
|
||||
for d in data:
|
||||
rules[d.get('promotional_scheme_id')] = d.get('name')
|
||||
|
||||
docs = get_pricing_rules(self, rules)
|
||||
|
||||
@@ -66,9 +57,9 @@ class PromotionalScheme(Document):
|
||||
frappe.msgprint(_("New {0} pricing rules are created").format(count))
|
||||
|
||||
def on_trash(self):
|
||||
for rule in frappe.get_all('Pricing Rule',
|
||||
for d in frappe.get_all('Pricing Rule',
|
||||
{'promotional_scheme': self.name}):
|
||||
frappe.delete_doc('Pricing Rule', rule.name)
|
||||
frappe.delete_doc('Pricing Rule', d.name)
|
||||
|
||||
def get_pricing_rules(doc, rules = {}):
|
||||
new_doc = []
|
||||
@@ -82,80 +73,42 @@ def get_pricing_rules(doc, rules = {}):
|
||||
def _get_pricing_rules(doc, child_doc, discount_fields, rules = {}):
|
||||
new_doc = []
|
||||
args = get_args_for_pricing_rule(doc)
|
||||
applicable_for = frappe.scrub(doc.get('applicable_for'))
|
||||
for idx, d in enumerate(doc.get(child_doc)):
|
||||
for d in doc.get(child_doc):
|
||||
if d.name in rules:
|
||||
for applicable_for_value in args.get(applicable_for):
|
||||
temp_args = args.copy()
|
||||
docname = frappe.get_all(
|
||||
'Pricing Rule',
|
||||
fields = ["promotional_scheme_id", "name", applicable_for],
|
||||
filters = {
|
||||
'promotional_scheme_id': d.name,
|
||||
applicable_for: applicable_for_value
|
||||
}
|
||||
)
|
||||
|
||||
if docname:
|
||||
pr = frappe.get_doc('Pricing Rule', docname[0].get('name'))
|
||||
temp_args[applicable_for] = applicable_for_value
|
||||
pr = set_args(temp_args, pr, doc, child_doc, discount_fields, d)
|
||||
else:
|
||||
pr = frappe.new_doc("Pricing Rule")
|
||||
pr.title = doc.name
|
||||
temp_args[applicable_for] = applicable_for_value
|
||||
pr = set_args(temp_args, pr, doc, child_doc, discount_fields, d)
|
||||
|
||||
new_doc.append(pr)
|
||||
|
||||
pr = frappe.get_doc('Pricing Rule', rules.get(d.name))
|
||||
else:
|
||||
applicable_for_values = args.get(applicable_for) or []
|
||||
for applicable_for_value in applicable_for_values:
|
||||
pr = frappe.new_doc("Pricing Rule")
|
||||
pr.title = doc.name
|
||||
temp_args = args.copy()
|
||||
temp_args[applicable_for] = applicable_for_value
|
||||
pr = set_args(temp_args, pr, doc, child_doc, discount_fields, d)
|
||||
new_doc.append(pr)
|
||||
pr = frappe.new_doc("Pricing Rule")
|
||||
pr.title = make_autoname("{0}/.####".format(doc.name))
|
||||
|
||||
pr.update(args)
|
||||
for field in (other_fields + discount_fields):
|
||||
pr.set(field, d.get(field))
|
||||
|
||||
pr.promotional_scheme_id = d.name
|
||||
pr.promotional_scheme = doc.name
|
||||
pr.disable = d.disable if d.disable else doc.disable
|
||||
pr.price_or_product_discount = ('Price'
|
||||
if child_doc == 'price_discount_slabs' else 'Product')
|
||||
|
||||
for field in ['items', 'item_groups', 'brands']:
|
||||
if doc.get(field):
|
||||
pr.set(field, [])
|
||||
|
||||
apply_on = frappe.scrub(doc.get('apply_on'))
|
||||
for d in doc.get(field):
|
||||
pr.append(field, {
|
||||
apply_on: d.get(apply_on),
|
||||
'uom': d.uom
|
||||
})
|
||||
|
||||
new_doc.append(pr)
|
||||
|
||||
return new_doc
|
||||
|
||||
|
||||
|
||||
|
||||
def set_args(args, pr, doc, child_doc, discount_fields, child_doc_fields):
|
||||
pr.update(args)
|
||||
for field in (other_fields + discount_fields):
|
||||
pr.set(field, child_doc_fields.get(field))
|
||||
|
||||
pr.promotional_scheme_id = child_doc_fields.name
|
||||
pr.promotional_scheme = doc.name
|
||||
pr.disable = child_doc_fields.disable if child_doc_fields.disable else doc.disable
|
||||
pr.price_or_product_discount = ('Price'
|
||||
if child_doc == 'price_discount_slabs' else 'Product')
|
||||
|
||||
for field in ['items', 'item_groups', 'brands']:
|
||||
if doc.get(field):
|
||||
pr.set(field, [])
|
||||
|
||||
apply_on = frappe.scrub(doc.get('apply_on'))
|
||||
for d in doc.get(field):
|
||||
pr.append(field, {
|
||||
apply_on: d.get(apply_on),
|
||||
'uom': d.uom
|
||||
})
|
||||
return pr
|
||||
|
||||
def get_args_for_pricing_rule(doc):
|
||||
args = { 'promotional_scheme': doc.name }
|
||||
applicable_for = frappe.scrub(doc.get('applicable_for'))
|
||||
|
||||
for d in pricing_rule_fields:
|
||||
if d == applicable_for:
|
||||
items = []
|
||||
for applicable_for_values in doc.get(applicable_for):
|
||||
items.append(applicable_for_values.get(applicable_for))
|
||||
args[d] = items
|
||||
else:
|
||||
args[d] = doc.get(d)
|
||||
args[d] = doc.get(d)
|
||||
|
||||
return args
|
||||
|
||||
@@ -7,54 +7,4 @@ import frappe
|
||||
import unittest
|
||||
|
||||
class TestPromotionalScheme(unittest.TestCase):
|
||||
def test_promotional_scheme(self):
|
||||
ps = make_promotional_scheme()
|
||||
price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name", "creation"],
|
||||
filters = {'promotional_scheme': ps.name})
|
||||
self.assertTrue(len(price_rules),1)
|
||||
price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[0].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1)
|
||||
self.assertTrue(price_doc_details.customer, '_Test Customer')
|
||||
self.assertTrue(price_doc_details.min_qty, 4)
|
||||
self.assertTrue(price_doc_details.discount_percentage, 20)
|
||||
|
||||
ps.price_discount_slabs[0].min_qty = 6
|
||||
ps.append('customer', {
|
||||
'customer': "_Test Customer 2"})
|
||||
ps.save()
|
||||
price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name"],
|
||||
filters = {'promotional_scheme': ps.name})
|
||||
self.assertTrue(len(price_rules), 2)
|
||||
|
||||
price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[1].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1)
|
||||
self.assertTrue(price_doc_details.customer, '_Test Customer 2')
|
||||
self.assertTrue(price_doc_details.min_qty, 6)
|
||||
self.assertTrue(price_doc_details.discount_percentage, 20)
|
||||
|
||||
price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[0].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1)
|
||||
self.assertTrue(price_doc_details.customer, '_Test Customer')
|
||||
self.assertTrue(price_doc_details.min_qty, 6)
|
||||
|
||||
frappe.delete_doc('Promotional Scheme', ps.name)
|
||||
price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name"],
|
||||
filters = {'promotional_scheme': ps.name})
|
||||
self.assertEqual(price_rules, [])
|
||||
|
||||
def make_promotional_scheme():
|
||||
ps = frappe.new_doc('Promotional Scheme')
|
||||
ps.name = '_Test Scheme'
|
||||
ps.append('items',{
|
||||
'item_code': '_Test Item'
|
||||
})
|
||||
ps.selling = 1
|
||||
ps.append('price_discount_slabs',{
|
||||
'min_qty': 4,
|
||||
'discount_percentage': 20,
|
||||
'rule_description': 'Test'
|
||||
})
|
||||
ps.applicable_for = 'Customer'
|
||||
ps.append('customer',{
|
||||
'customer': "_Test Customer"
|
||||
})
|
||||
ps.save()
|
||||
|
||||
return ps
|
||||
pass
|
||||
|
||||
@@ -134,7 +134,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
},
|
||||
get_query_filters: {
|
||||
docstatus: 1,
|
||||
status: ["not in", ["Closed", "Completed", "Return Issued"]],
|
||||
status: ["not in", ["Closed", "Completed"]],
|
||||
company: me.frm.doc.company,
|
||||
is_return: 0
|
||||
}
|
||||
@@ -275,7 +275,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
// Do not update if inter company reference is there as the details will already be updated
|
||||
if(this.frm.updating_party_details || this.frm.doc.inter_company_invoice_reference)
|
||||
return;
|
||||
|
||||
|
||||
erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details",
|
||||
{
|
||||
posting_date: this.frm.doc.posting_date,
|
||||
@@ -283,8 +283,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
party: this.frm.doc.supplier,
|
||||
party_type: "Supplier",
|
||||
account: this.frm.doc.credit_to,
|
||||
price_list: this.frm.doc.buying_price_list,
|
||||
fetch_payment_terms_template: cint(!this.frm.doc.ignore_default_payment_terms_template)
|
||||
price_list: this.frm.doc.buying_price_list
|
||||
}, function() {
|
||||
me.apply_pricing_rule();
|
||||
me.frm.doc.apply_tds = me.frm.supplier_tds ? 1 : 0;
|
||||
|
||||
@@ -132,7 +132,6 @@
|
||||
"advances",
|
||||
"payment_schedule_section",
|
||||
"payment_terms_template",
|
||||
"ignore_default_payment_terms_template",
|
||||
"payment_schedule",
|
||||
"terms_section_break",
|
||||
"tc_name",
|
||||
@@ -713,9 +712,7 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "Supplied Items",
|
||||
"no_copy": 1,
|
||||
"options": "Purchase Receipt Item Supplied",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Purchase Receipt Item Supplied"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_26",
|
||||
@@ -1633,9 +1630,7 @@
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_internal_supplier",
|
||||
@@ -1681,8 +1676,6 @@
|
||||
"options": "Warehouse",
|
||||
"print_hide": 1,
|
||||
"print_width": "50px",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1,
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
@@ -1699,23 +1692,13 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Additional Discount Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_default_payment_terms_template",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Ignore Default Payment Terms Template",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-07 17:53:14.351439",
|
||||
"modified": "2021-07-17 17:37:50.570595",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -22,7 +22,7 @@ from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accoun
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from six import iteritems
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_doc,\
|
||||
unlink_inter_company_doc, check_if_return_invoice_linked_with_payment_entry
|
||||
unlink_inter_company_doc
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
|
||||
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_item_account_wise_additional_cost
|
||||
@@ -519,8 +519,6 @@ class PurchaseInvoice(BuyingController):
|
||||
if d.category in ('Valuation', 'Total and Valuation')
|
||||
and flt(d.base_tax_amount_after_discount_amount)]
|
||||
|
||||
enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
|
||||
|
||||
for item in self.get("items"):
|
||||
if flt(item.base_net_amount):
|
||||
account_currency = get_account_currency(item.expense_account)
|
||||
@@ -611,7 +609,11 @@ class PurchaseInvoice(BuyingController):
|
||||
if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account)
|
||||
|
||||
if not item.is_fixed_asset:
|
||||
dummy, amount = self.get_amount_and_base_amount(item, enable_discount_accounting)
|
||||
if frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'):
|
||||
amount = flt(item.base_amount, item.precision("base_amount"))
|
||||
else:
|
||||
amount = flt(item.base_net_amount, item.precision("base_net_amount"))
|
||||
|
||||
else:
|
||||
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
|
||||
|
||||
@@ -825,11 +827,8 @@ class PurchaseInvoice(BuyingController):
|
||||
def make_tax_gl_entries(self, gl_entries):
|
||||
# tax table gl entries
|
||||
valuation_tax = {}
|
||||
enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
|
||||
|
||||
for tax in self.get("taxes"):
|
||||
amount, base_amount = self.get_tax_amounts(tax, enable_discount_accounting)
|
||||
if tax.category in ("Total", "Valuation and Total") and flt(base_amount):
|
||||
if tax.category in ("Total", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount):
|
||||
account_currency = get_account_currency(tax.account_head)
|
||||
|
||||
dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
|
||||
@@ -838,21 +837,21 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict({
|
||||
"account": tax.account_head,
|
||||
"against": self.supplier,
|
||||
dr_or_cr: base_amount,
|
||||
dr_or_cr + "_in_account_currency": base_amount
|
||||
if account_currency==self.company_currency
|
||||
else amount,
|
||||
dr_or_cr: tax.base_tax_amount_after_discount_amount,
|
||||
dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \
|
||||
if account_currency==self.company_currency \
|
||||
else tax.tax_amount_after_discount_amount,
|
||||
"cost_center": tax.cost_center
|
||||
}, account_currency, item=tax)
|
||||
)
|
||||
# accumulate valuation tax
|
||||
if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(base_amount) \
|
||||
if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount) \
|
||||
and not self.is_internal_transfer():
|
||||
if self.auto_accounting_for_stock and not tax.cost_center:
|
||||
frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category)))
|
||||
valuation_tax.setdefault(tax.name, 0)
|
||||
valuation_tax[tax.name] += \
|
||||
(tax.add_deduct_tax == "Add" and 1 or -1) * flt(base_amount)
|
||||
(tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount)
|
||||
|
||||
if self.is_opening == "No" and self.negative_expense_to_be_booked and valuation_tax:
|
||||
# credit valuation tax amount in "Expenses Included In Valuation"
|
||||
@@ -988,8 +987,6 @@ class PurchaseInvoice(BuyingController):
|
||||
}, item=self))
|
||||
|
||||
def on_cancel(self):
|
||||
check_if_return_invoice_linked_with_payment_entry(self)
|
||||
|
||||
super(PurchaseInvoice, self).on_cancel()
|
||||
|
||||
self.check_on_hold_or_closed_status()
|
||||
|
||||
@@ -235,41 +235,39 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
|
||||
discount_account = create_account(account_name="Discount Account",
|
||||
parent_account="Indirect Expenses - _TC", company="_Test Company")
|
||||
pi = make_purchase_invoice(discount_account=discount_account, rate=45)
|
||||
pi = make_purchase_invoice(discount_account=discount_account, discount_amount=100)
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
|
||||
["Creditors - _TC", 0.0, 225.0, nowdate()],
|
||||
["Discount Account - _TC", 0.0, 25.0, nowdate()]
|
||||
["_Test Account Cost for Goods Sold - _TC", 350.0, 0.0, nowdate()],
|
||||
["Creditors - _TC", 0.0, 250.0, nowdate()],
|
||||
["Discount Account - _TC", 0.0, 100.0, nowdate()]
|
||||
]
|
||||
|
||||
check_gl_entries(self, pi.name, expected_gle, nowdate())
|
||||
enable_discount_accounting(enable=0)
|
||||
|
||||
def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self):
|
||||
enable_discount_accounting()
|
||||
additional_discount_account = create_account(account_name="Discount Account",
|
||||
parent_account="Indirect Expenses - _TC", company="_Test Company")
|
||||
|
||||
pi = make_purchase_invoice(do_not_save=1, parent_cost_center="Main - _TC")
|
||||
pi = make_purchase_invoice(qty=1, rate=100, do_not_save=1, parent_cost_center="Main - _TC")
|
||||
pi.apply_discount_on = "Grand Total"
|
||||
pi.additional_discount_account = additional_discount_account
|
||||
pi.additional_discount_percentage = 10
|
||||
pi.disable_rounded_total = 1
|
||||
pi.additional_discount_percentage = 20
|
||||
pi.append("taxes", {
|
||||
"charge_type": "On Net Total",
|
||||
"charge_type": "Actual",
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"cost_center": "Main - _TC",
|
||||
"description": "Test",
|
||||
"rate": 10
|
||||
"tax_amount": 20
|
||||
})
|
||||
pi.submit()
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
|
||||
["_Test Account VAT - _TC", 25.0, 0.0, nowdate()],
|
||||
["Creditors - _TC", 0.0, 247.5, nowdate()],
|
||||
["Discount Account - _TC", 0.0, 27.5, nowdate()]
|
||||
["_Test Account Cost for Goods Sold - _TC", 100.0, 0.0, nowdate()],
|
||||
["_Test Account VAT - _TC", 20.0, 0.0, nowdate()],
|
||||
["Creditors - _TC", 0.0, 96.0, nowdate()],
|
||||
["Discount Account - _TC", 0.0, 24.0, nowdate()]
|
||||
]
|
||||
|
||||
check_gl_entries(self, pi.name, expected_gle, nowdate())
|
||||
@@ -1262,7 +1260,6 @@ def make_purchase_invoice(**args):
|
||||
"received_qty": args.received_qty or 0,
|
||||
"rejected_qty": args.rejected_qty or 0,
|
||||
"rate": args.rate or 50,
|
||||
"price_list_rate": args.price_list_rate or 50,
|
||||
"expense_account": args.expense_account or '_Test Account Cost for Goods Sold - _TC',
|
||||
"discount_account": args.discount_account or None,
|
||||
"discount_amount": args.discount_amount or 0,
|
||||
|
||||
@@ -447,15 +447,6 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
this.frm.refresh_field("outstanding_amount");
|
||||
this.frm.refresh_field("paid_amount");
|
||||
this.frm.refresh_field("base_paid_amount");
|
||||
},
|
||||
|
||||
currency() {
|
||||
this._super();
|
||||
$.each(cur_frm.doc.timesheets, function(i, d) {
|
||||
let row = frappe.get_doc(d.doctype, d.name)
|
||||
set_timesheet_detail_rate(row.doctype, row.name, cur_frm.doc.currency, row.timesheet_detail)
|
||||
});
|
||||
calculate_total_billing_amount(cur_frm)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -855,8 +846,7 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
'time_sheet': row.parent,
|
||||
'billing_hours': row.billing_hours,
|
||||
'billing_amount': flt(row.billing_amount) * flt(exchange_rate),
|
||||
'timesheet_detail': row.name,
|
||||
'project_name': row.project_name
|
||||
'timesheet_detail': row.name
|
||||
});
|
||||
frm.refresh_field('timesheets');
|
||||
calculate_total_billing_amount(frm);
|
||||
@@ -975,34 +965,43 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
}
|
||||
})
|
||||
|
||||
frappe.ui.form.on('Sales Invoice Timesheet', {
|
||||
time_sheet: function(frm, cdt, cdn){
|
||||
var d = locals[cdt][cdn];
|
||||
if(d.time_sheet) {
|
||||
frappe.call({
|
||||
method: "erpnext.projects.doctype.timesheet.timesheet.get_timesheet_data",
|
||||
args: {
|
||||
'name': d.time_sheet,
|
||||
'project': frm.doc.project || null
|
||||
},
|
||||
callback: function(r, rt) {
|
||||
if(r.message){
|
||||
let data = r.message;
|
||||
frappe.model.set_value(cdt, cdn, "billing_hours", data.billing_hours);
|
||||
frappe.model.set_value(cdt, cdn, "billing_amount", data.billing_amount);
|
||||
frappe.model.set_value(cdt, cdn, "timesheet_detail", data.timesheet_detail);
|
||||
calculate_total_billing_amount(frm)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var calculate_total_billing_amount = function(frm) {
|
||||
var doc = frm.doc;
|
||||
|
||||
doc.total_billing_amount = 0.0
|
||||
if (doc.timesheets) {
|
||||
if(doc.timesheets) {
|
||||
$.each(doc.timesheets, function(index, data){
|
||||
doc.total_billing_amount += flt(data.billing_amount)
|
||||
doc.total_billing_amount += data.billing_amount
|
||||
})
|
||||
}
|
||||
|
||||
refresh_field('total_billing_amount')
|
||||
}
|
||||
|
||||
var set_timesheet_detail_rate = function(cdt, cdn, currency, timelog) {
|
||||
frappe.call({
|
||||
method: "erpnext.projects.doctype.timesheet.timesheet.get_timesheet_detail_rate",
|
||||
args: {
|
||||
timelog: timelog,
|
||||
currency: currency
|
||||
},
|
||||
callback: function(r) {
|
||||
if (!r.exc && r.message) {
|
||||
frappe.model.set_value(cdt, cdn, 'billing_amount', r.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var select_loyalty_program = function(frm, loyalty_programs) {
|
||||
var dialog = new frappe.ui.Dialog({
|
||||
title: __("Select Loyalty Program"),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -290,8 +290,6 @@ class SalesInvoice(SellingController):
|
||||
self.update_time_sheet(None)
|
||||
|
||||
def on_cancel(self):
|
||||
check_if_return_invoice_linked_with_payment_entry(self)
|
||||
|
||||
super(SalesInvoice, self).on_cancel()
|
||||
|
||||
self.check_sales_order_on_hold_or_close("sales_order")
|
||||
@@ -482,7 +480,7 @@ class SalesInvoice(SellingController):
|
||||
if not self.pos_profile:
|
||||
pos_profile = get_pos_profile(self.company) or {}
|
||||
if not pos_profile:
|
||||
return
|
||||
frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
|
||||
self.pos_profile = pos_profile.get('name')
|
||||
|
||||
pos = {}
|
||||
@@ -973,7 +971,7 @@ class SalesInvoice(SellingController):
|
||||
def set_asset_status(self, asset):
|
||||
if self.is_return:
|
||||
asset.set_status()
|
||||
else:
|
||||
else:
|
||||
asset.set_status("Sold" if self.docstatus==1 else None)
|
||||
|
||||
def make_loyalty_point_redemption_gle(self, gl_entries):
|
||||
@@ -1941,41 +1939,3 @@ def create_dunning(source_name, target_doc=None):
|
||||
}
|
||||
}, target_doc, set_missing_values)
|
||||
return doclist
|
||||
|
||||
def check_if_return_invoice_linked_with_payment_entry(self):
|
||||
# If a Return invoice is linked with payment entry along with other invoices,
|
||||
# the cancellation of the Return causes allocated amount to be greater than paid
|
||||
|
||||
if not frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
|
||||
return
|
||||
|
||||
payment_entries = []
|
||||
if self.is_return and self.return_against:
|
||||
invoice = self.return_against
|
||||
else:
|
||||
invoice = self.name
|
||||
|
||||
payment_entries = frappe.db.sql_list("""
|
||||
SELECT
|
||||
t1.name
|
||||
FROM
|
||||
`tabPayment Entry` t1, `tabPayment Entry Reference` t2
|
||||
WHERE
|
||||
t1.name = t2.parent
|
||||
and t1.docstatus = 1
|
||||
and t2.reference_name = %s
|
||||
and t2.allocated_amount < 0
|
||||
""", invoice)
|
||||
|
||||
links_to_pe = []
|
||||
if payment_entries:
|
||||
for payment in payment_entries:
|
||||
payment_entry = frappe.get_doc("Payment Entry", payment)
|
||||
if len(payment_entry.references) > 1:
|
||||
links_to_pe.append(payment_entry.name)
|
||||
if links_to_pe:
|
||||
payment_entries_link = [get_link_to_form('Payment Entry', name, label=name) for name in links_to_pe]
|
||||
message = _("Please cancel and amend the Payment Entry")
|
||||
message += " " + ", ".join(payment_entries_link) + " "
|
||||
message += _("to unallocate the amount of this Return Invoice before cancelling it.")
|
||||
frappe.throw(message)
|
||||
|
||||
@@ -1908,8 +1908,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
|
||||
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
|
||||
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):
|
||||
# init
|
||||
@@ -1993,16 +1991,15 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
discount_account = create_account(account_name="Discount Account",
|
||||
parent_account="Indirect Expenses - _TC", company="_Test Company")
|
||||
si = create_sales_invoice(discount_account=discount_account, discount_percentage=10, rate=90)
|
||||
si = create_sales_invoice(discount_account=discount_account, discount_amount=100)
|
||||
|
||||
expected_gle = [
|
||||
["Debtors - _TC", 90.0, 0.0, nowdate()],
|
||||
["Discount Account - _TC", 10.0, 0.0, nowdate()],
|
||||
["Sales - _TC", 0.0, 100.0, nowdate()]
|
||||
["Debtors - _TC", 100.0, 0.0, nowdate()],
|
||||
["Discount Account - _TC", 100.0, 0.0, nowdate()],
|
||||
["Sales - _TC", 0.0, 200.0, nowdate()]
|
||||
]
|
||||
|
||||
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
|
||||
enable_discount_accounting(enable=0)
|
||||
|
||||
def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import enable_discount_accounting
|
||||
@@ -2011,28 +2008,28 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
additional_discount_account = create_account(account_name="Discount Account",
|
||||
parent_account="Indirect Expenses - _TC", company="_Test Company")
|
||||
|
||||
si = create_sales_invoice(parent_cost_center='Main - _TC', do_not_save=1)
|
||||
si = create_sales_invoice(rate=100, parent_cost_center='Main - _TC', do_not_save=1)
|
||||
si.apply_discount_on = "Grand Total"
|
||||
si.additional_discount_account = additional_discount_account
|
||||
si.additional_discount_percentage = 20
|
||||
si.append("taxes", {
|
||||
"charge_type": "On Net Total",
|
||||
"charge_type": "Actual",
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"cost_center": "Main - _TC",
|
||||
"description": "Test",
|
||||
"rate": 10
|
||||
"rate": 0,
|
||||
"tax_amount": 20
|
||||
})
|
||||
si.submit()
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account VAT - _TC", 0.0, 10.0, nowdate()],
|
||||
["Debtors - _TC", 88, 0.0, nowdate()],
|
||||
["Discount Account - _TC", 22.0, 0.0, nowdate()],
|
||||
["_Test Account VAT - _TC", 0.0, 20.0, nowdate()],
|
||||
["Debtors - _TC", 96.0, 0.0, nowdate()],
|
||||
["Discount Account - _TC", 24.0, 0.0, nowdate()],
|
||||
["Sales - _TC", 0.0, 100.0, nowdate()]
|
||||
]
|
||||
|
||||
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
|
||||
enable_discount_accounting(enable=0)
|
||||
|
||||
def get_sales_invoice_for_e_invoice():
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
@@ -2112,30 +2109,6 @@ def make_test_address_for_ewaybill():
|
||||
|
||||
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():
|
||||
if not frappe.db.exists('Supplier', '_Test Transporter'):
|
||||
frappe.get_doc({
|
||||
@@ -2174,7 +2147,6 @@ def make_sales_invoice_for_ewaybill():
|
||||
si.distance = 2000
|
||||
si.company_address = "_Test Address for Eway bill-Billing"
|
||||
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.gst_category = "Registered Regular"
|
||||
si.mode_of_transport = 'Road'
|
||||
@@ -2239,7 +2211,6 @@ def create_sales_invoice(**args):
|
||||
"uom": args.uom or "Nos",
|
||||
"stock_uom": args.uom or "Nos",
|
||||
"rate": args.rate if args.get("rate") is not None else 100,
|
||||
"price_list_rate": args.price_list_rate if args.get("price_list_rate") is not None else 100,
|
||||
"income_account": args.income_account or "Sales - _TC",
|
||||
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
||||
"discount_account": args.discount_account or None,
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
"description",
|
||||
"billing_hours",
|
||||
"billing_amount",
|
||||
"column_break_5",
|
||||
"time_sheet",
|
||||
"project_name",
|
||||
"timesheet_detail"
|
||||
],
|
||||
"fields": [
|
||||
@@ -63,21 +61,11 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_5",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "project_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Project Name",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-08 14:43:02.748981",
|
||||
"modified": "2021-05-20 22:33:57.234846",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Timesheet",
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-05-06 16:17:44.329943",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"sales_partner"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "sales_partner",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Sales Partner ",
|
||||
"options": "Sales Partner"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-07 10:43:37.532095",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Partner Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
# 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 SalesPartnerItem(Document):
|
||||
pass
|
||||
@@ -6,7 +6,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from frappe.model.document import Document
|
||||
from erpnext.controllers.accounts_controller import validate_taxes_and_charges, validate_inclusive_tax, validate_cost_center, validate_account_head
|
||||
from erpnext.controllers.accounts_controller import validate_taxes_and_charges, validate_inclusive_tax
|
||||
|
||||
class SalesTaxesandChargesTemplate(Document):
|
||||
def validate(self):
|
||||
@@ -39,8 +39,6 @@ def valdiate_taxes_and_charges_template(doc):
|
||||
|
||||
for tax in doc.get("taxes"):
|
||||
validate_taxes_and_charges(tax)
|
||||
validate_account_head(tax, doc)
|
||||
validate_cost_center(tax, doc)
|
||||
validate_inclusive_tax(tax, doc)
|
||||
|
||||
def validate_disabled(doc):
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"charge_type": "On Net Total",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"cost_center": "Main - _TC",
|
||||
"parentfield": "taxes",
|
||||
"rate": 6
|
||||
},
|
||||
@@ -17,7 +16,6 @@
|
||||
"charge_type": "On Net Total",
|
||||
"description": "Service Tax",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"cost_center": "Main - _TC",
|
||||
"parentfield": "taxes",
|
||||
"rate": 6.36
|
||||
}
|
||||
@@ -116,7 +114,6 @@
|
||||
"charge_type": "On Net Total",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"cost_center": "Main - _TC",
|
||||
"parentfield": "taxes",
|
||||
"rate": 12
|
||||
},
|
||||
@@ -125,7 +122,6 @@
|
||||
"charge_type": "On Net Total",
|
||||
"description": "Service Tax",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"cost_center": "Main - _TC",
|
||||
"parentfield": "taxes",
|
||||
"rate": 4
|
||||
}
|
||||
@@ -141,7 +137,6 @@
|
||||
"charge_type": "On Net Total",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"cost_center": "Main - _TC",
|
||||
"parentfield": "taxes",
|
||||
"rate": 12
|
||||
},
|
||||
@@ -150,7 +145,6 @@
|
||||
"charge_type": "On Net Total",
|
||||
"description": "Service Tax",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"cost_center": "Main - _TC",
|
||||
"parentfield": "taxes",
|
||||
"rate": 4
|
||||
}
|
||||
@@ -166,7 +160,6 @@
|
||||
"charge_type": "On Net Total",
|
||||
"description": "VAT",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"cost_center": "Main - _TC",
|
||||
"parentfield": "taxes",
|
||||
"rate": 12
|
||||
},
|
||||
@@ -175,7 +168,6 @@
|
||||
"charge_type": "On Net Total",
|
||||
"description": "Service Tax",
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"cost_center": "Main - _TC",
|
||||
"parentfield": "taxes",
|
||||
"rate": 4
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
"label": "Cost"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.price_determination==\"Based On Price List\"",
|
||||
"depends_on": "eval:doc.price_determination==\"Based on price list\"",
|
||||
"fieldname": "price_list",
|
||||
"fieldtype": "Link",
|
||||
"label": "Price List",
|
||||
@@ -147,7 +147,7 @@
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2021-08-09 10:53:44.205774",
|
||||
"modified": "2020-06-25 10:53:44.205774",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Subscription Plan",
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-05-06 16:19:22.040795",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"supplier_group"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "supplier_group",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Supplier Group",
|
||||
"options": "Supplier Group"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-07 10:43:59.877938",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Supplier Group Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
# 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 SupplierGroupItem(Document):
|
||||
pass
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-05-06 16:18:54.758468",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"supplier"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "supplier",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Supplier",
|
||||
"options": "Supplier"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-07 10:44:09.707778",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Supplier Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
# 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 SupplierItem(Document):
|
||||
pass
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-05-06 16:16:51.885441",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"territory"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Territory",
|
||||
"options": "Territory"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-07 10:43:26.641030",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Territory Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
# 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 TerritoryItem(Document):
|
||||
pass
|
||||
@@ -100,8 +100,8 @@ def merge_similar_entries(gl_map, precision=None):
|
||||
return merged_gl_map
|
||||
|
||||
def check_if_in_list(gle, gl_map, dimensions=None):
|
||||
account_head_fieldnames = ['voucher_detail_no', 'party', 'against_voucher',
|
||||
'cost_center', 'against_voucher_type', 'party_type', 'project']
|
||||
account_head_fieldnames = ['party_type', 'party', 'against_voucher', 'against_voucher_type',
|
||||
'cost_center', 'project', 'voucher_detail_no']
|
||||
|
||||
if dimensions:
|
||||
account_head_fieldnames = account_head_fieldnames + dimensions
|
||||
@@ -110,12 +110,10 @@ def check_if_in_list(gle, gl_map, dimensions=None):
|
||||
same_head = True
|
||||
if e.account != gle.account:
|
||||
same_head = False
|
||||
continue
|
||||
|
||||
for fieldname in account_head_fieldnames:
|
||||
if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)):
|
||||
same_head = False
|
||||
break
|
||||
|
||||
if same_head:
|
||||
return e
|
||||
@@ -145,19 +143,16 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False):
|
||||
validate_expense_against_budget(args)
|
||||
|
||||
def validate_cwip_accounts(gl_map):
|
||||
"""Validate that CWIP account are not used in Journal Entry"""
|
||||
if gl_map and gl_map[0].voucher_type != "Journal Entry":
|
||||
return
|
||||
cwip_enabled = any(cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting"))
|
||||
|
||||
cwip_enabled = any(cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category", "enable_cwip_accounting"))
|
||||
if cwip_enabled:
|
||||
cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
|
||||
where account_type = 'Capital Work in Progress' and is_group=0""")]
|
||||
if cwip_enabled and gl_map[0].voucher_type == "Journal Entry":
|
||||
cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
|
||||
where account_type = 'Capital Work in Progress' and is_group=0""")]
|
||||
|
||||
for entry in gl_map:
|
||||
if entry.account in cwip_accounts:
|
||||
frappe.throw(
|
||||
_("Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry").format(entry.account))
|
||||
for entry in gl_map:
|
||||
if entry.account in cwip_accounts:
|
||||
frappe.throw(
|
||||
_("Account: <b>{0}</b> is capital Work in progress and can not be updated by Journal Entry").format(entry.account))
|
||||
|
||||
def round_off_debit_credit(gl_map):
|
||||
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
|
||||
|
||||
@@ -8,7 +8,7 @@ from frappe import _, msgprint, scrub
|
||||
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.utils import (add_days, getdate, formatdate, date_diff,
|
||||
add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day, cint)
|
||||
add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day)
|
||||
from frappe.contacts.doctype.address.address import (get_address_display,
|
||||
get_default_address, get_company_address)
|
||||
from frappe.contacts.doctype.contact.contact import get_contact_details
|
||||
@@ -58,7 +58,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
|
||||
customer_group=party_details.customer_group, supplier_group=party_details.supplier_group, tax_category=party_details.tax_category,
|
||||
billing_address=party_address, shipping_address=shipping_address)
|
||||
|
||||
if cint(fetch_payment_terms_template):
|
||||
if fetch_payment_terms_template:
|
||||
party_details["payment_terms_template"] = get_payment_terms_template(party.name, party_type, company)
|
||||
|
||||
if not party_details.get("currency"):
|
||||
|
||||
@@ -553,10 +553,14 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no):
|
||||
and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no))
|
||||
|
||||
for pe in linked_pe:
|
||||
pe_doc = frappe.get_doc("Payment Entry", pe)
|
||||
pe_doc.set_total_allocated_amount()
|
||||
pe_doc.set_unallocated_amount()
|
||||
pe_doc.clear_unallocated_reference_document_rows()
|
||||
try:
|
||||
pe_doc = frappe.get_doc("Payment Entry", pe)
|
||||
pe_doc.validate(on_reference_unlink=True)
|
||||
except Exception as e:
|
||||
msg = _("There were issues unlinking payment entry {0}.").format(pe_doc.name)
|
||||
msg += '<br>'
|
||||
msg += _("Please cancel payment entry manually first and then resubmit")
|
||||
frappe.throw(msg, title=_("Payment Unlink Error"))
|
||||
|
||||
frappe.db.sql("""update `tabPayment Entry` set total_allocated_amount=%s,
|
||||
base_total_allocated_amount=%s, unallocated_amount=%s, modified=%s, modified_by=%s
|
||||
@@ -920,6 +924,7 @@ def repost_gle_for_stock_vouchers(stock_vouchers, posting_date, company=None, wa
|
||||
_delete_gl_entries(voucher_type, voucher_no)
|
||||
|
||||
def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None, company=None):
|
||||
future_stock_vouchers = []
|
||||
|
||||
values = []
|
||||
condition = ""
|
||||
@@ -935,46 +940,30 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f
|
||||
condition += " and company = %s"
|
||||
values.append(company)
|
||||
|
||||
future_stock_vouchers = frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
|
||||
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
|
||||
from `tabStock Ledger Entry` sle
|
||||
where
|
||||
timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s)
|
||||
and is_cancelled = 0
|
||||
{condition}
|
||||
order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition),
|
||||
tuple([posting_date, posting_time] + values), as_dict=True)
|
||||
tuple([posting_date, posting_time] + values), as_dict=True):
|
||||
future_stock_vouchers.append([d.voucher_type, d.voucher_no])
|
||||
|
||||
return [(d.voucher_type, d.voucher_no) for d in future_stock_vouchers]
|
||||
return future_stock_vouchers
|
||||
|
||||
def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
|
||||
""" Get voucherwise list of GL entries.
|
||||
|
||||
Only fetches GLE fields required for comparing with new GLE.
|
||||
Check compare_existing_and_expected_gle function below.
|
||||
"""
|
||||
gl_entries = {}
|
||||
if not future_stock_vouchers:
|
||||
return gl_entries
|
||||
|
||||
voucher_nos = [d[1] for d in future_stock_vouchers]
|
||||
|
||||
gles = frappe.db.sql("""
|
||||
select name, account, credit, debit, cost_center, project
|
||||
from `tabGL Entry`
|
||||
where
|
||||
posting_date >= %s and voucher_no in (%s)""" %
|
||||
('%s', ', '.join(['%s'] * len(voucher_nos))),
|
||||
tuple([posting_date] + voucher_nos), as_dict=1)
|
||||
|
||||
for d in gles:
|
||||
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
|
||||
if future_stock_vouchers:
|
||||
for d in frappe.db.sql("""select * from `tabGL Entry`
|
||||
where posting_date >= %s and voucher_no in (%s)""" %
|
||||
('%s', ', '.join(['%s']*len(future_stock_vouchers))),
|
||||
tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1):
|
||||
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
|
||||
|
||||
return gl_entries
|
||||
|
||||
def compare_existing_and_expected_gle(existing_gle, expected_gle, precision):
|
||||
if len(existing_gle) != len(expected_gle):
|
||||
return False
|
||||
|
||||
matched = True
|
||||
for entry in expected_gle:
|
||||
account_existed = False
|
||||
|
||||
@@ -82,46 +82,24 @@ frappe.ui.form.on('Asset', {
|
||||
if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) {
|
||||
frm.add_custom_button("Transfer Asset", function() {
|
||||
erpnext.asset.transfer_asset(frm);
|
||||
}, __("Manage"));
|
||||
});
|
||||
|
||||
frm.add_custom_button("Scrap Asset", function() {
|
||||
erpnext.asset.scrap_asset(frm);
|
||||
}, __("Manage"));
|
||||
});
|
||||
|
||||
frm.add_custom_button("Sell Asset", function() {
|
||||
frm.trigger("make_sales_invoice");
|
||||
}, __("Manage"));
|
||||
});
|
||||
|
||||
} else if (frm.doc.status=='Scrapped') {
|
||||
frm.add_custom_button("Restore Asset", function() {
|
||||
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) {
|
||||
frm.add_custom_button("View General Ledger", function() {
|
||||
frm.add_custom_button("General Ledger", function() {
|
||||
frappe.route_options = {
|
||||
"voucher_no": frm.doc.name,
|
||||
"from_date": frm.doc.available_for_use_date,
|
||||
@@ -129,9 +107,27 @@ frappe.ui.form.on('Asset', {
|
||||
"company": frm.doc.company
|
||||
};
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -308,20 +304,6 @@ 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) {
|
||||
frappe.call({
|
||||
args: {
|
||||
|
||||
@@ -502,7 +502,7 @@
|
||||
"link_fieldname": "asset"
|
||||
}
|
||||
],
|
||||
"modified": "2021-06-24 14:58:51.097908",
|
||||
"modified": "2021-01-22 12:38:59.091510",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
|
||||
@@ -168,24 +168,17 @@ class Asset(AccountsController):
|
||||
d.precision("rate_of_depreciation"))
|
||||
|
||||
def make_depreciation_schedule(self):
|
||||
if 'Manual' not in [d.depreciation_method for d in self.finance_books] and not self.schedules:
|
||||
if 'Manual' not in [d.depreciation_method for d in self.finance_books]:
|
||||
self.schedules = []
|
||||
|
||||
if not self.available_for_use_date:
|
||||
if self.get("schedules") or not self.available_for_use_date:
|
||||
return
|
||||
|
||||
for d in self.get('finance_books'):
|
||||
self.validate_asset_finance_books(d)
|
||||
|
||||
start = self.clear_depreciation_schedule()
|
||||
|
||||
# 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))
|
||||
value_after_depreciation = (flt(self.gross_purchase_amount) -
|
||||
flt(self.opening_accumulated_depreciation))
|
||||
|
||||
d.value_after_depreciation = value_after_depreciation
|
||||
|
||||
@@ -198,7 +191,7 @@ class Asset(AccountsController):
|
||||
number_of_pending_depreciations += 1
|
||||
|
||||
skip_row = False
|
||||
for n in range(start, number_of_pending_depreciations):
|
||||
for n in range(number_of_pending_depreciations):
|
||||
# If depreciation is already completed (for double declining balance)
|
||||
if skip_row: continue
|
||||
|
||||
@@ -223,13 +216,11 @@ class Asset(AccountsController):
|
||||
|
||||
# For last row
|
||||
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
|
||||
if not self.flags.increase_in_asset_life:
|
||||
# In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
|
||||
self.to_date = add_months(self.available_for_use_date,
|
||||
n * cint(d.frequency_of_depreciation))
|
||||
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, schedule_date, self.to_date)
|
||||
depreciation_amount, schedule_date, to_date)
|
||||
|
||||
monthly_schedule_date = add_months(schedule_date, 1)
|
||||
|
||||
@@ -293,23 +284,10 @@ class Asset(AccountsController):
|
||||
"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):
|
||||
has_pro_rata = False
|
||||
days = date_diff(row.depreciation_start_date, self.available_for_use_date) + 1
|
||||
|
||||
# if frequency_of_depreciation is 12 months, total_days = 365
|
||||
days = date_diff(row.depreciation_start_date, self.available_for_use_date) + 1
|
||||
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
|
||||
|
||||
if days < total_days:
|
||||
@@ -368,12 +346,11 @@ class Asset(AccountsController):
|
||||
if d.finance_book_id not in finance_books:
|
||||
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
|
||||
value_after_depreciation = flt(self.get_value_after_depreciation(d.finance_book_id))
|
||||
finance_books.append(int(d.finance_book_id))
|
||||
finance_books.append(d.finance_book_id)
|
||||
|
||||
depreciation_amount = flt(d.depreciation_amount, d.precision("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:
|
||||
book = self.get('finance_books')[cint(d.finance_book_id) - 1]
|
||||
depreciation_amount += flt(value_after_depreciation -
|
||||
@@ -648,18 +625,9 @@ def create_asset_maintenance(asset, item_code, item_name, asset_category, compan
|
||||
})
|
||||
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()
|
||||
def create_asset_adjustment(asset, asset_category, company):
|
||||
asset_maintenance = frappe.get_doc("Asset Value Adjustment")
|
||||
asset_maintenance = frappe.new_doc("Asset Value Adjustment")
|
||||
asset_maintenance.update({
|
||||
"asset": asset,
|
||||
"company": company,
|
||||
@@ -789,16 +757,9 @@ def get_depreciation_amount(asset, depreciable_value, row):
|
||||
depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
|
||||
|
||||
if row.depreciation_method in ("Straight Line", "Manual"):
|
||||
# if the Depreciation Schedule is being prepared for the first time
|
||||
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)
|
||||
depreciation_amount = (flt(row.value_after_depreciation) -
|
||||
flt(row.expected_value_after_useful_life)) / depreciation_left
|
||||
else:
|
||||
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
|
||||
|
||||
return depreciation_amount
|
||||
return depreciation_amount
|
||||
@@ -125,6 +125,7 @@ class TestAsset(unittest.TestCase):
|
||||
"frequency_of_depreciation": 12,
|
||||
"depreciation_start_date": "2030-12-31"
|
||||
})
|
||||
asset.insert()
|
||||
self.assertEqual(asset.status, "Draft")
|
||||
asset.save()
|
||||
expected_schedules = [
|
||||
@@ -153,8 +154,9 @@ class TestAsset(unittest.TestCase):
|
||||
"frequency_of_depreciation": 12,
|
||||
"depreciation_start_date": '2030-12-31'
|
||||
})
|
||||
asset.save()
|
||||
asset.insert()
|
||||
self.assertEqual(asset.status, "Draft")
|
||||
asset.save()
|
||||
|
||||
expected_schedules = [
|
||||
['2030-12-31', 66667.00, 66667.00],
|
||||
@@ -183,7 +185,7 @@ class TestAsset(unittest.TestCase):
|
||||
"frequency_of_depreciation": 12,
|
||||
"depreciation_start_date": "2030-12-31"
|
||||
})
|
||||
asset.save()
|
||||
asset.insert()
|
||||
self.assertEqual(asset.status, "Draft")
|
||||
|
||||
expected_schedules = [
|
||||
@@ -214,6 +216,7 @@ class TestAsset(unittest.TestCase):
|
||||
"depreciation_start_date": "2030-12-31"
|
||||
})
|
||||
|
||||
asset.insert()
|
||||
asset.save()
|
||||
|
||||
expected_schedules = [
|
||||
@@ -244,6 +247,7 @@ class TestAsset(unittest.TestCase):
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-12-31"
|
||||
})
|
||||
asset.insert()
|
||||
asset.submit()
|
||||
asset.load_from_db()
|
||||
self.assertEqual(asset.status, "Submitted")
|
||||
@@ -346,6 +350,7 @@ class TestAsset(unittest.TestCase):
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-12-31"
|
||||
})
|
||||
asset.insert()
|
||||
asset.submit()
|
||||
post_depreciation_entries(date="2021-01-01")
|
||||
|
||||
@@ -375,6 +380,7 @@ class TestAsset(unittest.TestCase):
|
||||
"total_number_of_depreciations": 10,
|
||||
"frequency_of_depreciation": 1
|
||||
})
|
||||
asset.insert()
|
||||
asset.submit()
|
||||
|
||||
post_depreciation_entries(date=add_months('2020-01-01', 4))
|
||||
@@ -418,6 +424,7 @@ class TestAsset(unittest.TestCase):
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-12-31"
|
||||
})
|
||||
asset.insert()
|
||||
asset.submit()
|
||||
post_depreciation_entries(date="2021-01-01")
|
||||
|
||||
@@ -461,7 +468,7 @@ class TestAsset(unittest.TestCase):
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10
|
||||
})
|
||||
asset.save()
|
||||
asset.insert()
|
||||
accumulated_depreciation_after_full_schedule = \
|
||||
max(d.accumulated_depreciation_amount for d in asset.get("schedules"))
|
||||
|
||||
@@ -639,7 +646,7 @@ class TestAsset(unittest.TestCase):
|
||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
|
||||
asset = frappe.get_doc('Asset', asset_name)
|
||||
asset.calculate_depreciation = 1
|
||||
asset.available_for_use_date = '2030-07-12'
|
||||
asset.available_for_use_date = '2030-06-12'
|
||||
asset.purchase_date = '2030-01-01'
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 1000,
|
||||
@@ -653,10 +660,10 @@ class TestAsset(unittest.TestCase):
|
||||
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
|
||||
|
||||
expected_schedules = [
|
||||
["2030-12-31", 942.47, 942.47],
|
||||
["2031-12-31", 3528.77, 4471.24],
|
||||
["2032-12-31", 1764.38, 6235.62],
|
||||
["2033-07-12", 764.38, 7000.00]
|
||||
["2030-12-31", 1106.85, 1106.85],
|
||||
["2031-12-31", 3446.58, 4553.43],
|
||||
["2032-12-31", 1723.29, 6276.72],
|
||||
["2033-06-12", 723.28, 7000.00]
|
||||
]
|
||||
|
||||
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
|
||||
@@ -692,7 +699,7 @@ def create_asset(**args):
|
||||
"item_code": args.item_code or "Macbook Pro",
|
||||
"company": args.company or"_Test Company",
|
||||
"purchase_date": "2015-01-01",
|
||||
"calculate_depreciation": args.calculate_depreciation or 0,
|
||||
"calculate_depreciation": 0,
|
||||
"gross_purchase_amount": 100000,
|
||||
"purchase_receipt_amount": 100000,
|
||||
"expected_value_after_useful_life": 10000,
|
||||
@@ -700,16 +707,9 @@ def create_asset(**args):
|
||||
"available_for_use_date": "2020-06-06",
|
||||
"location": "Test Location",
|
||||
"asset_owner": "Company",
|
||||
"is_existing_asset": 1
|
||||
"is_existing_asset": args.is_existing_asset or 0
|
||||
})
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
asset.append("finance_books", {
|
||||
"depreciation_method": "Straight Line",
|
||||
"frequency_of_depreciation": 12,
|
||||
"total_number_of_depreciations": 5
|
||||
})
|
||||
|
||||
try:
|
||||
asset.save()
|
||||
except frappe.DuplicateEntryError:
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
{
|
||||
"fieldname": "value_after_depreciation",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Value After Depreciation",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
@@ -84,7 +85,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-17 12:59:05.743683",
|
||||
"modified": "2020-11-05 16:30:09.213479",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Finance Book",
|
||||
|
||||
@@ -2,45 +2,6 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
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) => {
|
||||
if (frm.doc.completion_date && frm.doc.repair_status == "Completed") {
|
||||
frappe.call ({
|
||||
@@ -56,16 +17,5 @@ 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,43 +7,38 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"asset",
|
||||
"company",
|
||||
"column_break_2",
|
||||
"asset_name",
|
||||
"naming_series",
|
||||
"asset_name",
|
||||
"column_break_2",
|
||||
"item_code",
|
||||
"item_name",
|
||||
"section_break_5",
|
||||
"failure_date",
|
||||
"repair_status",
|
||||
"assign_to",
|
||||
"assign_to_name",
|
||||
"column_break_6",
|
||||
"completion_date",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"column_break_14",
|
||||
"project",
|
||||
"accounting_details",
|
||||
"repair_status",
|
||||
"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",
|
||||
"description",
|
||||
"column_break_9",
|
||||
"actions_performed",
|
||||
"section_break_23",
|
||||
"section_break_17",
|
||||
"downtime",
|
||||
"column_break_19",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"columns": 1,
|
||||
"fieldname": "asset_name",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Asset",
|
||||
"options": "Asset",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
@@ -55,6 +50,18 @@
|
||||
"fieldname": "column_break_2",
|
||||
"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",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -67,20 +74,33 @@
|
||||
"label": "Failure Date",
|
||||
"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",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "completion_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Completion Date",
|
||||
"no_copy": 1
|
||||
"label": "Completion Date"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "Pending",
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"fieldname": "repair_status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Repair Status",
|
||||
@@ -96,18 +116,25 @@
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Long Text",
|
||||
"label": "Error Description"
|
||||
"label": "Error Description",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "actions_performed",
|
||||
"fieldtype": "Long Text",
|
||||
"label": "Actions performed"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_17",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "downtime",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
@@ -119,7 +146,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "repair_cost",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Repair Cost"
|
||||
@@ -132,138 +159,12 @@
|
||||
"options": "Asset Repair",
|
||||
"print_hide": 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"
|
||||
},
|
||||
{
|
||||
"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,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-25 13:14:38.307723",
|
||||
"modified": "2021-01-22 15:08:12.495850",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Repair",
|
||||
@@ -302,7 +203,6 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "asset_name",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
}
|
||||
@@ -5,252 +5,16 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import time_diff_in_hours, getdate, add_months, flt, cint
|
||||
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
|
||||
from frappe.utils import time_diff_in_hours
|
||||
from frappe.model.document import Document
|
||||
|
||||
class AssetRepair(AccountsController):
|
||||
class AssetRepair(Document):
|
||||
def validate(self):
|
||||
self.asset_doc = frappe.get_doc('Asset', self.asset)
|
||||
self.update_status()
|
||||
if self.repair_status == "Completed" and not self.completion_date:
|
||||
frappe.throw(_("Please select Completion Date for Completed Repair"))
|
||||
|
||||
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()
|
||||
def get_downtime(failure_date, completion_date):
|
||||
downtime = time_diff_in_hours(completion_date, failure_date)
|
||||
return round(downtime, 2)
|
||||
return round(downtime, 2)
|
||||
@@ -2,167 +2,8 @@
|
||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import nowdate, flt
|
||||
|
||||
import unittest
|
||||
from erpnext.assets.doctype.asset.test_asset import create_asset_data, create_asset, set_depreciation_settings_in_company
|
||||
|
||||
class TestAssetRepair(unittest.TestCase):
|
||||
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
|
||||
pass
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
# 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
|
||||
@@ -447,11 +447,10 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
||||
target.flags.ignore_permissions = ignore_permissions
|
||||
set_missing_values(source, target)
|
||||
#Get the advance paid Journal Entries in Purchase Invoice Advance
|
||||
|
||||
if target.get("allocate_advances_automatically"):
|
||||
target.set_advances()
|
||||
|
||||
target.set_payment_schedule()
|
||||
|
||||
def update_item(obj, target, source_parent):
|
||||
target.amount = flt(obj.amount) - flt(obj.billed_amt)
|
||||
target.base_amount = target.amount * flt(source_parent.conversion_rate)
|
||||
@@ -471,7 +470,6 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
||||
"party_account_currency": "party_account_currency",
|
||||
"supplier_warehouse":"supplier_warehouse"
|
||||
},
|
||||
"field_no_map" : ["payment_terms_template"],
|
||||
"validation": {
|
||||
"docstatus": ["=", 1],
|
||||
}
|
||||
@@ -491,6 +489,12 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
||||
},
|
||||
}
|
||||
|
||||
if frappe.get_single("Accounts Settings").automatically_fetch_payment_terms == 1:
|
||||
fields["Payment Schedule"] = {
|
||||
"doctype": "Payment Schedule",
|
||||
"add_if_empty": True
|
||||
}
|
||||
|
||||
doc = get_mapped_doc("Purchase Order", source_name, fields,
|
||||
target_doc, postprocess, ignore_permissions=ignore_permissions)
|
||||
|
||||
|
||||
@@ -484,9 +484,6 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
|
||||
|
||||
def test_make_purchase_invoice_with_terms(self):
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import automatically_fetch_payment_terms, compare_payment_schedules
|
||||
|
||||
automatically_fetch_payment_terms()
|
||||
po = create_purchase_order(do_not_save=True)
|
||||
|
||||
self.assertRaises(frappe.ValidationError, make_pi_from_po, po.name)
|
||||
@@ -512,7 +509,6 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
self.assertEqual(getdate(pi.payment_schedule[0].due_date), getdate(po.transaction_date))
|
||||
self.assertEqual(pi.payment_schedule[1].payment_amount, 2500.0)
|
||||
self.assertEqual(getdate(pi.payment_schedule[1].due_date), add_days(getdate(po.transaction_date), 30))
|
||||
automatically_fetch_payment_terms(enable=0)
|
||||
|
||||
def test_subcontracting(self):
|
||||
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
|
||||
@@ -636,18 +632,14 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
else:
|
||||
raise Exception
|
||||
|
||||
def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self):
|
||||
po = create_purchase_order(do_not_save=1)
|
||||
po.payment_terms_template = '_Test Payment Term Template'
|
||||
po.save()
|
||||
po.submit()
|
||||
def test_terms_does_not_copy(self):
|
||||
po = create_purchase_order()
|
||||
|
||||
self.assertTrue(po.get('payment_schedule'))
|
||||
|
||||
frappe.db.set_value('Company', '_Test Company', 'payment_terms', '_Test Payment Term Template 1')
|
||||
pi = make_pi_from_po(po.name)
|
||||
pi.save()
|
||||
|
||||
self.assertEqual(pi.get('payment_terms_template'), '_Test Payment Term Template 1')
|
||||
frappe.db.set_value('Company', '_Test Company', 'payment_terms', '')
|
||||
self.assertFalse(pi.get('payment_schedule'))
|
||||
|
||||
def test_terms_copied(self):
|
||||
po = create_purchase_order(do_not_save=1)
|
||||
@@ -976,27 +968,8 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
# To test if the PO does NOT have a Blanket Order
|
||||
self.assertEqual(po_doc.items[0].blanket_order, None)
|
||||
|
||||
def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import automatically_fetch_payment_terms, compare_payment_schedules
|
||||
|
||||
automatically_fetch_payment_terms()
|
||||
|
||||
po = create_purchase_order(qty=10, rate=100, do_not_save=1)
|
||||
create_payment_terms_template()
|
||||
po.payment_terms_template = 'Test Receivable Template'
|
||||
po.submit()
|
||||
|
||||
pi = make_purchase_invoice(qty=10, rate=100, do_not_save=1)
|
||||
pi.items[0].purchase_order = po.name
|
||||
pi.items[0].po_detail = po.items[0].name
|
||||
pi.insert()
|
||||
|
||||
# self.assertEqual(po.payment_terms_template, pi.payment_terms_template)
|
||||
compare_payment_schedules(self, po, pi)
|
||||
|
||||
automatically_fetch_payment_terms(enable=0)
|
||||
|
||||
def make_pr_against_po(po, received_qty=0):
|
||||
pr = make_purchase_receipt(po)
|
||||
|
||||
@@ -60,10 +60,23 @@ frappe.ui.form.on("Supplier", {
|
||||
erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name);
|
||||
}, __('Create'));
|
||||
|
||||
frm.add_custom_button(__('Get Supplier Group Details'), function () {
|
||||
frm.trigger("get_supplier_group_details");
|
||||
}, __('Actions'));
|
||||
|
||||
// indicators
|
||||
erpnext.utils.set_party_dashboard_indicators(frm);
|
||||
}
|
||||
},
|
||||
get_supplier_group_details: function(frm) {
|
||||
frappe.call({
|
||||
method: "get_supplier_group_details",
|
||||
doc: frm.doc,
|
||||
callback: function() {
|
||||
frm.refresh();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
is_internal_supplier: function(frm) {
|
||||
if (frm.doc.is_internal_supplier == 1) {
|
||||
|
||||
@@ -51,6 +51,23 @@ class Supplier(TransactionBase):
|
||||
validate_party_accounts(self)
|
||||
self.validate_internal_supplier()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_supplier_group_details(self):
|
||||
doc = frappe.get_doc('Supplier Group', self.supplier_group)
|
||||
self.payment_terms = ""
|
||||
self.accounts = []
|
||||
|
||||
if doc.accounts:
|
||||
for account in doc.accounts:
|
||||
child = self.append('accounts')
|
||||
child.company = account.company
|
||||
child.account = account.account
|
||||
|
||||
if doc.payment_terms:
|
||||
self.payment_terms = doc.payment_terms
|
||||
|
||||
self.save()
|
||||
|
||||
def validate_internal_supplier(self):
|
||||
internal_supplier = frappe.db.get_value("Supplier",
|
||||
{"is_internal_supplier": 1, "represents_company": self.represents_company, "name": ("!=", self.name)}, "name")
|
||||
@@ -86,4 +103,4 @@ class Supplier(TransactionBase):
|
||||
create_contact(supplier, 'Supplier',
|
||||
doc.name, args.get('supplier_email_' + str(i)))
|
||||
except frappe.NameError:
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -13,6 +13,30 @@ test_records = frappe.get_test_records('Supplier')
|
||||
|
||||
|
||||
class TestSupplier(unittest.TestCase):
|
||||
def test_get_supplier_group_details(self):
|
||||
doc = frappe.new_doc("Supplier Group")
|
||||
doc.supplier_group_name = "_Testing Supplier Group"
|
||||
doc.payment_terms = "_Test Payment Term Template 3"
|
||||
doc.accounts = []
|
||||
test_account_details = {
|
||||
"company": "_Test Company",
|
||||
"account": "Creditors - _TC",
|
||||
}
|
||||
doc.append("accounts", test_account_details)
|
||||
doc.save()
|
||||
s_doc = frappe.new_doc("Supplier")
|
||||
s_doc.supplier_name = "Testing Supplier"
|
||||
s_doc.supplier_group = "_Testing Supplier Group"
|
||||
s_doc.payment_terms = ""
|
||||
s_doc.accounts = []
|
||||
s_doc.insert()
|
||||
s_doc.get_supplier_group_details()
|
||||
self.assertEqual(s_doc.payment_terms, "_Test Payment Term Template 3")
|
||||
self.assertEqual(s_doc.accounts[0].company, "_Test Company")
|
||||
self.assertEqual(s_doc.accounts[0].account, "Creditors - _TC")
|
||||
s_doc.delete()
|
||||
doc.delete()
|
||||
|
||||
def test_supplier_default_payment_terms(self):
|
||||
# Payment Term based on Days after invoice date
|
||||
frappe.db.set_value(
|
||||
@@ -136,4 +160,4 @@ def create_supplier(**args):
|
||||
return doc
|
||||
|
||||
except frappe.DuplicateEntryError:
|
||||
return frappe.get_doc("Supplier", args.supplier_name)
|
||||
return frappe.get_doc("Supplier", args.supplier_name)
|
||||
|
||||
73
erpnext/change_log/v13/v13_3_0.md
Normal file
73
erpnext/change_log/v13/v13_3_0.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Version 13.3.0 Release Notes
|
||||
|
||||
### Features & Enhancements
|
||||
|
||||
- Purchase receipt creation from purchase invoice ([#25126](https://github.com/frappe/erpnext/pull/25126))
|
||||
- New Document Transaction Deletion ([#25354](https://github.com/frappe/erpnext/pull/25354))
|
||||
- Employee Referral ([#24997](https://github.com/frappe/erpnext/pull/24997))
|
||||
- Add Create Expense Claim button in Delivery Trip ([#25526](https://github.com/frappe/erpnext/pull/25526))
|
||||
- Reduced rate of asset depreciation as per IT Act ([#25648](https://github.com/frappe/erpnext/pull/25648))
|
||||
- Improve DATEV export ([#25238](https://github.com/frappe/erpnext/pull/25238))
|
||||
- Add pick batch button ([#25413](https://github.com/frappe/erpnext/pull/25413))
|
||||
- Enable custom field search on POS ([#25421](https://github.com/frappe/erpnext/pull/25421))
|
||||
- New check field in subscriptions for (not) submitting invoices ([#25394](https://github.com/frappe/erpnext/pull/25394))
|
||||
- Show POS reserved stock in stock projected qty report ([#25593](https://github.com/frappe/erpnext/pull/25593))
|
||||
- e-way bill validity field ([#25555](https://github.com/frappe/erpnext/pull/25555))
|
||||
- Significant reduction in time taken to save sales documents ([#25475](https://github.com/frappe/erpnext/pull/25475))
|
||||
|
||||
### Fixes
|
||||
|
||||
- Bank statement import via google sheet ([#25677](https://github.com/frappe/erpnext/pull/25677))
|
||||
- Invoices not getting fetched during payment reconciliation ([#25598](https://github.com/frappe/erpnext/pull/25598))
|
||||
- Error on applying TDS without party ([#25632](https://github.com/frappe/erpnext/pull/25632))
|
||||
- Allow to cancel loan with cancelled repayment entry ([#25507](https://github.com/frappe/erpnext/pull/25507))
|
||||
- Can't open general ledger from consolidated financial report ([#25542](https://github.com/frappe/erpnext/pull/25542))
|
||||
- Add 'Partially Received' to Status drop-down list in Material Request ([#24857](https://github.com/frappe/erpnext/pull/24857))
|
||||
- Updated item filters for material request ([#25531](https://github.com/frappe/erpnext/pull/25531))
|
||||
- Added validation in stock entry to check duplicate serial nos ([#25611](https://github.com/frappe/erpnext/pull/25611))
|
||||
- Update shopify api version ([#25600](https://github.com/frappe/erpnext/pull/25600))
|
||||
- Dialog variable assignment after definition in POS ([#25680](https://github.com/frappe/erpnext/pull/25680))
|
||||
- Added tax_types list ([#25587](https://github.com/frappe/erpnext/pull/25587))
|
||||
- Include search fields in Project Link field query ([#25505](https://github.com/frappe/erpnext/pull/25505))
|
||||
- Item stock levels displaying inconsistently ([#25506](https://github.com/frappe/erpnext/pull/25506))
|
||||
- Change today to now to get data for reposting ([#25703](https://github.com/frappe/erpnext/pull/25703))
|
||||
- Parameter for get_filtered_list_for_consolidated_report in consolidated balance sheet ([#25700](https://github.com/frappe/erpnext/pull/25700))
|
||||
- Minor fixes in loan ([#25546](https://github.com/frappe/erpnext/pull/25546))
|
||||
- Fieldname when updating docfield property ([#25516](https://github.com/frappe/erpnext/pull/25516))
|
||||
- Use get_serial_nos for splitting ([#25590](https://github.com/frappe/erpnext/pull/25590))
|
||||
- Show item's full name on hover over item in POS ([#25554](https://github.com/frappe/erpnext/pull/25554))
|
||||
- Stock ledger entry created against draft stock entry ([#25540](https://github.com/frappe/erpnext/pull/25540))
|
||||
- Incorrect expense account set in pos invoice ([#25543](https://github.com/frappe/erpnext/pull/25543))
|
||||
- Stock balance and batch-wise balance history report showing different closing stock ([#25575](https://github.com/frappe/erpnext/pull/25575))
|
||||
- Make strings translatable ([#25521](https://github.com/frappe/erpnext/pull/25521))
|
||||
- Serial no changed after saving stock reconciliation ([#25541](https://github.com/frappe/erpnext/pull/25541))
|
||||
- Ignore fraction difference while making round off gl entry ([#25438](https://github.com/frappe/erpnext/pull/25438))
|
||||
- Sync shopify customer addresses ([#25481](https://github.com/frappe/erpnext/pull/25481))
|
||||
- Total stock summary report not working ([#25551](https://github.com/frappe/erpnext/pull/25551))
|
||||
- Rename field has not updated value of deposit and withdrawal fields ([#25545](https://github.com/frappe/erpnext/pull/25545))
|
||||
- Unexpected keyword argument 'merge_logs' ([#25489](https://github.com/frappe/erpnext/pull/25489))
|
||||
- Validation message of quality inspection in purchase receipt ([#25667](https://github.com/frappe/erpnext/pull/25667))
|
||||
- Added is_stock_item filter ([#25530](https://github.com/frappe/erpnext/pull/25530))
|
||||
- Fetch total stock at company in PO ([#25532](https://github.com/frappe/erpnext/pull/25532))
|
||||
- Updated filters for process statement of accounts ([#25384](https://github.com/frappe/erpnext/pull/25384))
|
||||
- Incorrect expense account set in pos invoice ([#25571](https://github.com/frappe/erpnext/pull/25571))
|
||||
- Client script breaking while settings tax labels ([#25653](https://github.com/frappe/erpnext/pull/25653))
|
||||
- Empty payment term column in accounts receivable report ([#25556](https://github.com/frappe/erpnext/pull/25556))
|
||||
- Designation insufficient permission on lead doctype. ([#25331](https://github.com/frappe/erpnext/pull/25331))
|
||||
- Force https for shopify webhook registration ([#25630](https://github.com/frappe/erpnext/pull/25630))
|
||||
- Patch regional fields for old companies ([#25673](https://github.com/frappe/erpnext/pull/25673))
|
||||
- Woocommerce order sync issue ([#25692](https://github.com/frappe/erpnext/pull/25692))
|
||||
- Allow to receive same serial numbers multiple times ([#25471](https://github.com/frappe/erpnext/pull/25471))
|
||||
- Update Allocated amount after Paid Amount is changed in PE ([#25515](https://github.com/frappe/erpnext/pull/25515))
|
||||
- Updating Standard Notification's channel field ([#25564](https://github.com/frappe/erpnext/pull/25564))
|
||||
- Report summary showing inflated values when values are accumulated in Group Company ([#25577](https://github.com/frappe/erpnext/pull/25577))
|
||||
- UI fixes related to overflowing payment section ([#25652](https://github.com/frappe/erpnext/pull/25652))
|
||||
- List invoices in Payment Reconciliation Payment ([#25524](https://github.com/frappe/erpnext/pull/25524))
|
||||
- Ageing errors in PSOA ([#25490](https://github.com/frappe/erpnext/pull/25490))
|
||||
- Prevent spurious defaults for items when making prec from dnote ([#25559](https://github.com/frappe/erpnext/pull/25559))
|
||||
- Stock reconciliation getting time out error during submission ([#25557](https://github.com/frappe/erpnext/pull/25557))
|
||||
- Timesheet filter date exclusive issue ([#25626](https://github.com/frappe/erpnext/pull/25626))
|
||||
- Update cost center in the item table fetched from POS Profile ([#25609](https://github.com/frappe/erpnext/pull/25609))
|
||||
- Updated modified time in purchase invoice to pull new fields ([#25678](https://github.com/frappe/erpnext/pull/25678))
|
||||
- Stock and Accounts Settings form refactor ([#25534](https://github.com/frappe/erpnext/pull/25534))
|
||||
- Payment amount showing in foreign currency ([#25292](https://github.com/frappe/erpnext/pull/25292))
|
||||
54
erpnext/change_log/v13/v13_4_0.md
Normal file
54
erpnext/change_log/v13/v13_4_0.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Version 13.4.0 Release Notes
|
||||
|
||||
### Features & Enhancements
|
||||
|
||||
- Multiple GST enhancement and fixes ([#25249](https://github.com/frappe/erpnext/pull/25249))
|
||||
- Linking supplier with an item group for filtering items ([#25683](https://github.com/frappe/erpnext/pull/25683))
|
||||
- Leave Policy Assignment Refactor ([#24327](https://github.com/frappe/erpnext/pull/24327))
|
||||
- Dimension-wise Accounts Balance Report ([#25260](https://github.com/frappe/erpnext/pull/25260))
|
||||
- Show net values in Party Accounts ([#25714](https://github.com/frappe/erpnext/pull/25714))
|
||||
- Add pending qty section to batch/serial selector dialog ([#25519](https://github.com/frappe/erpnext/pull/25519))
|
||||
- enhancements in Training Event ([#25782](https://github.com/frappe/erpnext/pull/25782))
|
||||
- Refactored timesheet ([#25701](https://github.com/frappe/erpnext/pull/25701))
|
||||
|
||||
### Fixes
|
||||
|
||||
- Process Statement of Accounts formatting ([#25777](https://github.com/frappe/erpnext/pull/25777))
|
||||
- Removed serial no validation for sales invoice ([#25817](https://github.com/frappe/erpnext/pull/25817))
|
||||
- Fetch email id from dialog box in pos past order summary ([#25808](https://github.com/frappe/erpnext/pull/25808))
|
||||
- Don't map set warehouse from delivery note to purchase receipt ([#25672](https://github.com/frappe/erpnext/pull/25672))
|
||||
- Apply permission while selecting projects ([#25765](https://github.com/frappe/erpnext/pull/25765))
|
||||
- Error on adding bank account to plaid ([#25658](https://github.com/frappe/erpnext/pull/25658))
|
||||
- Set disable rounded total if it is globally enabled ([#25789](https://github.com/frappe/erpnext/pull/25789))
|
||||
- Wrong amount on CR side in general ledger report for customer when different account currencies are involved ([#25654](https://github.com/frappe/erpnext/pull/25654))
|
||||
- Stock move dialog duplicate submit actions (V13) ([#25486](https://github.com/frappe/erpnext/pull/25486))
|
||||
- Cashflow mapper not showing data ([#25815](https://github.com/frappe/erpnext/pull/25815))
|
||||
- Ignore rounding diff while importing JV using data import ([#25816](https://github.com/frappe/erpnext/pull/25816))
|
||||
- Woocommerce order sync issue ([#25688](https://github.com/frappe/erpnext/pull/25688))
|
||||
- Expected amount in pos closing payments table ([#25737](https://github.com/frappe/erpnext/pull/25737))
|
||||
- Show only company addresses for ITC reversal entry ([#25867](https://github.com/frappe/erpnext/pull/25867))
|
||||
- Timeout error while loading warehouse tree ([#25694](https://github.com/frappe/erpnext/pull/25694))
|
||||
- Plaid Withdrawals and Deposits are recorded incorrectly ([#25784](https://github.com/frappe/erpnext/pull/25784))
|
||||
- Return case for item with available qty equal to one ([#25760](https://github.com/frappe/erpnext/pull/25760))
|
||||
- The status of repost item valuation showing In Progress since long time ([#25754](https://github.com/frappe/erpnext/pull/25754))
|
||||
- Updated applicable charges form in landed cost voucher ([#25732](https://github.com/frappe/erpnext/pull/25732))
|
||||
- Rearrange buttons for Company DocType ([#25617](https://github.com/frappe/erpnext/pull/25617))
|
||||
- Show uom for item in selector dialog ([#25697](https://github.com/frappe/erpnext/pull/25697))
|
||||
- Warehouse not found in stock entry ([#25776](https://github.com/frappe/erpnext/pull/25776))
|
||||
- Use dictionary filter instead of list (bp #25874 pre-release) ([#25875](https://github.com/frappe/erpnext/pull/25875))
|
||||
- Send emails on rfq submit ([#25695](https://github.com/frappe/erpnext/pull/25695))
|
||||
- Cannot bypass e-invoicing for non gst item invoices ([#25759](https://github.com/frappe/erpnext/pull/25759))
|
||||
- Validation message of quality inspection in purchase receipt ([#25666](https://github.com/frappe/erpnext/pull/25666))
|
||||
- Dialog variable assignment after definition in POS ([#25681](https://github.com/frappe/erpnext/pull/25681))
|
||||
- Wrong quantity after transaction for parallel stock transactions ([#25779](https://github.com/frappe/erpnext/pull/25779))
|
||||
- Item Variant Details Report ([#25797](https://github.com/frappe/erpnext/pull/25797))
|
||||
- Duplicate stock entry on multiple click ([#25742](https://github.com/frappe/erpnext/pull/25742))
|
||||
- Bank statement import via google sheet ([#25676](https://github.com/frappe/erpnext/pull/25676))
|
||||
- Change today to now to get data for reposting ([#25702](https://github.com/frappe/erpnext/pull/25702))
|
||||
- Parameter for get_filtered_list_for_consolidated_report in consolidated balance sheet ([#25698](https://github.com/frappe/erpnext/pull/25698))
|
||||
- Ageing error in PSOA ([#25857](https://github.com/frappe/erpnext/pull/25857))
|
||||
- Breaking cost center validation ([#25660](https://github.com/frappe/erpnext/pull/25660))
|
||||
- Project filter for Kanban Board ([#25744](https://github.com/frappe/erpnext/pull/25744))
|
||||
- Show allow zero valuation only when auto checked ([#25778](https://github.com/frappe/erpnext/pull/25778))
|
||||
- Missing cost center message on creating gl entries ([#25755](https://github.com/frappe/erpnext/pull/25755))
|
||||
- Address template with upper filter throws jinja error ([#25756](https://github.com/frappe/erpnext/pull/25756))
|
||||
54
erpnext/change_log/v13/v13_5_0.md
Normal file
54
erpnext/change_log/v13/v13_5_0.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Version 13.5.0 Release Notes
|
||||
|
||||
### Features & Enhancements
|
||||
|
||||
- Tax deduction against advance payments ([#25831](https://github.com/frappe/erpnext/pull/25831))
|
||||
- Cost-center wise period closing entry ([#25766](https://github.com/frappe/erpnext/pull/25766))
|
||||
- Create Quality Inspections from account and stock documents ([#25221](https://github.com/frappe/erpnext/pull/25221))
|
||||
- Item Taxes based on net rate ([#25961](https://github.com/frappe/erpnext/pull/25961))
|
||||
- Enable/disable gl entry posting for change given in pos ([#25822](https://github.com/frappe/erpnext/pull/25822))
|
||||
- Add Inactive status to Employee ([#26029](https://github.com/frappe/erpnext/pull/26029))
|
||||
- Added check box to combine items with same BOM ([#25478](https://github.com/frappe/erpnext/pull/25478))
|
||||
- Item Tax Templates for Germany ([#25858](https://github.com/frappe/erpnext/pull/25858))
|
||||
- Refactored leave balance report ([#25771](https://github.com/frappe/erpnext/pull/25771))
|
||||
- Refactored Vehicle Expenses Report ([#25727](https://github.com/frappe/erpnext/pull/25727))
|
||||
- Refactored maintenance schedule and visit document ([#25358](https://github.com/frappe/erpnext/pull/25358))
|
||||
|
||||
### Fixes
|
||||
|
||||
- Cannot add same item with different rates ([#25849](https://github.com/frappe/erpnext/pull/25849))
|
||||
- Show only company addresses for ITC reversal entry ([#25866](https://github.com/frappe/erpnext/pull/25866))
|
||||
- Hiding Rounding Adjustment field ([#25380](https://github.com/frappe/erpnext/pull/25380))
|
||||
- Auto tax calculations in Payment Entry ([#26055](https://github.com/frappe/erpnext/pull/26055))
|
||||
- Not able to select the item code in work order ([#25915](https://github.com/frappe/erpnext/pull/25915))
|
||||
- Cannot reset plaid link for a bank account ([#25869](https://github.com/frappe/erpnext/pull/25869))
|
||||
- Student invalid password reset link ([#25826](https://github.com/frappe/erpnext/pull/25826))
|
||||
- Multiple pos issues ([#25928](https://github.com/frappe/erpnext/pull/25928))
|
||||
- Add Product Bundles to POS ([#25860](https://github.com/frappe/erpnext/pull/25860))
|
||||
- Enable Parallel tests ([#25862](https://github.com/frappe/erpnext/pull/25862))
|
||||
- Service item check on e-Invoicing ([#25986](https://github.com/frappe/erpnext/pull/25986))
|
||||
- Choose correct Salary Structure Assignment when getting data for formula eval ([#25981](https://github.com/frappe/erpnext/pull/25981))
|
||||
- Ignore internal transfer invoices from GST Reports ([#25969](https://github.com/frappe/erpnext/pull/25969))
|
||||
- Taxable value for invoices with additional discount ([#26056](https://github.com/frappe/erpnext/pull/26056))
|
||||
- Validate negative allocated amount in Payment Entry ([#25799](https://github.com/frappe/erpnext/pull/25799))
|
||||
- Allow all System Managers to delete company transactions ([#25834](https://github.com/frappe/erpnext/pull/25834))
|
||||
- Wrong round off gl entry posted in case of purchase invoice ([#25775](https://github.com/frappe/erpnext/pull/25775))
|
||||
- Use dictionary filter instead of list ([#25874](https://github.com/frappe/erpnext/pull/25874))
|
||||
- Ageing error in PSOA ([#25855](https://github.com/frappe/erpnext/pull/25855))
|
||||
- On click of duplicate button system has not copied the difference account ([#25988](https://github.com/frappe/erpnext/pull/25988))
|
||||
- Assign Product Bundle's conversion_factor to Pack… ([#25840](https://github.com/frappe/erpnext/pull/25840))
|
||||
- Rename Loan Management workspace to Loans ([#25856](https://github.com/frappe/erpnext/pull/25856))
|
||||
- Fix stock quantity calculation when negative_stock_allowe… ([#25859](https://github.com/frappe/erpnext/pull/25859))
|
||||
- Update cost center from pos profile ([#25971](https://github.com/frappe/erpnext/pull/25971))
|
||||
- Ensure website theme is applied correctly ([#25863](https://github.com/frappe/erpnext/pull/25863))
|
||||
- Only display GST card in Accounting Workspace if it's in India ([#26000](https://github.com/frappe/erpnext/pull/26000))
|
||||
- Incorrect gstin fetched incase of branch company address ([#25841](https://github.com/frappe/erpnext/pull/25841))
|
||||
- Sort account balances by account name ([#26009](https://github.com/frappe/erpnext/pull/26009))
|
||||
- Custom conversion factor field not mapped from job card to stock entry ([#25956](https://github.com/frappe/erpnext/pull/25956))
|
||||
- Chart of accounts importer always error ([#25882](https://github.com/frappe/erpnext/pull/25882))
|
||||
- Create POS Invoice for Product Bundles ([#25847](https://github.com/frappe/erpnext/pull/25847))
|
||||
- Wrap dates in getdate for leave application ([#25899](https://github.com/frappe/erpnext/pull/25899))
|
||||
- Closing entry shows incorrect expected amount ([#25868](https://github.com/frappe/erpnext/pull/25868))
|
||||
- Add Hold status column in the Issue Summary Report ([#25828](https://github.com/frappe/erpnext/pull/25828))
|
||||
- Rendering of broken image on pos ([#25872](https://github.com/frappe/erpnext/pull/25872))
|
||||
- Timeout error in the repost item valuation ([#25854](https://github.com/frappe/erpnext/pull/25854))
|
||||
72
erpnext/change_log/v13/v13_6_0.md
Normal file
72
erpnext/change_log/v13/v13_6_0.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Version 13.6.0 Release Notes
|
||||
|
||||
### Features & Enhancements
|
||||
|
||||
- Job Card Enhancements ([#24523](https://github.com/frappe/erpnext/pull/24523))
|
||||
- Implement multi-account selection in General Ledger([#26044](https://github.com/frappe/erpnext/pull/26044))
|
||||
- Fetching of qty as per received qty from PR to PI ([#26184](https://github.com/frappe/erpnext/pull/26184))
|
||||
- Subcontract code refactor and enhancement ([#25878](https://github.com/frappe/erpnext/pull/25878))
|
||||
- Employee Grievance ([#25705](https://github.com/frappe/erpnext/pull/25705))
|
||||
- Add Inactive status to Employee ([#26030](https://github.com/frappe/erpnext/pull/26030))
|
||||
- Incorrect valuation rate report for serialized items ([#25696](https://github.com/frappe/erpnext/pull/25696))
|
||||
- Update cost updates operation time and hour rates in BOM ([#25891](https://github.com/frappe/erpnext/pull/25891))
|
||||
|
||||
### Fixes
|
||||
|
||||
- Precision rate for packed items in internal transfers ([#26046](https://github.com/frappe/erpnext/pull/26046))
|
||||
- User is not able to change item tax template ([#26176](https://github.com/frappe/erpnext/pull/26176))
|
||||
- Insufficient permission for Dunning error ([#26092](https://github.com/frappe/erpnext/pull/26092))
|
||||
- Validate Product Bundle for existing transactions before deletion ([#25978](https://github.com/frappe/erpnext/pull/25978))
|
||||
- Auto unlink warehouse from item on delete ([#26073](https://github.com/frappe/erpnext/pull/26073))
|
||||
- Employee Inactive status implications ([#26245](https://github.com/frappe/erpnext/pull/26245))
|
||||
- Fetch batch items in stock reconciliation ([#26230](https://github.com/frappe/erpnext/pull/26230))
|
||||
- Disabled cancellation for sales order if linked to drafted sales invoice ([#26125](https://github.com/frappe/erpnext/pull/26125))
|
||||
- Sort website products by weightage mentioned in Item master ([#26134](https://github.com/frappe/erpnext/pull/26134))
|
||||
- Added freeze when trying to stop work order (#26192) ([#26196](https://github.com/frappe/erpnext/pull/26196))
|
||||
- Accounting Dimensions for payroll entry accrual Journal Entry ([#26083](https://github.com/frappe/erpnext/pull/26083))
|
||||
- Staffing plan vacancies data type issue ([#25941](https://github.com/frappe/erpnext/pull/25941))
|
||||
- Unable to enter score in Assessment Result details grid ([#25945](https://github.com/frappe/erpnext/pull/25945))
|
||||
- Report Subcontracted Raw Materials to be Transferred ([#26011](https://github.com/frappe/erpnext/pull/26011))
|
||||
- Label for enabling ledger posting of change amount ([#26070](https://github.com/frappe/erpnext/pull/26070))
|
||||
- Training event ([#26071](https://github.com/frappe/erpnext/pull/26071))
|
||||
- Rate not able to change in purchase order ([#26122](https://github.com/frappe/erpnext/pull/26122))
|
||||
- Error while fetching item taxes ([#26220](https://github.com/frappe/erpnext/pull/26220))
|
||||
- Check for duplicate payment terms in Payment Term Template ([#26003](https://github.com/frappe/erpnext/pull/26003))
|
||||
- Removed values out of sync validation from stock transactions ([#26229](https://github.com/frappe/erpnext/pull/26229))
|
||||
- Fetching employee in payroll entry ([#26269](https://github.com/frappe/erpnext/pull/26269))
|
||||
- Filter Cost Center and Project drop-down lists by Company ([#26045](https://github.com/frappe/erpnext/pull/26045))
|
||||
- Website item group logic for product listing in Item Group pages ([#26170](https://github.com/frappe/erpnext/pull/26170))
|
||||
- Chart not visible for First Response Time reports ([#26032](https://github.com/frappe/erpnext/pull/26032))
|
||||
- Incorrect billed qty in Sales Order analytics ([#26095](https://github.com/frappe/erpnext/pull/26095))
|
||||
- Material request and supplier quotation not linked if supplier quotation created from supplier portal ([#26023](https://github.com/frappe/erpnext/pull/26023))
|
||||
- Update leave allocation after submit ([#26191](https://github.com/frappe/erpnext/pull/26191))
|
||||
- Taxes on Internal Transfer payment entry ([#26188](https://github.com/frappe/erpnext/pull/26188))
|
||||
- Precision rate for packed items (bp #26046) ([#26217](https://github.com/frappe/erpnext/pull/26217))
|
||||
- Fixed rounding off ordered percent to 100 in condition ([#26152](https://github.com/frappe/erpnext/pull/26152))
|
||||
- Sanctioned loan amount limit check ([#26108](https://github.com/frappe/erpnext/pull/26108))
|
||||
- Purchase receipt gl entries with same item code ([#26202](https://github.com/frappe/erpnext/pull/26202))
|
||||
- Taxable value for invoices with additional discount ([#25906](https://github.com/frappe/erpnext/pull/25906))
|
||||
- Correct South Africa VAT Rate (Updated) ([#25894](https://github.com/frappe/erpnext/pull/25894))
|
||||
- Remove response_by and resolution_by if sla is removed ([#25997](https://github.com/frappe/erpnext/pull/25997))
|
||||
- POS loyalty card alignment ([#26051](https://github.com/frappe/erpnext/pull/26051))
|
||||
- Flaky test for Report Subcontracted Raw materials to be transferred ([#26043](https://github.com/frappe/erpnext/pull/26043))
|
||||
- Export invoices not visible in GSTR-1 report ([#26143](https://github.com/frappe/erpnext/pull/26143))
|
||||
- Account filter not working with accounting dimension filter ([#26211](https://github.com/frappe/erpnext/pull/26211))
|
||||
- Allow to select group warehouse while downloading materials from production plan ([#26126](https://github.com/frappe/erpnext/pull/26126))
|
||||
- Added freeze when trying to stop work order ([#26192](https://github.com/frappe/erpnext/pull/26192))
|
||||
- Time out while submit / cancel the stock transactions with more than 50 Items ([#26081](https://github.com/frappe/erpnext/pull/26081))
|
||||
- Address Card issues in e-commerce ([#26187](https://github.com/frappe/erpnext/pull/26187))
|
||||
- Error while booking deferred revenue ([#26195](https://github.com/frappe/erpnext/pull/26195))
|
||||
- Eliminate repeat creation of HSN codes ([#25947](https://github.com/frappe/erpnext/pull/25947))
|
||||
- Opening invoices can alter profit and loss of a closed year ([#25951](https://github.com/frappe/erpnext/pull/25951))
|
||||
- Payroll entry employee detail issue ([#25968](https://github.com/frappe/erpnext/pull/25968))
|
||||
- Auto tax calculations in Payment Entry ([#26037](https://github.com/frappe/erpnext/pull/26037))
|
||||
- Use pos invoice item name as unique identifier ([#26198](https://github.com/frappe/erpnext/pull/26198))
|
||||
- Billing address not fetched in Purchase Invoice ([#26100](https://github.com/frappe/erpnext/pull/26100))
|
||||
- Timeout while cancelling stock reconciliation ([#26098](https://github.com/frappe/erpnext/pull/26098))
|
||||
- Status indicator for delivery notes ([#26062](https://github.com/frappe/erpnext/pull/26062))
|
||||
- Unable to enter score in Assessment Result details grid ([#26031](https://github.com/frappe/erpnext/pull/26031))
|
||||
- Too many writes while renaming company abbreviation ([#26203](https://github.com/frappe/erpnext/pull/26203))
|
||||
- Chart not visible for First Response Time reports ([#26185](https://github.com/frappe/erpnext/pull/26185))
|
||||
- Job applicant link issue ([#25934](https://github.com/frappe/erpnext/pull/25934))
|
||||
- Fetch preferred shipping address (bp #26132) ([#26201](https://github.com/frappe/erpnext/pull/26201))
|
||||
69
erpnext/change_log/v13/v13_7_0.md
Normal file
69
erpnext/change_log/v13/v13_7_0.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# Version 13.7.0 Release Notes
|
||||
|
||||
### Features & Enhancements
|
||||
- Optionally allow rejected quality inspection on submission ([#26133](https://github.com/frappe/erpnext/pull/26133))
|
||||
- Bootstrapped GST Setup for India ([#25415](https://github.com/frappe/erpnext/pull/25415))
|
||||
- Fetching details from supplier/customer groups ([#26454](https://github.com/frappe/erpnext/pull/26454))
|
||||
- Provision to make subcontracted purchase order from the production plan ([#26240](https://github.com/frappe/erpnext/pull/26240))
|
||||
- Optimized code for reposting item valuation ([#26432](https://github.com/frappe/erpnext/pull/26432))
|
||||
|
||||
### Fixes
|
||||
- Auto process deferred accounting for multi-company setup ([#26277](https://github.com/frappe/erpnext/pull/26277))
|
||||
- Error while fetching item taxes ([#26218](https://github.com/frappe/erpnext/pull/26218))
|
||||
- Validation check for batch for stock reconciliation type in stock entry(bp #26370 ) ([#26488](https://github.com/frappe/erpnext/pull/26488))
|
||||
- Error popup for COA errors ([#26358](https://github.com/frappe/erpnext/pull/26358))
|
||||
- Precision for expected values in payment entry test ([#26394](https://github.com/frappe/erpnext/pull/26394))
|
||||
- Bank statement import ([#26287](https://github.com/frappe/erpnext/pull/26287))
|
||||
- LMS progress issue ([#26253](https://github.com/frappe/erpnext/pull/26253))
|
||||
- Paging buttons not working on item group portal page ([#26497](https://github.com/frappe/erpnext/pull/26497))
|
||||
- Omit item discount amount for e-invoicing ([#26353](https://github.com/frappe/erpnext/pull/26353))
|
||||
- Validate LCV for Invoices without Update Stock ([#26333](https://github.com/frappe/erpnext/pull/26333))
|
||||
- Remove cancelled entries in consolidated financial statements ([#26331](https://github.com/frappe/erpnext/pull/26331))
|
||||
- Fetching employee in payroll entry ([#26271](https://github.com/frappe/erpnext/pull/26271))
|
||||
- To fetch the correct field in Tax Rule ([#25927](https://github.com/frappe/erpnext/pull/25927))
|
||||
- Order and time of operations in multilevel BOM work order ([#25886](https://github.com/frappe/erpnext/pull/25886))
|
||||
- Fixed Budget Variance Graph color from all black to default ([#26368](https://github.com/frappe/erpnext/pull/26368))
|
||||
- TDS computation summary shows cancelled invoices (#26456) ([#26486](https://github.com/frappe/erpnext/pull/26486))
|
||||
- Do not consider cancelled entries in party dashboard ([#26231](https://github.com/frappe/erpnext/pull/26231))
|
||||
- Add validation for 'for_qty' else throws errors ([#25829](https://github.com/frappe/erpnext/pull/25829))
|
||||
- Move the rename abbreviation job to long queue (#26434) ([#26462](https://github.com/frappe/erpnext/pull/26462))
|
||||
- Query for Training Event ([#26388](https://github.com/frappe/erpnext/pull/26388))
|
||||
- Item group portal issues (backport) ([#26493](https://github.com/frappe/erpnext/pull/26493))
|
||||
- When lead is created with mobile_no, mobile_no value gets lost ([#26298](https://github.com/frappe/erpnext/pull/26298))
|
||||
- WIP needs to be set before submit on skip_transfer (bp #26499) ([#26507](https://github.com/frappe/erpnext/pull/26507))
|
||||
- Incorrect valuation rate in stock reconciliation ([#26259](https://github.com/frappe/erpnext/pull/26259))
|
||||
- Precision rate for packed items in internal transfers ([#26046](https://github.com/frappe/erpnext/pull/26046))
|
||||
- Changed profitability analysis report width ([#26165](https://github.com/frappe/erpnext/pull/26165))
|
||||
- Unable to download GSTR-1 json ([#26468](https://github.com/frappe/erpnext/pull/26468))
|
||||
- Unallocated amount in Payment Entry after taxes ([#26472](https://github.com/frappe/erpnext/pull/26472))
|
||||
- Include Stock Reco logic in `update_qty_in_future_sle` ([#26158](https://github.com/frappe/erpnext/pull/26158))
|
||||
- Update cost not working in the draft BOM ([#26279](https://github.com/frappe/erpnext/pull/26279))
|
||||
- Cancellation of Loan Security Pledges ([#26252](https://github.com/frappe/erpnext/pull/26252))
|
||||
- fix(e-invoicing): allow export invoice even if no taxes applied (#26363) ([#26405](https://github.com/frappe/erpnext/pull/26405))
|
||||
- Delete accounts (an empty file) ([#25323](https://github.com/frappe/erpnext/pull/25323))
|
||||
- Errors on parallel requests creation of company for India ([#26470](https://github.com/frappe/erpnext/pull/26470))
|
||||
- Incorrect bom no added for non-variant items on variant boms ([#26320](https://github.com/frappe/erpnext/pull/26320))
|
||||
- Incorrect discount amount on amended document ([#26466](https://github.com/frappe/erpnext/pull/26466))
|
||||
- Added a message to enable appointment booking if disabled ([#26334](https://github.com/frappe/erpnext/pull/26334))
|
||||
- fix(pos): taxes amount in pos item cart ([#26411](https://github.com/frappe/erpnext/pull/26411))
|
||||
- Track changes on batch ([#26382](https://github.com/frappe/erpnext/pull/26382))
|
||||
- Stock entry with putaway rule not working ([#26350](https://github.com/frappe/erpnext/pull/26350))
|
||||
- Only "Tax" type accounts should be shown for selection in GST Settings ([#26300](https://github.com/frappe/erpnext/pull/26300))
|
||||
- Added permission for employee to book appointment ([#26255](https://github.com/frappe/erpnext/pull/26255))
|
||||
- Allow to make job card without employee ([#26312](https://github.com/frappe/erpnext/pull/26312))
|
||||
- Project Portal Enhancements ([#26290](https://github.com/frappe/erpnext/pull/26290))
|
||||
- BOM stock report not working ([#26332](https://github.com/frappe/erpnext/pull/26332))
|
||||
- Order Items by weightage in the web items query ([#26284](https://github.com/frappe/erpnext/pull/26284))
|
||||
- Removed values out of sync validation from stock transactions ([#26226](https://github.com/frappe/erpnext/pull/26226))
|
||||
- Payroll-entry minor fix ([#26349](https://github.com/frappe/erpnext/pull/26349))
|
||||
- Allow user to change the To Date in the blanket order even after submit of order ([#26241](https://github.com/frappe/erpnext/pull/26241))
|
||||
- Value fetching for custom field in POS ([#26367](https://github.com/frappe/erpnext/pull/26367))
|
||||
- Iteration through accounts only when accounts exist ([#26391](https://github.com/frappe/erpnext/pull/26391))
|
||||
- Employee Inactive status implications ([#26244](https://github.com/frappe/erpnext/pull/26244))
|
||||
- Multi-currency issue ([#26458](https://github.com/frappe/erpnext/pull/26458))
|
||||
- FG item not fetched in manufacture entry ([#26509](https://github.com/frappe/erpnext/pull/26509))
|
||||
- Set query for training events ([#26303](https://github.com/frappe/erpnext/pull/26303))
|
||||
- Fetch batch items in stock reconciliation ([#26213](https://github.com/frappe/erpnext/pull/26213))
|
||||
- Employee selection not working in payroll entry ([#26278](https://github.com/frappe/erpnext/pull/26278))
|
||||
- POS item cart dom updates (#26459) ([#26461](https://github.com/frappe/erpnext/pull/26461))
|
||||
- dunning calculation of grand total when rate of interest is 0% ([#26285](https://github.com/frappe/erpnext/pull/26285))
|
||||
39
erpnext/change_log/v13/v13_8_0.md
Normal file
39
erpnext/change_log/v13/v13_8_0.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Version 13.8.0 Release Notes
|
||||
|
||||
### Features & Enhancements
|
||||
- Report to show COGS by item groups ([#26222](https://github.com/frappe/erpnext/pull/26222))
|
||||
- Enhancements in TDS ([#26677](https://github.com/frappe/erpnext/pull/26677))
|
||||
- API Endpoint to update halted Razorpay subscriptions ([#26564](https://github.com/frappe/erpnext/pull/26564))
|
||||
|
||||
### Fixes
|
||||
- Incorrect bom name ([#26600](https://github.com/frappe/erpnext/pull/26600))
|
||||
- Exchange rate revaluation posting date and precision fixes ([#26651](https://github.com/frappe/erpnext/pull/26651))
|
||||
- POS item cart dom updates ([#26460](https://github.com/frappe/erpnext/pull/26460))
|
||||
- General Ledger report not working with filter group by ([#26439](https://github.com/frappe/erpnext/pull/26438))
|
||||
- Tax calculation for Recurring additional salary ([#24206](https://github.com/frappe/erpnext/pull/24206))
|
||||
- Validation check for batch for stock reconciliation type in stock entry ([#26487](https://github.com/frappe/erpnext/pull/26487))
|
||||
- Improved UX for additional discount field ([#26502](https://github.com/frappe/erpnext/pull/26502))
|
||||
- Add missing cess amount in GSTR-3B report ([#26644](https://github.com/frappe/erpnext/pull/26644))
|
||||
- Optimized code for reposting item valuation ([#26431](https://github.com/frappe/erpnext/pull/26431))
|
||||
- FG item not fetched in manufacture entry ([#26508](https://github.com/frappe/erpnext/pull/26508))
|
||||
- Errors on parallel requests creation of company for India ([#26420](https://github.com/frappe/erpnext/pull/26420))
|
||||
- Incorrect valuation rate calculation in gross profit report ([#26558](https://github.com/frappe/erpnext/pull/26558))
|
||||
- Empty "against account" in Purchase Receipt GLE ([#26712](https://github.com/frappe/erpnext/pull/26712))
|
||||
- Remove cancelled entries from Stock and Account Value comparison report ([#26721](https://github.com/frappe/erpnext/pull/26721))
|
||||
- Remove manual permission checking ([#26691](https://github.com/frappe/erpnext/pull/26691))
|
||||
- Delete child docs when parent doc is deleted ([#26518](https://github.com/frappe/erpnext/pull/26518))
|
||||
- GST Reports timeout issue ([#26646](https://github.com/frappe/erpnext/pull/26646))
|
||||
- Parent condition in pricing rules ([#26727](https://github.com/frappe/erpnext/pull/26727))
|
||||
- Added Company filters for Loan ([#26294](https://github.com/frappe/erpnext/pull/26294))
|
||||
- Incorrect discount amount on amended document ([#26292](https://github.com/frappe/erpnext/pull/26292))
|
||||
- Exchange gain loss not set for advances linked with invoices ([#26436](https://github.com/frappe/erpnext/pull/26436))
|
||||
- Unallocated amount in Payment Entry after taxes ([#26412](https://github.com/frappe/erpnext/pull/26412))
|
||||
- Wrong operation time in Work Order ([#26613](https://github.com/frappe/erpnext/pull/26613))
|
||||
- Serial No and Batch validation ([#26614](https://github.com/frappe/erpnext/pull/26614))
|
||||
- Gl Entries for exchange gain loss ([#26734](https://github.com/frappe/erpnext/pull/26734))
|
||||
- TDS computation summary shows cancelled invoices ([#26485](https://github.com/frappe/erpnext/pull/26485))
|
||||
- Price List rate not fetched for return sales invoice fixed ([#26560](https://github.com/frappe/erpnext/pull/26560))
|
||||
- Included company in link document type filters for contact ([#26576](https://github.com/frappe/erpnext/pull/26576))
|
||||
- Ignore mandatory fields while creating payment reconciliation Journal Entry ([#26643](https://github.com/frappe/erpnext/pull/26643))
|
||||
- Unable to download GSTR-1 json ([#26418](https://github.com/frappe/erpnext/pull/26418))
|
||||
- Paging buttons not working on item group portal page ([#26498](https://github.com/frappe/erpnext/pull/26498))
|
||||
@@ -1179,8 +1179,6 @@ class AccountsController(TransactionBase):
|
||||
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
|
||||
grand_total = grand_total - flt(self.write_off_amount)
|
||||
po_or_so, doctype, fieldname = self.get_order_details()
|
||||
automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms'))
|
||||
|
||||
if self.get("total_advance"):
|
||||
if party_account_currency == self.company_currency:
|
||||
@@ -1191,86 +1189,22 @@ class AccountsController(TransactionBase):
|
||||
base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
|
||||
|
||||
if not self.get("payment_schedule"):
|
||||
if self.doctype in ["Sales Invoice", "Purchase Invoice"] and automatically_fetch_payment_terms \
|
||||
and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype):
|
||||
self.fetch_payment_terms_from_order(po_or_so, doctype)
|
||||
if self.get('payment_terms_template'):
|
||||
self.ignore_default_payment_terms_template = 1
|
||||
elif self.get("payment_terms_template"):
|
||||
if self.get("payment_terms_template"):
|
||||
data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total)
|
||||
for item in data:
|
||||
self.append("payment_schedule", item)
|
||||
elif self.doctype not in ["Purchase Receipt"]:
|
||||
else:
|
||||
data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total)
|
||||
self.append("payment_schedule", data)
|
||||
|
||||
for d in self.get("payment_schedule"):
|
||||
if d.invoice_portion:
|
||||
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
||||
d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount'))
|
||||
d.outstanding = d.payment_amount
|
||||
elif not d.invoice_portion:
|
||||
d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount'))
|
||||
|
||||
|
||||
def get_order_details(self):
|
||||
if self.doctype == "Sales Invoice":
|
||||
po_or_so = self.get('items')[0].get('sales_order')
|
||||
po_or_so_doctype = "Sales Order"
|
||||
po_or_so_doctype_name = "sales_order"
|
||||
|
||||
else:
|
||||
po_or_so = self.get('items')[0].get('purchase_order')
|
||||
po_or_so_doctype = "Purchase Order"
|
||||
po_or_so_doctype_name = "purchase_order"
|
||||
|
||||
return po_or_so, po_or_so_doctype, po_or_so_doctype_name
|
||||
for d in self.get("payment_schedule"):
|
||||
if d.invoice_portion:
|
||||
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
||||
d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount'))
|
||||
d.outstanding = d.payment_amount
|
||||
elif not d.invoice_portion:
|
||||
d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount'))
|
||||
|
||||
def linked_order_has_payment_terms(self, po_or_so, fieldname, doctype):
|
||||
if po_or_so and self.all_items_have_same_po_or_so(po_or_so, fieldname):
|
||||
if self.linked_order_has_payment_terms_template(po_or_so, doctype):
|
||||
return True
|
||||
elif self.linked_order_has_payment_schedule(po_or_so):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def all_items_have_same_po_or_so(self, po_or_so, fieldname):
|
||||
for item in self.get('items'):
|
||||
if item.get(fieldname) != po_or_so:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def linked_order_has_payment_terms_template(self, po_or_so, doctype):
|
||||
return frappe.get_value(doctype, po_or_so, 'payment_terms_template')
|
||||
|
||||
def linked_order_has_payment_schedule(self, po_or_so):
|
||||
return frappe.get_all('Payment Schedule', filters={'parent': po_or_so})
|
||||
|
||||
def fetch_payment_terms_from_order(self, po_or_so, po_or_so_doctype):
|
||||
"""
|
||||
Fetch Payment Terms from Purchase/Sales Order on creating a new Purchase/Sales Invoice.
|
||||
"""
|
||||
po_or_so = frappe.get_cached_doc(po_or_so_doctype, po_or_so)
|
||||
|
||||
self.payment_schedule = []
|
||||
self.payment_terms_template = po_or_so.payment_terms_template
|
||||
|
||||
for schedule in po_or_so.payment_schedule:
|
||||
payment_schedule = {
|
||||
'payment_term': schedule.payment_term,
|
||||
'due_date': schedule.due_date,
|
||||
'invoice_portion': schedule.invoice_portion,
|
||||
'mode_of_payment': schedule.mode_of_payment,
|
||||
'description': schedule.description
|
||||
}
|
||||
|
||||
if schedule.discount_type == 'Percentage':
|
||||
payment_schedule['discount_type'] = schedule.discount_type
|
||||
payment_schedule['discount'] = schedule.discount
|
||||
|
||||
self.append("payment_schedule", payment_schedule)
|
||||
|
||||
def set_due_date(self):
|
||||
due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
|
||||
@@ -1437,27 +1371,6 @@ def validate_taxes_and_charges(tax):
|
||||
tax.rate = None
|
||||
|
||||
|
||||
def validate_account_head(tax, doc):
|
||||
company = frappe.get_cached_value('Account',
|
||||
tax.account_head, 'company')
|
||||
|
||||
if company != doc.company:
|
||||
frappe.throw(_('Row {0}: Account {1} does not belong to Company {2}')
|
||||
.format(tax.idx, frappe.bold(tax.account_head), frappe.bold(doc.company)), title=_('Invalid Account'))
|
||||
|
||||
|
||||
def validate_cost_center(tax, doc):
|
||||
if not tax.cost_center:
|
||||
return
|
||||
|
||||
company = frappe.get_cached_value('Cost Center',
|
||||
tax.cost_center, 'company')
|
||||
|
||||
if company != doc.company:
|
||||
frappe.throw(_('Row {0}: Cost Center {1} does not belong to Company {2}')
|
||||
.format(tax.idx, frappe.bold(tax.cost_center), frappe.bold(doc.company)), title=_('Invalid Cost Center'))
|
||||
|
||||
|
||||
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))
|
||||
@@ -1677,7 +1590,7 @@ def set_child_tax_template_and_map(item, child_item, parent_doc):
|
||||
if child_item.get("item_tax_template"):
|
||||
child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True)
|
||||
|
||||
def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True):
|
||||
def add_taxes_from_tax_template(child_item, parent_doc):
|
||||
add_taxes_from_item_tax_template = frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template")
|
||||
|
||||
if child_item.get("item_tax_rate") and add_taxes_from_item_tax_template:
|
||||
@@ -1700,8 +1613,7 @@ def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True):
|
||||
"category" : "Total",
|
||||
"add_deduct_tax" : "Add"
|
||||
})
|
||||
if db_insert:
|
||||
tax_row.db_insert()
|
||||
tax_row.db_insert()
|
||||
|
||||
def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, trans_item):
|
||||
"""
|
||||
@@ -1978,4 +1890,4 @@ def validate_regional(doc):
|
||||
|
||||
@erpnext.allow_regional
|
||||
def validate_einvoice_fields(doc):
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -72,8 +72,7 @@ class BuyingController(StockController, Subcontracting):
|
||||
# set contact and address details for supplier, if they are not mentioned
|
||||
if getattr(self, "supplier", None):
|
||||
self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions,
|
||||
doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.get('shipping_address'),
|
||||
fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template')))
|
||||
doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.get('shipping_address')))
|
||||
|
||||
self.set_missing_item_details(for_validate)
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ class StockController(AccountsController):
|
||||
if not self.get('is_return'):
|
||||
self.validate_inspection()
|
||||
self.validate_serialized_batch()
|
||||
self.clean_serial_nos()
|
||||
self.validate_customer_provided_item()
|
||||
self.set_rate_of_stock_uom()
|
||||
self.validate_internal_transfer()
|
||||
@@ -73,12 +72,6 @@ class StockController(AccountsController):
|
||||
frappe.throw(_("Row #{0}: The batch {1} has already expired.")
|
||||
.format(d.idx, get_link_to_form("Batch", d.get("batch_no"))))
|
||||
|
||||
def clean_serial_nos(self):
|
||||
for row in self.get("items"):
|
||||
if hasattr(row, "serial_no") and row.serial_no:
|
||||
# replace commas by linefeed and remove all spaces in string
|
||||
row.serial_no = row.serial_no.replace(",", "\n").replace(" ", "")
|
||||
|
||||
def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
|
||||
default_cost_center=None):
|
||||
|
||||
|
||||
@@ -679,13 +679,17 @@ class calculate_taxes_and_totals(object):
|
||||
default_mode_of_payment = frappe.db.get_value('POS Payment Method',
|
||||
{'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1)
|
||||
|
||||
self.doc.payments = []
|
||||
|
||||
if default_mode_of_payment:
|
||||
self.doc.payments = []
|
||||
self.doc.append('payments', {
|
||||
'mode_of_payment': default_mode_of_payment.mode_of_payment,
|
||||
'amount': total_amount_to_pay,
|
||||
'default': 1
|
||||
})
|
||||
else:
|
||||
self.doc.is_pos = 0
|
||||
self.doc.pos_profile = ''
|
||||
|
||||
self.calculate_paid_amount()
|
||||
|
||||
|
||||
@@ -53,13 +53,6 @@ frappe.ui.form.on("Opportunity", {
|
||||
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
|
||||
},
|
||||
|
||||
status:function(frm){
|
||||
if (frm.doc.status == "Lost"){
|
||||
frm.trigger('set_as_lost_dialog');
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
customer_address: function(frm, cdt, cdn) {
|
||||
erpnext.utils.get_address_display(frm, 'customer_address', 'address_display', false);
|
||||
},
|
||||
@@ -98,6 +91,11 @@ frappe.ui.form.on("Opportunity", {
|
||||
frm.add_custom_button(__('Quotation'),
|
||||
cur_frm.cscript.create_quotation, __('Create'));
|
||||
|
||||
if(doc.status!=="Quotation") {
|
||||
frm.add_custom_button(__('Lost'), () => {
|
||||
frm.trigger('set_as_lost_dialog');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(!frm.doc.__islocal && frm.perm[0].write && frm.doc.docstatus==0) {
|
||||
|
||||
@@ -34,14 +34,11 @@ def enroll_student(source_name):
|
||||
}
|
||||
}}, ignore_permissions=True)
|
||||
student.save()
|
||||
|
||||
student_applicant = frappe.db.get_value("Student Applicant", source_name,
|
||||
["student_category", "program"], as_dict=True)
|
||||
program_enrollment = frappe.new_doc("Program Enrollment")
|
||||
program_enrollment.student = student.name
|
||||
program_enrollment.student_category = student_applicant.student_category
|
||||
program_enrollment.student_category = student.student_category
|
||||
program_enrollment.student_name = student.title
|
||||
program_enrollment.program = student_applicant.program
|
||||
program_enrollment.program = frappe.db.get_value("Student Applicant", source_name, "program")
|
||||
frappe.publish_realtime('enroll_student_progress', {"progress": [2, 4]}, user=frappe.session.user)
|
||||
return program_enrollment
|
||||
|
||||
|
||||
@@ -1,68 +1,195 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2016-06-10 03:29:02.539914",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"student_applicant",
|
||||
"student",
|
||||
"student_name",
|
||||
"column_break_3",
|
||||
"student_batch_name",
|
||||
"student_category"
|
||||
],
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2016-06-10 03:29:02.539914",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "student_applicant",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Student Applicant",
|
||||
"options": "Student Applicant"
|
||||
},
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "student_applicant",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Student Applicant",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Student Applicant",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "student",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Student",
|
||||
"options": "Student"
|
||||
},
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fieldname": "student",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Student",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Student",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "student_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Student Name",
|
||||
"read_only": 1
|
||||
},
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "student_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Student Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "student_batch_name",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Student Batch Name",
|
||||
"options": "Student Batch Name"
|
||||
},
|
||||
{
|
||||
"fieldname": "student_category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Student Category",
|
||||
"options": "Student Category",
|
||||
"read_only": 1
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "student_batch_name",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Student Batch Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Student Batch Name",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-29 18:19:54.471594",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Program Enrollment Tool Student",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"restrict_to_domain": "Education",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-01-02 12:03:53.890741",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Program Enrollment Tool Student",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"restrict_to_domain": "Education",
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
}
|
||||
@@ -18,8 +18,5 @@ frappe.ui.form.on('Shopify Log', {
|
||||
})
|
||||
}).addClass('btn-primary');
|
||||
}
|
||||
|
||||
let app_link = "<a href='https://frappecloud.com/marketplace/apps/ecommerce-integrations' target='_blank'>Ecommerce Integrations</a>"
|
||||
frm.dashboard.add_comment(__("Shopify Integration will be removed from ERPNext in Version 14. Please install {0} app to continue using it.", [app_link]), "yellow", true);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -36,10 +36,6 @@ frappe.ui.form.on("Shopify Settings", "refresh", function(frm){
|
||||
frm.toggle_reqd("delivery_note_series", frm.doc.sync_delivery_note);
|
||||
|
||||
}
|
||||
|
||||
let app_link = "<a href='https://frappecloud.com/marketplace/apps/ecommerce-integrations' target='_blank'>Ecommerce Integrations</a>"
|
||||
frm.dashboard.add_comment(__("Shopify Integration will be removed from ERPNext in Version 14. Please install {0} app to continue using it.", [app_link]), "yellow", true);
|
||||
|
||||
})
|
||||
|
||||
$.extend(erpnext_integrations.shopify_settings, {
|
||||
|
||||
@@ -430,8 +430,7 @@ regional_overrides = {
|
||||
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
|
||||
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
|
||||
'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields',
|
||||
'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount',
|
||||
'erpnext.stock.doctype.item.item.set_item_tax_from_hsn_code': 'erpnext.regional.india.utils.set_item_tax_from_hsn_code'
|
||||
'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount'
|
||||
},
|
||||
'United Arab Emirates': {
|
||||
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data',
|
||||
|
||||
@@ -1,73 +1,15 @@
|
||||
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.ui.form.on('Attendance', {
|
||||
onload: function(frm) {
|
||||
cur_frm.add_fetch('employee', 'company', 'company');
|
||||
cur_frm.add_fetch('employee', 'employee_name', 'employee_name');
|
||||
|
||||
frappe.db.get_single_value("Payroll Settings", "fetch_standard_working_hours_from_shift_type").then((r)=>{
|
||||
if (!r) {
|
||||
// for not fetching from Shift Type
|
||||
delete cur_frm.fetch_dict["shift"];
|
||||
}
|
||||
});
|
||||
cur_frm.cscript.onload = function(doc, cdt, cdn) {
|
||||
if(doc.__islocal) cur_frm.set_value("attendance_date", frappe.datetime.get_today());
|
||||
}
|
||||
|
||||
if (frm.doc.__islocal) {
|
||||
frm.set_value("attendance_date", frappe.datetime.get_today());
|
||||
}
|
||||
|
||||
frm.set_query("employee", () => {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.employee_query"
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
employee: function(frm) {
|
||||
if (frm.doc.employee) {
|
||||
frm.events.set_shift(frm);
|
||||
frm.events.set_overtime_type(frm);
|
||||
}
|
||||
},
|
||||
|
||||
set_shift: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.hr.doctype.attendance.attendance.get_shift_type",
|
||||
args: {
|
||||
employee: frm.doc.employee,
|
||||
attendance_date: frm.doc.attendance_date
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value("shift", r.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
set_overtime_type: function(frm) {
|
||||
frappe.db.get_single_value("Payroll Settings", "overtime_based_on").then((r)=>{
|
||||
if (r == "Attendance") {
|
||||
frappe.call({
|
||||
method: "erpnext.hr.doctype.attendance.attendance.get_overtime_type",
|
||||
args: {
|
||||
employee: frm.doc.employee,
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
frm.set_value("overtime_type", r.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frm.set_value("overtime_type", '');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
overtime_duration: function(frm) {
|
||||
let duration = frm.doc.overtime_duration.split(":");
|
||||
let overtime_duration_words = duration[0] + " Hours " + duration[1] + " Minutes";
|
||||
frm.set_value("overtime_duration_words", overtime_duration_words);
|
||||
cur_frm.fields_dict.employee.get_query = function(doc,cdt,cdn) {
|
||||
return{
|
||||
query: "erpnext.controllers.queries.employee_query"
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"naming_series",
|
||||
"employee",
|
||||
"employee_name",
|
||||
"working_hours",
|
||||
"status",
|
||||
"leave_type",
|
||||
"leave_application",
|
||||
@@ -19,19 +20,13 @@
|
||||
"company",
|
||||
"department",
|
||||
"attendance_request",
|
||||
"shift_details_section",
|
||||
"details_section",
|
||||
"shift",
|
||||
"in_time",
|
||||
"out_time",
|
||||
"column_break_18",
|
||||
"shift_duration",
|
||||
"working_time",
|
||||
"late_entry",
|
||||
"early_exit",
|
||||
"overtime_details_section",
|
||||
"overtime_type",
|
||||
"column_break_27",
|
||||
"overtime_duration",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
@@ -74,6 +69,14 @@
|
||||
"oldfieldtype": "Data",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "working_hours",
|
||||
"fieldname": "working_hours",
|
||||
"fieldtype": "Float",
|
||||
"label": "Working Hours",
|
||||
"precision": "1",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Present",
|
||||
"fieldname": "status",
|
||||
@@ -122,7 +125,6 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "employee.company",
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
@@ -144,8 +146,7 @@
|
||||
"fieldname": "shift",
|
||||
"fieldtype": "Link",
|
||||
"label": "Shift",
|
||||
"options": "Shift Type",
|
||||
"read_only": 1
|
||||
"options": "Shift Type"
|
||||
},
|
||||
{
|
||||
"fieldname": "attendance_request",
|
||||
@@ -176,6 +177,11 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Early Exit"
|
||||
},
|
||||
{
|
||||
"fieldname": "details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Details"
|
||||
},
|
||||
{
|
||||
"depends_on": "shift",
|
||||
"fieldname": "in_time",
|
||||
@@ -193,60 +199,13 @@
|
||||
{
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "overtime_type",
|
||||
"fieldname": "overtime_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Overtime Details"
|
||||
},
|
||||
{
|
||||
"depends_on": "overtime_type",
|
||||
"fieldname": "overtime_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Overtime Type",
|
||||
"options": "Overtime Type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "working_time",
|
||||
"fieldname": "working_time",
|
||||
"fieldtype": "Duration",
|
||||
"label": "Total Working Time",
|
||||
"precision": "1",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0000",
|
||||
"fieldname": "overtime_duration",
|
||||
"fieldtype": "Duration",
|
||||
"hide_days": 1,
|
||||
"label": "Overtime Duration"
|
||||
},
|
||||
{
|
||||
"depends_on": "shift",
|
||||
"fieldname": "shift_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Shift Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_27",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "Shift duration for a day",
|
||||
"fetch_from": "shift.standard_working_time",
|
||||
"fieldname": "shift_duration",
|
||||
"fieldtype": "Duration",
|
||||
"label": "Shift Duration",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-ok",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-11 11:55:50.076043",
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Attendance",
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.utils import getdate, nowdate
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cstr, get_datetime, formatdate
|
||||
from erpnext.hr.utils import validate_active_employee
|
||||
from frappe.utils import cstr, get_datetime, formatdate, getdate, nowdate
|
||||
|
||||
class Attendance(Document):
|
||||
def validate(self):
|
||||
@@ -18,17 +19,12 @@ class Attendance(Document):
|
||||
self.validate_duplicate_record()
|
||||
self.validate_employee_status()
|
||||
self.check_leave_record()
|
||||
self.set_overtime_type()
|
||||
self.set_default_shift()
|
||||
|
||||
if not frappe.db.get_single_value("Payroll Settings", "fetch_standard_working_hours_from_shift_type"):
|
||||
self.shift_duration = None
|
||||
|
||||
def validate_attendance_date(self):
|
||||
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
|
||||
|
||||
# leaves can be marked for future dates
|
||||
if self.status != "On Leave" and not self.leave_application and getdate(self.attendance_date) > getdate(nowdate()):
|
||||
if self.status != 'On Leave' and not self.leave_application and getdate(self.attendance_date) > getdate(nowdate()):
|
||||
frappe.throw(_("Attendance can not be marked for future dates"))
|
||||
elif date_of_joining and getdate(self.attendance_date) < getdate(date_of_joining):
|
||||
frappe.throw(_("Attendance date can not be less than employee's joining date"))
|
||||
@@ -49,25 +45,6 @@ class Attendance(Document):
|
||||
if frappe.db.get_value("Employee", self.employee, "status") == "Inactive":
|
||||
frappe.throw(_("Cannot mark attendance for an Inactive employee {0}").format(self.employee))
|
||||
|
||||
def set_default_shift(self):
|
||||
if not self.shift:
|
||||
self.shift = get_shift_type(self.employee, self.attendance_date)
|
||||
|
||||
def set_overtime_type(self):
|
||||
self.overtime_type = get_overtime_type(self.employee)
|
||||
|
||||
if self.overtime_type:
|
||||
if frappe.db.get_single_value("Payroll Settings", "overtime_based_on") != "Attendance":
|
||||
frappe.msgprint(_('Set "Calculate Overtime Based On Attendance" to Attendance for Overtime Slip Creation'))
|
||||
|
||||
maximum_overtime_hours_allowed = frappe.db.get_single_value("Payroll Settings", "maximum_overtime_hours_allowed")
|
||||
|
||||
if maximum_overtime_hours_allowed and maximum_overtime_hours_allowed * 3600 < self.overtime_duration:
|
||||
self.overtime_duration = maximum_overtime_hours_allowed * 3600
|
||||
frappe.msgprint(_("Overtime Duration can not be greater than {0} Hours. You can change this in Payroll settings").format(
|
||||
str(maximum_overtime_hours_allowed)
|
||||
))
|
||||
|
||||
def check_leave_record(self):
|
||||
leave_record = frappe.db.sql("""
|
||||
select leave_type, half_day, half_day_date
|
||||
@@ -81,11 +58,11 @@ class Attendance(Document):
|
||||
for d in leave_record:
|
||||
self.leave_type = d.leave_type
|
||||
if d.half_day_date == getdate(self.attendance_date):
|
||||
self.status = "Half Day"
|
||||
self.status = 'Half Day'
|
||||
frappe.msgprint(_("Employee {0} on Half day on {1}")
|
||||
.format(self.employee, formatdate(self.attendance_date)))
|
||||
else:
|
||||
self.status = "On Leave"
|
||||
self.status = 'On Leave'
|
||||
frappe.msgprint(_("Employee {0} is on Leave on {1}")
|
||||
.format(self.employee, formatdate(self.attendance_date)))
|
||||
|
||||
@@ -103,66 +80,6 @@ class Attendance(Document):
|
||||
if not emp:
|
||||
frappe.throw(_("Employee {0} is not active or does not exist").format(self.employee))
|
||||
|
||||
def calculate_overtime_duration(self):
|
||||
#this method is only for Calculation of overtime based on Attendance through Employee Checkins
|
||||
self.overtime_duration = None
|
||||
|
||||
if not self.shift_duration and self.shift:
|
||||
self.shift_duration = frappe.db.get_value("Shift Type", self.shift, "shift_duration")
|
||||
|
||||
if not self.overtime_type:
|
||||
self.overtime_type = get_overtime_type(self.employee)
|
||||
|
||||
if int(self.working_time) > int(self.shift_duration):
|
||||
self.overtime_duration = int(self.working_time) - int(self.shift_duration)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_shift_type(employee, attendance_date):
|
||||
shift_assignment = frappe.db.sql('''SELECT name, shift_type
|
||||
FROM
|
||||
`tabShift Assignment`
|
||||
WHERE
|
||||
docstatus = 1
|
||||
AND employee = %(employee)s AND start_date <= %(attendance_date)s
|
||||
AND (end_date >= %(attendance_date)s OR end_date IS null)
|
||||
AND status = "Active"
|
||||
''', {
|
||||
"employee": employee,
|
||||
"attendance_date": attendance_date,
|
||||
}, as_dict = 1)
|
||||
|
||||
if len(shift_assignment):
|
||||
shift = shift_assignment[0].shift_type
|
||||
else:
|
||||
shift = frappe.db.get_value("Employee", employee, "default_shift")
|
||||
return shift
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_overtime_type(employee):
|
||||
overtime_type = None
|
||||
emp_details = frappe.db.get_value("Employee", employee, ["department", "grade"], as_dict=1)
|
||||
|
||||
emp_department = emp_details.department
|
||||
if emp_department:
|
||||
overtime_type_doc = frappe.get_list("Overtime Type", filters={
|
||||
"applicable_for": "Department", "department": emp_department}, fields=["name"])
|
||||
if len(overtime_type_doc):
|
||||
overtime_type = overtime_type_doc[0].name
|
||||
|
||||
emp_grade = emp_details.grade
|
||||
if emp_grade:
|
||||
overtime_type_doc = frappe.get_list("Overtime Type", filters={
|
||||
"applicable_for": "Employee Grade", "employee_grade": emp_grade},
|
||||
fields=["name"])
|
||||
if len(overtime_type_doc):
|
||||
overtime_type = overtime_type_doc[0].name
|
||||
|
||||
overtime_type_doc = frappe.get_list("Overtime Type", filters={
|
||||
"applicable_for": "Employee", "employee": employee}, fields=["name"])
|
||||
if len(overtime_type_doc):
|
||||
overtime_type = overtime_type_doc[0].name
|
||||
return overtime_type
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_events(start, end, filters=None):
|
||||
events = []
|
||||
@@ -217,6 +134,7 @@ def mark_attendance(employee, attendance_date, status, shift=None, leave_type=No
|
||||
@frappe.whitelist()
|
||||
def mark_bulk_attendance(data):
|
||||
import json
|
||||
from pprint import pprint
|
||||
if isinstance(data, frappe.string_types):
|
||||
data = json.loads(data)
|
||||
data = frappe._dict(data)
|
||||
@@ -266,7 +184,7 @@ def get_unmarked_days(employee, month):
|
||||
month_start, month_end = dates_of_month[0], dates_of_month[length-1]
|
||||
|
||||
|
||||
records = frappe.get_all("Attendance", fields = ["attendance_date", "employee"] , filters = [
|
||||
records = frappe.get_all("Attendance", fields = ['attendance_date', 'employee'] , filters = [
|
||||
["attendance_date", ">=", month_start],
|
||||
["attendance_date", "<=", month_end],
|
||||
["employee", "=", employee],
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user