Merge branch 'version-13-pre-release' into version-13
This commit is contained in:
commit
db76612462
46
.github/helper/install.sh
vendored
Normal file
46
.github/helper/install.sh
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd ~ || exit
|
||||||
|
|
||||||
|
sudo apt-get install redis-server
|
||||||
|
|
||||||
|
sudo apt install nodejs
|
||||||
|
|
||||||
|
sudo apt install npm
|
||||||
|
|
||||||
|
pip install frappe-bench
|
||||||
|
|
||||||
|
git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1
|
||||||
|
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
|
||||||
|
|
||||||
|
mkdir ~/frappe-bench/sites/test_site
|
||||||
|
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/test_site/
|
||||||
|
|
||||||
|
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
|
||||||
|
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
|
||||||
|
|
||||||
|
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
|
||||||
|
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe"
|
||||||
|
mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
|
||||||
|
|
||||||
|
mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
|
||||||
|
mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES"
|
||||||
|
|
||||||
|
wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
|
||||||
|
tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
|
||||||
|
sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
|
||||||
|
sudo chmod o+x /usr/local/bin/wkhtmltopdf
|
||||||
|
sudo apt-get install libcups2-dev
|
||||||
|
|
||||||
|
cd ~/frappe-bench || exit
|
||||||
|
|
||||||
|
sed -i 's/watch:/# watch:/g' Procfile
|
||||||
|
sed -i 's/schedule:/# schedule:/g' Procfile
|
||||||
|
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 --site test_site reinstall --yes
|
@ -1,4 +1,6 @@
|
|||||||
{
|
{
|
||||||
|
"db_host": "127.0.0.1",
|
||||||
|
"db_port": 3306,
|
||||||
"db_name": "test_frappe",
|
"db_name": "test_frappe",
|
||||||
"db_password": "test_frappe",
|
"db_password": "test_frappe",
|
||||||
"auto_email_id": "test@example.com",
|
"auto_email_id": "test@example.com",
|
94
.github/workflows/ci-tests.yml
vendored
Normal file
94
.github/workflows/ci-tests.yml
vendored
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on: [pull_request, workflow_dispatch, push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- TYPE: "server"
|
||||||
|
JOB_NAME: "Server"
|
||||||
|
RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-tests --app erpnext --coverage
|
||||||
|
- TYPE: "patch"
|
||||||
|
JOB_NAME: "Patch"
|
||||||
|
RUN_COMMAND: cd ~/frappe-bench/ && wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz && bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz && bench --site test_site migrate
|
||||||
|
|
||||||
|
name: ${{ matrix.JOB_NAME }}
|
||||||
|
|
||||||
|
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.6
|
||||||
|
|
||||||
|
- 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: Install
|
||||||
|
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||||
|
|
||||||
|
- name: Run Tests
|
||||||
|
run: ${{ matrix.RUN_COMMAND }}
|
||||||
|
env:
|
||||||
|
TYPE: ${{ matrix.TYPE }}
|
||||||
|
|
||||||
|
- name: Coverage
|
||||||
|
if: matrix.TYPE == 'server'
|
||||||
|
run: |
|
||||||
|
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
|
||||||
|
cd ${GITHUB_WORKSPACE}
|
||||||
|
pip install coveralls==2.2.0
|
||||||
|
pip install coverage==4.5.4
|
||||||
|
coveralls
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
|
||||||
|
|
69
.travis.yml
69
.travis.yml
@ -1,69 +0,0 @@
|
|||||||
language: python
|
|
||||||
dist: trusty
|
|
||||||
|
|
||||||
git:
|
|
||||||
depth: 1
|
|
||||||
|
|
||||||
cache:
|
|
||||||
- pip
|
|
||||||
|
|
||||||
addons:
|
|
||||||
hosts: test_site
|
|
||||||
mariadb: 10.3
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
- name: "Python 3.6 Server Side Test"
|
|
||||||
python: 3.6
|
|
||||||
script: bench --site test_site run-tests --app erpnext --coverage
|
|
||||||
|
|
||||||
- name: "Python 3.6 Patch Test"
|
|
||||||
python: 3.6
|
|
||||||
before_script:
|
|
||||||
- wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz
|
|
||||||
- bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz
|
|
||||||
script: bench --site test_site migrate
|
|
||||||
|
|
||||||
install:
|
|
||||||
- cd ~
|
|
||||||
- nvm install 10
|
|
||||||
|
|
||||||
- pip install frappe-bench
|
|
||||||
|
|
||||||
- git clone https://github.com/frappe/frappe --branch $TRAVIS_BRANCH --depth 1
|
|
||||||
- bench init --skip-assets --frappe-path ~/frappe --python $(which python) frappe-bench
|
|
||||||
|
|
||||||
- mkdir ~/frappe-bench/sites/test_site
|
|
||||||
- cp -r $TRAVIS_BUILD_DIR/.travis/site_config.json ~/frappe-bench/sites/test_site/
|
|
||||||
|
|
||||||
- mysql -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
|
|
||||||
- mysql -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
|
|
||||||
|
|
||||||
- mysql -u root -e "CREATE DATABASE test_frappe"
|
|
||||||
- mysql -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
|
|
||||||
- mysql -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
|
|
||||||
|
|
||||||
- mysql -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
|
|
||||||
- mysql -u root -e "FLUSH PRIVILEGES"
|
|
||||||
|
|
||||||
- wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
|
|
||||||
- tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
|
|
||||||
- sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
|
|
||||||
- sudo chmod o+x /usr/local/bin/wkhtmltopdf
|
|
||||||
- sudo apt-get install libcups2-dev
|
|
||||||
|
|
||||||
- cd ~/frappe-bench
|
|
||||||
|
|
||||||
- sed -i 's/watch:/# watch:/g' Procfile
|
|
||||||
- sed -i 's/schedule:/# schedule:/g' Procfile
|
|
||||||
- sed -i 's/socketio:/# socketio:/g' Procfile
|
|
||||||
- sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
|
|
||||||
|
|
||||||
- bench get-app erpnext $TRAVIS_BUILD_DIR
|
|
||||||
- bench start &
|
|
||||||
- bench --site test_site reinstall --yes
|
|
||||||
|
|
||||||
after_script:
|
|
||||||
- pip install coverage==4.5.4
|
|
||||||
- pip install python-coveralls
|
|
||||||
- coveralls -b apps/erpnext -d ../../sites/.coverage
|
|
@ -5,7 +5,7 @@
|
|||||||
<p>ERP made simple</p>
|
<p>ERP made simple</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[![Build Status](https://api.travis-ci.com/frappe/erpnext.svg?branch=develop)](https://travis-ci.com/frappe/erpnext)
|
[![CI](https://github.com/frappe/erpnext/actions/workflows/ci-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/ci-tests.yml)
|
||||||
[![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
|
[![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
|
||||||
[![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop)
|
[![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop)
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import frappe
|
|||||||
from erpnext.hooks import regional_overrides
|
from erpnext.hooks import regional_overrides
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
__version__ = '13.0.2'
|
__version__ = '13.1.0'
|
||||||
|
|
||||||
def get_default_company(user=None):
|
def get_default_company(user=None):
|
||||||
'''Get default company for user'''
|
'''Get default company for user'''
|
||||||
|
@ -214,6 +214,7 @@ class Account(NestedSet):
|
|||||||
if parent_value_changed:
|
if parent_value_changed:
|
||||||
doc.save()
|
doc.save()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def convert_group_to_ledger(self):
|
def convert_group_to_ledger(self):
|
||||||
if self.check_if_child_exists():
|
if self.check_if_child_exists():
|
||||||
throw(_("Account with child nodes cannot be converted to ledger"))
|
throw(_("Account with child nodes cannot be converted to ledger"))
|
||||||
@ -224,6 +225,7 @@ class Account(NestedSet):
|
|||||||
self.save()
|
self.save()
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def convert_ledger_to_group(self):
|
def convert_ledger_to_group(self):
|
||||||
if self.check_gle_exists():
|
if self.check_gle_exists():
|
||||||
throw(_("Account with existing transaction can not be converted to group."))
|
throw(_("Account with existing transaction can not be converted to group."))
|
||||||
|
@ -39,6 +39,7 @@ class AccountingPeriod(Document):
|
|||||||
frappe.throw(_("Accounting Period overlaps with {0}")
|
frappe.throw(_("Accounting Period overlaps with {0}")
|
||||||
.format(existing_accounting_period[0].get("name")), OverlapError)
|
.format(existing_accounting_period[0].get("name")), OverlapError)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_doctypes_for_closing(self):
|
def get_doctypes_for_closing(self):
|
||||||
docs_for_closing = []
|
docs_for_closing = []
|
||||||
doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \
|
doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \
|
||||||
|
@ -11,36 +11,36 @@ from erpnext.accounts.doctype.accounting_period.accounting_period import Overlap
|
|||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
|
||||||
class TestAccountingPeriod(unittest.TestCase):
|
class TestAccountingPeriod(unittest.TestCase):
|
||||||
def test_overlap(self):
|
def test_overlap(self):
|
||||||
ap1 = create_accounting_period(start_date = "2018-04-01",
|
ap1 = create_accounting_period(start_date = "2018-04-01",
|
||||||
end_date = "2018-06-30", company = "Wind Power LLC")
|
end_date = "2018-06-30", company = "Wind Power LLC")
|
||||||
ap1.save()
|
ap1.save()
|
||||||
|
|
||||||
ap2 = create_accounting_period(start_date = "2018-06-30",
|
ap2 = create_accounting_period(start_date = "2018-06-30",
|
||||||
end_date = "2018-07-10", company = "Wind Power LLC", period_name = "Test Accounting Period 1")
|
end_date = "2018-07-10", company = "Wind Power LLC", period_name = "Test Accounting Period 1")
|
||||||
self.assertRaises(OverlapError, ap2.save)
|
self.assertRaises(OverlapError, ap2.save)
|
||||||
|
|
||||||
def test_accounting_period(self):
|
def test_accounting_period(self):
|
||||||
ap1 = create_accounting_period(period_name = "Test Accounting Period 2")
|
ap1 = create_accounting_period(period_name = "Test Accounting Period 2")
|
||||||
ap1.save()
|
ap1.save()
|
||||||
|
|
||||||
doc = create_sales_invoice(do_not_submit=1, cost_center = "_Test Company - _TC", warehouse = "Stores - _TC")
|
doc = create_sales_invoice(do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC")
|
||||||
self.assertRaises(ClosedAccountingPeriod, doc.submit)
|
self.assertRaises(ClosedAccountingPeriod, doc.submit)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
for d in frappe.get_all("Accounting Period"):
|
for d in frappe.get_all("Accounting Period"):
|
||||||
frappe.delete_doc("Accounting Period", d.name)
|
frappe.delete_doc("Accounting Period", d.name)
|
||||||
|
|
||||||
def create_accounting_period(**args):
|
def create_accounting_period(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|
||||||
accounting_period = frappe.new_doc("Accounting Period")
|
accounting_period = frappe.new_doc("Accounting Period")
|
||||||
accounting_period.start_date = args.start_date or nowdate()
|
accounting_period.start_date = args.start_date or nowdate()
|
||||||
accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
|
accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
|
||||||
accounting_period.company = args.company or "_Test Company"
|
accounting_period.company = args.company or "_Test Company"
|
||||||
accounting_period.period_name =args.period_name or "_Test_Period_Name_1"
|
accounting_period.period_name =args.period_name or "_Test_Period_Name_1"
|
||||||
accounting_period.append("closed_documents", {
|
accounting_period.append("closed_documents", {
|
||||||
"document_type": 'Sales Invoice', "closed": 1
|
"document_type": 'Sales Invoice', "closed": 1
|
||||||
})
|
})
|
||||||
|
|
||||||
return accounting_period
|
return accounting_period
|
||||||
|
@ -42,10 +42,9 @@ let add_fields_to_mapping_table = function (frm) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.meta.get_docfield("Bank Transaction Mapping", "bank_transaction_field",
|
frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property(
|
||||||
frm.doc.name).options = options;
|
'bank_transaction_field', 'options', options
|
||||||
|
);
|
||||||
frm.fields_dict.bank_transaction_mapping.grid.refresh();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
||||||
|
@ -12,6 +12,7 @@ form_grid_templates = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class BankClearance(Document):
|
class BankClearance(Document):
|
||||||
|
@frappe.whitelist()
|
||||||
def get_payment_entries(self):
|
def get_payment_entries(self):
|
||||||
if not (self.from_date and self.to_date):
|
if not (self.from_date and self.to_date):
|
||||||
frappe.throw(_("From Date and To Date are Mandatory"))
|
frappe.throw(_("From Date and To Date are Mandatory"))
|
||||||
@ -108,6 +109,7 @@ class BankClearance(Document):
|
|||||||
row.update(d)
|
row.update(d)
|
||||||
self.total_amount += flt(amount)
|
self.total_amount += flt(amount)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def update_clearance_date(self):
|
def update_clearance_date(self):
|
||||||
clearance_date_updated = False
|
clearance_date_updated = False
|
||||||
for d in self.get('payment_entries'):
|
for d in self.get('payment_entries'):
|
||||||
|
@ -8,6 +8,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
|
|||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
company: ["in", frm.doc.company],
|
company: ["in", frm.doc.company],
|
||||||
|
'is_company_account': 1
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -532,43 +532,4 @@ frappe.ui.form.on("Bank Statement Import", {
|
|||||||
</table>
|
</table>
|
||||||
`);
|
`);
|
||||||
},
|
},
|
||||||
|
|
||||||
show_missing_link_values(frm, missing_link_values) {
|
|
||||||
let can_be_created_automatically = missing_link_values.every(
|
|
||||||
(d) => d.has_one_mandatory_field
|
|
||||||
);
|
|
||||||
|
|
||||||
let html = missing_link_values
|
|
||||||
.map((d) => {
|
|
||||||
let doctype = d.doctype;
|
|
||||||
let values = d.missing_values;
|
|
||||||
return `
|
|
||||||
<h5>${doctype}</h5>
|
|
||||||
<ul>${values.map((v) => `<li>${v}</li>`).join("")}</ul>
|
|
||||||
`;
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
|
|
||||||
if (can_be_created_automatically) {
|
|
||||||
// prettier-ignore
|
|
||||||
let message = __('There are some linked records which needs to be created before we can import your file. Do you want to create the following missing records automatically?');
|
|
||||||
frappe.confirm(message + html, () => {
|
|
||||||
frm.call("create_missing_link_values", {
|
|
||||||
missing_link_values,
|
|
||||||
}).then((r) => {
|
|
||||||
let records = r.message;
|
|
||||||
frappe.msgprint(__(
|
|
||||||
"Created {0} records successfully.", [
|
|
||||||
records.length,
|
|
||||||
]
|
|
||||||
));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
frappe.msgprint(
|
|
||||||
// prettier-ignore
|
|
||||||
__('The following records needs to be created before we can import your file.') + html
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
@ -15,12 +15,14 @@ from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profi
|
|||||||
test_dependencies = ["Item", "Cost Center"]
|
test_dependencies = ["Item", "Cost Center"]
|
||||||
|
|
||||||
class TestBankTransaction(unittest.TestCase):
|
class TestBankTransaction(unittest.TestCase):
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
make_pos_profile()
|
make_pos_profile()
|
||||||
add_transactions()
|
add_transactions()
|
||||||
add_vouchers()
|
add_vouchers()
|
||||||
|
|
||||||
def tearDown(self):
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
for bt in frappe.get_all("Bank Transaction"):
|
for bt in frappe.get_all("Bank Transaction"):
|
||||||
doc = frappe.get_doc("Bank Transaction", bt.name)
|
doc = frappe.get_doc("Bank Transaction", bt.name)
|
||||||
doc.cancel()
|
doc.cancel()
|
||||||
@ -33,9 +35,6 @@ class TestBankTransaction(unittest.TestCase):
|
|||||||
# Delete POS Profile
|
# Delete POS Profile
|
||||||
frappe.db.sql("delete from `tabPOS Profile`")
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
|
||||||
frappe.flags.test_bank_transactions_created = False
|
|
||||||
frappe.flags.test_payments_created = False
|
|
||||||
|
|
||||||
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
|
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
|
||||||
def test_linked_payments(self):
|
def test_linked_payments(self):
|
||||||
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"))
|
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"))
|
||||||
@ -44,8 +43,8 @@ class TestBankTransaction(unittest.TestCase):
|
|||||||
|
|
||||||
# This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment
|
# This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment
|
||||||
def test_reconcile(self):
|
def test_reconcile(self):
|
||||||
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"))
|
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"))
|
||||||
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200))
|
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1700))
|
||||||
vouchers = json.dumps([{
|
vouchers = json.dumps([{
|
||||||
"payment_doctype":"Payment Entry",
|
"payment_doctype":"Payment Entry",
|
||||||
"payment_name":payment.name,
|
"payment_name":payment.name,
|
||||||
@ -62,7 +61,6 @@ class TestBankTransaction(unittest.TestCase):
|
|||||||
def test_debit_credit_output(self):
|
def test_debit_credit_output(self):
|
||||||
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"))
|
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"))
|
||||||
linked_payments = get_linked_payments(bank_transaction.name, ['payment_entry', 'exact_match'])
|
linked_payments = get_linked_payments(bank_transaction.name, ['payment_entry', 'exact_match'])
|
||||||
print(linked_payments)
|
|
||||||
self.assertTrue(linked_payments[0][3])
|
self.assertTrue(linked_payments[0][3])
|
||||||
|
|
||||||
# Check error if already reconciled
|
# Check error if already reconciled
|
||||||
@ -116,10 +114,6 @@ def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def add_transactions():
|
def add_transactions():
|
||||||
if frappe.flags.test_bank_transactions_created:
|
|
||||||
return
|
|
||||||
|
|
||||||
frappe.set_user("Administrator")
|
|
||||||
create_bank_account()
|
create_bank_account()
|
||||||
|
|
||||||
doc = frappe.get_doc({
|
doc = frappe.get_doc({
|
||||||
@ -172,14 +166,8 @@ def add_transactions():
|
|||||||
}).insert()
|
}).insert()
|
||||||
doc.submit()
|
doc.submit()
|
||||||
|
|
||||||
frappe.flags.test_bank_transactions_created = True
|
|
||||||
|
|
||||||
def add_vouchers():
|
def add_vouchers():
|
||||||
if frappe.flags.test_payments_created:
|
|
||||||
return
|
|
||||||
|
|
||||||
frappe.set_user("Administrator")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
frappe.get_doc({
|
frappe.get_doc({
|
||||||
"doctype": "Supplier",
|
"doctype": "Supplier",
|
||||||
@ -272,13 +260,6 @@ def add_vouchers():
|
|||||||
except frappe.DuplicateEntryError:
|
except frappe.DuplicateEntryError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080)
|
|
||||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
|
|
||||||
pe.reference_no = "Fayva Oct 18"
|
|
||||||
pe.reference_date = "2018-10-29"
|
|
||||||
pe.insert()
|
|
||||||
pe.submit()
|
|
||||||
|
|
||||||
mode_of_payment = frappe.get_doc({
|
mode_of_payment = frappe.get_doc({
|
||||||
"doctype": "Mode of Payment",
|
"doctype": "Mode of Payment",
|
||||||
"name": "Cash"
|
"name": "Cash"
|
||||||
@ -291,14 +272,12 @@ def add_vouchers():
|
|||||||
})
|
})
|
||||||
mode_of_payment.save()
|
mode_of_payment.save()
|
||||||
|
|
||||||
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_submit=1)
|
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
|
||||||
si.is_pos = 1
|
si.is_pos = 1
|
||||||
si.append("payments", {
|
si.append("payments", {
|
||||||
"mode_of_payment": "Cash",
|
"mode_of_payment": "Cash",
|
||||||
"account": "_Test Bank - _TC",
|
"account": "_Test Bank - _TC",
|
||||||
"amount": 109080
|
"amount": 109080
|
||||||
})
|
})
|
||||||
si.save()
|
si.insert()
|
||||||
si.submit()
|
si.submit()
|
||||||
|
|
||||||
frappe.flags.test_payments_created = True
|
|
||||||
|
@ -57,6 +57,7 @@ class CForm(Document):
|
|||||||
total = sum([flt(d.grand_total) for d in self.get('invoices')])
|
total = sum([flt(d.grand_total) for d in self.get('invoices')])
|
||||||
frappe.db.set(self, 'total_invoiced_amount', total)
|
frappe.db.set(self, 'total_invoiced_amount', total)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_invoice_details(self, invoice_no):
|
def get_invoice_details(self, invoice_no):
|
||||||
""" Pull details from invoices for referrence """
|
""" Pull details from invoices for referrence """
|
||||||
if invoice_no:
|
if invoice_no:
|
||||||
|
@ -293,6 +293,11 @@ def validate_accounts(file_name):
|
|||||||
accounts_dict = {}
|
accounts_dict = {}
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
accounts_dict.setdefault(account["account_name"], account)
|
accounts_dict.setdefault(account["account_name"], account)
|
||||||
|
if not hasattr(account, "parent_account"):
|
||||||
|
msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
|
||||||
|
msg += "<br><br>"
|
||||||
|
msg += _("Alternatively, you can download the template and fill your data in.")
|
||||||
|
frappe.throw(msg, title=_("Parent Account Missing"))
|
||||||
if account["parent_account"] and accounts_dict.get(account["parent_account"]):
|
if account["parent_account"] and accounts_dict.get(account["parent_account"]):
|
||||||
accounts_dict[account["parent_account"]]["is_group"] = 1
|
accounts_dict[account["parent_account"]]["is_group"] = 1
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ class CostCenter(NestedSet):
|
|||||||
frappe.throw(_("{0} is not a group node. Please select a group node as parent cost center").format(
|
frappe.throw(_("{0} is not a group node. Please select a group node as parent cost center").format(
|
||||||
frappe.bold(self.parent_cost_center)))
|
frappe.bold(self.parent_cost_center)))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def convert_group_to_ledger(self):
|
def convert_group_to_ledger(self):
|
||||||
if self.check_if_child_exists():
|
if self.check_if_child_exists():
|
||||||
frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes"))
|
frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes"))
|
||||||
@ -60,6 +61,7 @@ class CostCenter(NestedSet):
|
|||||||
self.save()
|
self.save()
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def convert_ledger_to_group(self):
|
def convert_ledger_to_group(self):
|
||||||
if cint(self.enable_distributed_cost_center):
|
if cint(self.enable_distributed_cost_center):
|
||||||
frappe.throw(_("Cost Center with enabled distributed cost center can not be converted to group"))
|
frappe.throw(_("Cost Center with enabled distributed cost center can not be converted to group"))
|
||||||
|
@ -27,6 +27,7 @@ class ExchangeRateRevaluation(Document):
|
|||||||
if not (self.company and self.posting_date):
|
if not (self.company and self.posting_date):
|
||||||
frappe.throw(_("Please select Company and Posting Date to getting entries"))
|
frappe.throw(_("Please select Company and Posting Date to getting entries"))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_accounts_data(self, account=None):
|
def get_accounts_data(self, account=None):
|
||||||
accounts = []
|
accounts = []
|
||||||
self.validate_mandatory()
|
self.validate_mandatory()
|
||||||
@ -95,6 +96,7 @@ class ExchangeRateRevaluation(Document):
|
|||||||
message = _("No outstanding invoices found")
|
message = _("No outstanding invoices found")
|
||||||
frappe.msgprint(message)
|
frappe.msgprint(message)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def make_jv_entry(self):
|
def make_jv_entry(self):
|
||||||
if self.total_gain_loss == 0:
|
if self.total_gain_loss == 0:
|
||||||
return
|
return
|
||||||
|
@ -12,6 +12,7 @@ from frappe.model.document import Document
|
|||||||
class FiscalYearIncorrectDate(frappe.ValidationError): pass
|
class FiscalYearIncorrectDate(frappe.ValidationError): pass
|
||||||
|
|
||||||
class FiscalYear(Document):
|
class FiscalYear(Document):
|
||||||
|
@frappe.whitelist()
|
||||||
def set_as_default(self):
|
def set_as_default(self):
|
||||||
frappe.db.set_value("Global Defaults", None, "current_fiscal_year", self.name)
|
frappe.db.set_value("Global Defaults", None, "current_fiscal_year", self.name)
|
||||||
global_defaults = frappe.get_doc("Global Defaults")
|
global_defaults = frappe.get_doc("Global Defaults")
|
||||||
@ -54,7 +55,7 @@ class FiscalYear(Document):
|
|||||||
def on_update(self):
|
def on_update(self):
|
||||||
check_duplicate_fiscal_year(self)
|
check_duplicate_fiscal_year(self)
|
||||||
frappe.cache().delete_value("fiscal_years")
|
frappe.cache().delete_value("fiscal_years")
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
global_defaults = frappe.get_doc("Global Defaults")
|
global_defaults = frappe.get_doc("Global Defaults")
|
||||||
if global_defaults.current_fiscal_year == self.name:
|
if global_defaults.current_fiscal_year == self.name:
|
||||||
|
@ -290,4 +290,8 @@ def rename_temporarily_named_docs(doctype):
|
|||||||
oldname = doc.name
|
oldname = doc.name
|
||||||
set_name_from_naming_options(frappe.get_meta(doctype).autoname, doc)
|
set_name_from_naming_options(frappe.get_meta(doctype).autoname, doc)
|
||||||
newname = doc.name
|
newname = doc.name
|
||||||
frappe.db.sql("""UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s""".format(doctype), (newname, oldname))
|
frappe.db.sql(
|
||||||
|
"UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s".format(doctype),
|
||||||
|
(newname, oldname),
|
||||||
|
auto_commit=True
|
||||||
|
)
|
||||||
|
@ -125,6 +125,7 @@ class InvoiceDiscounting(AccountsController):
|
|||||||
|
|
||||||
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding='No')
|
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding='No')
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def create_disbursement_entry(self):
|
def create_disbursement_entry(self):
|
||||||
je = frappe.new_doc("Journal Entry")
|
je = frappe.new_doc("Journal Entry")
|
||||||
je.voucher_type = 'Journal Entry'
|
je.voucher_type = 'Journal Entry'
|
||||||
@ -174,6 +175,7 @@ class InvoiceDiscounting(AccountsController):
|
|||||||
|
|
||||||
return je
|
return je
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def close_loan(self):
|
def close_loan(self):
|
||||||
je = frappe.new_doc("Journal Entry")
|
je = frappe.new_doc("Journal Entry")
|
||||||
je.voucher_type = 'Journal Entry'
|
je.voucher_type = 'Journal Entry'
|
||||||
|
@ -327,18 +327,16 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
setup_balance_formatter: function() {
|
setup_balance_formatter: function() {
|
||||||
var me = this;
|
const formatter = function(value, df, options, doc) {
|
||||||
$.each(["balance", "party_balance"], function(i, field) {
|
var currency = frappe.meta.get_field_currency(df, doc);
|
||||||
var df = frappe.meta.get_docfield("Journal Entry Account", field, me.frm.doc.name);
|
var dr_or_cr = value ? ('<label>' + (value > 0.0 ? __("Dr") : __("Cr")) + '</label>') : "";
|
||||||
df.formatter = function(value, df, options, doc) {
|
return "<div style='text-align: right'>"
|
||||||
var currency = frappe.meta.get_field_currency(df, doc);
|
+ ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency))
|
||||||
var dr_or_cr = value ? ('<label>' + (value > 0.0 ? __("Dr") : __("Cr")) + '</label>') : "";
|
+ " " + dr_or_cr
|
||||||
return "<div style='text-align: right'>"
|
+ "</div>";
|
||||||
+ ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency))
|
};
|
||||||
+ " " + dr_or_cr
|
this.frm.fields_dict.accounts.grid.update_docfield_property('balance', 'formatter', formatter);
|
||||||
+ "</div>";
|
this.frm.fields_dict.accounts.grid.update_docfield_property('party_balance', 'formatter', formatter);
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
reference_name: function(doc, cdt, cdn) {
|
reference_name: function(doc, cdt, cdn) {
|
||||||
@ -431,15 +429,6 @@ cur_frm.cscript.validate = function(doc,cdt,cdn) {
|
|||||||
cur_frm.cscript.update_totals(doc);
|
cur_frm.cscript.update_totals(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
|
|
||||||
if(doc.select_print_heading){
|
|
||||||
// print heading
|
|
||||||
cur_frm.pformat.print_heading = doc.select_print_heading;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
cur_frm.pformat.print_heading = __("Journal Entry");
|
|
||||||
}
|
|
||||||
|
|
||||||
frappe.ui.form.on("Journal Entry Account", {
|
frappe.ui.form.on("Journal Entry Account", {
|
||||||
party: function(frm, cdt, cdn) {
|
party: function(frm, cdt, cdn) {
|
||||||
var d = frappe.get_doc(cdt, cdn);
|
var d = frappe.get_doc(cdt, cdn);
|
||||||
@ -511,8 +500,11 @@ $.extend(erpnext.journal_entry, {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$.each(field_label_map, function (fieldname, label) {
|
$.each(field_label_map, function (fieldname, label) {
|
||||||
var df = frappe.meta.get_docfield("Journal Entry Account", fieldname, frm.doc.name);
|
frm.fields_dict.accounts.grid.update_docfield_property(
|
||||||
df.label = frm.doc.multi_currency ? (label + " in Account Currency") : label;
|
fieldname,
|
||||||
|
'label',
|
||||||
|
frm.doc.multi_currency ? (label + " in Account Currency") : label
|
||||||
|
);
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -564,6 +564,7 @@ class JournalEntry(AccountsController):
|
|||||||
if gl_map:
|
if gl_map:
|
||||||
make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj, update_outstanding=update_outstanding)
|
make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj, update_outstanding=update_outstanding)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_balance(self):
|
def get_balance(self):
|
||||||
if not self.get('accounts'):
|
if not self.get('accounts'):
|
||||||
msgprint(_("'Entries' cannot be empty"), raise_exception=True)
|
msgprint(_("'Entries' cannot be empty"), raise_exception=True)
|
||||||
|
@ -8,6 +8,7 @@ from frappe.utils import (flt, add_months)
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
class MonthlyDistribution(Document):
|
class MonthlyDistribution(Document):
|
||||||
|
@frappe.whitelist()
|
||||||
def get_months(self):
|
def get_months(self):
|
||||||
month_list = ['January','February','March','April','May','June','July','August','September',
|
month_list = ['January','February','March','April','May','June','July','August','September',
|
||||||
'October','November','December']
|
'October','November','December']
|
||||||
|
@ -167,6 +167,7 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
|
|
||||||
return invoice
|
return invoice
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def make_invoices(self):
|
def make_invoices(self):
|
||||||
self.validate_company()
|
self.validate_company()
|
||||||
invoices = self.get_invoices()
|
invoices = self.get_invoices()
|
||||||
|
@ -6,10 +6,12 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
test_dependencies = ["Customer", "Supplier"]
|
from frappe.cache_manager import clear_doctype_cache
|
||||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||||
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account
|
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account
|
||||||
|
|
||||||
|
test_dependencies = ["Customer", "Supplier"]
|
||||||
|
|
||||||
class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
|
||||||
@ -24,22 +26,25 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
|
|||||||
|
|
||||||
def test_opening_sales_invoice_creation(self):
|
def test_opening_sales_invoice_creation(self):
|
||||||
property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check")
|
property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check")
|
||||||
invoices = self.make_invoices(company="_Test Opening Invoice Company")
|
try:
|
||||||
|
invoices = self.make_invoices(company="_Test Opening Invoice Company")
|
||||||
|
|
||||||
self.assertEqual(len(invoices), 2)
|
self.assertEqual(len(invoices), 2)
|
||||||
expected_value = {
|
expected_value = {
|
||||||
"keys": ["customer", "outstanding_amount", "status"],
|
"keys": ["customer", "outstanding_amount", "status"],
|
||||||
0: ["_Test Customer", 300, "Overdue"],
|
0: ["_Test Customer", 300, "Overdue"],
|
||||||
1: ["_Test Customer 1", 250, "Overdue"],
|
1: ["_Test Customer 1", 250, "Overdue"],
|
||||||
}
|
}
|
||||||
self.check_expected_values(invoices, expected_value)
|
self.check_expected_values(invoices, expected_value)
|
||||||
|
|
||||||
si = frappe.get_doc("Sales Invoice", invoices[0])
|
si = frappe.get_doc("Sales Invoice", invoices[0])
|
||||||
|
|
||||||
# Check if update stock is not enabled
|
# Check if update stock is not enabled
|
||||||
self.assertEqual(si.update_stock, 0)
|
self.assertEqual(si.update_stock, 0)
|
||||||
|
|
||||||
property_setter.delete()
|
finally:
|
||||||
|
property_setter.delete()
|
||||||
|
clear_doctype_cache("Sales Invoice")
|
||||||
|
|
||||||
def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
|
def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
|
||||||
doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
|
doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
|
||||||
@ -143,4 +148,4 @@ def make_customer(customer=None):
|
|||||||
customer.insert(ignore_permissions=True)
|
customer.insert(ignore_permissions=True)
|
||||||
return customer.name
|
return customer.name
|
||||||
else:
|
else:
|
||||||
return frappe.db.exists("Customer", customer_name)
|
return frappe.db.exists("Customer", customer_name)
|
||||||
|
@ -234,8 +234,9 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (invoices) {
|
if (invoices) {
|
||||||
frappe.meta.get_docfield("Payment Reconciliation Payment", "invoice_number",
|
this.frm.fields_dict.payment.grid.update_docfield_property(
|
||||||
me.frm.doc.name).options = "\n" + invoices.join("\n");
|
'invoice_number', 'options', "\n" + invoices.join("\n")
|
||||||
|
);
|
||||||
|
|
||||||
$.each(me.frm.doc.payments || [], function(i, p) {
|
$.each(me.frm.doc.payments || [], function(i, p) {
|
||||||
if(!in_list(invoices, cstr(p.invoice_number))) p.invoice_number = null;
|
if(!in_list(invoices, cstr(p.invoice_number))) p.invoice_number = null;
|
||||||
|
@ -11,6 +11,7 @@ from erpnext.accounts.utils import (get_outstanding_invoices,
|
|||||||
from erpnext.controllers.accounts_controller import get_advance_payment_entries
|
from erpnext.controllers.accounts_controller import get_advance_payment_entries
|
||||||
|
|
||||||
class PaymentReconciliation(Document):
|
class PaymentReconciliation(Document):
|
||||||
|
@frappe.whitelist()
|
||||||
def get_unreconciled_entries(self):
|
def get_unreconciled_entries(self):
|
||||||
self.get_nonreconciled_payment_entries()
|
self.get_nonreconciled_payment_entries()
|
||||||
self.get_invoice_entries()
|
self.get_invoice_entries()
|
||||||
@ -147,6 +148,7 @@ class PaymentReconciliation(Document):
|
|||||||
ent.currency = e.get('currency')
|
ent.currency = e.get('currency')
|
||||||
ent.outstanding_amount = e.get('outstanding_amount')
|
ent.outstanding_amount = e.get('outstanding_amount')
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def reconcile(self, args):
|
def reconcile(self, args):
|
||||||
for e in self.get('payments'):
|
for e in self.get('payments'):
|
||||||
e.invoice_type = None
|
e.invoice_type = None
|
||||||
@ -197,6 +199,7 @@ class PaymentReconciliation(Document):
|
|||||||
'difference_account': row.difference_account
|
'difference_account': row.difference_account
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_difference_amount(self, child_row):
|
def get_difference_amount(self, child_row):
|
||||||
if child_row.get("reference_type") != 'Payment Entry': return
|
if child_row.get("reference_type") != 'Payment Entry': return
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ class POSClosingEntry(StatusUpdater):
|
|||||||
|
|
||||||
self.validate_pos_closing()
|
self.validate_pos_closing()
|
||||||
self.validate_pos_invoices()
|
self.validate_pos_invoices()
|
||||||
|
|
||||||
def validate_pos_closing(self):
|
def validate_pos_closing(self):
|
||||||
user = frappe.db.sql("""
|
user = frappe.db.sql("""
|
||||||
SELECT name FROM `tabPOS Closing Entry`
|
SELECT name FROM `tabPOS Closing Entry`
|
||||||
@ -37,12 +37,12 @@ class POSClosingEntry(StatusUpdater):
|
|||||||
bold_user = frappe.bold(self.user)
|
bold_user = frappe.bold(self.user)
|
||||||
frappe.throw(_("POS Closing Entry {} against {} between selected period")
|
frappe.throw(_("POS Closing Entry {} against {} between selected period")
|
||||||
.format(bold_already_exists, bold_user), title=_("Invalid Period"))
|
.format(bold_already_exists, bold_user), title=_("Invalid Period"))
|
||||||
|
|
||||||
def validate_pos_invoices(self):
|
def validate_pos_invoices(self):
|
||||||
invalid_rows = []
|
invalid_rows = []
|
||||||
for d in self.pos_transactions:
|
for d in self.pos_transactions:
|
||||||
invalid_row = {'idx': d.idx}
|
invalid_row = {'idx': d.idx}
|
||||||
pos_invoice = frappe.db.get_values("POS Invoice", d.pos_invoice,
|
pos_invoice = frappe.db.get_values("POS Invoice", d.pos_invoice,
|
||||||
["consolidated_invoice", "pos_profile", "docstatus", "owner"], as_dict=1)[0]
|
["consolidated_invoice", "pos_profile", "docstatus", "owner"], as_dict=1)[0]
|
||||||
if pos_invoice.consolidated_invoice:
|
if pos_invoice.consolidated_invoice:
|
||||||
invalid_row.setdefault('msg', []).append(_('POS Invoice is {}').format(frappe.bold("already consolidated")))
|
invalid_row.setdefault('msg', []).append(_('POS Invoice is {}').format(frappe.bold("already consolidated")))
|
||||||
@ -68,14 +68,15 @@ class POSClosingEntry(StatusUpdater):
|
|||||||
|
|
||||||
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
|
frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_payment_reconciliation_details(self):
|
def get_payment_reconciliation_details(self):
|
||||||
currency = frappe.get_cached_value('Company', self.company, "default_currency")
|
currency = frappe.get_cached_value('Company', self.company, "default_currency")
|
||||||
return frappe.render_template("erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html",
|
return frappe.render_template("erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html",
|
||||||
{"data": self, "currency": currency})
|
{"data": self, "currency": currency})
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
consolidate_pos_invoices(closing_entry=self)
|
consolidate_pos_invoices(closing_entry=self)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
unconsolidate_pos_invoices(closing_entry=self)
|
unconsolidate_pos_invoices(closing_entry=self)
|
||||||
|
|
||||||
@ -88,8 +89,8 @@ class POSClosingEntry(StatusUpdater):
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
|
def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
|
||||||
cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'])
|
cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'], as_list=1)
|
||||||
return [c['user'] for c in cashiers_list]
|
return [c for c in cashiers_list]
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_pos_invoices(start, end, pos_profile, user):
|
def get_pos_invoices(start, end, pos_profile, user):
|
||||||
|
@ -5,12 +5,21 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
import unittest
|
import unittest
|
||||||
from frappe.utils import nowdate
|
from frappe.utils import nowdate
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
|
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
|
||||||
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import make_closing_entry_from_opening
|
from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import make_closing_entry_from_opening
|
||||||
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
|
from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
|
||||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||||
|
|
||||||
class TestPOSClosingEntry(unittest.TestCase):
|
class TestPOSClosingEntry(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
# Make stock available for POS Sales
|
||||||
|
make_stock_entry(target="_Test Warehouse - _TC", qty=2, basic_rate=100)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
|
||||||
def test_pos_closing_entry(self):
|
def test_pos_closing_entry(self):
|
||||||
test_user, pos_profile = init_user_and_profile()
|
test_user, pos_profile = init_user_and_profile()
|
||||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||||
@ -41,9 +50,6 @@ class TestPOSClosingEntry(unittest.TestCase):
|
|||||||
self.assertEqual(pcv_doc.total_quantity, 2)
|
self.assertEqual(pcv_doc.total_quantity, 2)
|
||||||
self.assertEqual(pcv_doc.net_total, 6700)
|
self.assertEqual(pcv_doc.net_total, 6700)
|
||||||
|
|
||||||
frappe.set_user("Administrator")
|
|
||||||
frappe.db.sql("delete from `tabPOS Profile`")
|
|
||||||
|
|
||||||
def test_cancelling_of_pos_closing_entry(self):
|
def test_cancelling_of_pos_closing_entry(self):
|
||||||
test_user, pos_profile = init_user_and_profile()
|
test_user, pos_profile = init_user_and_profile()
|
||||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||||
@ -84,8 +90,6 @@ class TestPOSClosingEntry(unittest.TestCase):
|
|||||||
self.assertEqual(si_doc.docstatus, 2)
|
self.assertEqual(si_doc.docstatus, 2)
|
||||||
self.assertEqual(pos_inv1.status, 'Paid')
|
self.assertEqual(pos_inv1.status, 'Paid')
|
||||||
|
|
||||||
frappe.set_user("Administrator")
|
|
||||||
frappe.db.sql("delete from `tabPOS Profile`")
|
|
||||||
|
|
||||||
def init_user_and_profile(**args):
|
def init_user_and_profile(**args):
|
||||||
user = 'test@example.com'
|
user = 'test@example.com'
|
||||||
@ -103,4 +107,4 @@ def init_user_and_profile(**args):
|
|||||||
|
|
||||||
pos_profile.save()
|
pos_profile.save()
|
||||||
|
|
||||||
return test_user, pos_profile
|
return test_user, pos_profile
|
||||||
|
@ -220,7 +220,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
base_grand_total = flt(self.base_rounded_total) or flt(self.base_grand_total)
|
base_grand_total = flt(self.base_rounded_total) or flt(self.base_grand_total)
|
||||||
if not flt(self.change_amount) and grand_total < flt(self.paid_amount):
|
if not flt(self.change_amount) and grand_total < flt(self.paid_amount):
|
||||||
self.change_amount = flt(self.paid_amount - grand_total + flt(self.write_off_amount))
|
self.change_amount = flt(self.paid_amount - grand_total + flt(self.write_off_amount))
|
||||||
self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount))
|
self.base_change_amount = flt(self.base_paid_amount) - base_grand_total + flt(self.base_write_off_amount)
|
||||||
|
|
||||||
if flt(self.change_amount) and not self.account_for_change_amount:
|
if flt(self.change_amount) and not self.account_for_change_amount:
|
||||||
frappe.msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
|
frappe.msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
|
||||||
@ -354,6 +354,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
|
|
||||||
return profile
|
return profile
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def set_missing_values(self, for_validate=False):
|
def set_missing_values(self, for_validate=False):
|
||||||
profile = self.set_pos_fields(for_validate)
|
profile = self.set_pos_fields(for_validate)
|
||||||
|
|
||||||
@ -376,12 +377,20 @@ class POSInvoice(SalesInvoice):
|
|||||||
"allow_print_before_pay": profile.get("allow_print_before_pay")
|
"allow_print_before_pay": profile.get("allow_print_before_pay")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def reset_mode_of_payments(self):
|
||||||
|
if self.pos_profile:
|
||||||
|
pos_profile = frappe.get_cached_doc('POS Profile', self.pos_profile)
|
||||||
|
update_multi_mode_option(self, pos_profile)
|
||||||
|
self.paid_amount = 0
|
||||||
|
|
||||||
def set_account_for_mode_of_payment(self):
|
def set_account_for_mode_of_payment(self):
|
||||||
self.payments = [d for d in self.payments if d.amount or d.base_amount or d.default]
|
self.payments = [d for d in self.payments if d.amount or d.base_amount or d.default]
|
||||||
for pay in self.payments:
|
for pay in self.payments:
|
||||||
if not pay.account:
|
if not pay.account:
|
||||||
pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
|
pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def create_payment_request(self):
|
def create_payment_request(self):
|
||||||
for pay in self.payments:
|
for pay in self.payments:
|
||||||
if pay.type == "Phone":
|
if pay.type == "Phone":
|
||||||
|
@ -9,8 +9,20 @@ from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profi
|
|||||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
|
||||||
class TestPOSInvoice(unittest.TestCase):
|
class TestPOSInvoice(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
frappe.db.sql("delete from `tabTax Rule`")
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
if frappe.session.user != "Administrator":
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
|
if frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
||||||
|
frappe.db.set_value("Selling Settings", None, "validate_selling_price", 0)
|
||||||
|
|
||||||
def test_timestamp_change(self):
|
def test_timestamp_change(self):
|
||||||
w = create_pos_invoice(do_not_save=1)
|
w = create_pos_invoice(do_not_save=1)
|
||||||
w.docstatus = 0
|
w.docstatus = 0
|
||||||
@ -370,7 +382,6 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
pos_inv.load_from_db()
|
pos_inv.load_from_db()
|
||||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
||||||
self.assertEqual(rounded_total, 3470)
|
self.assertEqual(rounded_total, 3470)
|
||||||
frappe.set_user("Administrator")
|
|
||||||
|
|
||||||
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
|
def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
|
||||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||||
@ -412,7 +423,6 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
pos_inv.load_from_db()
|
pos_inv.load_from_db()
|
||||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
|
||||||
self.assertEqual(rounded_total, 840)
|
self.assertEqual(rounded_total, 840)
|
||||||
frappe.set_user("Administrator")
|
|
||||||
|
|
||||||
def test_merging_with_validate_selling_price(self):
|
def test_merging_with_validate_selling_price(self):
|
||||||
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
|
||||||
@ -421,10 +431,12 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
||||||
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
|
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
|
||||||
|
|
||||||
make_purchase_receipt(item_code="_Test Item", warehouse="_Test Warehouse - _TC", qty=1, rate=300)
|
item = "Test Selling Price Validation"
|
||||||
|
make_item(item, {"is_stock_item": 1})
|
||||||
|
make_purchase_receipt(item_code=item, warehouse="_Test Warehouse - _TC", qty=1, rate=300)
|
||||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
test_user, pos_profile = init_user_and_profile()
|
test_user, pos_profile = init_user_and_profile()
|
||||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
pos_inv = create_pos_invoice(item=item, rate=300, do_not_submit=1)
|
||||||
pos_inv.append('payments', {
|
pos_inv.append('payments', {
|
||||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||||
})
|
})
|
||||||
@ -438,7 +450,7 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
self.assertRaises(frappe.ValidationError, pos_inv.submit)
|
self.assertRaises(frappe.ValidationError, pos_inv.submit)
|
||||||
|
|
||||||
pos_inv2 = create_pos_invoice(rate=400, do_not_submit=1)
|
pos_inv2 = create_pos_invoice(item=item, rate=400, do_not_submit=1)
|
||||||
pos_inv2.append('payments', {
|
pos_inv2.append('payments', {
|
||||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 400
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 400
|
||||||
})
|
})
|
||||||
@ -457,8 +469,6 @@ class TestPOSInvoice(unittest.TestCase):
|
|||||||
pos_inv2.load_from_db()
|
pos_inv2.load_from_db()
|
||||||
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
|
rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
|
||||||
self.assertEqual(rounded_total, 400)
|
self.assertEqual(rounded_total, 400)
|
||||||
frappe.set_user("Administrator")
|
|
||||||
frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 0)
|
|
||||||
|
|
||||||
def create_pos_invoice(**args):
|
def create_pos_invoice(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
@ -508,4 +518,4 @@ def create_pos_invoice(**args):
|
|||||||
else:
|
else:
|
||||||
pos_inv.payment_schedule = []
|
pos_inv.payment_schedule = []
|
||||||
|
|
||||||
return pos_inv
|
return pos_inv
|
||||||
|
@ -12,6 +12,7 @@ from frappe.utils.background_jobs import enqueue
|
|||||||
from frappe.model.mapper import map_doc, map_child_doc
|
from frappe.model.mapper import map_doc, map_child_doc
|
||||||
from frappe.utils.scheduler import is_scheduler_inactive
|
from frappe.utils.scheduler import is_scheduler_inactive
|
||||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||||
|
import json
|
||||||
|
|
||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
@ -78,8 +79,11 @@ class POSInvoiceMergeLog(Document):
|
|||||||
sales_invoice = self.merge_pos_invoice_into(sales_invoice, data)
|
sales_invoice = self.merge_pos_invoice_into(sales_invoice, data)
|
||||||
|
|
||||||
sales_invoice.is_consolidated = 1
|
sales_invoice.is_consolidated = 1
|
||||||
|
sales_invoice.set_posting_time = 1
|
||||||
|
sales_invoice.posting_date = getdate(self.posting_date)
|
||||||
sales_invoice.save()
|
sales_invoice.save()
|
||||||
sales_invoice.submit()
|
sales_invoice.submit()
|
||||||
|
|
||||||
self.consolidated_invoice = sales_invoice.name
|
self.consolidated_invoice = sales_invoice.name
|
||||||
|
|
||||||
return sales_invoice.name
|
return sales_invoice.name
|
||||||
@ -91,10 +95,13 @@ class POSInvoiceMergeLog(Document):
|
|||||||
credit_note = self.merge_pos_invoice_into(credit_note, data)
|
credit_note = self.merge_pos_invoice_into(credit_note, data)
|
||||||
|
|
||||||
credit_note.is_consolidated = 1
|
credit_note.is_consolidated = 1
|
||||||
|
credit_note.set_posting_time = 1
|
||||||
|
credit_note.posting_date = getdate(self.posting_date)
|
||||||
# TODO: return could be against multiple sales invoice which could also have been consolidated?
|
# TODO: return could be against multiple sales invoice which could also have been consolidated?
|
||||||
# credit_note.return_against = self.consolidated_invoice
|
# credit_note.return_against = self.consolidated_invoice
|
||||||
credit_note.save()
|
credit_note.save()
|
||||||
credit_note.submit()
|
credit_note.submit()
|
||||||
|
|
||||||
self.consolidated_credit_note = credit_note.name
|
self.consolidated_credit_note = credit_note.name
|
||||||
|
|
||||||
return credit_note.name
|
return credit_note.name
|
||||||
@ -131,12 +138,14 @@ class POSInvoiceMergeLog(Document):
|
|||||||
if t.account_head == tax.account_head and t.cost_center == tax.cost_center:
|
if t.account_head == tax.account_head and t.cost_center == tax.cost_center:
|
||||||
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount)
|
t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount)
|
||||||
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount)
|
t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount)
|
||||||
|
update_item_wise_tax_detail(t, tax)
|
||||||
found = True
|
found = True
|
||||||
if not found:
|
if not found:
|
||||||
tax.charge_type = 'Actual'
|
tax.charge_type = 'Actual'
|
||||||
tax.included_in_print_rate = 0
|
tax.included_in_print_rate = 0
|
||||||
tax.tax_amount = tax.tax_amount_after_discount_amount
|
tax.tax_amount = tax.tax_amount_after_discount_amount
|
||||||
tax.base_tax_amount = tax.base_tax_amount_after_discount_amount
|
tax.base_tax_amount = tax.base_tax_amount_after_discount_amount
|
||||||
|
tax.item_wise_tax_detail = tax.item_wise_tax_detail
|
||||||
taxes.append(tax)
|
taxes.append(tax)
|
||||||
|
|
||||||
for payment in doc.get('payments'):
|
for payment in doc.get('payments'):
|
||||||
@ -168,11 +177,9 @@ class POSInvoiceMergeLog(Document):
|
|||||||
sales_invoice = frappe.new_doc('Sales Invoice')
|
sales_invoice = frappe.new_doc('Sales Invoice')
|
||||||
sales_invoice.customer = self.customer
|
sales_invoice.customer = self.customer
|
||||||
sales_invoice.is_pos = 1
|
sales_invoice.is_pos = 1
|
||||||
# date can be pos closing date?
|
|
||||||
sales_invoice.posting_date = getdate(nowdate())
|
|
||||||
|
|
||||||
return sales_invoice
|
return sales_invoice
|
||||||
|
|
||||||
def update_pos_invoices(self, invoice_docs, sales_invoice='', credit_note=''):
|
def update_pos_invoices(self, invoice_docs, sales_invoice='', credit_note=''):
|
||||||
for doc in invoice_docs:
|
for doc in invoice_docs:
|
||||||
doc.load_from_db()
|
doc.load_from_db()
|
||||||
@ -187,6 +194,26 @@ class POSInvoiceMergeLog(Document):
|
|||||||
si.flags.ignore_validate = True
|
si.flags.ignore_validate = True
|
||||||
si.cancel()
|
si.cancel()
|
||||||
|
|
||||||
|
def update_item_wise_tax_detail(consolidate_tax_row, tax_row):
|
||||||
|
consolidated_tax_detail = json.loads(consolidate_tax_row.item_wise_tax_detail)
|
||||||
|
tax_row_detail = json.loads(tax_row.item_wise_tax_detail)
|
||||||
|
|
||||||
|
if not consolidated_tax_detail:
|
||||||
|
consolidated_tax_detail = {}
|
||||||
|
|
||||||
|
for item_code, tax_data in tax_row_detail.items():
|
||||||
|
if consolidated_tax_detail.get(item_code):
|
||||||
|
consolidated_tax_data = consolidated_tax_detail.get(item_code)
|
||||||
|
consolidated_tax_detail.update({
|
||||||
|
item_code: [consolidated_tax_data[0], consolidated_tax_data[1] + tax_data[1]]
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
consolidated_tax_detail.update({
|
||||||
|
item_code: [tax_data[0], tax_data[1]]
|
||||||
|
})
|
||||||
|
|
||||||
|
consolidate_tax_row.item_wise_tax_detail = json.dumps(consolidated_tax_detail, separators=(',', ':'))
|
||||||
|
|
||||||
def get_all_unconsolidated_invoices():
|
def get_all_unconsolidated_invoices():
|
||||||
filters = {
|
filters = {
|
||||||
'consolidated_invoice': [ 'in', [ '', None ]],
|
'consolidated_invoice': [ 'in', [ '', None ]],
|
||||||
@ -214,7 +241,7 @@ def consolidate_pos_invoices(pos_invoices=[], closing_entry={}):
|
|||||||
|
|
||||||
if len(invoices) >= 5 and closing_entry:
|
if len(invoices) >= 5 and closing_entry:
|
||||||
closing_entry.set_status(update=True, status='Queued')
|
closing_entry.set_status(update=True, status='Queued')
|
||||||
enqueue_job(create_merge_logs, invoice_by_customer, closing_entry)
|
enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry)
|
||||||
else:
|
else:
|
||||||
create_merge_logs(invoice_by_customer, closing_entry)
|
create_merge_logs(invoice_by_customer, closing_entry)
|
||||||
|
|
||||||
@ -227,21 +254,21 @@ def unconsolidate_pos_invoices(closing_entry):
|
|||||||
|
|
||||||
if len(merge_logs) >= 5:
|
if len(merge_logs) >= 5:
|
||||||
closing_entry.set_status(update=True, status='Queued')
|
closing_entry.set_status(update=True, status='Queued')
|
||||||
enqueue_job(cancel_merge_logs, merge_logs, closing_entry)
|
enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry)
|
||||||
else:
|
else:
|
||||||
cancel_merge_logs(merge_logs, closing_entry)
|
cancel_merge_logs(merge_logs, closing_entry)
|
||||||
|
|
||||||
def create_merge_logs(invoice_by_customer, closing_entry={}):
|
def create_merge_logs(invoice_by_customer, closing_entry={}):
|
||||||
for customer, invoices in iteritems(invoice_by_customer):
|
for customer, invoices in iteritems(invoice_by_customer):
|
||||||
merge_log = frappe.new_doc('POS Invoice Merge Log')
|
merge_log = frappe.new_doc('POS Invoice Merge Log')
|
||||||
merge_log.posting_date = getdate(nowdate())
|
merge_log.posting_date = getdate(closing_entry.get('posting_date'))
|
||||||
merge_log.customer = customer
|
merge_log.customer = customer
|
||||||
merge_log.pos_closing_entry = closing_entry.get('name', None)
|
merge_log.pos_closing_entry = closing_entry.get('name', None)
|
||||||
|
|
||||||
merge_log.set('pos_invoices', invoices)
|
merge_log.set('pos_invoices', invoices)
|
||||||
merge_log.save(ignore_permissions=True)
|
merge_log.save(ignore_permissions=True)
|
||||||
merge_log.submit()
|
merge_log.submit()
|
||||||
|
|
||||||
if closing_entry:
|
if closing_entry:
|
||||||
closing_entry.set_status(update=True, status='Submitted')
|
closing_entry.set_status(update=True, status='Submitted')
|
||||||
closing_entry.update_opening_entry()
|
closing_entry.update_opening_entry()
|
||||||
@ -256,7 +283,7 @@ def cancel_merge_logs(merge_logs, closing_entry={}):
|
|||||||
closing_entry.set_status(update=True, status='Cancelled')
|
closing_entry.set_status(update=True, status='Cancelled')
|
||||||
closing_entry.update_opening_entry(for_cancel=True)
|
closing_entry.update_opening_entry(for_cancel=True)
|
||||||
|
|
||||||
def enqueue_job(job, invoice_by_customer, closing_entry):
|
def enqueue_job(job, merge_logs=None, invoice_by_customer=None, closing_entry=None):
|
||||||
check_scheduler_status()
|
check_scheduler_status()
|
||||||
|
|
||||||
job_name = closing_entry.get("name")
|
job_name = closing_entry.get("name")
|
||||||
@ -269,6 +296,7 @@ def enqueue_job(job, invoice_by_customer, closing_entry):
|
|||||||
job_name=job_name,
|
job_name=job_name,
|
||||||
closing_entry=closing_entry,
|
closing_entry=closing_entry,
|
||||||
invoice_by_customer=invoice_by_customer,
|
invoice_by_customer=invoice_by_customer,
|
||||||
|
merge_logs=merge_logs,
|
||||||
now=frappe.conf.developer_mode or frappe.flags.in_test
|
now=frappe.conf.developer_mode or frappe.flags.in_test
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import unittest
|
import unittest
|
||||||
|
import json
|
||||||
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
|
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
|
||||||
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
|
||||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
|
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
|
||||||
@ -14,85 +15,136 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
|
|||||||
def test_consolidated_invoice_creation(self):
|
def test_consolidated_invoice_creation(self):
|
||||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
|
||||||
test_user, pos_profile = init_user_and_profile()
|
try:
|
||||||
|
test_user, pos_profile = init_user_and_profile()
|
||||||
|
|
||||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||||
pos_inv.append('payments', {
|
pos_inv.append('payments', {
|
||||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||||
})
|
})
|
||||||
pos_inv.submit()
|
pos_inv.submit()
|
||||||
|
|
||||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||||
pos_inv2.append('payments', {
|
pos_inv2.append('payments', {
|
||||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
|
||||||
})
|
})
|
||||||
pos_inv2.submit()
|
pos_inv2.submit()
|
||||||
|
|
||||||
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
||||||
pos_inv3.append('payments', {
|
pos_inv3.append('payments', {
|
||||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
|
||||||
})
|
})
|
||||||
pos_inv3.submit()
|
pos_inv3.submit()
|
||||||
|
|
||||||
consolidate_pos_invoices()
|
consolidate_pos_invoices()
|
||||||
|
|
||||||
pos_inv.load_from_db()
|
pos_inv.load_from_db()
|
||||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||||
|
|
||||||
pos_inv3.load_from_db()
|
pos_inv3.load_from_db()
|
||||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
||||||
|
|
||||||
self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
|
self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
|
||||||
frappe.set_user("Administrator")
|
|
||||||
frappe.db.sql("delete from `tabPOS Profile`")
|
|
||||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
|
||||||
|
|
||||||
def test_consolidated_credit_note_creation(self):
|
def test_consolidated_credit_note_creation(self):
|
||||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
|
||||||
test_user, pos_profile = init_user_and_profile()
|
try:
|
||||||
|
test_user, pos_profile = init_user_and_profile()
|
||||||
|
|
||||||
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
|
||||||
pos_inv.append('payments', {
|
pos_inv.append('payments', {
|
||||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
|
||||||
})
|
})
|
||||||
pos_inv.submit()
|
pos_inv.submit()
|
||||||
|
|
||||||
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
|
||||||
pos_inv2.append('payments', {
|
pos_inv2.append('payments', {
|
||||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
|
||||||
})
|
})
|
||||||
pos_inv2.submit()
|
pos_inv2.submit()
|
||||||
|
|
||||||
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
|
||||||
pos_inv3.append('payments', {
|
pos_inv3.append('payments', {
|
||||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
|
||||||
})
|
})
|
||||||
pos_inv3.submit()
|
pos_inv3.submit()
|
||||||
|
|
||||||
pos_inv_cn = make_sales_return(pos_inv.name)
|
pos_inv_cn = make_sales_return(pos_inv.name)
|
||||||
pos_inv_cn.set("payments", [])
|
pos_inv_cn.set("payments", [])
|
||||||
pos_inv_cn.append('payments', {
|
pos_inv_cn.append('payments', {
|
||||||
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300
|
||||||
})
|
})
|
||||||
pos_inv_cn.paid_amount = -300
|
pos_inv_cn.paid_amount = -300
|
||||||
pos_inv_cn.submit()
|
pos_inv_cn.submit()
|
||||||
|
|
||||||
consolidate_pos_invoices()
|
consolidate_pos_invoices()
|
||||||
|
|
||||||
pos_inv.load_from_db()
|
pos_inv.load_from_db()
|
||||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
|
||||||
|
|
||||||
pos_inv3.load_from_db()
|
pos_inv3.load_from_db()
|
||||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
|
||||||
|
|
||||||
pos_inv_cn.load_from_db()
|
pos_inv_cn.load_from_db()
|
||||||
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
|
self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
|
||||||
self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return"))
|
self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return"))
|
||||||
|
|
||||||
frappe.set_user("Administrator")
|
finally:
|
||||||
frappe.db.sql("delete from `tabPOS Profile`")
|
frappe.set_user("Administrator")
|
||||||
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
|
||||||
|
def test_consolidated_invoice_item_taxes(self):
|
||||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
|
||||||
|
try:
|
||||||
|
inv = create_pos_invoice(qty=1, rate=100, do_not_save=True)
|
||||||
|
|
||||||
|
inv.append("taxes", {
|
||||||
|
"account_head": "_Test Account VAT - _TC",
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "VAT",
|
||||||
|
"doctype": "Sales Taxes and Charges",
|
||||||
|
"rate": 9
|
||||||
|
})
|
||||||
|
inv.insert()
|
||||||
|
inv.submit()
|
||||||
|
|
||||||
|
inv2 = create_pos_invoice(qty=1, rate=100, do_not_save=True)
|
||||||
|
inv2.get('items')[0].item_code = '_Test Item 2'
|
||||||
|
inv2.append("taxes", {
|
||||||
|
"account_head": "_Test Account VAT - _TC",
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "VAT",
|
||||||
|
"doctype": "Sales Taxes and Charges",
|
||||||
|
"rate": 5
|
||||||
|
})
|
||||||
|
inv2.insert()
|
||||||
|
inv2.submit()
|
||||||
|
|
||||||
|
consolidate_pos_invoices()
|
||||||
|
inv.load_from_db()
|
||||||
|
|
||||||
|
consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
|
||||||
|
item_wise_tax_detail = json.loads(consolidated_invoice.get('taxes')[0].item_wise_tax_detail)
|
||||||
|
|
||||||
|
tax_rate, amount = item_wise_tax_detail.get('_Test Item')
|
||||||
|
self.assertEqual(tax_rate, 9)
|
||||||
|
self.assertEqual(amount, 9)
|
||||||
|
|
||||||
|
tax_rate2, amount2 = item_wise_tax_detail.get('_Test Item 2')
|
||||||
|
self.assertEqual(tax_rate2, 5)
|
||||||
|
self.assertEqual(amount2, 5)
|
||||||
|
finally:
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
|
||||||
|
@ -16,8 +16,11 @@ frappe.ui.form.on('POS Settings', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""].concat(fields);
|
frm.fields_dict.invoice_fields.grid.update_docfield_property(
|
||||||
|
'fieldname', 'options', [""].concat(fields)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -173,7 +173,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
|
|||||||
if parenttype in ["Customer Group", "Item Group", "Territory"]:
|
if parenttype in ["Customer Group", "Item Group", "Territory"]:
|
||||||
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
|
parent_field = "parent_{0}".format(frappe.scrub(parenttype))
|
||||||
root_name = frappe.db.get_list(parenttype,
|
root_name = frappe.db.get_list(parenttype,
|
||||||
{"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1)
|
{"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1, ignore_permissions=True)
|
||||||
|
|
||||||
if root_name and root_name[0][0]:
|
if root_name and root_name[0][0]:
|
||||||
parent_groups.append(root_name[0][0])
|
parent_groups.append(root_name[0][0])
|
||||||
@ -471,7 +471,7 @@ def apply_pricing_rule_on_transaction(doc):
|
|||||||
|
|
||||||
if not d.get(pr_field): continue
|
if not d.get(pr_field): continue
|
||||||
|
|
||||||
if d.validate_applied_rule and doc.get(field) < d.get(pr_field):
|
if d.validate_applied_rule and doc.get(field) is not None and doc.get(field) < d.get(pr_field):
|
||||||
frappe.msgprint(_("User has not applied rule on the invoice {0}")
|
frappe.msgprint(_("User has not applied rule on the invoice {0}")
|
||||||
.format(doc.name))
|
.format(doc.name))
|
||||||
else:
|
else:
|
||||||
|
@ -496,15 +496,6 @@ cur_frm.fields_dict['items'].grid.get_field('project').get_query = function(doc,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
|
|
||||||
if(doc.select_print_heading){
|
|
||||||
// print heading
|
|
||||||
cur_frm.pformat.print_heading = doc.select_print_heading;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
cur_frm.pformat.print_heading = __("Purchase Invoice");
|
|
||||||
}
|
|
||||||
|
|
||||||
frappe.ui.form.on("Purchase Invoice", {
|
frappe.ui.form.on("Purchase Invoice", {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frm.custom_make_buttons = {
|
frm.custom_make_buttons = {
|
||||||
|
@ -127,7 +127,6 @@
|
|||||||
"write_off_cost_center",
|
"write_off_cost_center",
|
||||||
"advances_section",
|
"advances_section",
|
||||||
"allocate_advances_automatically",
|
"allocate_advances_automatically",
|
||||||
"adjust_advance_taxes",
|
|
||||||
"get_advances",
|
"get_advances",
|
||||||
"advances",
|
"advances",
|
||||||
"payment_schedule_section",
|
"payment_schedule_section",
|
||||||
@ -1326,13 +1325,6 @@
|
|||||||
"label": "Project",
|
"label": "Project",
|
||||||
"options": "Project"
|
"options": "Project"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"description": "Taxes paid while advance payment will be adjusted against this invoice",
|
|
||||||
"fieldname": "adjust_advance_taxes",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Adjust Advance Taxes"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"depends_on": "eval:doc.is_internal_supplier",
|
"depends_on": "eval:doc.is_internal_supplier",
|
||||||
"description": "Unrealized Profit / Loss account for intra-company transfers",
|
"description": "Unrealized Profit / Loss account for intra-company transfers",
|
||||||
@ -1378,7 +1370,7 @@
|
|||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-09 21:15:30.422084",
|
"modified": "2021-03-30 22:45:58.334107",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
@ -898,7 +898,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
acc_settings.submit_journal_entries = 1
|
acc_settings.submit_journal_entries = 1
|
||||||
acc_settings.save()
|
acc_settings.save()
|
||||||
|
|
||||||
item = create_item("_Test Item for Deferred Accounting")
|
item = create_item("_Test Item for Deferred Accounting", is_purchase_item=True)
|
||||||
item.enable_deferred_expense = 1
|
item.enable_deferred_expense = 1
|
||||||
item.deferred_expense_account = deferred_account
|
item.deferred_expense_account = deferred_account
|
||||||
item.save()
|
item.save()
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
// print heading
|
|
||||||
cur_frm.pformat.print_heading = 'Invoice';
|
|
||||||
|
|
||||||
{% include 'erpnext/selling/sales_common.js' %};
|
{% include 'erpnext/selling/sales_common.js' %};
|
||||||
frappe.provide("erpnext.accounts");
|
frappe.provide("erpnext.accounts");
|
||||||
|
|
||||||
@ -916,7 +913,7 @@ frappe.ui.form.on('Sales Invoice Timesheet', {
|
|||||||
},
|
},
|
||||||
callback: function(r, rt) {
|
callback: function(r, rt) {
|
||||||
if(r.message){
|
if(r.message){
|
||||||
data = r.message;
|
let data = r.message;
|
||||||
frappe.model.set_value(cdt, cdn, "billing_hours", data.billing_hours);
|
frappe.model.set_value(cdt, cdn, "billing_hours", data.billing_hours);
|
||||||
frappe.model.set_value(cdt, cdn, "billing_amount", data.billing_amount);
|
frappe.model.set_value(cdt, cdn, "billing_amount", data.billing_amount);
|
||||||
frappe.model.set_value(cdt, cdn, "timesheet_detail", data.timesheet_detail);
|
frappe.model.set_value(cdt, cdn, "timesheet_detail", data.timesheet_detail);
|
||||||
|
@ -77,7 +77,7 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
if not self.is_pos:
|
if not self.is_pos:
|
||||||
self.so_dn_required()
|
self.so_dn_required()
|
||||||
|
|
||||||
self.set_tax_withholding()
|
self.set_tax_withholding()
|
||||||
|
|
||||||
self.validate_proj_cust()
|
self.validate_proj_cust()
|
||||||
@ -394,6 +394,7 @@ class SalesInvoice(SellingController):
|
|||||||
if validate_against_credit_limit:
|
if validate_against_credit_limit:
|
||||||
check_credit_limit(self.customer, self.company, bypass_credit_limit_check_at_sales_order)
|
check_credit_limit(self.customer, self.company, bypass_credit_limit_check_at_sales_order)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def set_missing_values(self, for_validate=False):
|
def set_missing_values(self, for_validate=False):
|
||||||
pos = self.set_pos_fields(for_validate)
|
pos = self.set_pos_fields(for_validate)
|
||||||
|
|
||||||
@ -733,6 +734,7 @@ class SalesInvoice(SellingController):
|
|||||||
else:
|
else:
|
||||||
self.calculate_billing_amount_for_timesheet()
|
self.calculate_billing_amount_for_timesheet()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def add_timesheet_data(self):
|
def add_timesheet_data(self):
|
||||||
self.set('timesheets', [])
|
self.set('timesheets', [])
|
||||||
if self.project:
|
if self.project:
|
||||||
@ -1290,6 +1292,7 @@ class SalesInvoice(SellingController):
|
|||||||
break
|
break
|
||||||
|
|
||||||
# Healthcare
|
# Healthcare
|
||||||
|
@frappe.whitelist()
|
||||||
def set_healthcare_services(self, checked_values):
|
def set_healthcare_services(self, checked_values):
|
||||||
self.set("items", [])
|
self.set("items", [])
|
||||||
from erpnext.stock.get_item_details import get_item_details
|
from erpnext.stock.get_item_details import get_item_details
|
||||||
|
@ -1802,6 +1802,15 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
si.selling_price_list = "_Test Price List Rest of the World"
|
si.selling_price_list = "_Test Price List Rest of the World"
|
||||||
si.update_stock = 1
|
si.update_stock = 1
|
||||||
si.items[0].target_warehouse = 'Work In Progress - TCP1'
|
si.items[0].target_warehouse = 'Work In Progress - TCP1'
|
||||||
|
|
||||||
|
# Add stock to stores for succesful stock transfer
|
||||||
|
make_stock_entry(
|
||||||
|
target="Stores - TCP1",
|
||||||
|
company = "_Test Company with perpetual inventory",
|
||||||
|
qty=1,
|
||||||
|
basic_rate=100
|
||||||
|
)
|
||||||
|
|
||||||
add_taxes(si)
|
add_taxes(si)
|
||||||
si.save()
|
si.save()
|
||||||
|
|
||||||
@ -1870,7 +1879,17 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
def test_einvoice_submission_without_irn(self):
|
def test_einvoice_submission_without_irn(self):
|
||||||
# init
|
# init
|
||||||
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1)
|
einvoice_settings = frappe.get_doc('E Invoice Settings')
|
||||||
|
einvoice_settings.enable = 1
|
||||||
|
einvoice_settings.applicable_from = nowdate()
|
||||||
|
einvoice_settings.append('credentials', {
|
||||||
|
'company': '_Test Company',
|
||||||
|
'gstin': '27AAECE4835E1ZR',
|
||||||
|
'username': 'test',
|
||||||
|
'password': 'test'
|
||||||
|
})
|
||||||
|
einvoice_settings.save()
|
||||||
|
|
||||||
country = frappe.flags.country
|
country = frappe.flags.country
|
||||||
frappe.flags.country = 'India'
|
frappe.flags.country = 'India'
|
||||||
|
|
||||||
@ -1881,7 +1900,8 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
si.submit()
|
si.submit()
|
||||||
|
|
||||||
# reset
|
# reset
|
||||||
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0)
|
einvoice_settings = frappe.get_doc('E Invoice Settings')
|
||||||
|
einvoice_settings.enable = 0
|
||||||
frappe.flags.country = country
|
frappe.flags.country = country
|
||||||
|
|
||||||
def test_einvoice_json(self):
|
def test_einvoice_json(self):
|
||||||
@ -2272,4 +2292,4 @@ def add_taxes(doc):
|
|||||||
"cost_center": "Main - TCP1",
|
"cost_center": "Main - TCP1",
|
||||||
"description": "Excise Duty",
|
"description": "Excise Duty",
|
||||||
"rate": 12
|
"rate": 12
|
||||||
})
|
})
|
||||||
|
@ -46,5 +46,5 @@ def validate_disabled(doc):
|
|||||||
frappe.throw(_("Disabled template must not be default template"))
|
frappe.throw(_("Disabled template must not be default template"))
|
||||||
|
|
||||||
def validate_for_tax_category(doc):
|
def validate_for_tax_category(doc):
|
||||||
if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0}):
|
if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0, "name": ["!=", doc.name]}):
|
||||||
frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category)))
|
frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category)))
|
||||||
|
@ -14,10 +14,15 @@ test_records = frappe.get_test_records('Tax Rule')
|
|||||||
from six import iteritems
|
from six import iteritems
|
||||||
|
|
||||||
class TestTaxRule(unittest.TestCase):
|
class TestTaxRule(unittest.TestCase):
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
frappe.db.set_value("Shopping Cart Settings", None, "enabled", 0)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
frappe.db.sql("delete from `tabTax Rule`")
|
frappe.db.sql("delete from `tabTax Rule`")
|
||||||
|
|
||||||
def tearDown(self):
|
def setUp(self):
|
||||||
frappe.db.sql("delete from `tabTax Rule`")
|
frappe.db.sql("delete from `tabTax Rule`")
|
||||||
|
|
||||||
def test_conflict(self):
|
def test_conflict(self):
|
||||||
|
@ -177,7 +177,7 @@ def cancel_invoices():
|
|||||||
|
|
||||||
for d in purchase_invoices:
|
for d in purchase_invoices:
|
||||||
frappe.get_doc('Purchase Invoice', d).cancel()
|
frappe.get_doc('Purchase Invoice', d).cancel()
|
||||||
|
|
||||||
for d in sales_invoices:
|
for d in sales_invoices:
|
||||||
frappe.get_doc('Sales Invoice', d).cancel()
|
frappe.get_doc('Sales Invoice', d).cancel()
|
||||||
|
|
||||||
@ -229,7 +229,8 @@ def create_sales_invoice(**args):
|
|||||||
'qty': args.qty or 1,
|
'qty': args.qty or 1,
|
||||||
'rate': args.rate or 10000,
|
'rate': args.rate or 10000,
|
||||||
'cost_center': 'Main - _TC',
|
'cost_center': 'Main - _TC',
|
||||||
'expense_account': 'Cost of Goods Sold - _TC'
|
'expense_account': 'Cost of Goods Sold - _TC',
|
||||||
|
'warehouse': args.warehouse or '_Test Warehouse - _TC'
|
||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -353,4 +354,4 @@ def create_tax_with_holding_category():
|
|||||||
'company': '_Test Company',
|
'company': '_Test Company',
|
||||||
'account': 'TDS - _TC'
|
'account': 'TDS - _TC'
|
||||||
}]
|
}]
|
||||||
}).insert()
|
}).insert()
|
||||||
|
@ -406,9 +406,10 @@ def check_if_advance_entry_modified(args):
|
|||||||
throw(_("""Payment Entry has been modified after you pulled it. Please pull it again."""))
|
throw(_("""Payment Entry has been modified after you pulled it. Please pull it again."""))
|
||||||
|
|
||||||
def validate_allocated_amount(args):
|
def validate_allocated_amount(args):
|
||||||
|
precision = args.get('precision') or frappe.db.get_single_value("System Settings", "currency_precision")
|
||||||
if args.get("allocated_amount") < 0:
|
if args.get("allocated_amount") < 0:
|
||||||
throw(_("Allocated amount cannot be negative"))
|
throw(_("Allocated amount cannot be negative"))
|
||||||
elif args.get("allocated_amount") > args.get("unadjusted_amount"):
|
elif flt(args.get("allocated_amount"), precision) > flt(args.get("unadjusted_amount"), precision):
|
||||||
throw(_("Allocated amount cannot be greater than unadjusted amount"))
|
throw(_("Allocated amount cannot be greater than unadjusted amount"))
|
||||||
|
|
||||||
def update_reference_in_journal_entry(d, jv_obj):
|
def update_reference_in_journal_entry(d, jv_obj):
|
||||||
|
@ -71,6 +71,7 @@ class CropCycle(Document):
|
|||||||
"exp_end_date": add_days(start_date, crop_task.get("end_day") - 1)
|
"exp_end_date": add_days(start_date, crop_task.get("end_day") - 1)
|
||||||
}).insert()
|
}).insert()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def reload_linked_analysis(self):
|
def reload_linked_analysis(self):
|
||||||
linked_doctypes = ['Soil Texture', 'Soil Analysis', 'Plant Analysis']
|
linked_doctypes = ['Soil Texture', 'Soil Analysis', 'Plant Analysis']
|
||||||
required_fields = ['location', 'name', 'collection_datetime']
|
required_fields = ['location', 'name', 'collection_datetime']
|
||||||
@ -87,6 +88,7 @@ class CropCycle(Document):
|
|||||||
frappe.publish_realtime("List of Linked Docs",
|
frappe.publish_realtime("List of Linked Docs",
|
||||||
output, user=frappe.session.user)
|
output, user=frappe.session.user)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def append_to_child(self, obj_to_append):
|
def append_to_child(self, obj_to_append):
|
||||||
for doctype in obj_to_append:
|
for doctype in obj_to_append:
|
||||||
for doc_name in set(obj_to_append[doctype]):
|
for doc_name in set(obj_to_append[doctype]):
|
||||||
|
@ -7,6 +7,7 @@ import frappe
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
class Fertilizer(Document):
|
class Fertilizer(Document):
|
||||||
|
@frappe.whitelist()
|
||||||
def load_contents(self):
|
def load_contents(self):
|
||||||
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Fertilizer'})
|
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Fertilizer'})
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
|
@ -8,6 +8,7 @@ from frappe.model.naming import make_autoname
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
class PlantAnalysis(Document):
|
class PlantAnalysis(Document):
|
||||||
|
@frappe.whitelist()
|
||||||
def load_contents(self):
|
def load_contents(self):
|
||||||
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Plant Analysis'})
|
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Plant Analysis'})
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
|
@ -7,6 +7,7 @@ import frappe
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
class SoilAnalysis(Document):
|
class SoilAnalysis(Document):
|
||||||
|
@frappe.whitelist()
|
||||||
def load_contents(self):
|
def load_contents(self):
|
||||||
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Soil Analysis'})
|
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Soil Analysis'})
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
|
@ -13,6 +13,7 @@ class SoilTexture(Document):
|
|||||||
soil_edit_order = [2, 1, 0]
|
soil_edit_order = [2, 1, 0]
|
||||||
soil_types = ['clay_composition', 'sand_composition', 'silt_composition']
|
soil_types = ['clay_composition', 'sand_composition', 'silt_composition']
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def load_contents(self):
|
def load_contents(self):
|
||||||
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Soil Texture'})
|
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Soil Texture'})
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
@ -26,6 +27,7 @@ class SoilTexture(Document):
|
|||||||
if sum(self.get(soil_type) for soil_type in self.soil_types) != 100:
|
if sum(self.get(soil_type) for soil_type in self.soil_types) != 100:
|
||||||
frappe.throw(_('Soil compositions do not add up to 100'))
|
frappe.throw(_('Soil compositions do not add up to 100'))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def update_soil_edit(self, soil_type):
|
def update_soil_edit(self, soil_type):
|
||||||
self.soil_edit_order[self.soil_types.index(soil_type)] = max(self.soil_edit_order)+1
|
self.soil_edit_order[self.soil_types.index(soil_type)] = max(self.soil_edit_order)+1
|
||||||
self.soil_type = self.get_soil_type()
|
self.soil_type = self.get_soil_type()
|
||||||
@ -35,8 +37,8 @@ class SoilTexture(Document):
|
|||||||
if sum(self.soil_edit_order) < 5: return
|
if sum(self.soil_edit_order) < 5: return
|
||||||
last_edit_index = self.soil_edit_order.index(min(self.soil_edit_order))
|
last_edit_index = self.soil_edit_order.index(min(self.soil_edit_order))
|
||||||
|
|
||||||
# set composition of the last edited soil
|
# set composition of the last edited soil
|
||||||
self.set( self.soil_types[last_edit_index],
|
self.set(self.soil_types[last_edit_index],
|
||||||
100 - sum(cint(self.get(soil_type)) for soil_type in self.soil_types) + cint(self.get(self.soil_types[last_edit_index])))
|
100 - sum(cint(self.get(soil_type)) for soil_type in self.soil_types) + cint(self.get(self.soil_types[last_edit_index])))
|
||||||
|
|
||||||
# calculate soil type
|
# calculate soil type
|
||||||
@ -67,4 +69,4 @@ class SoilTexture(Document):
|
|||||||
elif (c >= 40 and sa <= 45 and si < 40):
|
elif (c >= 40 and sa <= 45 and si < 40):
|
||||||
return 'Clay'
|
return 'Clay'
|
||||||
else:
|
else:
|
||||||
return 'Select'
|
return 'Select'
|
||||||
|
@ -9,11 +9,13 @@ from frappe.model.document import Document
|
|||||||
from frappe import _
|
from frappe import _
|
||||||
|
|
||||||
class WaterAnalysis(Document):
|
class WaterAnalysis(Document):
|
||||||
|
@frappe.whitelist()
|
||||||
def load_contents(self):
|
def load_contents(self):
|
||||||
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Water Analysis'})
|
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Water Analysis'})
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
self.append('water_analysis_criteria', {'title': str(doc.name)})
|
self.append('water_analysis_criteria', {'title': str(doc.name)})
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def update_lab_result_date(self):
|
def update_lab_result_date(self):
|
||||||
if not self.result_datetime:
|
if not self.result_datetime:
|
||||||
self.result_datetime = self.laboratory_testing_datetime
|
self.result_datetime = self.laboratory_testing_datetime
|
||||||
|
@ -7,6 +7,7 @@ import frappe
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
class Weather(Document):
|
class Weather(Document):
|
||||||
|
@frappe.whitelist()
|
||||||
def load_contents(self):
|
def load_contents(self):
|
||||||
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Weather'})
|
docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Weather'})
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
|
@ -553,6 +553,7 @@ class Asset(AccountsController):
|
|||||||
make_gl_entries(gl_entries)
|
make_gl_entries(gl_entries)
|
||||||
self.db_set('booked_fixed_asset', 1)
|
self.db_set('booked_fixed_asset', 1)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_depreciation_rate(self, args, on_validate=False):
|
def get_depreciation_rate(self, args, on_validate=False):
|
||||||
if isinstance(args, string_types):
|
if isinstance(args, string_types):
|
||||||
args = json.loads(args)
|
args = json.loads(args)
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
"po_required",
|
"po_required",
|
||||||
"pr_required",
|
"pr_required",
|
||||||
"maintain_same_rate",
|
"maintain_same_rate",
|
||||||
|
"maintain_same_rate_action",
|
||||||
|
"role_to_override_stop_action",
|
||||||
"allow_multiple_items",
|
"allow_multiple_items",
|
||||||
"subcontract",
|
"subcontract",
|
||||||
"backflush_raw_materials_of_subcontract_based_on",
|
"backflush_raw_materials_of_subcontract_based_on",
|
||||||
@ -89,6 +91,23 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_11",
|
"fieldname": "column_break_11",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Stop",
|
||||||
|
"depends_on": "maintain_same_rate",
|
||||||
|
"description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.",
|
||||||
|
"fieldname": "maintain_same_rate_action",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Action If Same Rate is Not Maintained",
|
||||||
|
"mandatory_depends_on": "maintain_same_rate",
|
||||||
|
"options": "Stop\nWarn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.maintain_same_rate_action == 'Stop'",
|
||||||
|
"fieldname": "role_to_override_stop_action",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Role Allowed to Override Stop Action",
|
||||||
|
"options": "Role"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-cog",
|
"icon": "fa fa-cog",
|
||||||
@ -96,7 +115,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-02 17:34:04.190677",
|
"modified": "2021-04-04 20:01:44.087066",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Buying Settings",
|
"name": "Buying Settings",
|
||||||
|
@ -133,6 +133,7 @@ class PurchaseOrder(BuyingController):
|
|||||||
d.material_request_item, "schedule_date")
|
d.material_request_item, "schedule_date")
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_last_purchase_rate(self):
|
def get_last_purchase_rate(self):
|
||||||
"""get last purchase rates for all items"""
|
"""get last purchase rates for all items"""
|
||||||
|
|
||||||
@ -367,7 +368,6 @@ def make_purchase_receipt(source_name, target_doc=None):
|
|||||||
"Purchase Order": {
|
"Purchase Order": {
|
||||||
"doctype": "Purchase Receipt",
|
"doctype": "Purchase Receipt",
|
||||||
"field_map": {
|
"field_map": {
|
||||||
"per_billed": "per_billed",
|
|
||||||
"supplier_warehouse":"supplier_warehouse"
|
"supplier_warehouse":"supplier_warehouse"
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
|
@ -778,7 +778,7 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
||||||
|
|
||||||
make_stock_entry(target="_Test Warehouse - _TC",
|
make_stock_entry(target="_Test Warehouse - _TC",
|
||||||
item_code="_Test Item Home Desktop 100", qty=10, basic_rate=100)
|
item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100)
|
||||||
make_stock_entry(target="_Test Warehouse - _TC",
|
make_stock_entry(target="_Test Warehouse - _TC",
|
||||||
item_code = "Test Extra Item 1", qty=100, basic_rate=100)
|
item_code = "Test Extra Item 1", qty=100, basic_rate=100)
|
||||||
make_stock_entry(target="_Test Warehouse - _TC",
|
make_stock_entry(target="_Test Warehouse - _TC",
|
||||||
|
@ -66,6 +66,7 @@ class RequestforQuotation(BuyingController):
|
|||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
frappe.db.set(self, 'status', 'Cancelled')
|
frappe.db.set(self, 'status', 'Cancelled')
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_supplier_email_preview(self, supplier):
|
def get_supplier_email_preview(self, supplier):
|
||||||
"""Returns formatted email preview as string."""
|
"""Returns formatted email preview as string."""
|
||||||
rfq_suppliers = list(filter(lambda row: row.supplier == supplier, self.suppliers))
|
rfq_suppliers = list(filter(lambda row: row.supplier == supplier, self.suppliers))
|
||||||
|
@ -9,9 +9,7 @@ import unittest
|
|||||||
class TestSupplierScorecard(unittest.TestCase):
|
class TestSupplierScorecard(unittest.TestCase):
|
||||||
|
|
||||||
def test_create_scorecard(self):
|
def test_create_scorecard(self):
|
||||||
delete_test_scorecards()
|
doc = make_supplier_scorecard().insert()
|
||||||
my_doc = make_supplier_scorecard()
|
|
||||||
doc = my_doc.insert()
|
|
||||||
self.assertEqual(doc.name, valid_scorecard[0].get("supplier"))
|
self.assertEqual(doc.name, valid_scorecard[0].get("supplier"))
|
||||||
|
|
||||||
def test_criteria_weight(self):
|
def test_criteria_weight(self):
|
||||||
@ -121,7 +119,8 @@ valid_scorecard = [
|
|||||||
{
|
{
|
||||||
"weight":100.0,
|
"weight":100.0,
|
||||||
"doctype":"Supplier Scorecard Scoring Criteria",
|
"doctype":"Supplier Scorecard Scoring Criteria",
|
||||||
"criteria_name":"Delivery"
|
"criteria_name":"Delivery",
|
||||||
|
"formula": "100"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"supplier":"_Test Supplier",
|
"supplier":"_Test Supplier",
|
||||||
|
129
erpnext/change_log/v13/v13_1_0.md
Normal file
129
erpnext/change_log/v13/v13_1_0.md
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
# Version 13.1.0 Release Notes
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Recursive pricing rule ([#24922](https://github.com/frappe/erpnext/pull/24922))
|
||||||
|
- Discount configuration on early payments ([#24586](https://github.com/frappe/erpnext/pull/24586))
|
||||||
|
- Bulk e-invoice generation ([#24969](https://github.com/frappe/erpnext/pull/24969))
|
||||||
|
- Employee Self Service ([#24408](https://github.com/frappe/erpnext/pull/24408))
|
||||||
|
- Share doc with employee approvers if they don't have access ([#25190](https://github.com/frappe/erpnext/pull/25190))
|
||||||
|
- Price margin in buying ([#24685](https://github.com/frappe/erpnext/pull/24685))
|
||||||
|
- Allow changing Work Stations in Work Order & Job Card ([#24897](https://github.com/frappe/erpnext/pull/24897))
|
||||||
|
- Add document type field for e-invoicing (Italy) ([#25256](https://github.com/frappe/erpnext/pull/25256))
|
||||||
|
- Add checkbox for disabling leave notification in HR Settings ([#24877](https://github.com/frappe/erpnext/pull/24877))
|
||||||
|
- Enhancements in Material Request Plan Item in Production Plan ([#25025](https://github.com/frappe/erpnext/pull/25025))
|
||||||
|
|
||||||
|
|
||||||
|
### Fixes and Enhancements
|
||||||
|
- Mode of payments disappear on loading draft pos invoice ([#24917](https://github.com/frappe/erpnext/pull/24917))
|
||||||
|
- Sales order not saving due type mismatch in promo scheme (#24748) ([#25222](https://github.com/frappe/erpnext/pull/25222))
|
||||||
|
- Zero amount completed delivery notes being shown in Sales Invoice get items ([#25317](https://github.com/frappe/erpnext/pull/25317))
|
||||||
|
- Incorrect status creating PR from PO after creating PI ([#25109](https://github.com/frappe/erpnext/pull/25109))
|
||||||
|
- Precision and formatted document for stock level in item dashboard. ([#24921](https://github.com/frappe/erpnext/pull/24921))
|
||||||
|
- Precision issues while allocating advance amount ([#25086](https://github.com/frappe/erpnext/pull/25086))
|
||||||
|
- Round off final tax amount instead of current tax amount ([#25188](https://github.com/frappe/erpnext/pull/25188))
|
||||||
|
- Redesign fixes ([#24896](https://github.com/frappe/erpnext/pull/24896))
|
||||||
|
- TDS check getting checked after reload ([#24972](https://github.com/frappe/erpnext/pull/24972))
|
||||||
|
- Github Action not failing when tests fail ([#24867](https://github.com/frappe/erpnext/pull/24867))
|
||||||
|
- Calculate 80g certificate amount on validate for memberships ([#24925](https://github.com/frappe/erpnext/pull/24925))
|
||||||
|
- Purchase from registered composition dealer ([#25040](https://github.com/frappe/erpnext/pull/25040))
|
||||||
|
- Reduce number of queries for checking if future SL entry exists ([#24881](https://github.com/frappe/erpnext/pull/24881))
|
||||||
|
- Remove unwanted parameter in calculate_rate_and_amount ([#24883](https://github.com/frappe/erpnext/pull/24883))
|
||||||
|
- Membership renewal validation ([#24963](https://github.com/frappe/erpnext/pull/24963))
|
||||||
|
- Not able to save material request ([#25112](https://github.com/frappe/erpnext/pull/25112))
|
||||||
|
- POS print receipt ([#25330](https://github.com/frappe/erpnext/pull/25330))
|
||||||
|
- Supplier was not able to Submit RFQ due to insufficient permission ([#24622](https://github.com/frappe/erpnext/pull/24622))
|
||||||
|
- Unequal debit and credit issue on RCM Invoice ([#24836](https://github.com/frappe/erpnext/pull/24836))
|
||||||
|
- Picked Qty conversion from Stock Qty to Qty while creating DN from Pick List ([#25105](https://github.com/frappe/erpnext/pull/25105))
|
||||||
|
- Salary Structure object has no attribute set_totals ([#25113](https://github.com/frappe/erpnext/pull/25113))
|
||||||
|
- Incorrect Nil Exempt and Non GST amount in GSTR3B report ([#24916](https://github.com/frappe/erpnext/pull/24916))
|
||||||
|
- Add method for regional round off account back ([#24893](https://github.com/frappe/erpnext/pull/24893))
|
||||||
|
- Employee profile pic upload access for erpnext user ([#25022](https://github.com/frappe/erpnext/pull/25022))
|
||||||
|
- Make filters for payroll entry ([#25386](https://github.com/frappe/erpnext/pull/25386))
|
||||||
|
- Fix dynamically changing grid properties ([#25310](https://github.com/frappe/erpnext/pull/25310))
|
||||||
|
- Consider paid repayment entries in subsequent loan repayments ([#25271](https://github.com/frappe/erpnext/pull/25271))
|
||||||
|
- Allow duplicate additional salaries ([#24842](https://github.com/frappe/erpnext/pull/24842))
|
||||||
|
- Object referencing the same address issue ([#25159](https://github.com/frappe/erpnext/pull/25159))
|
||||||
|
- Validating party currency with doc currency ([#24318](https://github.com/frappe/erpnext/pull/24318))
|
||||||
|
- Non Profit fixes ([#25060](https://github.com/frappe/erpnext/pull/25060))
|
||||||
|
- Additional Salary component amount not getting set ([#25356](https://github.com/frappe/erpnext/pull/25356))
|
||||||
|
- Allow user to update exchange rate in Multi-currency LCV ([#24912](https://github.com/frappe/erpnext/pull/24912))
|
||||||
|
- Allow creating stock entry based on work order for customer provided items ([#24885](https://github.com/frappe/erpnext/pull/24885))
|
||||||
|
- Create property setters for shorter naming series on setup ([#25128](https://github.com/frappe/erpnext/pull/25128))
|
||||||
|
- Add GST category field in Delivery Note ([#25053](https://github.com/frappe/erpnext/pull/25053))
|
||||||
|
- Ignore Permission for Leave Ledger Entry ([#25172](https://github.com/frappe/erpnext/pull/25172))
|
||||||
|
- Pending shortfall update on processing loan security shortfall ([#24971](https://github.com/frappe/erpnext/pull/24971))
|
||||||
|
- Added flag for dont_fetch_price_list_rate in transaction ([#25041](https://github.com/frappe/erpnext/pull/25041))
|
||||||
|
- Exchange Rate not getting set in Salary Slip ([#25004](https://github.com/frappe/erpnext/pull/25004))
|
||||||
|
- Repost not completed backdated transactions ([#24980](https://github.com/frappe/erpnext/pull/24980))
|
||||||
|
- frappe.whitelist for doc methods ([#25230](https://github.com/frappe/erpnext/pull/25230))
|
||||||
|
- Opportunity-quotation mapping order status ([#25001](https://github.com/frappe/erpnext/pull/25001))
|
||||||
|
- GST on freight charge in e-invoicing ([#25000](https://github.com/frappe/erpnext/pull/25000))
|
||||||
|
- Role to override maintain same rate check in transactions ([#25193](https://github.com/frappe/erpnext/pull/25193))
|
||||||
|
- Added blank option for status in report related to issue ([#25082](https://github.com/frappe/erpnext/pull/25082))
|
||||||
|
- Cashier query in POS Opening/Closing Entry ([#25399](https://github.com/frappe/erpnext/pull/25399))
|
||||||
|
- Lead Source's module ([#24583](https://github.com/frappe/erpnext/pull/24583))
|
||||||
|
- Hide alt tag if item is not shown in website ([#24937](https://github.com/frappe/erpnext/pull/24937))
|
||||||
|
- Ignore Customer Group Perm on All Products page ([#25397](https://github.com/frappe/erpnext/pull/25397))
|
||||||
|
- Give first preference to loan security on repayment ([#25212](https://github.com/frappe/erpnext/pull/25212))
|
||||||
|
- Add shortfall ratio in Loan Security Shortfall ([#25138](https://github.com/frappe/erpnext/pull/25138))
|
||||||
|
- Condition for SLA status banner ([#25261](https://github.com/frappe/erpnext/pull/25261))
|
||||||
|
- Component amount calculation based on formula with abbr not working ([#25117](https://github.com/frappe/erpnext/pull/25117))
|
||||||
|
- Remove gst name validation for purchase Invoice ([#25235](https://github.com/frappe/erpnext/pull/25235))
|
||||||
|
- Do not fetch stopped MR in production plan ([#25063](https://github.com/frappe/erpnext/pull/25063))
|
||||||
|
- Backport missing commits to develop branch ([#25305](https://github.com/frappe/erpnext/pull/25305))
|
||||||
|
- UOM length unit in global setup list is empty ([#24855](https://github.com/frappe/erpnext/pull/24855))
|
||||||
|
- Round total quantity in job card ([#25240](https://github.com/frappe/erpnext/pull/25240))
|
||||||
|
- Default total_estimated_cost to zero ([#24939](https://github.com/frappe/erpnext/pull/24939))
|
||||||
|
- Serial no refresh issue ([#25127](https://github.com/frappe/erpnext/pull/25127))
|
||||||
|
- Correct calculation for discount amount when margin is set ([#25179](https://github.com/frappe/erpnext/pull/25179))
|
||||||
|
- Get correct holiday list when calculating dates; test fixes ([#24901](https://github.com/frappe/erpnext/pull/24901))
|
||||||
|
- POS print receipt ([#24924](https://github.com/frappe/erpnext/pull/24924))
|
||||||
|
- Condition for setting agreement status ([#25255](https://github.com/frappe/erpnext/pull/25255))
|
||||||
|
- Loan Repayment entry cancellation on salary slip cancel ([#24879](https://github.com/frappe/erpnext/pull/24879))
|
||||||
|
- Add company validation for e-invoicing ([#25349](https://github.com/frappe/erpnext/pull/25349))
|
||||||
|
- Query values incorrectly escaped while back updating Quality Inspection ([#25118](https://github.com/frappe/erpnext/pull/25118))
|
||||||
|
- Update Bin via Update Item on Purchase/Sales Order ([#23509](https://github.com/frappe/erpnext/pull/23509))
|
||||||
|
- Declare data before assigning ([#25287](https://github.com/frappe/erpnext/pull/25287))
|
||||||
|
- Do not set standard link in Sales Invoice as custom ([#25096](https://github.com/frappe/erpnext/pull/25096))
|
||||||
|
- Hide serial and batch selector in Stock Entry ([#25107](https://github.com/frappe/erpnext/pull/25107))
|
||||||
|
- Taxable value including Freight and Forwarding charges in GSTR-1 Report ([#25290](https://github.com/frappe/erpnext/pull/25290))
|
||||||
|
- Remove nonexistent method from pick list ([#25279](https://github.com/frappe/erpnext/pull/25279))
|
||||||
|
- Allow zero valuation in stock reconciliation ([#24888](https://github.com/frappe/erpnext/pull/24888))
|
||||||
|
- Place of supply of e-invoicing ([#25148](https://github.com/frappe/erpnext/pull/25148))
|
||||||
|
- Delivery note print error ([#25080](https://github.com/frappe/erpnext/pull/25080))
|
||||||
|
- Fix Payment references from disappearing on adding Cost Center in Payment Entry ([#24831](https://github.com/frappe/erpnext/pull/24831))
|
||||||
|
- Company field in Warehouse ([#25196](https://github.com/frappe/erpnext/pull/25196))
|
||||||
|
- Available employee for selection ([#25378](https://github.com/frappe/erpnext/pull/25378))
|
||||||
|
- Cannot set qty to less than zero ([#25258](https://github.com/frappe/erpnext/pull/25258))
|
||||||
|
- Don't delete mode of payment account details while deleting comp… ([#25217](https://github.com/frappe/erpnext/pull/25217))
|
||||||
|
- Exclude current doc while validation. ([#24914](https://github.com/frappe/erpnext/pull/24914))
|
||||||
|
- POS Opening Entry with empty balance detail rows ([#24876](https://github.com/frappe/erpnext/pull/24876))
|
||||||
|
- Unable to submit stock entry ([#25033](https://github.com/frappe/erpnext/pull/25033))
|
||||||
|
- BOM cost test case ([#25242](https://github.com/frappe/erpnext/pull/25242))
|
||||||
|
- Filter for employees in salary slip ([#25361](https://github.com/frappe/erpnext/pull/25361))
|
||||||
|
- Added correct path in hooks ([#24862](https://github.com/frappe/erpnext/pull/24862))
|
||||||
|
- Patch regional fields for old companies ([#24988](https://github.com/frappe/erpnext/pull/24988))
|
||||||
|
- consolidated sales invoice posting date ([#25119](https://github.com/frappe/erpnext/pull/25119))
|
||||||
|
- Don't set "Company:company:default_currency" as default for currency link fields ([#25095](https://github.com/frappe/erpnext/pull/25095))
|
||||||
|
- Healthcare lab module rename fields ([#25276](https://github.com/frappe/erpnext/pull/25276))
|
||||||
|
- Error message compensatory leave request ([#25206](https://github.com/frappe/erpnext/pull/25206))
|
||||||
|
- Adding company link to e invoice settings patch condition ([#25301](https://github.com/frappe/erpnext/pull/25301))
|
||||||
|
- Membership and Donation API fixes ([#24900](https://github.com/frappe/erpnext/pull/24900))
|
||||||
|
- Set correct ack no. on irn generation ([#25251](https://github.com/frappe/erpnext/pull/25251))
|
||||||
|
- Report Issue Summary fix for zero issues ([#24934](https://github.com/frappe/erpnext/pull/24934))
|
||||||
|
- Validation msg for TransDocNo e-invoicing ([#25121](https://github.com/frappe/erpnext/pull/25121))
|
||||||
|
- Correct state code for 'Other Territory' ([#24993](https://github.com/frappe/erpnext/pull/24993))
|
||||||
|
- Commit individual SLE rename for large datasets (develop) ([#25084](https://github.com/frappe/erpnext/pull/25084))
|
||||||
|
- Remove shipping address GSTIN validation for e-invoice ([#25153](https://github.com/frappe/erpnext/pull/25153))
|
||||||
|
- Period list for exponential smoothing forecasting report ([#24982](https://github.com/frappe/erpnext/pull/24982))
|
||||||
|
- Customer creation from shopping cart ([#25136](https://github.com/frappe/erpnext/pull/25136))
|
||||||
|
- Simplified logic for additional salary ([#24824](https://github.com/frappe/erpnext/pull/24824))
|
||||||
|
- Item wise tax rate for consolidated POS invoice ([#25029](https://github.com/frappe/erpnext/pull/25029))
|
||||||
|
- Column width in Recruitment analytics report ([#25003](https://github.com/frappe/erpnext/pull/25003))
|
||||||
|
- Filter Bank Account drop-down list in Bank Reconciliation Tool ([#24873](https://github.com/frappe/erpnext/pull/24873))
|
||||||
|
- Payroll issues ([#24540](https://github.com/frappe/erpnext/pull/24540))
|
||||||
|
- PO not created against all selected suppliers (drop shipping) ([#24863](https://github.com/frappe/erpnext/pull/24863))
|
||||||
|
- Can't multiply sequence by non-int of type 'float' ([#25092](https://github.com/frappe/erpnext/pull/25092))
|
||||||
|
- Make Discharge Schedule Date as Datetime ([#24940](https://github.com/frappe/erpnext/pull/24940))
|
||||||
|
- Serial no trim issue ([#24949](https://github.com/frappe/erpnext/pull/24949))
|
@ -517,6 +517,7 @@ class AccountsController(TransactionBase):
|
|||||||
frappe.db.sql("""delete from `tab%s` where parentfield=%s and parent = %s
|
frappe.db.sql("""delete from `tab%s` where parentfield=%s and parent = %s
|
||||||
and allocated_amount = 0""" % (childtype, '%s', '%s'), (parentfield, self.name))
|
and allocated_amount = 0""" % (childtype, '%s', '%s'), (parentfield, self.name))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def apply_shipping_rule(self):
|
def apply_shipping_rule(self):
|
||||||
if self.shipping_rule:
|
if self.shipping_rule:
|
||||||
shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule)
|
shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule)
|
||||||
@ -537,6 +538,7 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def set_advances(self):
|
def set_advances(self):
|
||||||
"""Returns list of advances against Account, Party, Reference"""
|
"""Returns list of advances against Account, Party, Reference"""
|
||||||
|
|
||||||
@ -657,6 +659,7 @@ class AccountsController(TransactionBase):
|
|||||||
'dr_or_cr': dr_or_cr,
|
'dr_or_cr': dr_or_cr,
|
||||||
'unadjusted_amount': flt(d.advance_amount),
|
'unadjusted_amount': flt(d.advance_amount),
|
||||||
'allocated_amount': flt(d.allocated_amount),
|
'allocated_amount': flt(d.allocated_amount),
|
||||||
|
'precision': d.precision('advance_amount'),
|
||||||
'exchange_rate': (self.conversion_rate
|
'exchange_rate': (self.conversion_rate
|
||||||
if self.party_account_currency != self.company_currency else 1),
|
if self.party_account_currency != self.company_currency else 1),
|
||||||
'grand_total': (self.base_grand_total
|
'grand_total': (self.base_grand_total
|
||||||
@ -1444,7 +1447,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_new_child_item(item_row):
|
def get_new_child_item(item_row):
|
||||||
child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item"
|
child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item"
|
||||||
return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row)
|
return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row)
|
||||||
|
|
||||||
def validate_quantity(child_item, d):
|
def validate_quantity(child_item, d):
|
||||||
|
@ -325,7 +325,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
|
|||||||
and status not in ("Stopped", "Closed") %(fcond)s
|
and status not in ("Stopped", "Closed") %(fcond)s
|
||||||
and (
|
and (
|
||||||
(`tabDelivery Note`.is_return = 0 and `tabDelivery Note`.per_billed < 100)
|
(`tabDelivery Note`.is_return = 0 and `tabDelivery Note`.per_billed < 100)
|
||||||
or `tabDelivery Note`.grand_total = 0
|
or (`tabDelivery Note`.grand_total = 0 and `tabDelivery Note`.per_billed < 100)
|
||||||
or (
|
or (
|
||||||
`tabDelivery Note`.is_return = 1
|
`tabDelivery Note`.is_return = 1
|
||||||
and return_against in (select name from `tabDelivery Note` where per_billed < 100)
|
and return_against in (select name from `tabDelivery Note` where per_billed < 100)
|
||||||
|
@ -144,7 +144,7 @@ class SellingController(StockController):
|
|||||||
|
|
||||||
if sales_person.commission_rate:
|
if sales_person.commission_rate:
|
||||||
sales_person.incentives = flt(
|
sales_person.incentives = flt(
|
||||||
sales_person.allocated_amount * flt(sales_person.commission_rate) / 100.0,
|
sales_person.allocated_amount * flt(sales_person.commission_rate) / 100.0,
|
||||||
self.precision("incentives", sales_person))
|
self.precision("incentives", sales_person))
|
||||||
|
|
||||||
total += sales_person.allocated_percentage
|
total += sales_person.allocated_percentage
|
||||||
@ -504,4 +504,4 @@ def set_default_income_account_for_item(obj):
|
|||||||
for d in obj.get("items"):
|
for d in obj.get("items"):
|
||||||
if d.item_code:
|
if d.item_code:
|
||||||
if getattr(d, "income_account", None):
|
if getattr(d, "income_account", None):
|
||||||
set_item_default(d.item_code, obj.company, 'income_account', d.income_account)
|
set_item_default(d.item_code, obj.company, 'income_account', d.income_account)
|
||||||
|
@ -149,7 +149,9 @@ class calculate_taxes_and_totals(object):
|
|||||||
validate_taxes_and_charges(tax)
|
validate_taxes_and_charges(tax)
|
||||||
validate_inclusive_tax(tax, self.doc)
|
validate_inclusive_tax(tax, self.doc)
|
||||||
|
|
||||||
tax.item_wise_tax_detail = {}
|
if not self.doc.get('is_consolidated'):
|
||||||
|
tax.item_wise_tax_detail = {}
|
||||||
|
|
||||||
tax_fields = ["total", "tax_amount_after_discount_amount",
|
tax_fields = ["total", "tax_amount_after_discount_amount",
|
||||||
"tax_amount_for_current_item", "grand_total_for_current_item",
|
"tax_amount_for_current_item", "grand_total_for_current_item",
|
||||||
"tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
|
"tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
|
||||||
@ -289,10 +291,13 @@ class calculate_taxes_and_totals(object):
|
|||||||
# set precision in the last item iteration
|
# set precision in the last item iteration
|
||||||
if n == len(self.doc.get("items")) - 1:
|
if n == len(self.doc.get("items")) - 1:
|
||||||
self.round_off_totals(tax)
|
self.round_off_totals(tax)
|
||||||
|
self._set_in_company_currency(tax,
|
||||||
|
["tax_amount", "tax_amount_after_discount_amount"])
|
||||||
|
|
||||||
|
self.round_off_base_values(tax)
|
||||||
self.set_cumulative_total(i, tax)
|
self.set_cumulative_total(i, tax)
|
||||||
|
|
||||||
self._set_in_company_currency(tax,
|
self._set_in_company_currency(tax, ["total"])
|
||||||
["total", "tax_amount", "tax_amount_after_discount_amount"])
|
|
||||||
|
|
||||||
# adjust Discount Amount loss in last tax iteration
|
# adjust Discount Amount loss in last tax iteration
|
||||||
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
|
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
|
||||||
@ -339,18 +344,11 @@ class calculate_taxes_and_totals(object):
|
|||||||
elif tax.charge_type == "On Item Quantity":
|
elif tax.charge_type == "On Item Quantity":
|
||||||
current_tax_amount = tax_rate * item.qty
|
current_tax_amount = tax_rate * item.qty
|
||||||
|
|
||||||
current_tax_amount = self.get_final_current_tax_amount(tax, current_tax_amount)
|
if not self.doc.get("is_consolidated"):
|
||||||
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
|
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
|
||||||
|
|
||||||
return current_tax_amount
|
return current_tax_amount
|
||||||
|
|
||||||
def get_final_current_tax_amount(self, tax, current_tax_amount):
|
|
||||||
# Some countries need individual tax components to be rounded
|
|
||||||
# Handeled via regional doctypess
|
|
||||||
if tax.account_head in frappe.flags.round_off_applicable_accounts:
|
|
||||||
current_tax_amount = round(current_tax_amount, 0)
|
|
||||||
return current_tax_amount
|
|
||||||
|
|
||||||
def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
|
def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
|
||||||
# store tax breakup for each item
|
# store tax breakup for each item
|
||||||
key = item.item_code or item.item_name
|
key = item.item_code or item.item_name
|
||||||
@ -361,10 +359,20 @@ class calculate_taxes_and_totals(object):
|
|||||||
tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)]
|
tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)]
|
||||||
|
|
||||||
def round_off_totals(self, tax):
|
def round_off_totals(self, tax):
|
||||||
|
if tax.account_head in frappe.flags.round_off_applicable_accounts:
|
||||||
|
tax.tax_amount = round(tax.tax_amount, 0)
|
||||||
|
tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0)
|
||||||
|
|
||||||
tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
|
tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
|
||||||
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
|
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
|
||||||
tax.precision("tax_amount"))
|
tax.precision("tax_amount"))
|
||||||
|
|
||||||
|
def round_off_base_values(self, tax):
|
||||||
|
# Round off to nearest integer based on regional settings
|
||||||
|
if tax.account_head in frappe.flags.round_off_applicable_accounts:
|
||||||
|
tax.base_tax_amount = round(tax.base_tax_amount, 0)
|
||||||
|
tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
|
||||||
|
|
||||||
def manipulate_grand_total_for_inclusive_tax(self):
|
def manipulate_grand_total_for_inclusive_tax(self):
|
||||||
# if fully inclusive taxes and diff
|
# if fully inclusive taxes and diff
|
||||||
if self.doc.get("taxes") and any([cint(t.included_in_print_rate) for t in self.doc.get("taxes")]):
|
if self.doc.get("taxes") and any([cint(t.included_in_print_rate) for t in self.doc.get("taxes")]):
|
||||||
@ -442,8 +450,9 @@ class calculate_taxes_and_totals(object):
|
|||||||
self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
|
self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
|
||||||
|
|
||||||
def _cleanup(self):
|
def _cleanup(self):
|
||||||
for tax in self.doc.get("taxes"):
|
if not self.doc.get('is_consolidated'):
|
||||||
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
|
for tax in self.doc.get("taxes"):
|
||||||
|
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
|
||||||
|
|
||||||
def set_discount_amount(self):
|
def set_discount_amount(self):
|
||||||
if self.doc.additional_discount_percentage:
|
if self.doc.additional_discount_percentage:
|
||||||
@ -810,4 +819,4 @@ class init_landed_taxes_and_totals(object):
|
|||||||
def set_amounts_in_company_currency(self):
|
def set_amounts_in_company_currency(self):
|
||||||
for d in self.doc.get(self.tax_field):
|
for d in self.doc.get(self.tax_field):
|
||||||
d.amount = flt(d.amount, d.precision("amount"))
|
d.amount = flt(d.amount, d.precision("amount"))
|
||||||
d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))
|
d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))
|
||||||
|
8
erpnext/crm/doctype/lead_source/lead_source.js
Normal file
8
erpnext/crm/doctype/lead_source/lead_source.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Lead Source', {
|
||||||
|
// refresh: function(frm) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
});
|
62
erpnext/crm/doctype/lead_source/lead_source.json
Normal file
62
erpnext/crm/doctype/lead_source/lead_source.json
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_rename": 1,
|
||||||
|
"autoname": "field:source_name",
|
||||||
|
"creation": "2016-09-16 01:47:47.382372",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"source_name",
|
||||||
|
"details"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "source_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Source Name",
|
||||||
|
"reqd": 1,
|
||||||
|
"unique": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "details",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"label": "Details"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [],
|
||||||
|
"modified": "2021-02-08 12:51:48.971517",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "CRM",
|
||||||
|
"name": "Lead Source",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Sales Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Sales User",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC"
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
# import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
class LeadSource(Document):
|
class LeadSource(Document):
|
@ -1,12 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import frappe
|
# import frappe
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
# test_records = frappe.get_test_records('Lead Source')
|
|
||||||
|
|
||||||
class TestLeadSource(unittest.TestCase):
|
class TestLeadSource(unittest.TestCase):
|
||||||
pass
|
pass
|
@ -11,7 +11,8 @@ from frappe.utils.file_manager import get_file, get_file_path
|
|||||||
from six.moves.urllib.parse import urlencode
|
from six.moves.urllib.parse import urlencode
|
||||||
|
|
||||||
class LinkedInSettings(Document):
|
class LinkedInSettings(Document):
|
||||||
def get_authorization_url(self):
|
@frappe.whitelist()
|
||||||
|
def get_authorization_url(self):
|
||||||
params = urlencode({
|
params = urlencode({
|
||||||
"response_type":"code",
|
"response_type":"code",
|
||||||
"client_id": self.consumer_key,
|
"client_id": self.consumer_key,
|
||||||
@ -35,7 +36,7 @@ class LinkedInSettings(Document):
|
|||||||
headers = {
|
headers = {
|
||||||
"Content-Type": "application/x-www-form-urlencoded"
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.http_post(url=url, data=body, headers=headers)
|
response = self.http_post(url=url, data=body, headers=headers)
|
||||||
response = frappe.parse_json(response.content.decode())
|
response = frappe.parse_json(response.content.decode())
|
||||||
self.db_set("access_token", response["access_token"])
|
self.db_set("access_token", response["access_token"])
|
||||||
|
@ -85,6 +85,7 @@ class Opportunity(TransactionBase):
|
|||||||
self.opportunity_from = "Lead"
|
self.opportunity_from = "Lead"
|
||||||
self.party_name = lead_name
|
self.party_name = lead_name
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None):
|
def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None):
|
||||||
if not self.has_active_quotation():
|
if not self.has_active_quotation():
|
||||||
frappe.db.set(self, 'status', 'Lost')
|
frappe.db.set(self, 'status', 'Lost')
|
||||||
@ -248,7 +249,6 @@ def make_quotation(source_name, target_doc=None):
|
|||||||
"doctype": "Quotation",
|
"doctype": "Quotation",
|
||||||
"field_map": {
|
"field_map": {
|
||||||
"opportunity_from": "quotation_to",
|
"opportunity_from": "quotation_to",
|
||||||
"opportunity_type": "order_type",
|
|
||||||
"name": "enq_no",
|
"name": "enq_no",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -11,6 +11,7 @@ from frappe.utils import get_url_to_form, get_link_to_form
|
|||||||
from tweepy.error import TweepError
|
from tweepy.error import TweepError
|
||||||
|
|
||||||
class TwitterSettings(Document):
|
class TwitterSettings(Document):
|
||||||
|
@frappe.whitelist()
|
||||||
def get_authorize_url(self):
|
def get_authorize_url(self):
|
||||||
callback_url = "{0}/api/method/erpnext.crm.doctype.twitter_settings.twitter_settings.callback?".format(frappe.utils.get_url())
|
callback_url = "{0}/api/method/erpnext.crm.doctype.twitter_settings.twitter_settings.callback?".format(frappe.utils.get_url())
|
||||||
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"), callback_url)
|
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"), callback_url)
|
||||||
@ -21,12 +22,12 @@ class TwitterSettings(Document):
|
|||||||
frappe.msgprint(_("Error! Failed to get request token."))
|
frappe.msgprint(_("Error! Failed to get request token."))
|
||||||
frappe.throw(_('Invalid {0} or {1}').format(frappe.bold("Consumer Key"), frappe.bold("Consumer Secret Key")))
|
frappe.throw(_('Invalid {0} or {1}').format(frappe.bold("Consumer Key"), frappe.bold("Consumer Secret Key")))
|
||||||
|
|
||||||
|
|
||||||
def get_access_token(self, oauth_token, oauth_verifier):
|
def get_access_token(self, oauth_token, oauth_verifier):
|
||||||
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
|
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
|
||||||
auth.request_token = {
|
auth.request_token = {
|
||||||
'oauth_token' : oauth_token,
|
'oauth_token' : oauth_token,
|
||||||
'oauth_token_secret' : oauth_verifier
|
'oauth_token_secret' : oauth_verifier
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -50,10 +51,10 @@ class TwitterSettings(Document):
|
|||||||
frappe.throw(_('Invalid Consumer Key or Consumer Secret Key'))
|
frappe.throw(_('Invalid Consumer Key or Consumer Secret Key'))
|
||||||
|
|
||||||
def get_api(self, access_token, access_token_secret):
|
def get_api(self, access_token, access_token_secret):
|
||||||
# authentication of consumer key and secret
|
# authentication of consumer key and secret
|
||||||
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
|
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
|
||||||
# authentication of access token and secret
|
# authentication of access token and secret
|
||||||
auth.set_access_token(access_token, access_token_secret)
|
auth.set_access_token(access_token, access_token_secret)
|
||||||
|
|
||||||
return tweepy.API(auth)
|
return tweepy.API(auth)
|
||||||
|
|
||||||
@ -64,7 +65,7 @@ class TwitterSettings(Document):
|
|||||||
if media:
|
if media:
|
||||||
media_id = self.upload_image(media)
|
media_id = self.upload_image(media)
|
||||||
return self.send_tweet(text, media_id)
|
return self.send_tweet(text, media_id)
|
||||||
|
|
||||||
def upload_image(self, media):
|
def upload_image(self, media):
|
||||||
media = get_file_path(media)
|
media = get_file_path(media)
|
||||||
api = self.get_api(self.access_token, self.access_token_secret)
|
api = self.get_api(self.access_token, self.access_token_secret)
|
||||||
|
@ -13,6 +13,7 @@ from erpnext.education.utils import OverlapError
|
|||||||
|
|
||||||
class CourseSchedulingTool(Document):
|
class CourseSchedulingTool(Document):
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def schedule_course(self):
|
def schedule_course(self):
|
||||||
"""Creates course schedules as per specified parameters"""
|
"""Creates course schedules as per specified parameters"""
|
||||||
|
|
||||||
|
@ -52,6 +52,7 @@ class FeeSchedule(Document):
|
|||||||
self.grand_total = no_of_students*self.total_amount
|
self.grand_total = no_of_students*self.total_amount
|
||||||
self.grand_total_in_words = money_in_words(self.grand_total)
|
self.grand_total_in_words = money_in_words(self.grand_total)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def create_fees(self):
|
def create_fees(self):
|
||||||
self.db_set("fee_creation_status", "In Process")
|
self.db_set("fee_creation_status", "In Process")
|
||||||
frappe.publish_realtime("fee_schedule_progress",
|
frappe.publish_realtime("fee_schedule_progress",
|
||||||
|
@ -91,6 +91,8 @@ class ProgramEnrollment(Document):
|
|||||||
(fee, fee) for fee in fee_list]
|
(fee, fee) for fee in fee_list]
|
||||||
msgprint(_("Fee Records Created - {0}").format(comma_and(fee_list)))
|
msgprint(_("Fee Records Created - {0}").format(comma_and(fee_list)))
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_courses(self):
|
def get_courses(self):
|
||||||
return frappe.db.sql('''select course from `tabProgram Course` where parent = %s and required = 1''', (self.program), as_dict=1)
|
return frappe.db.sql('''select course from `tabProgram Course` where parent = %s and required = 1''', (self.program), as_dict=1)
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ class ProgramEnrollmentTool(Document):
|
|||||||
academic_term_reqd = cint(frappe.db.get_single_value('Education Settings', 'academic_term_reqd'))
|
academic_term_reqd = cint(frappe.db.get_single_value('Education Settings', 'academic_term_reqd'))
|
||||||
self.set_onload("academic_term_reqd", academic_term_reqd)
|
self.set_onload("academic_term_reqd", academic_term_reqd)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_students(self):
|
def get_students(self):
|
||||||
students = []
|
students = []
|
||||||
if not self.get_students_from:
|
if not self.get_students_from:
|
||||||
@ -49,6 +50,7 @@ class ProgramEnrollmentTool(Document):
|
|||||||
else:
|
else:
|
||||||
frappe.throw(_("No students Found"))
|
frappe.throw(_("No students Found"))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def enroll_students(self):
|
def enroll_students(self):
|
||||||
total = len(self.students)
|
total = len(self.students)
|
||||||
for i, stud in enumerate(self.students):
|
for i, stud in enumerate(self.students):
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"naming_series",
|
"naming_series",
|
||||||
"student",
|
"student",
|
||||||
"student_name",
|
"student_name",
|
||||||
|
"student_mobile_number",
|
||||||
"course_schedule",
|
"course_schedule",
|
||||||
"student_group",
|
"student_group",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
@ -93,11 +94,19 @@
|
|||||||
"options": "Student Attendance",
|
"options": "Student Attendance",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "student.student_mobile_number",
|
||||||
|
"fieldname": "student_mobile_number",
|
||||||
|
"fieldtype": "Read Only",
|
||||||
|
"label": "Student Mobile Number",
|
||||||
|
"options": "Phone"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-07-08 13:55:42.580181",
|
"modified": "2021-03-24 00:02:11.005895",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Education",
|
"module": "Education",
|
||||||
"name": "Student Attendance",
|
"name": "Student Attendance",
|
||||||
|
@ -9,6 +9,7 @@ from frappe.model.document import Document
|
|||||||
from erpnext.education.doctype.student_group.student_group import get_students
|
from erpnext.education.doctype.student_group.student_group import get_students
|
||||||
|
|
||||||
class StudentGroupCreationTool(Document):
|
class StudentGroupCreationTool(Document):
|
||||||
|
@frappe.whitelist()
|
||||||
def get_courses(self):
|
def get_courses(self):
|
||||||
group_list = []
|
group_list = []
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ class StudentGroupCreationTool(Document):
|
|||||||
|
|
||||||
return group_list
|
return group_list
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def create_student_groups(self):
|
def create_student_groups(self):
|
||||||
if not self.courses:
|
if not self.courses:
|
||||||
frappe.throw(_("""No Student Groups created."""))
|
frappe.throw(_("""No Student Groups created."""))
|
||||||
|
@ -59,9 +59,10 @@ class MpesaSettings(Document):
|
|||||||
request_amounts.append(amount)
|
request_amounts.append(amount)
|
||||||
else:
|
else:
|
||||||
request_amounts = [request_amount]
|
request_amounts = [request_amount]
|
||||||
|
|
||||||
return request_amounts
|
return request_amounts
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_account_balance_info(self):
|
def get_account_balance_info(self):
|
||||||
payload = dict(
|
payload = dict(
|
||||||
reference_doctype="Mpesa Settings",
|
reference_doctype="Mpesa Settings",
|
||||||
@ -198,7 +199,7 @@ def get_completed_integration_requests_info(reference_doctype, reference_docname
|
|||||||
completed_mpesa_receipt = fetch_param_value(item_response, "MpesaReceiptNumber", "Name")
|
completed_mpesa_receipt = fetch_param_value(item_response, "MpesaReceiptNumber", "Name")
|
||||||
completed_payments.append(completed_amount)
|
completed_payments.append(completed_amount)
|
||||||
mpesa_receipts.append(completed_mpesa_receipt)
|
mpesa_receipts.append(completed_mpesa_receipt)
|
||||||
|
|
||||||
return mpesa_receipts, completed_payments
|
return mpesa_receipts, completed_payments
|
||||||
|
|
||||||
def get_account_balance(request_payload):
|
def get_account_balance(request_payload):
|
||||||
|
@ -15,6 +15,7 @@ from frappe.utils import add_months, formatdate, getdate, today
|
|||||||
|
|
||||||
class PlaidSettings(Document):
|
class PlaidSettings(Document):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@frappe.whitelist()
|
||||||
def get_link_token():
|
def get_link_token():
|
||||||
plaid = PlaidConnector()
|
plaid = PlaidConnector()
|
||||||
return plaid.get_link_token()
|
return plaid.get_link_token()
|
||||||
|
@ -23,14 +23,9 @@ class TestPlaidSettings(unittest.TestCase):
|
|||||||
doc.cancel()
|
doc.cancel()
|
||||||
doc.delete()
|
doc.delete()
|
||||||
|
|
||||||
for ba in frappe.get_all("Bank Account"):
|
for doctype in ("Bank Account", "Bank Account Type", "Bank Account Subtype"):
|
||||||
frappe.get_doc("Bank Account", ba.name).delete()
|
for d in frappe.get_all(doctype):
|
||||||
|
frappe.delete_doc(doctype, d.name, force=True)
|
||||||
for at in frappe.get_all("Bank Account Type"):
|
|
||||||
frappe.get_doc("Bank Account Type", at.name).delete()
|
|
||||||
|
|
||||||
for ast in frappe.get_all("Bank Account Subtype"):
|
|
||||||
frappe.get_doc("Bank Account Subtype", ast.name).delete()
|
|
||||||
|
|
||||||
def test_plaid_disabled(self):
|
def test_plaid_disabled(self):
|
||||||
frappe.db.set_value("Plaid Settings", None, "enabled", 0)
|
frappe.db.set_value("Plaid Settings", None, "enabled", 0)
|
||||||
|
@ -54,6 +54,7 @@ class QuickBooksMigrator(Document):
|
|||||||
self.authorization_url = self.oauth.authorization_url(self.authorization_endpoint)[0]
|
self.authorization_url = self.oauth.authorization_url(self.authorization_endpoint)[0]
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def migrate(self):
|
def migrate(self):
|
||||||
frappe.enqueue_doc("QuickBooks Migrator", "QuickBooks Migrator", "_migrate", queue="long")
|
frappe.enqueue_doc("QuickBooks Migrator", "QuickBooks Migrator", "_migrate", queue="long")
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
import unittest, os, json
|
import unittest, os, json
|
||||||
from frappe.utils import cstr
|
from frappe.utils import cstr, cint
|
||||||
from erpnext.erpnext_integrations.connectors.shopify_connection import create_order
|
from erpnext.erpnext_integrations.connectors.shopify_connection import create_order
|
||||||
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import make_item
|
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import make_item
|
||||||
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer
|
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer
|
||||||
@ -13,9 +13,14 @@ from frappe.core.doctype.data_import.data_import import import_doc
|
|||||||
|
|
||||||
|
|
||||||
class ShopifySettings(unittest.TestCase):
|
class ShopifySettings(unittest.TestCase):
|
||||||
def setUp(self):
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
|
cls.allow_negative_stock = cint(frappe.db.get_value('Stock Settings', None, 'allow_negative_stock'))
|
||||||
|
if not cls.allow_negative_stock:
|
||||||
|
frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 1)
|
||||||
|
|
||||||
# use the fixture data
|
# use the fixture data
|
||||||
import_doc(path=frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json"))
|
import_doc(path=frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json"))
|
||||||
|
|
||||||
@ -24,9 +29,15 @@ class ShopifySettings(unittest.TestCase):
|
|||||||
frappe.reload_doctype("Delivery Note")
|
frappe.reload_doctype("Delivery Note")
|
||||||
frappe.reload_doctype("Sales Invoice")
|
frappe.reload_doctype("Sales Invoice")
|
||||||
|
|
||||||
self.setup_shopify()
|
cls.setup_shopify()
|
||||||
|
|
||||||
def setup_shopify(self):
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
if not cls.allow_negative_stock:
|
||||||
|
frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setup_shopify(cls):
|
||||||
shopify_settings = frappe.get_doc("Shopify Settings")
|
shopify_settings = frappe.get_doc("Shopify Settings")
|
||||||
shopify_settings.taxes = []
|
shopify_settings.taxes = []
|
||||||
|
|
||||||
@ -56,21 +67,20 @@ class ShopifySettings(unittest.TestCase):
|
|||||||
"delivery_note_series": "DN-"
|
"delivery_note_series": "DN-"
|
||||||
}).save(ignore_permissions=True)
|
}).save(ignore_permissions=True)
|
||||||
|
|
||||||
self.shopify_settings = shopify_settings
|
cls.shopify_settings = shopify_settings
|
||||||
|
|
||||||
def test_order(self):
|
def test_order(self):
|
||||||
### Create Customer ###
|
# Create Customer
|
||||||
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer:
|
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer:
|
||||||
shopify_customer = json.load(shopify_customer)
|
shopify_customer = json.load(shopify_customer)
|
||||||
create_customer(shopify_customer.get("customer"), self.shopify_settings)
|
create_customer(shopify_customer.get("customer"), self.shopify_settings)
|
||||||
|
|
||||||
### Create Item ###
|
# Create Item
|
||||||
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_item.json")) as shopify_item:
|
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_item.json")) as shopify_item:
|
||||||
shopify_item = json.load(shopify_item)
|
shopify_item = json.load(shopify_item)
|
||||||
make_item("_Test Warehouse - _TC", shopify_item.get("product"))
|
make_item("_Test Warehouse - _TC", shopify_item.get("product"))
|
||||||
|
|
||||||
|
# Create Order
|
||||||
### Create Order ###
|
|
||||||
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_order.json")) as shopify_order:
|
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_order.json")) as shopify_order:
|
||||||
shopify_order = json.load(shopify_order)
|
shopify_order = json.load(shopify_order)
|
||||||
|
|
||||||
@ -80,17 +90,17 @@ class ShopifySettings(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(cstr(shopify_order.get("order").get("id")), sales_order.shopify_order_id)
|
self.assertEqual(cstr(shopify_order.get("order").get("id")), sales_order.shopify_order_id)
|
||||||
|
|
||||||
#check for customer
|
# Check for customer
|
||||||
shopify_order_customer_id = cstr(shopify_order.get("order").get("customer").get("id"))
|
shopify_order_customer_id = cstr(shopify_order.get("order").get("customer").get("id"))
|
||||||
sales_order_customer_id = frappe.get_value("Customer", sales_order.customer, "shopify_customer_id")
|
sales_order_customer_id = frappe.get_value("Customer", sales_order.customer, "shopify_customer_id")
|
||||||
|
|
||||||
self.assertEqual(shopify_order_customer_id, sales_order_customer_id)
|
self.assertEqual(shopify_order_customer_id, sales_order_customer_id)
|
||||||
|
|
||||||
#check sales invoice
|
# Check sales invoice
|
||||||
sales_invoice = frappe.get_doc("Sales Invoice", {"shopify_order_id": sales_order.shopify_order_id})
|
sales_invoice = frappe.get_doc("Sales Invoice", {"shopify_order_id": sales_order.shopify_order_id})
|
||||||
self.assertEqual(sales_invoice.rounded_total, sales_order.rounded_total)
|
self.assertEqual(sales_invoice.rounded_total, sales_order.rounded_total)
|
||||||
|
|
||||||
#check delivery note
|
# Check delivery note
|
||||||
delivery_note_count = frappe.db.sql("""select count(*) from `tabDelivery Note`
|
delivery_note_count = frappe.db.sql("""select count(*) from `tabDelivery Note`
|
||||||
where shopify_order_id = %s""", sales_order.shopify_order_id)[0][0]
|
where shopify_order_id = %s""", sales_order.shopify_order_id)[0][0]
|
||||||
|
|
||||||
|
@ -594,18 +594,22 @@ class TallyMigration(Document):
|
|||||||
frappe.db.set_value("Price List", "Tally Price List", "enabled", 0)
|
frappe.db.set_value("Price List", "Tally Price List", "enabled", 0)
|
||||||
frappe.flags.in_migrate = False
|
frappe.flags.in_migrate = False
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def process_master_data(self):
|
def process_master_data(self):
|
||||||
self.set_status("Processing Master Data")
|
self.set_status("Processing Master Data")
|
||||||
frappe.enqueue_doc(self.doctype, self.name, "_process_master_data", queue="long", timeout=3600)
|
frappe.enqueue_doc(self.doctype, self.name, "_process_master_data", queue="long", timeout=3600)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def import_master_data(self):
|
def import_master_data(self):
|
||||||
self.set_status("Importing Master Data")
|
self.set_status("Importing Master Data")
|
||||||
frappe.enqueue_doc(self.doctype, self.name, "_import_master_data", queue="long", timeout=3600)
|
frappe.enqueue_doc(self.doctype, self.name, "_import_master_data", queue="long", timeout=3600)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def process_day_book_data(self):
|
def process_day_book_data(self):
|
||||||
self.set_status("Processing Day Book Data")
|
self.set_status("Processing Day Book Data")
|
||||||
frappe.enqueue_doc(self.doctype, self.name, "_process_day_book_data", queue="long", timeout=3600)
|
frappe.enqueue_doc(self.doctype, self.name, "_process_day_book_data", queue="long", timeout=3600)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def import_day_book_data(self):
|
def import_day_book_data(self):
|
||||||
self.set_status("Importing Day Book Data")
|
self.set_status("Importing Day Book Data")
|
||||||
frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600)
|
frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600)
|
||||||
|
@ -54,6 +54,7 @@ class ClinicalProcedure(Document):
|
|||||||
def set_title(self):
|
def set_title(self):
|
||||||
self.title = _('{0} - {1}').format(self.patient_name or self.patient, self.procedure_template)[:100]
|
self.title = _('{0} - {1}').format(self.patient_name or self.patient, self.procedure_template)[:100]
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def complete_procedure(self):
|
def complete_procedure(self):
|
||||||
if self.consume_stock and self.items:
|
if self.consume_stock and self.items:
|
||||||
stock_entry = make_stock_entry(self)
|
stock_entry = make_stock_entry(self)
|
||||||
@ -96,6 +97,7 @@ class ClinicalProcedure(Document):
|
|||||||
if self.consume_stock and self.items:
|
if self.consume_stock and self.items:
|
||||||
return stock_entry
|
return stock_entry
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def start_procedure(self):
|
def start_procedure(self):
|
||||||
allow_start = self.set_actual_qty()
|
allow_start = self.set_actual_qty()
|
||||||
if allow_start:
|
if allow_start:
|
||||||
@ -116,6 +118,7 @@ class ClinicalProcedure(Document):
|
|||||||
|
|
||||||
return allow_start
|
return allow_start
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def make_material_receipt(self, submit=False):
|
def make_material_receipt(self, submit=False):
|
||||||
stock_entry = frappe.new_doc('Stock Entry')
|
stock_entry = frappe.new_doc('Stock Entry')
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ class InpatientMedicationEntry(Document):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_medication_orders()
|
self.validate_medication_orders()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_medication_orders(self):
|
def get_medication_orders(self):
|
||||||
# pull inpatient medication orders based on selected filters
|
# pull inpatient medication orders based on selected filters
|
||||||
orders = get_pending_medication_orders(self)
|
orders = get_pending_medication_orders(self)
|
||||||
|
@ -57,6 +57,7 @@ class InpatientMedicationOrder(Document):
|
|||||||
|
|
||||||
self.db_set('status', status)
|
self.db_set('status', status)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def add_order_entries(self, order):
|
def add_order_entries(self, order):
|
||||||
if order.get('drug_code'):
|
if order.get('drug_code'):
|
||||||
dosage = frappe.get_doc('Prescription Dosage', order.get('dosage'))
|
dosage = frappe.get_doc('Prescription Dosage', order.get('dosage'))
|
||||||
|
@ -81,15 +81,8 @@ class TestInpatientMedicationOrder(unittest.TestCase):
|
|||||||
self.ip_record.reload()
|
self.ip_record.reload()
|
||||||
discharge_patient(self.ip_record)
|
discharge_patient(self.ip_record)
|
||||||
|
|
||||||
for entry in frappe.get_all('Inpatient Medication Entry'):
|
for doctype in ["Inpatient Medication Entry", "Inpatient Medication Order"]:
|
||||||
doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
|
frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
|
||||||
doc.cancel()
|
|
||||||
doc.delete()
|
|
||||||
|
|
||||||
for entry in frappe.get_all('Inpatient Medication Order'):
|
|
||||||
doc = frappe.get_doc('Inpatient Medication Order', entry.name)
|
|
||||||
doc.cancel()
|
|
||||||
doc.delete()
|
|
||||||
|
|
||||||
def create_dosage_form():
|
def create_dosage_form():
|
||||||
if not frappe.db.exists('Dosage Form', 'Tablet'):
|
if not frappe.db.exists('Dosage Form', 'Tablet'):
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
"discharge_ordered_date",
|
"discharge_ordered_date",
|
||||||
"discharge_practitioner",
|
"discharge_practitioner",
|
||||||
"discharge_encounter",
|
"discharge_encounter",
|
||||||
"discharge_date",
|
"discharge_datetime",
|
||||||
"cb_discharge",
|
"cb_discharge",
|
||||||
"discharge_instructions",
|
"discharge_instructions",
|
||||||
"followup_date",
|
"followup_date",
|
||||||
@ -404,14 +404,15 @@
|
|||||||
"permlevel": 1
|
"permlevel": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "discharge_date",
|
"fieldname": "discharge_datetime",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Datetime",
|
||||||
"label": "Discharge Date",
|
"label": "Discharge Date",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-05-21 02:26:22.144575",
|
"modified": "2021-03-18 14:44:11.689956",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Healthcare",
|
"module": "Healthcare",
|
||||||
"name": "Inpatient Record",
|
"name": "Inpatient Record",
|
||||||
|
@ -53,12 +53,15 @@ class InpatientRecord(Document):
|
|||||||
+ """ <b><a href="/app/Form/Inpatient Record/{0}">{0}</a></b>""".format(ip_record[0].name))
|
+ """ <b><a href="/app/Form/Inpatient Record/{0}">{0}</a></b>""".format(ip_record[0].name))
|
||||||
frappe.throw(msg)
|
frappe.throw(msg)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def admit(self, service_unit, check_in, expected_discharge=None):
|
def admit(self, service_unit, check_in, expected_discharge=None):
|
||||||
admit_patient(self, service_unit, check_in, expected_discharge)
|
admit_patient(self, service_unit, check_in, expected_discharge)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def discharge(self):
|
def discharge(self):
|
||||||
discharge_patient(self)
|
discharge_patient(self)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def transfer(self, service_unit, check_in, leave_from):
|
def transfer(self, service_unit, check_in, leave_from):
|
||||||
if leave_from:
|
if leave_from:
|
||||||
patient_leave_service_unit(self, check_in, leave_from)
|
patient_leave_service_unit(self, check_in, leave_from)
|
||||||
@ -151,7 +154,7 @@ def check_out_inpatient(inpatient_record):
|
|||||||
|
|
||||||
def discharge_patient(inpatient_record):
|
def discharge_patient(inpatient_record):
|
||||||
validate_inpatient_invoicing(inpatient_record)
|
validate_inpatient_invoicing(inpatient_record)
|
||||||
inpatient_record.discharge_date = today()
|
inpatient_record.discharge_datetime = now_datetime()
|
||||||
inpatient_record.status = "Discharged"
|
inpatient_record.status = "Discharged"
|
||||||
|
|
||||||
inpatient_record.save(ignore_permissions = True)
|
inpatient_record.save(ignore_permissions = True)
|
||||||
|
@ -111,6 +111,7 @@ class Patient(Document):
|
|||||||
age_str = str(age.years) + ' ' + _("Years(s)") + ' ' + str(age.months) + ' ' + _("Month(s)") + ' ' + str(age.days) + ' ' + _("Day(s)")
|
age_str = str(age.years) + ' ' + _("Years(s)") + ' ' + str(age.months) + ' ' + _("Month(s)") + ' ' + str(age.days) + ' ' + _("Day(s)")
|
||||||
return age_str
|
return age_str
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def invoice_patient_registration(self):
|
def invoice_patient_registration(self):
|
||||||
if frappe.db.get_single_value('Healthcare Settings', 'registration_fee'):
|
if frappe.db.get_single_value('Healthcare Settings', 'registration_fee'):
|
||||||
company = frappe.defaults.get_user_default('company')
|
company = frappe.defaults.get_user_default('company')
|
||||||
|
@ -113,6 +113,7 @@ class PatientAppointment(Document):
|
|||||||
if fee_validity:
|
if fee_validity:
|
||||||
frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till))
|
frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_therapy_types(self):
|
def get_therapy_types(self):
|
||||||
if not self.therapy_plan:
|
if not self.therapy_plan:
|
||||||
return
|
return
|
||||||
|
@ -39,11 +39,13 @@ frappe.ui.form.on('Patient Assessment', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
set_score_range: function(frm) {
|
set_score_range: function(frm) {
|
||||||
let options = [];
|
let options = [''];
|
||||||
for(let i = frm.doc.scale_min; i <= frm.doc.scale_max; i++) {
|
for(let i = frm.doc.scale_min; i <= frm.doc.scale_max; i++) {
|
||||||
options.push(i);
|
options.push(i);
|
||||||
}
|
}
|
||||||
frappe.meta.get_docfield('Patient Assessment Sheet', 'score', frm.doc.name).options = [''].concat(options);
|
frm.fields_dict.assessment_sheet.grid.update_docfield_property(
|
||||||
|
'score', 'options', options
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
calculate_total_score: function(frm, cdt, cdn) {
|
calculate_total_score: function(frm, cdt, cdn) {
|
||||||
@ -83,4 +85,4 @@ frappe.ui.form.on('Patient Assessment Sheet', {
|
|||||||
score: function(frm, cdt, cdn) {
|
score: function(frm, cdt, cdn) {
|
||||||
frm.events.calculate_total_score(frm, cdt, cdn);
|
frm.events.calculate_total_score(frm, cdt, cdn);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -34,6 +34,7 @@ class PatientHistorySettings(Document):
|
|||||||
frappe.throw(_('Row #{0}: Field {1} in Document Type {2} is not a Date / Datetime field.').format(
|
frappe.throw(_('Row #{0}: Field {1} in Document Type {2} is not a Date / Datetime field.').format(
|
||||||
entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type)))
|
entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type)))
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_doctype_fields(self, document_type, fields):
|
def get_doctype_fields(self, document_type, fields):
|
||||||
multicheck_fields = []
|
multicheck_fields = []
|
||||||
doc_fields = frappe.get_meta(document_type).fields
|
doc_fields = frappe.get_meta(document_type).fields
|
||||||
@ -49,6 +50,7 @@ class PatientHistorySettings(Document):
|
|||||||
|
|
||||||
return multicheck_fields
|
return multicheck_fields
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def get_date_field_for_dt(self, document_type):
|
def get_date_field_for_dt(self, document_type):
|
||||||
meta = frappe.get_meta(document_type)
|
meta = frappe.get_meta(document_type)
|
||||||
date_fields = meta.get('fields', {
|
date_fields = meta.get('fields', {
|
||||||
|
@ -58,8 +58,12 @@ frappe.ui.form.on('Therapy Plan', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (frm.doc.therapy_plan_template) {
|
if (frm.doc.therapy_plan_template) {
|
||||||
frappe.meta.get_docfield('Therapy Plan Detail', 'therapy_type', frm.doc.name).read_only = 1;
|
frm.fields_dict.therapy_plan_details.grid.update_docfield_property(
|
||||||
frappe.meta.get_docfield('Therapy Plan Detail', 'no_of_sessions', frm.doc.name).read_only = 1;
|
'therapy_type', 'read_only', 1
|
||||||
|
);
|
||||||
|
frm.fields_dict.therapy_plan_details.grid.update_docfield_property(
|
||||||
|
'no_of_sessions', 'read_only', 1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -126,4 +130,4 @@ frappe.ui.form.on('Therapy Plan Detail', {
|
|||||||
frm.set_value('total_sessions', total);
|
frm.set_value('total_sessions', total);
|
||||||
refresh_field('total_sessions');
|
refresh_field('total_sessions');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -33,6 +33,7 @@ class TherapyPlan(Document):
|
|||||||
self.db_set('total_sessions', total_sessions)
|
self.db_set('total_sessions', total_sessions)
|
||||||
self.db_set('total_sessions_completed', total_sessions_completed)
|
self.db_set('total_sessions_completed', total_sessions_completed)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
def set_therapy_details_from_template(self):
|
def set_therapy_details_from_template(self):
|
||||||
# Add therapy types in the child table
|
# Add therapy types in the child table
|
||||||
self.set('therapy_plan_details', [])
|
self.set('therapy_plan_details', [])
|
||||||
|
@ -195,6 +195,10 @@ sounds = [
|
|||||||
{"name": "call-disconnect", "src": "/assets/erpnext/sounds/call-disconnect.mp3", "volume": 0.2},
|
{"name": "call-disconnect", "src": "/assets/erpnext/sounds/call-disconnect.mp3", "volume": 0.2},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
has_upload_permission = {
|
||||||
|
"Employee": "erpnext.hr.doctype.employee.employee.has_upload_permission"
|
||||||
|
}
|
||||||
|
|
||||||
has_website_permission = {
|
has_website_permission = {
|
||||||
"Sales Order": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
"Sales Order": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
||||||
"Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
"Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission",
|
||||||
@ -258,6 +262,7 @@ doc_events = {
|
|||||||
],
|
],
|
||||||
"on_trash": "erpnext.regional.check_deletion_permission",
|
"on_trash": "erpnext.regional.check_deletion_permission",
|
||||||
"validate": [
|
"validate": [
|
||||||
|
"erpnext.regional.india.utils.validate_document_name",
|
||||||
"erpnext.regional.india.utils.update_taxable_values"
|
"erpnext.regional.india.utils.update_taxable_values"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -281,9 +286,6 @@ doc_events = {
|
|||||||
('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): {
|
('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): {
|
||||||
'validate': ['erpnext.regional.india.utils.set_place_of_supply']
|
'validate': ['erpnext.regional.india.utils.set_place_of_supply']
|
||||||
},
|
},
|
||||||
('Sales Invoice', 'Purchase Invoice'): {
|
|
||||||
'validate': ['erpnext.regional.india.utils.validate_document_name']
|
|
||||||
},
|
|
||||||
"Contact": {
|
"Contact": {
|
||||||
"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
|
"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
|
||||||
"after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations",
|
"after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations",
|
||||||
@ -305,6 +307,8 @@ auto_cancel_exempted_doctypes= [
|
|||||||
"Inpatient Medication Entry"
|
"Inpatient Medication Entry"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
after_migrate = ["erpnext.setup.install.update_select_perm_after_install"]
|
||||||
|
|
||||||
scheduler_events = {
|
scheduler_events = {
|
||||||
"cron": {
|
"cron": {
|
||||||
"0/30 * * * *": [
|
"0/30 * * * *": [
|
||||||
|
@ -8,6 +8,8 @@ import unittest
|
|||||||
from frappe.utils import nowdate
|
from frappe.utils import nowdate
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
|
test_dependencies = ["Employee"]
|
||||||
|
|
||||||
class TestAttendanceRequest(unittest.TestCase):
|
class TestAttendanceRequest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
for doctype in ["Attendance Request", "Attendance"]:
|
for doctype in ["Attendance Request", "Attendance"]:
|
||||||
@ -56,4 +58,4 @@ class TestAttendanceRequest(unittest.TestCase):
|
|||||||
self.assertEqual(attendance.docstatus, 2)
|
self.assertEqual(attendance.docstatus, 2)
|
||||||
|
|
||||||
def get_employee():
|
def get_employee():
|
||||||
return frappe.get_doc("Employee", "_T-Employee-00001")
|
return frappe.get_doc("Employee", "_T-Employee-00001")
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import date_diff, add_days, getdate, cint
|
from frappe.utils import date_diff, add_days, getdate, cint, format_date
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \
|
from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \
|
||||||
get_holidays_for_employee, create_additional_leave_ledger_entry
|
get_holidays_for_employee, create_additional_leave_ledger_entry
|
||||||
@ -40,7 +40,12 @@ class CompensatoryLeaveRequest(Document):
|
|||||||
def validate_holidays(self):
|
def validate_holidays(self):
|
||||||
holidays = get_holidays_for_employee(self.employee, self.work_from_date, self.work_end_date)
|
holidays = get_holidays_for_employee(self.employee, self.work_from_date, self.work_end_date)
|
||||||
if len(holidays) < date_diff(self.work_end_date, self.work_from_date) + 1:
|
if len(holidays) < date_diff(self.work_end_date, self.work_from_date) + 1:
|
||||||
frappe.throw(_("Compensatory leave request days not in valid holidays"))
|
if date_diff(self.work_end_date, self.work_from_date):
|
||||||
|
msg = _("The days between {0} to {1} are not valid holidays.").format(frappe.bold(format_date(self.work_from_date)), frappe.bold(format_date(self.work_end_date)))
|
||||||
|
else:
|
||||||
|
msg = _("{0} is not a holiday.").format(frappe.bold(format_date(self.work_from_date)))
|
||||||
|
|
||||||
|
frappe.throw(msg)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
company = frappe.db.get_value("Employee", self.employee, "company")
|
company = frappe.db.get_value("Employee", self.employee, "company")
|
||||||
@ -61,9 +66,9 @@ class CompensatoryLeaveRequest(Document):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
leave_allocation = self.create_leave_allocation(leave_period, date_difference)
|
leave_allocation = self.create_leave_allocation(leave_period, date_difference)
|
||||||
self.leave_allocation=leave_allocation.name
|
self.db_set("leave_allocation", leave_allocation.name)
|
||||||
else:
|
else:
|
||||||
frappe.throw(_("There is no leave period in between {0} and {1}").format(self.work_from_date, self.work_end_date))
|
frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date)))
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
if self.leave_allocation:
|
if self.leave_allocation:
|
||||||
@ -119,4 +124,4 @@ class CompensatoryLeaveRequest(Document):
|
|||||||
))
|
))
|
||||||
allocation.insert(ignore_permissions=True)
|
allocation.insert(ignore_permissions=True)
|
||||||
allocation.submit()
|
allocation.submit()
|
||||||
return allocation
|
return allocation
|
||||||
|
@ -10,6 +10,8 @@ from erpnext.hr.doctype.attendance_request.test_attendance_request import get_em
|
|||||||
from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period
|
from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period
|
||||||
from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
|
from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
|
||||||
|
|
||||||
|
test_dependencies = ["Employee"]
|
||||||
|
|
||||||
class TestCompensatoryLeaveRequest(unittest.TestCase):
|
class TestCompensatoryLeaveRequest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
frappe.db.sql(''' delete from `tabCompensatory Leave Request`''')
|
frappe.db.sql(''' delete from `tabCompensatory Leave Request`''')
|
||||||
@ -129,4 +131,4 @@ def create_holiday_list():
|
|||||||
],
|
],
|
||||||
"holiday_list_name": "_Test Compensatory Leave"
|
"holiday_list_name": "_Test Compensatory Leave"
|
||||||
})
|
})
|
||||||
holiday_list.save()
|
holiday_list.save()
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user