Compare commits
220 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
734e635ef6 | ||
|
|
6f9ef5b890 | ||
|
|
145393b12f | ||
|
|
9c6e2c3637 | ||
|
|
cfc2693b2e | ||
|
|
448d919cc1 | ||
|
|
87ec6a12ef | ||
|
|
059f99e621 | ||
|
|
4c28fa77bd | ||
|
|
394dbca0e4 | ||
|
|
33ebd9f88e | ||
|
|
4c1caa7e98 | ||
|
|
08c3b3c925 | ||
|
|
bb5812cf0f | ||
|
|
36645e4e2f | ||
|
|
b296bb1551 | ||
|
|
e7c14fcc3d | ||
|
|
31bb34bbae | ||
|
|
6436b9d089 | ||
|
|
d62fa84ed9 | ||
|
|
baa937aa52 | ||
|
|
3b128cabb2 | ||
|
|
dfc5a454b3 | ||
|
|
1c32f5ace9 | ||
|
|
283d5550e6 | ||
|
|
dda608dd00 | ||
|
|
e355f99786 | ||
|
|
27334c28a9 | ||
|
|
6c8d4678db | ||
|
|
ea50c9d1be | ||
|
|
35da7d1fb4 | ||
|
|
ced14cc789 | ||
|
|
9d27cf3c62 | ||
|
|
0e6933a1e8 | ||
|
|
51a76885b8 | ||
|
|
a919be111a | ||
|
|
9c42161061 | ||
|
|
79d6266c7b | ||
|
|
11d23f84d7 | ||
|
|
8e0f23efc7 | ||
|
|
ac9b1332d2 | ||
|
|
7b021e0fac | ||
|
|
d6dd25a666 | ||
|
|
f86100a734 | ||
|
|
96bb6099d6 | ||
|
|
e6c2ae3682 | ||
|
|
8d56f2959b | ||
|
|
d2d24554b3 | ||
|
|
c7d2bc67e8 | ||
|
|
1283f6308d | ||
|
|
d36c136fc6 | ||
|
|
3951f6971e | ||
|
|
75e65e7079 | ||
|
|
7f95d587b2 | ||
|
|
75b145fe2c | ||
|
|
bc3acdd0ba | ||
|
|
8f42f60dc9 | ||
|
|
823b3ca540 | ||
|
|
5e75e3ba03 | ||
|
|
113df55e64 | ||
|
|
3e4b2743c6 | ||
|
|
338c28e78e | ||
|
|
7e14996995 | ||
|
|
6e30f04181 | ||
|
|
4a10f18ee3 | ||
|
|
f37d43d0c1 | ||
|
|
aea60f349f | ||
|
|
90bd5681d1 | ||
|
|
30e03cc4c8 | ||
|
|
4c40a416e6 | ||
|
|
3020c8086c | ||
|
|
8b3ef1e70a | ||
|
|
c446bf6117 | ||
|
|
660de515b5 | ||
|
|
e012e24423 | ||
|
|
e2d0d0a0c1 | ||
|
|
22e82dff20 | ||
|
|
78d2f542d0 | ||
|
|
b962fc1573 | ||
|
|
1ee534889f | ||
|
|
fa04236c8d | ||
|
|
36025468a1 | ||
|
|
0e376a464b | ||
|
|
8333b5754b | ||
|
|
dab1172a18 | ||
|
|
ea4497c8d2 | ||
|
|
b994b3dcda | ||
|
|
805a41d06c | ||
|
|
e06526ffff | ||
|
|
2df7db0346 | ||
|
|
c9877c5c1e | ||
|
|
372a881d8c | ||
|
|
71b5250cbd | ||
|
|
ece7881ab1 | ||
|
|
3ceebaec3f | ||
|
|
30e987a835 | ||
|
|
087da2e571 | ||
|
|
ad7eb9d03c | ||
|
|
35d0de8276 | ||
|
|
812853aa86 | ||
|
|
319c58266b | ||
|
|
dcf10ee4f6 | ||
|
|
1394a6557d | ||
|
|
00e825a8af | ||
|
|
ed89a83584 | ||
|
|
2c5b3e83f5 | ||
|
|
8e2531e2bb | ||
|
|
d5dd9f1706 | ||
|
|
394c4d718d | ||
|
|
ae20748dec | ||
|
|
3df2c9421a | ||
|
|
393becce0b | ||
|
|
777b16ffda | ||
|
|
efaf9f59db | ||
|
|
8f2e21def2 | ||
|
|
7231f29e78 | ||
|
|
239c9387d1 | ||
|
|
e74e4b18c7 | ||
|
|
012f5b0a50 | ||
|
|
05d62127d0 | ||
|
|
7549a83b9b | ||
|
|
c3153655eb | ||
|
|
96488b0f34 | ||
|
|
1a60931435 | ||
|
|
8f507a984e | ||
|
|
57d3cecd68 | ||
|
|
de609a2fb6 | ||
|
|
7312186c76 | ||
|
|
0a32b7a6eb | ||
|
|
c1a1e62c0d | ||
|
|
b12f2109b5 | ||
|
|
a13c6a1bef | ||
|
|
79ed58fd36 | ||
|
|
860144feb7 | ||
|
|
1efb05233c | ||
|
|
ef8d6dc8f8 | ||
|
|
79c2191aa3 | ||
|
|
f012a9db70 | ||
|
|
4e1a3c1d58 | ||
|
|
fb8e59234b | ||
|
|
31af0849db | ||
|
|
bfdb726072 | ||
|
|
a8406e1544 | ||
|
|
b2aa867b70 | ||
|
|
30f2bcbccc | ||
|
|
7d885432eb | ||
|
|
3b5f774144 | ||
|
|
3a200bbc44 | ||
|
|
2bedca04ae | ||
|
|
1a0536bff4 | ||
|
|
cdba021802 | ||
|
|
3fe5ecc611 | ||
|
|
296fbfeaac | ||
|
|
4a7b4efbec | ||
|
|
b1f0fd4ac3 | ||
|
|
f7d2a59c18 | ||
|
|
0ac8542eaa | ||
|
|
c8b6d3badb | ||
|
|
cdf4320b3b | ||
|
|
5b2d3222f3 | ||
|
|
5d202ca31a | ||
|
|
7088db9e1f | ||
|
|
ec344ffa96 | ||
|
|
a52e726b6b | ||
|
|
cf82c3828e | ||
|
|
6fcfbaa1f9 | ||
|
|
a6c733d06c | ||
|
|
46ef26df71 | ||
|
|
99d571a786 | ||
|
|
e3ae600277 | ||
|
|
dd7a723214 | ||
|
|
ccaf36a00f | ||
|
|
101a021f7b | ||
|
|
68ed0488a3 | ||
|
|
75a233b472 | ||
|
|
5a174d61bc | ||
|
|
7a2815299e | ||
|
|
195d2b577f | ||
|
|
8c85562ceb | ||
|
|
77aa4762b8 | ||
|
|
35ecab6a52 | ||
|
|
097da8cc89 | ||
|
|
1d52a4df22 | ||
|
|
cd61a20fb4 | ||
|
|
8f7eb358b8 | ||
|
|
0e285265b1 | ||
|
|
b866fcf14f | ||
|
|
3d190a15ab | ||
|
|
ab59e4769b | ||
|
|
7b5ca3e494 | ||
|
|
8f2c8f6e9d | ||
|
|
76c5924cbe | ||
|
|
6eb55042d8 | ||
|
|
9589527784 | ||
|
|
dba3f0048b | ||
|
|
3f6a5b2539 | ||
|
|
cf7f72e586 | ||
|
|
74d07c695b | ||
|
|
5cf3868d03 | ||
|
|
1b36336fc3 | ||
|
|
3f3ac5652f | ||
|
|
7b8d366e3a | ||
|
|
a29442d6bf | ||
|
|
bbe16c80ff | ||
|
|
4e21f11864 | ||
|
|
19d52dc503 | ||
|
|
701f7cccbf | ||
|
|
8b486b0f28 | ||
|
|
6705ab3eaf | ||
|
|
233a19a373 | ||
|
|
fbb5945ff3 | ||
|
|
7773ee8960 | ||
|
|
8ad168ac67 | ||
|
|
3e9520b276 | ||
|
|
934e69fe0b | ||
|
|
bedb486c55 | ||
|
|
19d9381197 | ||
|
|
7640858510 | ||
|
|
690de64734 | ||
|
|
c7e3a09cfb |
50
.travis.yml
@@ -1,6 +1,12 @@
|
||||
language: python
|
||||
dist: trusty
|
||||
group: deprecated-2017Q2
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- google-chrome
|
||||
packages:
|
||||
- google-chrome-stable
|
||||
|
||||
python:
|
||||
- "2.7"
|
||||
@@ -8,43 +14,43 @@ python:
|
||||
services:
|
||||
- mysql
|
||||
|
||||
before_install:
|
||||
- "export DISPLAY=:99.0"
|
||||
- "sh -e /etc/init.d/xvfb start"
|
||||
|
||||
install:
|
||||
- sudo rm /etc/apt/sources.list.d/docker.list
|
||||
- sudo apt-get purge -y mysql-common mysql-server mysql-client
|
||||
- nvm install v7.10.0
|
||||
# - wget https://raw.githubusercontent.com/frappe/bench/master/install_scripts/setup_frappe.sh
|
||||
# - sudo bash setup_frappe.sh --skip-setup-bench --mysql-root-password travis --bench-branch develop
|
||||
- wget https://raw.githubusercontent.com/frappe/bench/master/playbooks/install.py
|
||||
- sudo python install.py --develop --user travis --without-bench-setup
|
||||
- sudo pip install -e ~/bench
|
||||
|
||||
# - sudo pip install --upgrade pip
|
||||
- rm $TRAVIS_BUILD_DIR/.git/shallow
|
||||
- bash $TRAVIS_BUILD_DIR/travis/bench_init.sh
|
||||
- cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/
|
||||
|
||||
script:
|
||||
before_script:
|
||||
- wget http://chromedriver.storage.googleapis.com/2.27/chromedriver_linux64.zip
|
||||
- unzip chromedriver_linux64.zip
|
||||
- sudo apt-get install libnss3
|
||||
- sudo apt-get --only-upgrade install google-chrome-stable
|
||||
- sudo cp chromedriver /usr/local/bin/.
|
||||
- sudo chmod +x /usr/local/bin/chromedriver
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
- sleep 3
|
||||
- mysql -u root -ptravis -e 'create database test_frappe'
|
||||
- echo "USE mysql;\nCREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe';\nFLUSH PRIVILEGES;\n" | mysql -u root -ptravis
|
||||
- echo "USE mysql;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root -ptravis
|
||||
|
||||
- cd ~/frappe-bench
|
||||
- bench get-app erpnext $TRAVIS_BUILD_DIR
|
||||
- bench use test_site
|
||||
- bench reinstall --yes
|
||||
- bench build
|
||||
- bench scheduler disable
|
||||
- bench start &
|
||||
- sleep 10
|
||||
- bench --verbose run-tests --driver Firefox
|
||||
|
||||
before_script:
|
||||
- mysql -u root -ptravis -e 'create database test_frappe'
|
||||
- echo "USE mysql;\nCREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe';\nFLUSH PRIVILEGES;\n" | mysql -u root -ptravis
|
||||
- echo "USE mysql;\nGRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost';\n" | mysql -u root -ptravis
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/92b3bea86d8c5397beef
|
||||
on_success: always
|
||||
on_failure: always
|
||||
on_start: never
|
||||
script:
|
||||
- set -e
|
||||
- bench --verbose run-tests
|
||||
- sleep 5
|
||||
- bench --verbose run-ui-tests --app erpnext
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
import inspect
|
||||
import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
|
||||
__version__ = '8.3.1'
|
||||
|
||||
__version__ = '8.6.3'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
@@ -66,3 +67,34 @@ def is_perpetual_inventory_enabled(company):
|
||||
company, "enable_perpetual_inventory") or 0
|
||||
|
||||
return frappe.local.enable_perpetual_inventory[company]
|
||||
|
||||
def get_region(company=None):
|
||||
'''Return the default country based on flag, company or global settings
|
||||
|
||||
You can also set global company flag in `frappe.flags.company`
|
||||
'''
|
||||
if company or frappe.flags.company:
|
||||
return frappe.db.get_value('Company',
|
||||
company or frappe.flags.company, 'country')
|
||||
elif frappe.flags.country:
|
||||
return frappe.flags.country
|
||||
else:
|
||||
return frappe.get_system_settings('country')
|
||||
|
||||
def allow_regional(fn):
|
||||
'''Decorator to make a function regionally overridable
|
||||
|
||||
Example:
|
||||
@erpnext.allow_regional
|
||||
def myfunction():
|
||||
pass'''
|
||||
def caller(*args, **kwargs):
|
||||
region = get_region()
|
||||
fn_name = inspect.getmodule(fn).__name__ + '.' + fn.__name__
|
||||
if region in regional_overrides and fn_name in regional_overrides[region]:
|
||||
return frappe.get_attr(regional_overrides[region][fn_name])(*args, **kwargs)
|
||||
else:
|
||||
return fn(*args, **kwargs)
|
||||
|
||||
return caller
|
||||
|
||||
|
||||
@@ -1,94 +1,94 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
cur_frm.cscript.refresh = function (doc, cdt, cdn) {
|
||||
if (doc.__islocal) {
|
||||
frappe.msgprint(__("Please create new account from Chart of Accounts."));
|
||||
throw "cannot create";
|
||||
}
|
||||
|
||||
cur_frm.toggle_display('account_name', doc.__islocal);
|
||||
|
||||
// hide fields if group
|
||||
cur_frm.toggle_display(['account_type', 'tax_rate'], cint(doc.is_group) == 0)
|
||||
|
||||
// disable fields
|
||||
cur_frm.toggle_enable(['account_name', 'is_group', 'company'], false);
|
||||
|
||||
if (cint(doc.is_group) == 0) {
|
||||
cur_frm.toggle_display('freeze_account', doc.__onload && doc.__onload.can_freeze_account);
|
||||
}
|
||||
|
||||
// read-only for root accounts
|
||||
if (!doc.parent_account) {
|
||||
cur_frm.set_read_only();
|
||||
cur_frm.set_intro(__("This is a root account and cannot be edited."));
|
||||
} else {
|
||||
// credit days and type if customer or supplier
|
||||
cur_frm.set_intro(null);
|
||||
|
||||
cur_frm.cscript.account_type(doc, cdt, cdn);
|
||||
|
||||
// show / hide convert buttons
|
||||
cur_frm.cscript.add_toolbar_buttons(doc);
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.add_fetch('parent_account', 'report_type', 'report_type');
|
||||
cur_frm.add_fetch('parent_account', 'root_type', 'root_type');
|
||||
|
||||
cur_frm.cscript.account_type = function (doc, cdt, cdn) {
|
||||
if (doc.is_group == 0) {
|
||||
cur_frm.toggle_display(['tax_rate'], doc.account_type == 'Tax');
|
||||
cur_frm.toggle_display('warehouse', doc.account_type == 'Stock');
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.cscript.add_toolbar_buttons = function (doc) {
|
||||
cur_frm.add_custom_button(__('Chart of Accounts'),
|
||||
function () { frappe.set_route("Tree", "Account"); });
|
||||
|
||||
if (doc.is_group == 1) {
|
||||
cur_frm.add_custom_button(__('Group to Non-Group'),
|
||||
function () { cur_frm.cscript.convert_to_ledger(); }, 'fa fa-retweet', 'btn-default');
|
||||
} else if (cint(doc.is_group) == 0) {
|
||||
cur_frm.add_custom_button(__('Ledger'), function () {
|
||||
frappe.route_options = {
|
||||
"account": doc.name,
|
||||
"from_date": frappe.sys_defaults.year_start_date,
|
||||
"to_date": frappe.sys_defaults.year_end_date,
|
||||
"company": doc.company
|
||||
frappe.ui.form.on('Account', {
|
||||
setup: function(frm) {
|
||||
frm.add_fetch('parent_account', 'report_type', 'report_type');
|
||||
frm.add_fetch('parent_account', 'root_type', 'root_type');
|
||||
},
|
||||
onload: function(frm) {
|
||||
frm.set_query('parent_account', function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
"is_group": 1,
|
||||
"company": doc.company
|
||||
}
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
});
|
||||
},
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.__islocal) {
|
||||
frappe.msgprint(__("Please create new account from Chart of Accounts."));
|
||||
throw "cannot create";
|
||||
}
|
||||
|
||||
frm.toggle_display('account_name', frm.doc.__islocal);
|
||||
|
||||
// hide fields if group
|
||||
frm.toggle_display(['account_type', 'tax_rate'], cint(frm.doc.is_group) == 0);
|
||||
|
||||
// disable fields
|
||||
frm.toggle_enable(['account_name', 'is_group', 'company'], false);
|
||||
|
||||
if (cint(frm.doc.is_group) == 0) {
|
||||
frm.toggle_display('freeze_account', frm.doc.__onload
|
||||
&& frm.doc.__onload.can_freeze_account);
|
||||
}
|
||||
|
||||
// read-only for root accounts
|
||||
if (!frm.doc.parent_account) {
|
||||
frm.set_read_only();
|
||||
frm.set_intro(__("This is a root account and cannot be edited."));
|
||||
} else {
|
||||
// credit days and type if customer or supplier
|
||||
frm.set_intro(null);
|
||||
frm.trigger('account_type');
|
||||
|
||||
// show / hide convert buttons
|
||||
frm.trigger('add_toolbar_buttons');
|
||||
}
|
||||
},
|
||||
account_type: function (frm) {
|
||||
if (frm.doc.is_group == 0) {
|
||||
frm.toggle_display(['tax_rate'], frm.doc.account_type == 'Tax');
|
||||
frm.toggle_display('warehouse', frm.doc.account_type == 'Stock');
|
||||
}
|
||||
},
|
||||
add_toolbar_buttons: function(frm) {
|
||||
frm.add_custom_button(__('Chart of Accounts'),
|
||||
function () { frappe.set_route("Tree", "Account"); });
|
||||
|
||||
if (frm.doc.is_group == 1) {
|
||||
frm.add_custom_button(__('Group to Non-Group'), function () {
|
||||
return frappe.call({
|
||||
doc: frm.doc,
|
||||
method: 'convert_group_to_ledger',
|
||||
callback: function() {
|
||||
frm.refresh();
|
||||
}
|
||||
});
|
||||
});
|
||||
} else if (cint(frm.doc.is_group) == 0) {
|
||||
cur_frm.add_custom_button(__('Ledger'), function () {
|
||||
frappe.route_options = {
|
||||
"account": frm.doc.name,
|
||||
"from_date": frappe.sys_defaults.year_start_date,
|
||||
"to_date": frappe.sys_defaults.year_end_date,
|
||||
"company": frm.doc.company
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
});
|
||||
|
||||
frm.add_custom_button(__('Non-Group to Group'), function () {
|
||||
return frappe.call({
|
||||
doc: frm.doc,
|
||||
method: 'convert_ledger_to_group',
|
||||
callback: function() {
|
||||
frm.refresh();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
cur_frm.add_custom_button(__('Non-Group to Group'),
|
||||
function () { cur_frm.cscript.convert_to_group(); }, 'fa fa-retweet', 'btn-default')
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.cscript.convert_to_ledger = function (doc, cdt, cdn) {
|
||||
return $c_obj(cur_frm.doc, 'convert_group_to_ledger', '', function (r, rt) {
|
||||
if (r.message == 1) {
|
||||
cur_frm.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cur_frm.cscript.convert_to_group = function (doc, cdt, cdn) {
|
||||
return $c_obj(cur_frm.doc, 'convert_ledger_to_group', '', function (r, rt) {
|
||||
if (r.message == 1) {
|
||||
cur_frm.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cur_frm.fields_dict['parent_account'].get_query = function (doc) {
|
||||
return {
|
||||
filters: {
|
||||
"is_group": 1,
|
||||
"company": doc.company
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
27
erpnext/accounts/doctype/account/test_account.js
Normal file
@@ -0,0 +1,27 @@
|
||||
QUnit.module('accounts');
|
||||
|
||||
QUnit.test("test account", function(assert) {
|
||||
assert.expect(4);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => frappe.set_route('Tree', 'Account'),
|
||||
() => frappe.click_button('Expand All'),
|
||||
() => frappe.click_link('Debtors'),
|
||||
() => frappe.click_button('Edit'),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
assert.ok(cur_frm.doc.root_type=='Asset');
|
||||
assert.ok(cur_frm.doc.report_type=='Balance Sheet');
|
||||
assert.ok(cur_frm.doc.account_type=='Receivable');
|
||||
},
|
||||
() => frappe.click_button('Ledger'),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
// check if general ledger report shown
|
||||
assert.deepEqual(frappe.get_route(), ['query-report', 'General Ledger']);
|
||||
window.history.back();
|
||||
return frappe.timeout(1);
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
@@ -83,7 +83,7 @@ def validate_expense_against_budget(args):
|
||||
|
||||
budget_records = frappe.db.sql("""
|
||||
select
|
||||
b.{budget_against_field}, ba.budget_amount, b.monthly_distribution,
|
||||
b.{budget_against_field} as budget_against, ba.budget_amount, b.monthly_distribution,
|
||||
b.action_if_annual_budget_exceeded,
|
||||
b.action_if_accumulated_monthly_budget_exceeded
|
||||
from
|
||||
@@ -111,15 +111,15 @@ def validate_budget_records(args, budget_records):
|
||||
args["month_end_date"] = get_last_day(args.posting_date)
|
||||
|
||||
compare_expense_with_budget(args, budget_amount,
|
||||
_("Accumulated Monthly"), monthly_action)
|
||||
_("Accumulated Monthly"), monthly_action, budget.budget_against)
|
||||
|
||||
if yearly_action in ("Stop", "Warn") and monthly_action != "Stop" \
|
||||
and yearly_action != monthly_action:
|
||||
compare_expense_with_budget(args, flt(budget.budget_amount),
|
||||
_("Annual"), yearly_action)
|
||||
_("Annual"), yearly_action, budget.budget_against)
|
||||
|
||||
|
||||
def compare_expense_with_budget(args, budget_amount, action_for, action):
|
||||
def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against):
|
||||
actual_expense = get_actual_expense(args)
|
||||
if actual_expense > budget_amount:
|
||||
diff = actual_expense - budget_amount
|
||||
@@ -127,7 +127,7 @@ def compare_expense_with_budget(args, budget_amount, action_for, action):
|
||||
|
||||
msg = _("{0} Budget for Account {1} against {2} {3} is {4}. It will exceed by {5}").format(
|
||||
_(action_for), frappe.bold(args.account), args.budget_against_field,
|
||||
frappe.bold(args.budget_against),
|
||||
frappe.bold(budget_against),
|
||||
frappe.bold(fmt_money(budget_amount, currency=currency)),
|
||||
frappe.bold(fmt_money(diff, currency=currency)))
|
||||
|
||||
|
||||
@@ -140,6 +140,33 @@ class TestBudget(unittest.TestCase):
|
||||
budget.load_from_db()
|
||||
budget.cancel()
|
||||
|
||||
def test_monthly_budget_against_parent_group_cost_center(self):
|
||||
cost_center = "_Test Cost Center 3 - _TC"
|
||||
|
||||
if not frappe.db.exists("Cost Center", cost_center):
|
||||
frappe.get_doc({
|
||||
'doctype': 'Cost Center',
|
||||
'cost_center_name': '_Test Cost Center 3',
|
||||
'parent_cost_center': "_Test Company - _TC",
|
||||
'company': '_Test Company',
|
||||
'is_group': 0
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
budget = make_budget("Cost Center", cost_center)
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
|
||||
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC", 40000, cost_center)
|
||||
|
||||
self.assertRaises(BudgetError, jv.submit)
|
||||
|
||||
budget.load_from_db()
|
||||
budget.cancel()
|
||||
jv.cancel()
|
||||
|
||||
frappe.delete_doc('Journal Entry', jv.name)
|
||||
frappe.delete_doc('Cost Center', cost_center)
|
||||
|
||||
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
|
||||
if budget_against_field == "Project":
|
||||
budget_against = "_Test Project"
|
||||
@@ -167,7 +194,8 @@ def make_budget(budget_against=None, cost_center=None):
|
||||
if budget_against == "Project":
|
||||
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", "_Test Project/_Test Fiscal Year 2013%")})
|
||||
else:
|
||||
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", "_Test Cost Center - _TC/_Test Fiscal Year 2013%")})
|
||||
cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/_Test Fiscal Year 2013")
|
||||
budget_list = frappe.get_all("Budget", fields=["name"], filters = {"name": ("like", cost_center_name)})
|
||||
for d in budget_list:
|
||||
frappe.db.sql("delete from `tabBudget` where name = %(name)s", d)
|
||||
frappe.db.sql("delete from `tabBudget Account` where parent = %(name)s", d)
|
||||
|
||||
@@ -96,7 +96,14 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
||||
|
||||
// expense claim
|
||||
if(jvd.reference_type==="Expense Claim") {
|
||||
return {};
|
||||
return {
|
||||
filters: {
|
||||
'approval_status': 'Approved',
|
||||
'total_sanctioned_amount': ['>', 0],
|
||||
'status': ['!=', 'Paid'],
|
||||
'docstatus': 1
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// journal entry
|
||||
@@ -122,10 +129,11 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
||||
|
||||
// account filter
|
||||
frappe.model.validate_missing(jvd, "account");
|
||||
|
||||
var party_account_field = jvd.reference_type==="Sales Invoice" ? "debit_to": "credit_to";
|
||||
out.filters.push([jvd.reference_type, party_account_field, "=", jvd.account]);
|
||||
} else {
|
||||
}
|
||||
|
||||
if(in_list(["Sales Order", "Purchase Order"], jvd.reference_type)) {
|
||||
// party_type and party mandatory
|
||||
frappe.model.validate_missing(jvd, "party_type");
|
||||
frappe.model.validate_missing(jvd, "party");
|
||||
|
||||
@@ -291,37 +291,39 @@ frappe.ui.form.on('Payment Entry', {
|
||||
|
||||
set_account_currency_and_balance: function(frm, account, currency_field,
|
||||
balance_field, callback_function) {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details",
|
||||
args: {
|
||||
"account": account,
|
||||
"date": frm.doc.posting_date
|
||||
},
|
||||
callback: function(r, rt) {
|
||||
if(r.message) {
|
||||
frm.set_value(currency_field, r.message['account_currency']);
|
||||
frm.set_value(balance_field, r.message['account_balance']);
|
||||
if (frm.doc.posting_date && account) {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details",
|
||||
args: {
|
||||
"account": account,
|
||||
"date": frm.doc.posting_date
|
||||
},
|
||||
callback: function(r, rt) {
|
||||
if(r.message) {
|
||||
frm.set_value(currency_field, r.message['account_currency']);
|
||||
frm.set_value(balance_field, r.message['account_balance']);
|
||||
|
||||
if(frm.doc.payment_type=="Receive" && currency_field=="paid_to_account_currency") {
|
||||
frm.toggle_reqd(["reference_no", "reference_date"],
|
||||
(r.message['account_type'] == "Bank" ? 1 : 0));
|
||||
if(!frm.doc.received_amount && frm.doc.paid_amount)
|
||||
frm.events.paid_amount(frm);
|
||||
} else if(frm.doc.payment_type=="Pay" && currency_field=="paid_from_account_currency") {
|
||||
frm.toggle_reqd(["reference_no", "reference_date"],
|
||||
(r.message['account_type'] == "Bank" ? 1 : 0));
|
||||
if(frm.doc.payment_type=="Receive" && currency_field=="paid_to_account_currency") {
|
||||
frm.toggle_reqd(["reference_no", "reference_date"],
|
||||
(r.message['account_type'] == "Bank" ? 1 : 0));
|
||||
if(!frm.doc.received_amount && frm.doc.paid_amount)
|
||||
frm.events.paid_amount(frm);
|
||||
} else if(frm.doc.payment_type=="Pay" && currency_field=="paid_from_account_currency") {
|
||||
frm.toggle_reqd(["reference_no", "reference_date"],
|
||||
(r.message['account_type'] == "Bank" ? 1 : 0));
|
||||
|
||||
if(!frm.doc.paid_amount && frm.doc.received_amount)
|
||||
frm.events.received_amount(frm);
|
||||
if(!frm.doc.paid_amount && frm.doc.received_amount)
|
||||
frm.events.received_amount(frm);
|
||||
}
|
||||
|
||||
if(callback_function) callback_function(frm);
|
||||
|
||||
frm.events.hide_unhide_fields(frm);
|
||||
frm.events.set_dynamic_labels(frm);
|
||||
}
|
||||
|
||||
if(callback_function) callback_function(frm);
|
||||
|
||||
frm.events.hide_unhide_fields(frm);
|
||||
frm.events.set_dynamic_labels(frm);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
paid_from_account_currency: function(frm) {
|
||||
|
||||
@@ -492,9 +492,13 @@ def get_outstanding_reference_documents(args):
|
||||
|
||||
for d in outstanding_invoices:
|
||||
d["exchange_rate"] = 1
|
||||
if party_account_currency != company_currency \
|
||||
and d.voucher_type in ("Sales Invoice", "Purchase Invoice"):
|
||||
if party_account_currency != company_currency:
|
||||
if d.voucher_type in ("Sales Invoice", "Purchase Invoice"):
|
||||
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
|
||||
elif d.voucher_type == "Journal Entry":
|
||||
d["exchange_rate"] = get_exchange_rate(
|
||||
party_account_currency, company_currency, d.posting_date
|
||||
)
|
||||
|
||||
# Get all SO / PO which are not fully billed or aginst which full advance not paid
|
||||
orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"), args.get("party"),
|
||||
|
||||
@@ -185,7 +185,7 @@ def get_pricing_rule_for_item(args):
|
||||
"discount_percentage": 0.0
|
||||
})
|
||||
else:
|
||||
item_details.discount_percentage = pricing_rule.discount_percentage
|
||||
item_details.discount_percentage = pricing_rule.discount_percentage or args.discount_percentage
|
||||
elif args.get('pricing_rule'):
|
||||
item_details = remove_pricing_rule_for_item(args.get("pricing_rule"), item_details)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from __future__ import unicode_literals
|
||||
import unittest
|
||||
import frappe
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.stock.get_item_details import get_item_details
|
||||
from frappe import MandatoryError
|
||||
|
||||
@@ -248,4 +249,51 @@ class TestPricingRule(unittest.TestCase):
|
||||
so.submit()
|
||||
so = frappe.get_doc('Sales Order', so.name)
|
||||
self.assertEquals(so.items[0].discount_percentage, 0)
|
||||
self.assertEquals(so.items[0].rate, 100)
|
||||
self.assertEquals(so.items[0].rate, 100)
|
||||
|
||||
def test_pricing_rule_with_margin_and_discount(self):
|
||||
make_pricing_rule(selling=1, margin_type="Percentage", margin_rate_or_amount=10)
|
||||
si = create_sales_invoice(do_not_save=True)
|
||||
si.items[0].price_list_rate = 1000
|
||||
si.insert(ignore_permissions=True)
|
||||
|
||||
item = si.items[0]
|
||||
self.assertEquals(item.rate, 1100)
|
||||
self.assertEquals(item.margin_rate_or_amount, 10)
|
||||
|
||||
# With discount
|
||||
item.discount_percentage = 10
|
||||
si.save()
|
||||
item = si.items[0]
|
||||
self.assertEquals(item.rate, 990)
|
||||
self.assertEquals(item.discount_percentage, 10)
|
||||
frappe.db.sql("delete from `tabPricing Rule`")
|
||||
|
||||
def make_pricing_rule(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Pricing Rule",
|
||||
"title": args.title or "_Test Pricing Rule",
|
||||
"company": args.company or "_Test Company",
|
||||
"apply_on": args.apply_on or "Item Code",
|
||||
"item_code": args.item_code or "_Test Item",
|
||||
"applicable_for": args.applicable_for,
|
||||
"selling": args.selling or 0,
|
||||
"buying": args.buying or 0,
|
||||
"min_qty": args.min_qty or 0.0,
|
||||
"max_qty": args.max_qty or 0.0,
|
||||
"price_or_discount": args.price_or_discount or "Discount Percentage",
|
||||
"discount_percentage": args.discount_percentage or 0.0,
|
||||
"price": args.price or 0.0,
|
||||
"margin_type": args.margin_type,
|
||||
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
apply_on = doc.apply_on.replace(' ', '_').lower()
|
||||
if args.get(apply_on) and apply_on != "item_code":
|
||||
doc.db_set(apply_on, args.get(apply_on))
|
||||
|
||||
applicable_for = doc.applicable_for.replace(' ', '_').lower()
|
||||
if args.get(applicable_for):
|
||||
doc.db_set(applicable_for, args.get(applicable_for))
|
||||
@@ -1516,6 +1516,36 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"fieldname": "sec_tax_breakup",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Tax Breakup",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@@ -1523,7 +1553,7 @@
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "HTML",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
@@ -1533,12 +1563,12 @@
|
||||
"in_standard_filter": 0,
|
||||
"label": "Taxes and Charges Calculation",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldtype": "HTML",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@@ -3767,7 +3797,7 @@
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2017-06-29 10:48:09.707735",
|
||||
"modified": "2017-07-19 13:53:48.673757",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -98,6 +98,26 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
this.set_default_print_format();
|
||||
},
|
||||
|
||||
on_submit: function(doc, dt, dn) {
|
||||
var me = this;
|
||||
|
||||
$.each(doc["items"], function(i, row) {
|
||||
if(row.delivery_note) frappe.model.clear_doc("Delivery Note", row.delivery_note)
|
||||
})
|
||||
|
||||
if(this.frm.doc.is_pos) {
|
||||
this.frm.msgbox = frappe.msgprint(
|
||||
`<a class="btn btn-primary" onclick="cur_frm.print_preview.printit(true)" style="margin-right: 5px;">
|
||||
${__('Print')}</a>
|
||||
<a class="btn btn-default" href="javascript:frappe.new_doc(cur_frm.doctype);">
|
||||
${__('New')}</a>`
|
||||
);
|
||||
|
||||
} else if(cint(frappe.boot.notification_settings.sales_invoice)) {
|
||||
this.frm.email_doc(frappe.boot.notification_settings.sales_invoice_message);
|
||||
}
|
||||
},
|
||||
|
||||
set_default_print_format: function() {
|
||||
// set default print format to POS type
|
||||
if(cur_frm.doc.is_pos) {
|
||||
@@ -306,7 +326,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
|
||||
this.frm.refresh_fields();
|
||||
},
|
||||
|
||||
|
||||
company_address: function() {
|
||||
var me = this;
|
||||
if(this.frm.doc.company_address) {
|
||||
@@ -343,13 +363,6 @@ cur_frm.cscript.hide_fields = function(doc) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var item_fields_stock = ['batch_no', 'actual_batch_qty', 'actual_qty', 'expense_account',
|
||||
'warehouse', 'expense_account', 'quality_inspection']
|
||||
cur_frm.fields_dict['items'].grid.set_column_disp(item_fields_stock,
|
||||
(cint(doc.update_stock)==1 || cint(doc.is_return)==1 ? true : false));
|
||||
|
||||
|
||||
// India related fields
|
||||
if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']);
|
||||
else hide_field(['c_form_applicable', 'c_form_no']);
|
||||
@@ -445,24 +458,6 @@ cur_frm.cscript.cost_center = function(doc, cdt, cdn) {
|
||||
erpnext.utils.copy_value_in_all_row(doc, cdt, cdn, "items", "cost_center");
|
||||
}
|
||||
|
||||
cur_frm.cscript.on_submit = function(doc, cdt, cdn) {
|
||||
$.each(doc["items"], function(i, row) {
|
||||
if(row.delivery_note) frappe.model.clear_doc("Delivery Note", row.delivery_note)
|
||||
})
|
||||
|
||||
if(cur_frm.doc.is_pos) {
|
||||
cur_frm.msgbox = frappe.msgprint(
|
||||
`<a class="btn btn-primary" onclick="cur_frm.print_preview.printit(true)" style="margin-right: 5px;">
|
||||
${__('Print')}</a>
|
||||
<a class="btn btn-default" href="javascript:frappe.new_doc(cur_frm.doctype);">
|
||||
${__('New')}</a>`
|
||||
);
|
||||
|
||||
} else if(cint(frappe.boot.notification_settings.sales_invoice)) {
|
||||
cur_frm.email_doc(frappe.boot.notification_settings.sales_invoice_message);
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.set_query("debit_to", function(doc) {
|
||||
// filter on Account
|
||||
if (doc.customer) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
@@ -1927,7 +1928,7 @@
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Item-wise Tax Breakup",
|
||||
"label": "Tax Breakup",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
@@ -4687,7 +4688,7 @@
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2017-07-04 17:11:09.477003",
|
||||
"modified": "2017-07-07 13:05:37.469682",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -18,6 +18,7 @@ from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timeshe
|
||||
from erpnext.accounts.doctype.asset.depreciation \
|
||||
import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal
|
||||
from erpnext.stock.doctype.batch.batch import set_batch_nos
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no
|
||||
|
||||
form_grid_templates = {
|
||||
"items": "templates/form_grid/item_grid.html"
|
||||
@@ -83,10 +84,10 @@ class SalesInvoice(SellingController):
|
||||
|
||||
if not self.is_opening:
|
||||
self.is_opening = 'No'
|
||||
|
||||
|
||||
if self._action != 'submit' and self.update_stock and not self.is_return:
|
||||
set_batch_nos(self, 'warehouse', True)
|
||||
|
||||
|
||||
|
||||
self.set_against_income_account()
|
||||
self.validate_c_form()
|
||||
@@ -98,7 +99,7 @@ class SalesInvoice(SellingController):
|
||||
self.set_billing_hours_and_amount()
|
||||
self.update_timesheet_billing_for_project()
|
||||
self.set_status()
|
||||
|
||||
|
||||
def before_save(self):
|
||||
set_account_for_mode_of_payment(self)
|
||||
|
||||
@@ -139,6 +140,8 @@ class SalesInvoice(SellingController):
|
||||
|
||||
self.update_time_sheet(self.name)
|
||||
|
||||
frappe.enqueue('erpnext.setup.doctype.company.company.update_company_current_month_sales', company=self.company)
|
||||
|
||||
def validate_pos_paid_amount(self):
|
||||
if len(self.payments) == 0 and self.is_pos:
|
||||
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
||||
@@ -803,19 +806,27 @@ class SalesInvoice(SellingController):
|
||||
continue
|
||||
|
||||
for serial_no in item.serial_no.split("\n"):
|
||||
sno = frappe.get_doc('Serial No', serial_no)
|
||||
sno.sales_invoice = invoice
|
||||
sno.db_update()
|
||||
if serial_no and frappe.db.exists('Serial No', serial_no):
|
||||
sno = frappe.get_doc('Serial No', serial_no)
|
||||
sno.sales_invoice = invoice
|
||||
sno.db_update()
|
||||
|
||||
def validate_serial_numbers(self):
|
||||
"""
|
||||
validate serial number agains Delivery Note and Sales Invoice
|
||||
"""
|
||||
self.set_serial_no_against_delivery_note()
|
||||
self.validate_serial_against_delivery_note()
|
||||
self.validate_serial_against_sales_invoice()
|
||||
|
||||
def set_serial_no_against_delivery_note(self):
|
||||
for item in self.items:
|
||||
if item.serial_no and item.delivery_note and \
|
||||
item.qty != len(get_serial_nos(item.serial_no)):
|
||||
item.serial_no = get_delivery_note_serial_no(item.item_code, item.qty, item.delivery_note)
|
||||
|
||||
def validate_serial_against_delivery_note(self):
|
||||
"""
|
||||
"""
|
||||
validate if the serial numbers in Sales Invoice Items are same as in
|
||||
Delivery Note Item
|
||||
"""
|
||||
@@ -825,14 +836,18 @@ class SalesInvoice(SellingController):
|
||||
continue
|
||||
|
||||
serial_nos = frappe.db.get_value("Delivery Note Item", item.dn_detail, "serial_no") or ""
|
||||
dn_serial_nos = set(serial_nos.split("\n"))
|
||||
dn_serial_nos = set(get_serial_nos(serial_nos))
|
||||
|
||||
serial_nos = item.serial_no or ""
|
||||
si_serial_nos = set(serial_nos.split("\n"))
|
||||
si_serial_nos = set(get_serial_nos(serial_nos))
|
||||
|
||||
if si_serial_nos - dn_serial_nos:
|
||||
frappe.throw(_("Serial Numbers in row {0} does not match with Delivery Note".format(item.idx)))
|
||||
|
||||
if item.serial_no and cint(item.qty) != len(si_serial_nos):
|
||||
frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.".format(
|
||||
item.idx, item.qty, item.item_code, len(si_serial_nos))))
|
||||
|
||||
def validate_serial_against_sales_invoice(self):
|
||||
""" check if serial number is already used in other sales invoice """
|
||||
for item in self.items:
|
||||
@@ -917,7 +932,6 @@ def make_delivery_note(source_name, target_doc=None):
|
||||
|
||||
return doclist
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_return(source_name, target_doc=None):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
|
||||
@@ -13,6 +13,7 @@ from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
||||
from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
|
||||
from frappe.model.naming import make_autoname
|
||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
|
||||
|
||||
class TestSalesInvoice(unittest.TestCase):
|
||||
def make(self):
|
||||
@@ -1105,8 +1106,83 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
for i, k in enumerate(expected_values["keys"]):
|
||||
self.assertEquals(d.get(k), expected_values[d.item_code][i])
|
||||
|
||||
def test_item_wise_tax_breakup(self):
|
||||
def test_item_wise_tax_breakup_india(self):
|
||||
frappe.flags.country = "India"
|
||||
|
||||
si = self.create_si_to_test_tax_breakup()
|
||||
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
|
||||
|
||||
expected_itemised_tax = {
|
||||
"999800": {
|
||||
"Service Tax": {
|
||||
"tax_rate": 10.0,
|
||||
"tax_amount": 1500.0
|
||||
}
|
||||
}
|
||||
}
|
||||
expected_itemised_taxable_amount = {
|
||||
"999800": 15000.0
|
||||
}
|
||||
|
||||
self.assertEqual(itemised_tax, expected_itemised_tax)
|
||||
self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount)
|
||||
|
||||
frappe.flags.country = None
|
||||
|
||||
def test_item_wise_tax_breakup_outside_india(self):
|
||||
frappe.flags.country = "United States"
|
||||
|
||||
si = self.create_si_to_test_tax_breakup()
|
||||
|
||||
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
|
||||
|
||||
expected_itemised_tax = {
|
||||
"_Test Item": {
|
||||
"Service Tax": {
|
||||
"tax_rate": 10.0,
|
||||
"tax_amount": 1000.0
|
||||
}
|
||||
},
|
||||
"_Test Item 2": {
|
||||
"Service Tax": {
|
||||
"tax_rate": 10.0,
|
||||
"tax_amount": 500.0
|
||||
}
|
||||
}
|
||||
}
|
||||
expected_itemised_taxable_amount = {
|
||||
"_Test Item": 10000.0,
|
||||
"_Test Item 2": 5000.0
|
||||
}
|
||||
|
||||
self.assertEqual(itemised_tax, expected_itemised_tax)
|
||||
self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount)
|
||||
|
||||
frappe.flags.country = None
|
||||
|
||||
def create_si_to_test_tax_breakup(self):
|
||||
si = create_sales_invoice(qty=100, rate=50, do_not_save=True)
|
||||
si.append("items", {
|
||||
"item_code": "_Test Item",
|
||||
"gst_hsn_code": "999800",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 100,
|
||||
"rate": 50,
|
||||
"income_account": "Sales - _TC",
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC"
|
||||
})
|
||||
si.append("items", {
|
||||
"item_code": "_Test Item 2",
|
||||
"gst_hsn_code": "999800",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 100,
|
||||
"rate": 50,
|
||||
"income_account": "Sales - _TC",
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC"
|
||||
})
|
||||
|
||||
si.append("taxes", {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account Service Tax - _TC",
|
||||
@@ -1115,11 +1191,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"rate": 10
|
||||
})
|
||||
si.insert()
|
||||
|
||||
tax_breakup_html = '''\n<div class="tax-break-up" style="overflow-x: auto;">\n\t<table class="table table-bordered table-hover">\n\t\t<thead><tr><th class="text-left" style="min-width: 120px;">Item Name</th>\n<th class="text-right" style="min-width: 80px;">Taxable Amount</th>\n<th class="text-right" style="min-width: 80px;">_Test Account Service Tax - _TC</th></tr></thead>\n\t\t<tbody><tr><td>_Test Item</td><td class="text-right">\u20b9 5,000.00</td><td class="text-right">(10.0%) \u20b9 500.00</td></tr></tbody>\n\t</table>\n</div>'''
|
||||
|
||||
self.assertEqual(si.other_charges_calculation, tax_breakup_html)
|
||||
|
||||
return si
|
||||
|
||||
def create_sales_invoice(**args):
|
||||
si = frappe.new_doc("Sales Invoice")
|
||||
@@ -1140,6 +1212,7 @@ def create_sales_invoice(**args):
|
||||
|
||||
si.append("items", {
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
"gst_hsn_code": "999800",
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"qty": args.qty or 1,
|
||||
"rate": args.rate or 100,
|
||||
|
||||
@@ -1424,7 +1424,7 @@
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:doc.serial_no || doc.batch_no",
|
||||
"columns": 0,
|
||||
"depends_on": "eval: parent.update_stock",
|
||||
"depends_on": "",
|
||||
"fieldname": "warehouse_and_reference",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
@@ -2166,7 +2166,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-07-06 17:54:03.347700",
|
||||
"modified": "2017-07-17 17:54:48.246507",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
||||
@@ -1078,7 +1078,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
|
||||
} else if (item.barcode == me.serach_item.$input.val()) {
|
||||
search_status = false;
|
||||
return item.barcode == me.serach_item.$input.val();
|
||||
} else if (reg.test(item.item_code.toLowerCase()) || reg.test(item.description.toLowerCase()) ||
|
||||
} else if (reg.test(item.item_code.toLowerCase()) || (item.description && reg.test(item.description.toLowerCase())) ||
|
||||
reg.test(item.item_name.toLowerCase()) || reg.test(item.item_group.toLowerCase())) {
|
||||
return true
|
||||
}
|
||||
@@ -1351,7 +1351,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
|
||||
this.child.item_name = this.items[0].item_name;
|
||||
this.child.stock_uom = this.items[0].stock_uom;
|
||||
this.child.brand = this.items[0].brand;
|
||||
this.child.description = this.items[0].description;
|
||||
this.child.description = this.items[0].description || this.items[0].item_name;
|
||||
this.child.discount_percentage = 0.0;
|
||||
this.child.qty = 1;
|
||||
this.child.item_group = this.items[0].item_group;
|
||||
@@ -1398,10 +1398,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
|
||||
return erpnext.get_currency(this.frm.doc.company);
|
||||
},
|
||||
|
||||
show_item_wise_taxes: function () {
|
||||
return null;
|
||||
},
|
||||
|
||||
show_items_in_item_cart: function () {
|
||||
var me = this;
|
||||
var $items = this.wrapper.find(".items").empty();
|
||||
|
||||
@@ -8,9 +8,10 @@ import datetime
|
||||
from frappe import _, msgprint, scrub
|
||||
from frappe.defaults import get_user_permissions
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.utils import add_days, getdate, formatdate, get_first_day, date_diff, \
|
||||
add_years, get_timestamp, nowdate, flt
|
||||
from frappe.contacts.doctype.address.address import get_address_display, get_default_address
|
||||
from frappe.utils import (add_days, getdate, formatdate, get_first_day, date_diff,
|
||||
add_years, get_timestamp, nowdate, flt)
|
||||
from frappe.contacts.doctype.address.address import (get_address_display,
|
||||
get_default_address, get_company_address)
|
||||
from frappe.contacts.doctype.contact.contact import get_contact_details, get_default_contact
|
||||
from erpnext.exceptions import PartyFrozen, PartyDisabled, InvalidAccountCurrency
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
@@ -77,8 +78,9 @@ def set_address_details(out, party, party_type, doctype=None, company=None):
|
||||
out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name))
|
||||
|
||||
if doctype and doctype in ['Sales Invoice']:
|
||||
out.company_address = get_default_address('Company', company)
|
||||
out.update(get_fetch_values(doctype, 'company_address', out.company_address))
|
||||
out.update(get_company_address(company))
|
||||
if out.company_address:
|
||||
out.update(get_fetch_values(doctype, 'company_address', out.company_address))
|
||||
|
||||
def set_contact_details(out, party, party_type):
|
||||
out.contact_person = get_default_contact(party_type, party.name)
|
||||
@@ -282,12 +284,14 @@ def get_credit_days(party_type, party, company):
|
||||
if not credit_days_based_on:
|
||||
if party_type == "Customer":
|
||||
credit_days_based_on, credit_days = \
|
||||
frappe.db.get_value("Customer Group", customer_group, ["credit_days_based_on", "credit_days"]) \
|
||||
or frappe.db.get_value("Company", company, ["credit_days_based_on", "credit_days"])
|
||||
frappe.db.get_value("Customer Group", customer_group, ["credit_days_based_on", "credit_days"])
|
||||
else:
|
||||
credit_days_based_on, credit_days = \
|
||||
frappe.db.get_value("Supplier Type", supplier_type, ["credit_days_based_on", "credit_days"])\
|
||||
or frappe.db.get_value("Company", company, ["credit_days_based_on", "credit_days"] )
|
||||
frappe.db.get_value("Supplier Type", supplier_type, ["credit_days_based_on", "credit_days"])
|
||||
|
||||
if not credit_days_based_on:
|
||||
credit_days_based_on, credit_days = \
|
||||
frappe.db.get_value("Company", company, ["credit_days_based_on", "credit_days"])
|
||||
|
||||
return credit_days_based_on, credit_days
|
||||
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
{% var letterhead= filters.letter_head || (frappe.get_doc(":Company", filters.company) && frappe.get_doc(":Company", filters.company).default_letter_head) || frappe.defaults.get_default("letter_head"); %}
|
||||
{% if(letterhead) { %}
|
||||
<div style="margin-bottom: 7px;" class="text-center">
|
||||
{%= frappe.boot.letter_heads[letterhead].header %}
|
||||
</div>
|
||||
{% } %}
|
||||
<h2 class="text-center">{%= __(report.report_name) %}</h2>
|
||||
<h4 class="text-center">{%= filters.customer || filters.supplier %} </h4>
|
||||
<h5 class="text-center">
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
{% var letterhead= filters.letter_head || (frappe.get_doc(":Company", filters.company) && frappe.get_doc(":Company", filters.company).default_letter_head) || frappe.defaults.get_default("letter_head"); %}
|
||||
{% if(letterhead) { %}
|
||||
<div style="margin-bottom: 7px;" class="text-center">
|
||||
{%= frappe.boot.letter_heads[letterhead].header %}
|
||||
</div>
|
||||
{% } %}
|
||||
<h2 class="text-center">{%= __("Statement of Account") %}</h2>
|
||||
<h4 class="text-center">
|
||||
{% if (filters.party_name) { %}
|
||||
|
||||
@@ -209,7 +209,10 @@ class GrossProfitGenerator(object):
|
||||
sle.voucher_detail_no == row.item_row:
|
||||
previous_stock_value = len(my_sle) > i+1 and \
|
||||
flt(my_sle[i+1].stock_value) or 0.0
|
||||
return previous_stock_value - flt(sle.stock_value)
|
||||
if previous_stock_value:
|
||||
return previous_stock_value - flt(sle.stock_value)
|
||||
else:
|
||||
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
|
||||
else:
|
||||
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
|
||||
|
||||
|
||||
@@ -1546,6 +1546,36 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"fieldname": "sec_tax_breakup",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Tax Breakup",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@@ -1553,7 +1583,7 @@
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "HTML",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
@@ -1568,7 +1598,7 @@
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@@ -3305,7 +3335,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-06-13 14:29:21.066814",
|
||||
"modified": "2017-07-19 14:03:51.838328",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
||||
@@ -11,3 +11,6 @@ from erpnext.controllers.print_settings import print_settings_for_item_table
|
||||
class PurchaseOrderItem(Document):
|
||||
def __setup__(self):
|
||||
print_settings_for_item_table(self)
|
||||
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("Purchase Order Item", ["item_code", "warehouse"])
|
||||
@@ -1120,6 +1120,36 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"fieldname": "tax_breakup",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Tax Breakup",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@@ -1127,7 +1157,7 @@
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "HTML",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
@@ -1142,7 +1172,7 @@
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@@ -2217,7 +2247,7 @@
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2017-06-13 14:28:54.466450",
|
||||
"modified": "2017-07-19 13:51:18.929697",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "hash",
|
||||
@@ -13,6 +14,7 @@
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
@@ -44,6 +46,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -73,34 +76,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -131,6 +107,66 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "lead_time_days",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Lead Time in days",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
@@ -160,6 +196,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -192,6 +229,7 @@
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -219,6 +257,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -248,6 +287,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -278,6 +318,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -306,6 +347,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
@@ -338,6 +380,7 @@
|
||||
"width": "60px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -368,6 +411,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -397,6 +441,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -426,6 +471,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -453,6 +499,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -486,6 +533,7 @@
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -515,6 +563,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -544,6 +593,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -571,6 +621,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
@@ -602,6 +653,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -633,6 +685,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -660,6 +713,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -694,6 +748,7 @@
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -725,6 +780,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -754,6 +810,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -782,6 +839,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -811,6 +869,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -841,6 +900,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -869,6 +929,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -899,6 +960,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -929,6 +991,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -957,6 +1020,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -988,6 +1052,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -1017,6 +1082,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -1047,6 +1113,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -1080,6 +1147,7 @@
|
||||
"width": "120px"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -1110,6 +1178,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -1137,6 +1206,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -1167,6 +1237,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -1196,6 +1267,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -1227,6 +1299,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -1259,6 +1332,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -1290,6 +1364,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -1318,6 +1393,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -1347,6 +1423,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -1377,17 +1454,17 @@
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-02-17 16:43:59.582188",
|
||||
"modified": "2017-07-10 09:08:52.015387",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation Item",
|
||||
|
||||
@@ -160,6 +160,7 @@ class AccountsController(TransactionBase):
|
||||
def set_missing_item_details(self, for_validate=False):
|
||||
"""set missing item values"""
|
||||
from erpnext.stock.get_item_details import get_item_details
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
if hasattr(self, "items"):
|
||||
parent_dict = {}
|
||||
@@ -196,7 +197,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
elif fieldname == "serial_no":
|
||||
stock_qty = item.get("stock_qty") * -1 if item.get("stock_qty") < 0 else item.get("stock_qty")
|
||||
if stock_qty != len(item.get('serial_no').split('\n')):
|
||||
if stock_qty != len(get_serial_nos(item.get('serial_no'))):
|
||||
item.set(fieldname, value)
|
||||
|
||||
elif fieldname == "conversion_factor" and not item.get("conversion_factor"):
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
||||
import json
|
||||
import frappe, erpnext
|
||||
from frappe import _, scrub
|
||||
from frappe.utils import cint, flt, cstr, fmt_money, round_based_on_smallest_currency_fraction
|
||||
from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
|
||||
from erpnext.controllers.accounts_controller import validate_conversion_rate, \
|
||||
validate_taxes_and_charges, validate_inclusive_tax
|
||||
|
||||
@@ -509,103 +509,75 @@ class calculate_taxes_and_totals(object):
|
||||
return rate_with_margin
|
||||
|
||||
def set_item_wise_tax_breakup(self):
|
||||
item_tax = {}
|
||||
tax_accounts = []
|
||||
company_currency = erpnext.get_company_currency(self.doc.company)
|
||||
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
|
||||
|
||||
item_tax, tax_accounts = self.get_item_tax(item_tax, tax_accounts, company_currency)
|
||||
|
||||
headings = get_table_column_headings(tax_accounts)
|
||||
|
||||
distinct_items = self.get_distinct_items()
|
||||
|
||||
rows = get_table_rows(distinct_items, item_tax, tax_accounts, company_currency)
|
||||
|
||||
if not rows:
|
||||
self.doc.other_charges_calculation = ""
|
||||
else:
|
||||
self.doc.other_charges_calculation = '''
|
||||
<div class="tax-break-up" style="overflow-x: auto;">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead><tr>{headings}</tr></thead>
|
||||
<tbody>{rows}</tbody>
|
||||
</table>
|
||||
</div>'''.format(**{
|
||||
"headings": "\n".join(headings),
|
||||
"rows": "\n".join(rows)
|
||||
})
|
||||
|
||||
def get_item_tax(self, item_tax, tax_accounts, company_currency):
|
||||
for tax in self.doc.taxes:
|
||||
tax_amount_precision = tax.precision("tax_amount")
|
||||
tax_rate_precision = tax.precision("rate");
|
||||
|
||||
item_tax_map = self._load_item_tax_rate(tax.item_wise_tax_detail)
|
||||
for item_code, tax_data in item_tax_map.items():
|
||||
if not item_tax.get(item_code):
|
||||
item_tax[item_code] = {}
|
||||
|
||||
if isinstance(tax_data, list):
|
||||
tax_rate = ""
|
||||
if tax_data[0]:
|
||||
if tax.charge_type == "Actual":
|
||||
tax_rate = fmt_money(flt(tax_data[0], tax_amount_precision),
|
||||
tax_amount_precision, company_currency)
|
||||
else:
|
||||
tax_rate = cstr(flt(tax_data[0], tax_rate_precision)) + "%"
|
||||
|
||||
tax_amount = fmt_money(flt(tax_data[1], tax_amount_precision),
|
||||
tax_amount_precision, company_currency)
|
||||
|
||||
item_tax[item_code][tax.name] = [tax_rate, tax_amount]
|
||||
else:
|
||||
item_tax[item_code][tax.name] = [cstr(flt(tax_data, tax_rate_precision)) + "%", ""]
|
||||
tax_accounts.append([tax.name, tax.account_head])
|
||||
|
||||
return item_tax, tax_accounts
|
||||
|
||||
def get_itemised_tax_breakup_html(doc):
|
||||
if not doc.taxes:
|
||||
return
|
||||
frappe.flags.company = doc.company
|
||||
|
||||
def get_distinct_items(self):
|
||||
distinct_item_names = []
|
||||
distinct_items = []
|
||||
for item in self.doc.items:
|
||||
item_code = item.item_code or item.item_name
|
||||
if item_code not in distinct_item_names:
|
||||
distinct_item_names.append(item_code)
|
||||
distinct_items.append(item)
|
||||
|
||||
return distinct_items
|
||||
# get headers
|
||||
tax_accounts = list(set([d.description for d in doc.taxes]))
|
||||
headers = get_itemised_tax_breakup_header(doc.doctype + " Item", tax_accounts)
|
||||
|
||||
# get tax breakup data
|
||||
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(doc)
|
||||
|
||||
frappe.flags.company = None
|
||||
|
||||
return frappe.render_template(
|
||||
"templates/includes/itemised_tax_breakup.html", dict(
|
||||
headers=headers,
|
||||
itemised_tax=itemised_tax,
|
||||
itemised_taxable_amount=itemised_taxable_amount,
|
||||
tax_accounts=tax_accounts,
|
||||
company_currency=erpnext.get_company_currency(doc.company)
|
||||
)
|
||||
)
|
||||
|
||||
def get_table_column_headings(tax_accounts):
|
||||
headings_name = [_("Item Name"), _("Taxable Amount")] + [d[1] for d in tax_accounts]
|
||||
headings = []
|
||||
for head in headings_name:
|
||||
if head == _("Item Name"):
|
||||
headings.append('<th style="min-width: 120px;" class="text-left">' + (head or "") + "</th>")
|
||||
else:
|
||||
headings.append('<th style="min-width: 80px;" class="text-right">' + (head or "") + "</th>")
|
||||
@erpnext.allow_regional
|
||||
def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
|
||||
return [_("Item"), _("Taxable Amount")] + tax_accounts
|
||||
|
||||
@erpnext.allow_regional
|
||||
def get_itemised_tax_breakup_data(doc):
|
||||
itemised_tax = get_itemised_tax(doc.taxes)
|
||||
|
||||
itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
|
||||
|
||||
return itemised_tax, itemised_taxable_amount
|
||||
|
||||
def get_itemised_tax(taxes):
|
||||
itemised_tax = {}
|
||||
for tax in taxes:
|
||||
tax_amount_precision = tax.precision("tax_amount")
|
||||
tax_rate_precision = tax.precision("rate")
|
||||
|
||||
item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
|
||||
|
||||
for item_code, tax_data in item_tax_map.items():
|
||||
itemised_tax.setdefault(item_code, frappe._dict())
|
||||
|
||||
return headings
|
||||
|
||||
def get_table_rows(distinct_items, item_tax, tax_accounts, company_currency):
|
||||
rows = []
|
||||
for item in distinct_items:
|
||||
item_tax_record = item_tax.get(item.item_code or item.item_name)
|
||||
if not item_tax_record:
|
||||
continue
|
||||
|
||||
taxes = []
|
||||
for head in tax_accounts:
|
||||
if item_tax_record[head[0]]:
|
||||
taxes.append("<td class='text-right'>(" + item_tax_record[head[0]][0] + ") "
|
||||
+ item_tax_record[head[0]][1] + "</td>")
|
||||
if isinstance(tax_data, list) and tax_data[0]:
|
||||
precision = tax_amount_precision if tax.charge_type == "Actual" else tax_rate_precision
|
||||
|
||||
itemised_tax[item_code][tax.description] = frappe._dict(dict(
|
||||
tax_rate=flt(tax_data[0], precision),
|
||||
tax_amount=flt(tax_data[1], tax_amount_precision)
|
||||
))
|
||||
else:
|
||||
taxes.append("<td></td>")
|
||||
|
||||
rows.append("<tr><td>{item_name}</td><td class='text-right'>{taxable_amount}</td>{taxes}</tr>".format(**{
|
||||
"item_name": item.item_name,
|
||||
"taxable_amount": fmt_money(item.net_amount, item.precision("net_amount"), company_currency),
|
||||
"taxes": "\n".join(taxes)
|
||||
}))
|
||||
|
||||
return rows
|
||||
itemised_tax[item_code][tax.description] = frappe._dict(dict(
|
||||
tax_rate=flt(tax_data, tax_rate_precision),
|
||||
tax_amount=0.0
|
||||
))
|
||||
|
||||
return itemised_tax
|
||||
|
||||
def get_itemised_taxable_amount(items):
|
||||
itemised_taxable_amount = frappe._dict()
|
||||
for item in items:
|
||||
item_code = item.item_code or item.item_name
|
||||
itemised_taxable_amount.setdefault(item_code, 0)
|
||||
itemised_taxable_amount[item_code] += item.net_amount
|
||||
|
||||
return itemised_taxable_amount
|
||||
@@ -27,7 +27,8 @@ def test_recurring_document(obj, test_records):
|
||||
base_doc.set(date_field, today)
|
||||
|
||||
if base_doc.doctype == "Sales Order":
|
||||
base_doc.set("delivery_date", add_days(today, 15))
|
||||
for d in base_doc.get("items"):
|
||||
d.set("delivery_date", add_days(today, 15))
|
||||
|
||||
# monthly
|
||||
doc1 = frappe.copy_doc(base_doc)
|
||||
|
||||
43
erpnext/crm/doctype/lead/test_lead.js
Normal file
@@ -0,0 +1,43 @@
|
||||
QUnit.module("sales");
|
||||
|
||||
QUnit.test("test: lead", function (assert) {
|
||||
assert.expect(4);
|
||||
let done = assert.async();
|
||||
let lead_name = frappe.utils.get_random(10);
|
||||
frappe.run_serially([
|
||||
// test lead creation
|
||||
() => frappe.set_route("List", "Lead"),
|
||||
() => frappe.new_doc("Lead"),
|
||||
() => frappe.timeout(1),
|
||||
() => cur_frm.set_value("lead_name", lead_name),
|
||||
() => cur_frm.save(),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
assert.ok(cur_frm.doc.lead_name.includes(lead_name),
|
||||
'name correctly set');
|
||||
frappe.lead_name = cur_frm.doc.name;
|
||||
},
|
||||
// create address and contact
|
||||
() => frappe.click_link('Address & Contact'),
|
||||
() => frappe.click_button('New Address'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.set_control('address_line1', 'Gateway'),
|
||||
() => frappe.set_control('city', 'Mumbai'),
|
||||
() => cur_frm.save(),
|
||||
() => frappe.timeout(3),
|
||||
() => assert.equal(frappe.get_route()[1], 'Lead',
|
||||
'back to lead form'),
|
||||
() => frappe.click_link('Address & Contact'),
|
||||
() => assert.ok($('.address-box').text().includes('Mumbai'),
|
||||
'city is seen in address box'),
|
||||
|
||||
// make opportunity
|
||||
() => frappe.click_button('Make'),
|
||||
() => frappe.click_link('Opportunity'),
|
||||
() => frappe.timeout(2),
|
||||
() => assert.equal(cur_frm.doc.lead, frappe.lead_name,
|
||||
'lead name correctly mapped'),
|
||||
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
@@ -56,7 +56,7 @@
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Series",
|
||||
"length": 0,
|
||||
@@ -88,7 +88,7 @@
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Opportunity From",
|
||||
"length": 0,
|
||||
@@ -277,8 +277,8 @@
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Opportunity Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
@@ -310,7 +310,7 @@
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"length": 0,
|
||||
@@ -1189,7 +1189,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-06-13 14:29:07.077697",
|
||||
"modified": "2017-07-10 15:29:23.921967",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Opportunity",
|
||||
|
||||
@@ -272,4 +272,6 @@ def auto_close_opportunity():
|
||||
for opportunity in opportunities:
|
||||
doc = frappe.get_doc("Opportunity", opportunity.get("name"))
|
||||
doc.status = "Closed"
|
||||
doc.save(ignore_permissions=True)
|
||||
doc.flags.ignore_permissions = True
|
||||
doc.flags.ignore_mandatory = True
|
||||
doc.save()
|
||||
56
erpnext/crm/doctype/opportunity/test_opportunity.js
Normal file
@@ -0,0 +1,56 @@
|
||||
QUnit.test("test: opportunity", function (assert) {
|
||||
assert.expect(8);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => frappe.set_route('List', 'Opportunity'),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button('New'),
|
||||
() => frappe.timeout(1),
|
||||
() => cur_frm.set_value('enquiry_from', 'Customer'),
|
||||
() => cur_frm.set_value('customer', 'Test Customer 1'),
|
||||
|
||||
// check items
|
||||
() => cur_frm.set_value('with_items', 1),
|
||||
() => frappe.tests.set_grid_values(cur_frm, 'items', [
|
||||
[
|
||||
{item_code:'Test Product 1'},
|
||||
{qty: 4}
|
||||
]
|
||||
]),
|
||||
() => cur_frm.save(),
|
||||
() => frappe.timeout(1),
|
||||
() => {
|
||||
assert.notOk(cur_frm.is_new(), 'saved');
|
||||
frappe.opportunity_name = cur_frm.doc.name;
|
||||
},
|
||||
|
||||
// close and re-open
|
||||
() => frappe.click_button('Close'),
|
||||
() => frappe.timeout(1),
|
||||
() => assert.equal(cur_frm.doc.status, 'Closed',
|
||||
'closed'),
|
||||
|
||||
() => frappe.click_button('Reopen'),
|
||||
() => assert.equal(cur_frm.doc.status, 'Open',
|
||||
'reopened'),
|
||||
() => frappe.timeout(1),
|
||||
|
||||
// make quotation
|
||||
() => frappe.click_button('Make'),
|
||||
() => frappe.click_link('Quotation', 1),
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
assert.equal(frappe.get_route()[1], 'Quotation',
|
||||
'made quotation');
|
||||
assert.equal(cur_frm.doc.customer, 'Test Customer 1',
|
||||
'customer set in quotation');
|
||||
assert.equal(cur_frm.doc.items[0].item_code, 'Test Product 1',
|
||||
'item set in quotation');
|
||||
assert.equal(cur_frm.doc.items[0].qty, 4,
|
||||
'qty set in quotation');
|
||||
assert.equal(cur_frm.doc.items[0].prevdoc_docname, frappe.opportunity_name,
|
||||
'opportunity set in quotation');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
@@ -118,7 +118,8 @@ def make_sales_order():
|
||||
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
||||
so = frappe.get_doc(make_sales_order(q))
|
||||
so.transaction_date = frappe.flags.current_date
|
||||
so.delivery_date = frappe.utils.add_days(frappe.flags.current_date, 10)
|
||||
for d in so.get("items"):
|
||||
d.delivery_date = frappe.utils.add_days(frappe.flags.current_date, 10)
|
||||
so.insert()
|
||||
frappe.db.commit()
|
||||
so.submit()
|
||||
|
||||
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 215 KiB |
BIN
erpnext/docs/assets/img/sales_goal/sales_goal_notification.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
erpnext/docs/assets/img/sales_goal/sales_history_graph.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
erpnext/docs/assets/img/sales_goal/setting_sales_goal.gif
Normal file
|
After Width: | Height: | Size: 7.8 MiB |
|
Before Width: | Height: | Size: 273 KiB After Width: | Height: | Size: 443 KiB |
BIN
erpnext/docs/assets/img/setup/email/email-alert-set-property.png
Normal file
|
After Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 38 KiB |
@@ -640,8 +640,8 @@ attach them to the start of each source file to most effectively state
|
||||
the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.</p>
|
||||
|
||||
<pre><code> <one line="" to="" give="" the="" program's="" name="" and="" a="" brief="" idea="" of="" what="" it="" does.="">
|
||||
Copyright (C) <year> <name of="" author="">
|
||||
<pre><code> <one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
@@ -15,5 +15,6 @@ third-party-backups
|
||||
workflows
|
||||
bar-code
|
||||
company-setup
|
||||
setting-company-sales-goal
|
||||
calculate-incentive-for-sales-team
|
||||
articles
|
||||
|
||||
@@ -31,7 +31,7 @@ Most of the information in your Sales Order is the same as the Quotation.
|
||||
There are a few amongst other things that a Sales Order will ask you to
|
||||
update.
|
||||
|
||||
* Expected date of delivery.
|
||||
* Enter delivery date agaist each item. If there are multiple items and if you enter delivery date in the first row, the date will be copied to other rows as well where it is blank.
|
||||
* Customer Purchase Order number: If your customer has sent you a Purchase Order, you can update its number for future reference (in billing).
|
||||
|
||||
### Packing List
|
||||
@@ -89,9 +89,9 @@ ERPNext will automatically create new Order and mail a notification to the Email
|
||||
Once you “Submit” your Sales Order, you can now trigger different aspects of
|
||||
your organization:
|
||||
|
||||
* To begin purchase click on “Make Purchase Request”
|
||||
* To make a shipment entry click on “Make Delivery Note”
|
||||
* To bill, make “Make Sales Invoice”
|
||||
* To begin purchase click on Make -> Purchase Request
|
||||
* To make a shipment entry click on Make -> Delivery Note. You can also make Delivery Note for selected items based on delivery date.
|
||||
* To bill, make Make -> Sales Invoice
|
||||
* To stop further process on this Sales Order, click on “Stop”
|
||||
|
||||
### Submission
|
||||
|
||||
@@ -42,6 +42,7 @@ Email alerts allow you to set conditions according to the field data in your doc
|
||||
|
||||
The above example will send an Email Alert when a Task is saved with the status "Open" and the Expected End Date for the Task is the date on or before the date on which it was saved on.
|
||||
|
||||
|
||||
### Setting a Message
|
||||
|
||||
You can use both Jinja Tags (`{% raw %}{{ doc.[field_name] }}{% endraw %}`) and HTML tags in the message textbox.
|
||||
@@ -64,6 +65,17 @@ You can use both Jinja Tags (`{% raw %}{{ doc.[field_name] }}{% endraw %}`) and
|
||||
|
||||
---
|
||||
|
||||
### Setting a Value after the Alert is Set
|
||||
|
||||
Sometimes to make sure that the email alert is not sent multiple times, you can
|
||||
define a custom property (via Customize Form) like "Email Alert Sent" and then
|
||||
set this property after the alert is sent by setting the **Set Property After Alert**
|
||||
field.
|
||||
|
||||
Then you can use that as a condition in the **Condition** rules to ensure emails are not sent multiple times
|
||||
|
||||
<img class="screenshot" alt="Setting Property in Email Alert" src="{{docs_base_url}}/assets/img/setup/email/email-alert-subject.png">
|
||||
|
||||
### Example
|
||||
|
||||
1. Defining the Criteria
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Setting Company Sales Goal
|
||||
|
||||
Monthly sales targets can be set for a company via the Company master. By default, the Company master dashboard features past sales stats.
|
||||
|
||||
<img class="screenshot" alt="Sales Graph" src="{{docs_base_url}}/assets/img/sales_goal/sales_history_graph.png">
|
||||
|
||||
You can set the **Sales Target** field to track progress to track progress with respect to it.
|
||||
|
||||
<img class="screenshot" alt="Setting Sales Goal" src="{{docs_base_url}}/assets/img/sales_goal/setting_sales_goal.gif">
|
||||
|
||||
The target progress is also shown in notifications:
|
||||
|
||||
<img class="screenshot" alt="Sales Notification" src="{{docs_base_url}}/assets/img/sales_goal/sales_goal_notification.png">
|
||||
|
||||
{next}
|
||||
@@ -3,10 +3,10 @@ WORK IN PROGRESS
|
||||
-->
|
||||
# Manual de Usuario (Español)
|
||||
|
||||
### Contenido:
|
||||
### Contenido:
|
||||
|
||||
{index}
|
||||
|
||||
**Trabajo en progreso.**
|
||||
|
||||
[The Spanish Translation of the ERPNext manual is in progress. Click here to see the english manual]({{ docs_base_url }}/user/manual/en)
|
||||
[La traducción al Español del manual de ERPNext está en progreso. Click aquí para ver el manual en ingles]({{ docs_base_url }}/user/manual/en)
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
introduction
|
||||
accounts
|
||||
|
||||
20
erpnext/docs/user/manual/es/introduction/do-i-need-an-erp.md
Normal file
@@ -0,0 +1,20 @@
|
||||
ERPNext es una herramienta moderna que no solo abarca el módulo de contabilidad,
|
||||
sino que también, cubre todas las otras funciones de su negocio en una plataforma integrada.
|
||||
Tiene muchos beneficios sobre los sistemas tradicionales de contabilidad y otros ERP en el mercado.
|
||||
|
||||
### Beneficios sobre los sistemas de contabilidad tradicionales:
|
||||
|
||||
* Es más que solo contabilidad! Gestionar inventario, facturación, cotizaciones, clientes potenciales, nómina y mucho más.
|
||||
* Mantiene toda tu información segura y en un solo lugar. No siga buscando sus datos cuando más lo necesitas en diferente hojas de calculo y en diferentes ordenadores.
|
||||
Gestiona a todos tus empleados en el mismo lugar. Todos los usuarios obtienen información actualizada.
|
||||
* No más trabajo doble. No introduzcas la misma información desde su procesador de textos a su herramienta de contabilidad. Todo está integrado!
|
||||
* Manten un historial. Obten el historial completo de un cliente o un acuerdo en un solo lugar
|
||||
|
||||
### Beneficios sobre ERPs más grandes
|
||||
|
||||
* $$$ - Ahorra dinero.
|
||||
* **Más facíl de configurar:** Grandes ERP son extramadamente complicados para configurar y van a preguntar demasiadas preguntas amtes de que puedas hacer algo utíl.
|
||||
* **Más facíl de usar:** Una moderna y limpia interfaz web va a mantener sus usuarios contentos y en un entorno mas familiar.
|
||||
* **Código Abierto :** Este sistema es completamente gratis y puedes instalarlo/configurarlo donde desees.
|
||||
|
||||
{next}
|
||||
@@ -0,0 +1,31 @@
|
||||
Hay muchas manera de comenzar a utilizar ERPNext.
|
||||
|
||||
### 1\. Ver el Demo
|
||||
|
||||
Si deseas entrar en contacto con la interfaz de usuario de ERPNext y **sentir** la aplicación, solo tienes que ver el demo en:
|
||||
see the demo at:
|
||||
|
||||
* <https://demo.erpnext.com>
|
||||
|
||||
### 2\. Comienza con una cuenta gratis en ERPNext.com
|
||||
|
||||
|
||||
ERPNext.com es manejado por la organización (Frappe) que publicó ERPNext.
|
||||
Puedes iniciar con su propia cuenta en [registrandote en la página](https://erpnext.com).
|
||||
|
||||
También, puedes hostear tu aplicación en erpnext.com comprando un plan de alojamiento.
|
||||
De esta forma, estas aportando a la organización que desarrolla y mejora ERPNext.
|
||||
También obten soporte de uno-a-uno con los planes de alojamiento.
|
||||
|
||||
### 3\. Descarga una Maquina Virtual
|
||||
|
||||
Para evitar las molestias de instalar el sistema, ERPNext está disponible como una image virtual (un sistema operativo completo con ERPNext instalado).
|
||||
Puedes usarla en **cualquier** plataforma incluyendo Microsoft Windows.
|
||||
|
||||
[Click aquí para ver las instrucciones de como usar la imagen](https://erpnext.com/download)
|
||||
|
||||
### 4\. Instalar ERPNext en su ordenador Unix/Linux/Mac
|
||||
|
||||
En caso de estar relacionado con la instalación de aplicaciones en plataformas *nix, leer las instrucciones de como instalarlo usando [Frappe Bench](https://github.com/frappe/bench).
|
||||
|
||||
{next}
|
||||
@@ -0,0 +1,32 @@
|
||||
Antes de que empieces a manejar todas tus operaciones en ERPNext, primero
|
||||
deberías estar familiarizado con el sistema y los términos que utiliza.
|
||||
Por esa razón recomendamos que la implementación pase en dos fases.
|
||||
|
||||
* La **Fase de Prueba**, donde introduces información de prueba que representan sus transacciones del día a día y la **Fase de Producción**, donde comenzamos a introducir información real.
|
||||
|
||||
### Fase de Prueba
|
||||
|
||||
* Leer el manual
|
||||
* Crea una cuenta gratis en [https://erpnext.com](https://erpnext.com) (La forma más facíl de experimental).
|
||||
* Crea su primer Cliente, Suplidor y Producto. Agrega varios de estos para que se familiarice con ellos.
|
||||
* Crea un Grupo de Clientes, Grupo de Productos, Almacenes, Grupo de Suplidores, para que puedas clasificar sus productos.
|
||||
* Completar un ciclo estandar de ventas - Iniciativa > Oportunidad > Cotización > Orden de Venta > Nota de Entrega > Factura de Venta > Pago (Entrada de diario)
|
||||
* Completa un ciclo estandar de compra - Solicitud de Material > Orden de Compra > Recibo de Compra > Pagos (Entrada de diario).
|
||||
* Completar un ciclo de manofactura (si aplica) - BOM > Herramienta de Planificación de Producción > Orden de Producción > Problema de material
|
||||
* Replicar un escenario de su día a día dentro del sistema.
|
||||
* Crea un custom fields, formato de impresión, etc como sea requerido.
|
||||
|
||||
### Fase de Producción
|
||||
|
||||
Una vez ya estes falimiliarizado con ERPNext, inicia introduciendo la información real!
|
||||
|
||||
* Borra toda la información de prueba de la cuenta o inicia con una nueva instalación.
|
||||
* Si solo quieres borrar las transacciones y no las demás informaciones sobre Productos, Clientes, Suplidores, BOM etc, puedes dar click en Eliminar Transacciones de su compañia y inicia desde cero. Para hacerlo, abre el registro de la compañia via Setup > Masters > Company y eliminar las transacciones de su compañia clickeando en el botón **Eliminar las transacciones de la compañia** al final del formulario de la compañia.
|
||||
* También puedes configurar una nueva cuenta en [https://erpnext.com](https://erpnext.com), y usa los 30 días gratis. [Encuentra mas formas de usar ERPNext](/introduction/getting-started-with-erpnext)
|
||||
* Configura todos los módulos con Grupos de Clientes, Grupos de Productos, Almacenes, BOMs etc.
|
||||
* Importar Clientes, Suplidores, Productos, Contactos y Direcciones usando la Herramienta de Importación de Data.
|
||||
* Importar el inventario de apertura usando la Herramienta de Reconciliación de Inventario.
|
||||
* Crear la entrada de apertura de cuenta usando la Entrada de Diario y crea facturas de ventas pendientes y facturas de compra.
|
||||
* Si necesitas ayuda, [puedes pagar por soporte](https://erpnext.com/pricing) o [preguntar en el foro de la comunidad](https://discuss.erpnext.com).
|
||||
|
||||
{next}
|
||||
40
erpnext/docs/user/manual/es/introduction/index.md
Normal file
@@ -0,0 +1,40 @@
|
||||
## ¿Qué es un ERP y Por qué debería interesarme?
|
||||
|
||||
(Si ya sabes que necesitas un sistema todo-en-uno para su compañia, puedes pasar a la siguiente página)
|
||||
|
||||
Si eres dueño de una pequeña empresa que tiene varios empleados, debes entender que es difícil manejar la naturaleza dinámica de hacer negocios.
|
||||
Pequeñas empresas no son tan diferentes que las grandes empresas. Las pequeñas empresas contienen la mayoria de las complejidades que posee una empresa grande junto a otras reestricciones.
|
||||
Las pequeñas empresas tienen que comunicarse con clientes, hacer contabilidad, pagar impuestos, pagar nómina, gestionar tiempos,
|
||||
proporsionar bienes y servicios de calidad, responder preguntar, y mantener a todos contentos como lo hacen las grandes empresas.
|
||||
|
||||
Grandes empresas tienen la venraja de usar sistemas avanzados para manejar sus procesos de una forma mas eficiente.
|
||||
Pequeñas empresas, sin embargo, luchan para mantener las cosas organizadas. Normalmente usan un conjuntos de aplicaciones como hojas de calculos, sistemas de contabilidad,
|
||||
un CRM etc para administrarse. El problema es que no todos estan en la misma página. Un ERP cambia todo eso.
|
||||
|
||||
## ¿Qué es ERPNext?
|
||||
|
||||
ERPNext es una solución de negocio de extremo a extremo que te ayuda a manejar toda la información de su negocio en una sola aplicación
|
||||
y usado no solo para manejar operaciones, sino que tambien le permite tomar decisiones efectivas y bien documentadas justo en el momento que las necesites.
|
||||
Forma una columna vertebral de su negocio para agregar fuerza, transparencia y control a su compañia.
|
||||
|
||||
Junto con otras cosas, ERPNext te ayudará con todo lo siguiente:
|
||||
|
||||
* Mantener registro de todas sus facturas y pagos.
|
||||
* Saber que cantidad de cada producto hay disponible en almacen.
|
||||
* Identificar y hacer seguimiento de los indicadores de rendimientos (KPI's)
|
||||
* Identificar consultas abiertas de los clientes.
|
||||
* Gestionar Nómina.
|
||||
* Asignar tareas y hacer seguimiento de las mismas.
|
||||
* Mantener una base de datos de todos sus clientes, suplidores y sus contactos.
|
||||
* Preparar presupuestos.
|
||||
* Hacer seguimiento a su presupuesto y sus gastos.
|
||||
* Determinar el precio efectivo para ventas basado en la materia prima disponible, maquinaria y costo de esfuerzo.
|
||||
* Obtener recordatorios sobre el calendario de mantenimientos.
|
||||
* Publicar su página web.
|
||||
|
||||
Y Mucho mucho más.
|
||||
|
||||
|
||||
### Temas
|
||||
|
||||
{index}
|
||||
7
erpnext/docs/user/manual/es/introduction/index.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
do-i-need-an-erp
|
||||
open-source
|
||||
getting-started-with-erpnext
|
||||
the-champion
|
||||
implementation-strategy
|
||||
key-workflows
|
||||
concepts-and-terms
|
||||
13
erpnext/docs/user/manual/es/introduction/key-workflows.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Flujo De Transacciones En ERPNext
|
||||
|
||||
Este diagrama cubre como ERPNext hace el seguimiento de la información de su compañia a través de funciones claves.
|
||||
Este diagrama no cubre toda la funcionalidad o características de ERPNext.
|
||||
|
||||

|
||||
|
||||
|
||||
<img class="screenshot" alt="Workflow" src="{{docs_base_url}}/assets/img/setup/overview.png">
|
||||
|
||||
_Nota: No todos los pasos son obligatorios. ERPNext te permite pasar algunos pasos si deseas simplificar el proceso._
|
||||
|
||||
{next}
|
||||
34
erpnext/docs/user/manual/es/introduction/open-source.md
Normal file
@@ -0,0 +1,34 @@
|
||||
El código fuente de ERPNext es de código abierto. Está abierto para que todos
|
||||
podamos entenderlo, extenderlo o mejorarlo. Y es gratis!
|
||||
|
||||
Las ventajas de un Sistema de Código Abierto:
|
||||
|
||||
1. Puedes cambiar tu proveedor de servicios cuando quieras.
|
||||
2. Puedas hostear la aplicación donde quieras, incluyendo en tu propio servidor para tener completa propiedad y privacidad de la información.
|
||||
3. Puedes pedir ayuda a la comunidad en caso de necesitarla. No estas atado a un proveedor de servicios.
|
||||
4. Te puedes beneficiar de un producto que es criticado y usado por una gran cantidad de personas,
|
||||
quienes han reportado cientos de fallos y sugerencias para mejorarlo, y esto siempre va a continuar así.
|
||||
|
||||
|
||||
---
|
||||
|
||||
### Código Fuente de ERPNext
|
||||
|
||||
El repositorio que contiene el código fuente de ERPnext está disponible en GitHub y puede ser encontrado aquí
|
||||
|
||||
- [https://github.com/frappe/erpnext](https://github.com/frappe/erpnext)
|
||||
|
||||
|
||||
---
|
||||
|
||||
### Alternativas
|
||||
|
||||
Hay muchas soluciones ERP que puedes considerar. Los más populares son:
|
||||
|
||||
1. Odoo
|
||||
2. OpenBravo
|
||||
3. Apache OfBiz
|
||||
4. xTuple
|
||||
5. Compiere (y clones)
|
||||
|
||||
{next}
|
||||
41
erpnext/docs/user/manual/es/introduction/the-champion.md
Normal file
@@ -0,0 +1,41 @@
|
||||
<!-- no-heading -->
|
||||
|
||||
<h1 class="white">El campeón</h1>
|
||||
|
||||
<img alt="Champion" class="screenshot" src="{{docs_base_url}}/assets/img/setup/implementation-image.png">
|
||||
|
||||
Hemos visto docenas de implementaciones de sistemas ERP en los últimos años
|
||||
y nos hemos dado cuenta que una implementación exitosa es más sobre cosas intangibles y actitudes.
|
||||
|
||||
**Los ERP no son requeridos.**
|
||||
|
||||
Como el ejercicio.
|
||||
|
||||
El cuerpo humano puede que parezca que no requiere ejercicio hoy ni quizas mañana, pero con el pasar del tiempo,
|
||||
si desea mantener su cuerpo y su salud deberá comenzar a hacer ejercicio.
|
||||
|
||||
En esta misma forma, ERPs mejoran la salud de su compañia a largo plazo manteniendola ajustada y eficiente.
|
||||
Mientas más demores en poner las cosas en orden, más tiempo pierdes, y estas más cerca de una desastre mayor.
|
||||
|
||||
Por tanto, cuando comienzas a implementar un ERP, manten la visión en beneficios a largo plazo.
|
||||
Como el ejercicio, es doloroso al comienzo, pero va a hacer cosas maravillosas si te mantienes haciendolo.
|
||||
|
||||
* * *
|
||||
|
||||
## El Campeón
|
||||
|
||||
Un ERP significa un cambio en la organización y un cambio no sucede sin exfuerzo.
|
||||
Cada cambio requiere un campeón y es la responsabilidad de el campeón el
|
||||
organizar y motivar al equipo completo durante la implementación.
|
||||
El campeón necesita ser activo en caso que algo salga mal.
|
||||
|
||||
En muchas organizaciones que hemos visto, frecuentemente el campeón es el dueño o un Administrador.
|
||||
Ocasionalmente, el campeón es una persona externa quien es contratado con un propósito específico.
|
||||
|
||||
En cualquier caso, debes identificar su campeón primero.
|
||||
|
||||
Lo más seguro es que sea **usted!**
|
||||
|
||||
Comencemos!
|
||||
|
||||
{next}
|
||||
@@ -1,7 +1,7 @@
|
||||
# User Interface and Navigation
|
||||
|
||||
<iframe width="660" height="371" src="https://www.youtube.com/embed/HdblX7kOWWs" frameborder="0" allowfullscreen></iframe>
|
||||
<iframe width="660" height="371" src="https://www.youtube.com/embed/vKjHRzMEei0" frameborder="0" allowfullscreen></iframe>
|
||||
|
||||
**Duration: 2:17**
|
||||
**Duration: 2:41**
|
||||
|
||||
This video walks you through using the ERPNext "Desk" interface. The ERPNext User Interface is very web friendly and if you have used a website or mobile app before, the navigation should be very intuitive.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
from . import __version__ as app_version
|
||||
|
||||
app_name = "erpnext"
|
||||
app_title = "ERPNext"
|
||||
@@ -80,6 +79,7 @@ website_route_rules = [
|
||||
"parents": [{"title": _("Supplier Quotation"), "name": "quotations"}]
|
||||
}
|
||||
},
|
||||
{"from_route": "/quotation", "to_route": "Quotation"},
|
||||
{"from_route": "/shipments", "to_route": "Delivery Note"},
|
||||
{"from_route": "/shipments/<path:name>", "to_route": "order",
|
||||
"defaults": {
|
||||
@@ -110,6 +110,7 @@ standard_portal_menu_items = [
|
||||
{"title": _("Projects"), "route": "/project", "reference_doctype": "Project"},
|
||||
{"title": _("Request for Quotations"), "route": "/rfq", "reference_doctype": "Request for Quotation", "role": "Supplier"},
|
||||
{"title": _("Supplier Quotation"), "route": "/quotations", "reference_doctype": "Supplier Quotation", "role": "Supplier"},
|
||||
{"title": _("Quotations"), "route": "/quotation", "reference_doctype": "Quotation", "role":"Customer"},
|
||||
{"title": _("Orders"), "route": "/orders", "reference_doctype": "Sales Order", "role":"Customer"},
|
||||
{"title": _("Invoices"), "route": "/invoices", "reference_doctype": "Sales Invoice", "role":"Customer"},
|
||||
{"title": _("Shipments"), "route": "/shipments", "reference_doctype": "Delivery Note", "role":"Customer"},
|
||||
@@ -182,10 +183,13 @@ scheduler_events = {
|
||||
"erpnext.projects.doctype.task.task.set_tasks_as_overdue",
|
||||
"erpnext.accounts.doctype.asset.depreciation.post_depreciation_entries",
|
||||
"erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings.send_summary",
|
||||
"erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status"
|
||||
"erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status",
|
||||
"erpnext.setup.doctype.company.company.cache_companies_monthly_sales_history"
|
||||
]
|
||||
}
|
||||
|
||||
email_brand_image = "assets/erpnext/images/erpnext-logo.jpg"
|
||||
|
||||
default_mail_footer = """<div style="text-align: center;">
|
||||
<a href="https://erpnext.com?source=via_email_footer" target="_blank" style="color: #8d99a6;">
|
||||
Sent via ERPNext
|
||||
@@ -203,3 +207,11 @@ bot_parsers = [
|
||||
get_site_info = 'erpnext.utilities.get_site_info'
|
||||
|
||||
payment_gateway_enabled = "erpnext.accounts.utils.create_payment_gateway_account"
|
||||
|
||||
regional_overrides = {
|
||||
'India': {
|
||||
'erpnext.tests.test_regional.test_method': 'erpnext.regional.india.utils.test_method',
|
||||
'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_header': 'erpnext.regional.india.utils.get_itemised_tax_breakup_header',
|
||||
'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_data': 'erpnext.regional.india.utils.get_itemised_tax_breakup_data'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ from frappe.model.document import Document
|
||||
from frappe import _
|
||||
from email_reply_parser import EmailReplyParser
|
||||
from erpnext.hr.doctype.employee.employee import is_holiday
|
||||
from frappe.utils import formatdate
|
||||
from frappe.utils import global_date_format
|
||||
from markdown2 import markdown
|
||||
|
||||
class DailyWorkSummary(Document):
|
||||
@@ -24,17 +24,18 @@ class DailyWorkSummary(Document):
|
||||
|
||||
def send_summary(self):
|
||||
'''Send summary of all replies. Called at midnight'''
|
||||
message = self.get_summary_message()
|
||||
args = self.get_message_details()
|
||||
|
||||
frappe.sendmail(recipients = get_employee_emails(self.company, False),
|
||||
message = message,
|
||||
template='daily_work_summary',
|
||||
args=args,
|
||||
subject = _('Daily Work Summary for {0}').format(self.company),
|
||||
reference_doctype=self.doctype, reference_name=self.name)
|
||||
|
||||
self.db_set('status', 'Sent')
|
||||
|
||||
def get_summary_message(self):
|
||||
'''Return summary of replies as HTML'''
|
||||
def get_message_details(self):
|
||||
'''Return args for template'''
|
||||
settings = frappe.get_doc('Daily Work Summary Settings')
|
||||
|
||||
replies = frappe.get_all('Communication', fields=['content', 'text_content', 'sender'],
|
||||
@@ -45,8 +46,12 @@ class DailyWorkSummary(Document):
|
||||
did_not_reply = self.email_sent_to.split()
|
||||
|
||||
for d in replies:
|
||||
d.sender_name = frappe.db.get_value("Employee", {"user_id": d.sender},
|
||||
"employee_name") or d.sender
|
||||
emp = frappe.db.get_values("Employee", {"user_id": d.sender},
|
||||
["employee_name", "image"], as_dict=True)
|
||||
|
||||
d.sender_name = emp[0].employee_name if emp else d.sender
|
||||
d.image = emp[0].image if emp and emp[0].image else None
|
||||
|
||||
if d.sender in did_not_reply:
|
||||
did_not_reply.remove(d.sender)
|
||||
if d.text_content:
|
||||
@@ -56,30 +61,12 @@ class DailyWorkSummary(Document):
|
||||
did_not_reply = [(frappe.db.get_value("Employee", {"user_id": email}, "employee_name") or email)
|
||||
for email in did_not_reply]
|
||||
|
||||
return frappe.render_template(self.get_summary_template(),
|
||||
dict(replies=replies,
|
||||
original_message=settings.message,
|
||||
title=_('Daily Work Summary for {0}'.format(formatdate(self.creation))),
|
||||
did_not_reply= ', '.join(did_not_reply) or '',
|
||||
did_not_reply_title = _('No replies from')))
|
||||
return dict(replies=replies,
|
||||
original_message=settings.message,
|
||||
title=_('Daily Work Summary for {0}'.format(global_date_format(self.creation))),
|
||||
did_not_reply= ', '.join(did_not_reply) or '',
|
||||
did_not_reply_title = _('No replies from'))
|
||||
|
||||
def get_summary_template(self):
|
||||
return '''
|
||||
<h3>{{ title }}</h3>
|
||||
|
||||
{% for reply in replies %}
|
||||
<h4>{{ reply.sender_name }}</h4>
|
||||
<p style="padding-bottom: 20px">
|
||||
{{ reply.content }}
|
||||
</p>
|
||||
<hr>
|
||||
{% endfor %}
|
||||
|
||||
{% if did_not_reply %}
|
||||
<p>{{ did_not_reply_title }}: {{ did_not_reply }}</p>
|
||||
{% endif %}
|
||||
|
||||
'''
|
||||
|
||||
def get_employee_emails(company, only_working=True):
|
||||
'''Returns list of Employee user ids for the given company who are working today
|
||||
|
||||
@@ -12,32 +12,31 @@ import frappe.utils
|
||||
|
||||
class TestDailyWorkSummary(unittest.TestCase):
|
||||
def test_email_trigger(self):
|
||||
settings, employees, emails = self.setup_and_prepare_test()
|
||||
|
||||
for d in employees:
|
||||
self.setup_and_prepare_test()
|
||||
for d in self.employees:
|
||||
# check that email is sent to this employee
|
||||
self.assertTrue(d.user_id in [d.recipient for d in emails
|
||||
if settings.subject in d.message])
|
||||
self.assertTrue(d.user_id in [d.recipient for d in self.emails
|
||||
if self.settings.subject in d.message])
|
||||
|
||||
def test_email_trigger_failed(self):
|
||||
hour = '00'
|
||||
if frappe.utils.nowtime().split(':')[0]=='00':
|
||||
hour = '01'
|
||||
|
||||
settings, employees, emails = self.setup_and_prepare_test(hour)
|
||||
self.setup_and_prepare_test(hour)
|
||||
|
||||
for d in employees:
|
||||
for d in self.employees:
|
||||
# check that email is sent to this employee
|
||||
self.assertFalse(d.user_id in [d.recipient for d in emails
|
||||
if settings.subject in d.message])
|
||||
self.assertFalse(d.user_id in [d.recipient for d in self.emails
|
||||
if self.settings.subject in d.message])
|
||||
|
||||
def test_incoming(self):
|
||||
settings, employees, emails = self.setup_and_prepare_test()
|
||||
|
||||
# get test mail with message-id as in-reply-to
|
||||
self.setup_and_prepare_test()
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "test_data", "test-reply.raw"), "r") as f:
|
||||
test_mails = [f.read().replace('{{ sender }}', employees[-1].user_id)\
|
||||
.replace('{{ message_id }}', emails[-1].message_id)]
|
||||
test_mails = [f.read().replace('{{ sender }}', self.employees[-1].user_id)\
|
||||
.replace('{{ message_id }}', self.emails[-1].message_id)]
|
||||
|
||||
# pull the mail
|
||||
email_account = frappe.get_doc("Email Account", "_Test Email Account 1")
|
||||
@@ -47,35 +46,39 @@ class TestDailyWorkSummary(unittest.TestCase):
|
||||
daily_work_summary = frappe.get_doc('Daily Work Summary',
|
||||
frappe.get_all('Daily Work Summary')[0].name)
|
||||
|
||||
summary = daily_work_summary.get_summary_message()
|
||||
args = daily_work_summary.get_message_details()
|
||||
|
||||
self.assertTrue('I built Daily Work Summary!' in summary)
|
||||
self.assertTrue('I built Daily Work Summary!' in args.get('replies')[0].content)
|
||||
|
||||
def setup_and_prepare_test(self, hour=None):
|
||||
if not hour:
|
||||
hour = frappe.utils.nowtime().split(':')[0]
|
||||
frappe.db.sql('delete from `tabDaily Work Summary`')
|
||||
frappe.db.sql('delete from `tabEmail Queue`')
|
||||
frappe.db.sql('delete from `tabEmail Queue Recipient`')
|
||||
frappe.db.sql('delete from `tabCommunication`')
|
||||
|
||||
# setup email to trigger at this our
|
||||
settings = frappe.get_doc('Daily Work Summary Settings')
|
||||
settings.companies = []
|
||||
|
||||
settings.append('companies', dict(company='_Test Company',
|
||||
send_emails_at=hour + ':00'))
|
||||
settings.test_subject = 'this is a subject for testing summary emails'
|
||||
settings.save()
|
||||
self.setup_settings(hour)
|
||||
|
||||
from erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings \
|
||||
import trigger_emails
|
||||
trigger_emails()
|
||||
|
||||
# check if emails are created
|
||||
employees = frappe.get_all('Employee', fields = ['user_id'],
|
||||
filters=dict(company='_Test Company', status='Active'))
|
||||
self.employees = frappe.get_all('Employee', fields = ['user_id'],
|
||||
filters=dict(company='_Test Company', status='Active', user_id=('!=', 'test@example.com')))
|
||||
|
||||
emails = frappe.db.sql("""select r.recipient, q.message, q.message_id from `tabEmail Queue` as q, `tabEmail Queue Recipient` as r where q.name = r.parent""", as_dict=1)
|
||||
self.emails = frappe.db.sql("""select r.recipient, q.message, q.message_id from `tabEmail Queue` as q, `tabEmail Queue Recipient` as r where q.name = r.parent""", as_dict=1)
|
||||
|
||||
frappe.db.commit()
|
||||
|
||||
def setup_settings(self, hour=None):
|
||||
# setup email to trigger at this our
|
||||
if not hour:
|
||||
hour = frappe.utils.nowtime().split(':')[0]
|
||||
self.settings = frappe.get_doc('Daily Work Summary Settings')
|
||||
self.settings.companies = []
|
||||
|
||||
self.settings.append('companies', dict(company='_Test Company',
|
||||
send_emails_at=hour + ':00'))
|
||||
self.settings.test_subject = 'this is a subject for testing summary emails'
|
||||
self.settings.save()
|
||||
|
||||
return settings, employees, emails
|
||||
@@ -27,6 +27,10 @@ erpnext.hr.ExpenseClaimController = frappe.ui.form.Controller.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
if(!d.expense_type) {
|
||||
return;
|
||||
}
|
||||
|
||||
return frappe.call({
|
||||
method: "erpnext.hr.doctype.expense_claim.expense_claim.get_expense_claim_account",
|
||||
args: {
|
||||
@@ -226,7 +230,7 @@ frappe.ui.form.on("Expense Claim",{
|
||||
frm.fields_dict["payable_account"].get_query = function() {
|
||||
return {
|
||||
filters: {
|
||||
"root_type": "Liability",
|
||||
"report_type": "Balance Sheet",
|
||||
"account_type": "Payable"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -905,7 +905,7 @@
|
||||
"label": "Status",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Draft\nPaid\nUnpaid\nSubmitted\nCancelled",
|
||||
"options": "Draft\nPaid\nUnpaid\nRejected\nSubmitted\nCancelled",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
@@ -964,7 +964,7 @@
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2017-06-13 14:29:16.914609",
|
||||
"modified": "2017-07-17 15:47:23.255142",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Expense Claim",
|
||||
|
||||
@@ -39,10 +39,13 @@ class ExpenseClaim(AccountsController):
|
||||
"2": "Cancelled"
|
||||
}[cstr(self.docstatus or 0)]
|
||||
|
||||
if self.total_sanctioned_amount == self.total_amount_reimbursed and self.docstatus == 1:
|
||||
if self.total_sanctioned_amount > 0 and self.total_sanctioned_amount == self.total_amount_reimbursed \
|
||||
and self.docstatus == 1 and self.approval_status == 'Approved':
|
||||
self.status = "Paid"
|
||||
elif self.docstatus == 1:
|
||||
elif self.total_sanctioned_amount > 0 and self.docstatus == 1 and self.approval_status == 'Approved':
|
||||
self.status = "Unpaid"
|
||||
elif self.docstatus == 1 and self.approval_status == 'Rejected':
|
||||
self.status = 'Rejected'
|
||||
|
||||
def set_payable_account(self):
|
||||
if not self.payable_account and not self.is_paid:
|
||||
@@ -157,6 +160,9 @@ class ExpenseClaim(AccountsController):
|
||||
self.total_claimed_amount = 0
|
||||
self.total_sanctioned_amount = 0
|
||||
for d in self.get('expenses'):
|
||||
if self.approval_status == 'Rejected':
|
||||
d.sanctioned_amount = 0.0
|
||||
|
||||
self.total_claimed_amount += flt(d.claim_amount)
|
||||
self.total_sanctioned_amount += flt(d.sanctioned_amount)
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@ frappe.listview_settings['Expense Claim'] = {
|
||||
get_indicator: function(doc) {
|
||||
if(doc.status == "Paid") {
|
||||
return [__("Paid"), "green", "status,=,'Paid'"];
|
||||
} else {
|
||||
}else if(doc.status == "Unpaid") {
|
||||
return [__("Unpaid"), "orange"];
|
||||
} else if(doc.status == "Rejected") {
|
||||
return [__("Rejected"), "grey"];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -113,5 +113,23 @@ class TestExpenseClaim(unittest.TestCase):
|
||||
self.assertEquals(expected_values[gle.account][1], gle.debit)
|
||||
self.assertEquals(expected_values[gle.account][2], gle.credit)
|
||||
|
||||
def test_rejected_expense_claim(self):
|
||||
payable_account = get_payable_account("Wind Power LLC")
|
||||
expense_claim = frappe.get_doc({
|
||||
"doctype": "Expense Claim",
|
||||
"employee": "_T-Employee-0001",
|
||||
"payable_account": payable_account,
|
||||
"approval_status": "Rejected",
|
||||
"expenses":
|
||||
[{ "expense_type": "Travel", "default_account": "Travel Expenses - WP", "claim_amount": 300, "sanctioned_amount": 200 }]
|
||||
})
|
||||
expense_claim.submit()
|
||||
|
||||
self.assertEquals(expense_claim.status, 'Rejected')
|
||||
self.assertEquals(expense_claim.total_sanctioned_amount, 0.0)
|
||||
|
||||
gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': expense_claim.name})
|
||||
self.assertEquals(len(gl_entry), 0)
|
||||
|
||||
def get_payable_account(company):
|
||||
return frappe.db.get_value('Company', company, 'default_payable_account')
|
||||
|
||||
@@ -69,27 +69,17 @@ def get_events(start, end, filters=None):
|
||||
:param end: End date-time.
|
||||
:param filters: Filters (JSON).
|
||||
"""
|
||||
condition = ''
|
||||
values = {
|
||||
"start_date": getdate(start),
|
||||
"end_date": getdate(end)
|
||||
}
|
||||
|
||||
if filters:
|
||||
if isinstance(filters, basestring):
|
||||
filters = json.loads(filters)
|
||||
filters = json.loads(filters)
|
||||
else:
|
||||
filters = []
|
||||
|
||||
if filters.get('holiday_list'):
|
||||
condition = 'and hlist.name=%(holiday_list)s'
|
||||
values['holiday_list'] = filters['holiday_list']
|
||||
if start:
|
||||
filters.append(['Holiday', 'holiday_date', '>', getdate(start)])
|
||||
if end:
|
||||
filters.append(['Holiday', 'holiday_date', '<', getdate(end)])
|
||||
|
||||
data = frappe.db.sql("""select hlist.name, h.holiday_date, h.description
|
||||
from `tabHoliday List` hlist, tabHoliday h
|
||||
where h.parent = hlist.name
|
||||
and h.holiday_date is not null
|
||||
and h.holiday_date >= %(start_date)s
|
||||
and h.holiday_date <= %(end_date)s
|
||||
{condition}""".format(condition=condition),
|
||||
values, as_dict=True, update={"allDay": 1})
|
||||
|
||||
return data
|
||||
return frappe.get_list('Holiday List',
|
||||
fields=['name', '`tabHoliday`.holiday_date', '`tabHoliday`.description'],
|
||||
filters = filters,
|
||||
update={"allDay": 1})
|
||||
|
||||
@@ -63,13 +63,13 @@ class LeaveApplication(Document):
|
||||
def validate_dates(self):
|
||||
if self.from_date and self.to_date and (getdate(self.to_date) < getdate(self.from_date)):
|
||||
frappe.throw(_("To date cannot be before from date"))
|
||||
|
||||
|
||||
if self.half_day and self.half_day_date \
|
||||
and (getdate(self.half_day_date) < getdate(self.from_date)
|
||||
and (getdate(self.half_day_date) < getdate(self.from_date)
|
||||
or getdate(self.half_day_date) > getdate(self.to_date)):
|
||||
|
||||
|
||||
frappe.throw(_("Half Day Date should be between From Date and To Date"))
|
||||
|
||||
|
||||
if not is_lwp(self.leave_type):
|
||||
self.validate_dates_acorss_allocation()
|
||||
self.validate_back_dated_application()
|
||||
@@ -158,7 +158,7 @@ class LeaveApplication(Document):
|
||||
self.name = "New Leave Application"
|
||||
|
||||
for d in frappe.db.sql("""
|
||||
select
|
||||
select
|
||||
name, leave_type, posting_date, from_date, to_date, total_leave_days, half_day_date
|
||||
from `tabLeave Application`
|
||||
where employee = %(employee)s and docstatus < 2 and status in ("Open", "Approved")
|
||||
@@ -169,12 +169,12 @@ class LeaveApplication(Document):
|
||||
"to_date": self.to_date,
|
||||
"name": self.name
|
||||
}, as_dict = 1):
|
||||
|
||||
|
||||
if cint(self.half_day)==1 and getdate(self.half_day_date) == getdate(d.half_day_date) and (
|
||||
flt(self.total_leave_days)==0.5
|
||||
or getdate(self.from_date) == getdate(d.to_date)
|
||||
flt(self.total_leave_days)==0.5
|
||||
or getdate(self.from_date) == getdate(d.to_date)
|
||||
or getdate(self.to_date) == getdate(d.from_date)):
|
||||
|
||||
|
||||
total_leaves_on_half_day = self.get_total_leaves_on_half_day()
|
||||
if total_leaves_on_half_day >= 1:
|
||||
self.throw_overlap_error(d)
|
||||
@@ -199,7 +199,7 @@ class LeaveApplication(Document):
|
||||
"half_day_date": self.half_day_date,
|
||||
"name": self.name
|
||||
})[0][0]
|
||||
|
||||
|
||||
return leave_count_on_half_day_date * 0.5
|
||||
|
||||
def validate_max_days(self):
|
||||
@@ -400,7 +400,7 @@ def is_lwp(leave_type):
|
||||
return lwp and cint(lwp[0][0]) or 0
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_events(start, end):
|
||||
def get_events(start, end, filters=None):
|
||||
events = []
|
||||
|
||||
employee = frappe.db.get_value("Employee", {"user_id": frappe.session.user}, ["name", "company"],
|
||||
@@ -411,14 +411,14 @@ def get_events(start, end):
|
||||
employee=''
|
||||
company=frappe.db.get_value("Global Defaults", None, "default_company")
|
||||
|
||||
from frappe.desk.reportview import build_match_conditions
|
||||
match_conditions = build_match_conditions("Leave Application")
|
||||
from frappe.desk.reportview import get_filters_cond
|
||||
conditions = get_filters_cond("Leave Application", filters, [])
|
||||
|
||||
# show department leaves for employee
|
||||
if "Employee" in frappe.get_roles():
|
||||
add_department_leaves(events, start, end, employee, company)
|
||||
|
||||
add_leaves(events, start, end, match_conditions)
|
||||
add_leaves(events, start, end, conditions)
|
||||
|
||||
add_block_dates(events, start, end, employee, company)
|
||||
add_holidays(events, start, end, employee, company)
|
||||
@@ -435,7 +435,7 @@ def add_department_leaves(events, start, end, employee, company):
|
||||
department_employees = frappe.db.sql_list("""select name from tabEmployee where department=%s
|
||||
and company=%s""", (department, company))
|
||||
|
||||
match_conditions = "employee in (\"%s\")" % '", "'.join(department_employees)
|
||||
match_conditions = "and employee in (\"%s\")" % '", "'.join(department_employees)
|
||||
add_leaves(events, start, end, match_conditions=match_conditions)
|
||||
|
||||
def add_leaves(events, start, end, match_conditions=None):
|
||||
@@ -446,7 +446,7 @@ def add_leaves(events, start, end, match_conditions=None):
|
||||
and docstatus < 2
|
||||
and status!="Rejected" """
|
||||
if match_conditions:
|
||||
query += " and " + match_conditions
|
||||
query += match_conditions
|
||||
|
||||
for d in frappe.db.sql(query, {"start":start, "end": end}, as_dict=True):
|
||||
e = {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
var in_progress = false;
|
||||
|
||||
frappe.ui.form.on("Process Payroll", {
|
||||
onload: function (frm) {
|
||||
frm.doc.posting_date = frappe.datetime.nowdate();
|
||||
@@ -47,11 +49,12 @@ frappe.ui.form.on("Process Payroll", {
|
||||
},
|
||||
|
||||
start_date: function (frm) {
|
||||
frm.trigger("set_start_end_dates");
|
||||
},
|
||||
|
||||
end_date: function (frm) {
|
||||
frm.trigger("set_start_end_dates");
|
||||
if(!in_progress && frm.doc.start_date){
|
||||
frm.trigger("set_end_date");
|
||||
}else{
|
||||
// reset flag
|
||||
in_progress = false
|
||||
}
|
||||
},
|
||||
|
||||
salary_slip_based_on_timesheet: function (frm) {
|
||||
@@ -68,10 +71,11 @@ frappe.ui.form.on("Process Payroll", {
|
||||
method: 'erpnext.hr.doctype.process_payroll.process_payroll.get_start_end_dates',
|
||||
args: {
|
||||
payroll_frequency: frm.doc.payroll_frequency,
|
||||
start_date: frm.doc.start_date || frm.doc.posting_date
|
||||
start_date: frm.doc.posting_date
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
in_progress = true;
|
||||
frm.set_value('start_date', r.message.start_date);
|
||||
frm.set_value('end_date', r.message.end_date);
|
||||
}
|
||||
@@ -79,6 +83,21 @@ frappe.ui.form.on("Process Payroll", {
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
set_end_date: function(frm){
|
||||
frappe.call({
|
||||
method: 'erpnext.hr.doctype.process_payroll.process_payroll.get_end_date',
|
||||
args: {
|
||||
frequency: frm.doc.payroll_frequency,
|
||||
start_date: frm.doc.start_date
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
frm.set_value('end_date', r.message.end_date);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
cur_frm.cscript.display_activity_log = function (msg) {
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import cint, flt, nowdate, add_days, getdate, fmt_money
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe.utils import cint, nowdate, add_days, getdate, fmt_money, add_to_date, DATE_FORMAT
|
||||
from frappe import _
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
@@ -47,7 +48,6 @@ class ProcessPayroll(Document):
|
||||
%s """% cond, {"sal_struct": sal_struct})
|
||||
return emp_list
|
||||
|
||||
|
||||
def get_filter_condition(self):
|
||||
self.check_mandatory()
|
||||
|
||||
@@ -58,7 +58,6 @@ class ProcessPayroll(Document):
|
||||
|
||||
return cond
|
||||
|
||||
|
||||
def get_joining_releiving_condition(self):
|
||||
cond = """
|
||||
and ifnull(t1.date_of_joining, '0000-00-00') <= '%(end_date)s'
|
||||
@@ -66,7 +65,6 @@ class ProcessPayroll(Document):
|
||||
""" % {"start_date": self.start_date, "end_date": self.end_date}
|
||||
return cond
|
||||
|
||||
|
||||
def check_mandatory(self):
|
||||
for fieldname in ['company', 'start_date', 'end_date']:
|
||||
if not self.get(fieldname):
|
||||
@@ -110,7 +108,6 @@ class ProcessPayroll(Document):
|
||||
ss_list.append(ss_dict)
|
||||
return self.create_log(ss_list)
|
||||
|
||||
|
||||
def create_log(self, ss_list):
|
||||
if not ss_list or len(ss_list) < 1:
|
||||
log = "<p>" + _("No employee for the above selected criteria OR salary slip already created") + "</p>"
|
||||
@@ -134,7 +131,6 @@ class ProcessPayroll(Document):
|
||||
""" % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict)
|
||||
return ss_list
|
||||
|
||||
|
||||
def submit_salary_slips(self):
|
||||
"""
|
||||
Submit all salary slips based on selected criteria
|
||||
@@ -194,7 +190,6 @@ class ProcessPayroll(Document):
|
||||
def format_as_links(self, salary_slip):
|
||||
return ['<a href="#Form/Salary Slip/{0}">{0}</a>'.format(salary_slip)]
|
||||
|
||||
|
||||
def get_total_salary_and_loan_amounts(self):
|
||||
"""
|
||||
Get total loan principal, loan interest and salary amount from submitted salary slip based on selected criteria
|
||||
@@ -257,7 +252,6 @@ class ProcessPayroll(Document):
|
||||
|
||||
return payroll_payable_account
|
||||
|
||||
|
||||
def make_accural_jv_entry(self):
|
||||
self.check_permission('write')
|
||||
earnings = self.get_salary_component_total(component_type = "earnings") or {}
|
||||
@@ -358,7 +352,6 @@ class ProcessPayroll(Document):
|
||||
self.update(get_start_end_dates(self.payroll_frequency,
|
||||
self.start_date or self.posting_date, self.company))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_start_end_dates(payroll_frequency, start_date=None, company=None):
|
||||
'''Returns dict of start and end dates for given payroll frequency based on start_date'''
|
||||
@@ -391,6 +384,29 @@ def get_start_end_dates(payroll_frequency, start_date=None, company=None):
|
||||
'start_date': start_date, 'end_date': end_date
|
||||
})
|
||||
|
||||
def get_frequency_kwargs(frequency_name):
|
||||
frequency_dict = {
|
||||
'monthly': {'months': 1},
|
||||
'fortnightly': {'days': 14},
|
||||
'weekly': {'days': 7},
|
||||
'daily': {'days': 1}
|
||||
}
|
||||
return frequency_dict.get(frequency_name)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_end_date(start_date, frequency):
|
||||
start_date = getdate(start_date)
|
||||
frequency = frequency.lower() if frequency else 'monthly'
|
||||
kwargs = get_frequency_kwargs(frequency) if frequency != 'bimonthly' else get_frequency_kwargs('monthly')
|
||||
|
||||
# weekly, fortnightly and daily intervals have fixed days so no problems
|
||||
end_date = add_to_date(start_date, **kwargs) - relativedelta(days=1)
|
||||
if frequency != 'bimonthly':
|
||||
return dict(end_date=end_date.strftime(DATE_FORMAT))
|
||||
|
||||
else:
|
||||
return dict(end_date='')
|
||||
|
||||
def get_month_details(year, month):
|
||||
ysd = frappe.db.get_value("Fiscal Year", year, "year_start_date")
|
||||
if ysd:
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import frappe
|
||||
|
||||
import erpnext
|
||||
from frappe.utils import flt, add_months, cint, nowdate, getdate, add_days, random_string
|
||||
from frappe.utils.make_random import get_random
|
||||
import frappe
|
||||
from frappe.utils import nowdate
|
||||
from erpnext.hr.doctype.process_payroll.process_payroll import get_end_date
|
||||
|
||||
|
||||
class TestProcessPayroll(unittest.TestCase):
|
||||
def test_process_payroll(self):
|
||||
@@ -30,6 +32,16 @@ class TestProcessPayroll(unittest.TestCase):
|
||||
process_payroll.submit_salary_slips()
|
||||
if process_payroll.get_sal_slip_list(ss_status = 1):
|
||||
r = process_payroll.make_payment_entry()
|
||||
|
||||
def test_get_end_date(self):
|
||||
self.assertEqual(get_end_date('2017-01-01', 'monthly'), {'end_date': '2017-01-31'})
|
||||
self.assertEqual(get_end_date('2017-02-01', 'monthly'), {'end_date': '2017-02-28'})
|
||||
self.assertEqual(get_end_date('2017-02-01', 'fortnightly'), {'end_date': '2017-02-14'})
|
||||
self.assertEqual(get_end_date('2017-02-01', 'bimonthly'), {'end_date': ''})
|
||||
self.assertEqual(get_end_date('2017-01-01', 'bimonthly'), {'end_date': ''})
|
||||
self.assertEqual(get_end_date('2020-02-15', 'bimonthly'), {'end_date': ''})
|
||||
self.assertEqual(get_end_date('2017-02-15', 'monthly'), {'end_date': '2017-03-14'})
|
||||
self.assertEqual(get_end_date('2017-02-15', 'daily'), {'end_date': '2017-02-15'})
|
||||
|
||||
|
||||
def get_salary_component_account(sal_comp):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
@@ -12,6 +13,7 @@
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -22,6 +24,7 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Payment Date",
|
||||
@@ -40,6 +43,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -50,11 +54,13 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Principal Amount",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
@@ -68,6 +74,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -78,11 +85,13 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Interest Amount",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
@@ -96,6 +105,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -106,11 +116,13 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Total Payment",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
@@ -124,6 +136,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -134,11 +147,13 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Balance Loan Amount",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
@@ -152,17 +167,17 @@
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-01-09 12:00:10.818772",
|
||||
"modified": "2017-07-26 14:47:29.862084",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Repayment Schedule",
|
||||
@@ -172,6 +187,7 @@
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
|
||||
@@ -29,6 +29,27 @@ frappe.ui.form.on("Salary Slip", {
|
||||
})
|
||||
},
|
||||
|
||||
start_date: function(frm){
|
||||
if(frm.doc.start_date){
|
||||
frm.trigger("set_end_date");
|
||||
}
|
||||
},
|
||||
|
||||
set_end_date: function(frm){
|
||||
frappe.call({
|
||||
method: 'erpnext.hr.doctype.process_payroll.process_payroll.get_end_date',
|
||||
args: {
|
||||
frequency: frm.doc.payroll_frequency,
|
||||
start_date: frm.doc.start_date
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
frm.set_value('end_date', r.message.end_date);
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
var company = locals[':Company'][frm.doc.company];
|
||||
if(!frm.doc.letter_head && company.default_letter_head) {
|
||||
@@ -45,11 +66,17 @@ frappe.ui.form.on("Salary Slip", {
|
||||
},
|
||||
|
||||
salary_slip_based_on_timesheet: function(frm) {
|
||||
frm.trigger("toggle_fields")
|
||||
frm.trigger("toggle_fields");
|
||||
frm.set_value('start_date', '');
|
||||
},
|
||||
|
||||
payroll_frequency: function(frm) {
|
||||
frm.trigger("toggle_fields")
|
||||
frm.trigger("toggle_fields");
|
||||
frm.set_value('start_date', '');
|
||||
},
|
||||
|
||||
employee: function(frm){
|
||||
frm.set_value('start_date', '');
|
||||
},
|
||||
|
||||
toggle_fields: function(frm) {
|
||||
@@ -74,18 +101,19 @@ frappe.ui.form.on('Salary Detail', {
|
||||
// Get leave details
|
||||
//---------------------------------------------------------------------
|
||||
cur_frm.cscript.start_date = function(doc, dt, dn){
|
||||
return frappe.call({
|
||||
method: 'get_emp_and_leave_details',
|
||||
doc: locals[dt][dn],
|
||||
callback: function(r, rt) {
|
||||
cur_frm.refresh();
|
||||
calculate_all(doc, dt, dn);
|
||||
}
|
||||
});
|
||||
if(!doc.start_date){
|
||||
return frappe.call({
|
||||
method: 'get_emp_and_leave_details',
|
||||
doc: locals[dt][dn],
|
||||
callback: function(r, rt) {
|
||||
cur_frm.refresh();
|
||||
calculate_all(doc, dt, dn);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.cscript.payroll_frequency = cur_frm.cscript.salary_slip_based_on_timesheet = cur_frm.cscript.start_date;
|
||||
cur_frm.cscript.end_date = cur_frm.cscript.start_date;
|
||||
|
||||
cur_frm.cscript.employee = function(doc,dt,dn){
|
||||
doc.salary_structure = ''
|
||||
|
||||
@@ -51,6 +51,7 @@ class BOM(WebsiteGenerator):
|
||||
from erpnext.utilities.transaction_base import validate_uom_is_integer
|
||||
validate_uom_is_integer(self, "stock_uom", "stock_qty", "BOM Item")
|
||||
|
||||
self.update_stock_qty()
|
||||
self.validate_materials()
|
||||
self.set_bom_material_details()
|
||||
self.validate_operations()
|
||||
@@ -365,9 +366,6 @@ class BOM(WebsiteGenerator):
|
||||
base_total_rm_cost = 0
|
||||
|
||||
for d in self.get('items'):
|
||||
if d.bom_no:
|
||||
d.rate = self.get_bom_unitcost(d.bom_no)
|
||||
|
||||
d.base_rate = flt(d.rate) * flt(self.conversion_rate)
|
||||
d.amount = flt(d.rate, self.precision("rate", d)) * flt(d.stock_qty, self.precision("stock_qty", d))
|
||||
d.base_amount = d.amount * flt(self.conversion_rate)
|
||||
|
||||
@@ -77,7 +77,6 @@ frappe.ui.form.on("Production Order", {
|
||||
if (!frm.doc.status)
|
||||
frm.doc.status = 'Draft';
|
||||
|
||||
frm.add_fetch("sales_order", "delivery_date", "expected_delivery_date");
|
||||
frm.add_fetch("sales_order", "project", "project");
|
||||
|
||||
if(frm.doc.__islocal) {
|
||||
@@ -151,26 +150,28 @@ frappe.ui.form.on("Production Order", {
|
||||
},
|
||||
|
||||
production_item: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.manufacturing.doctype.production_order.production_order.get_item_details",
|
||||
args: {
|
||||
item: frm.doc.production_item,
|
||||
project: frm.doc.project
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
erpnext.in_production_item_onchange = true;
|
||||
$.each(["description", "stock_uom", "project", "bom_no"], function(i, field) {
|
||||
frm.set_value(field, r.message[field]);
|
||||
});
|
||||
if (frm.doc.production_item) {
|
||||
frappe.call({
|
||||
method: "erpnext.manufacturing.doctype.production_order.production_order.get_item_details",
|
||||
args: {
|
||||
item: frm.doc.production_item,
|
||||
project: frm.doc.project
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
erpnext.in_production_item_onchange = true;
|
||||
$.each(["description", "stock_uom", "project", "bom_no"], function(i, field) {
|
||||
frm.set_value(field, r.message[field]);
|
||||
});
|
||||
|
||||
if(r.message["set_scrap_wh_mandatory"]){
|
||||
frm.toggle_reqd("scrap_warehouse", true);
|
||||
if(r.message["set_scrap_wh_mandatory"]){
|
||||
frm.toggle_reqd("scrap_warehouse", true);
|
||||
}
|
||||
erpnext.in_production_item_onchange = false;
|
||||
}
|
||||
erpnext.in_production_item_onchange = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
project: function(frm) {
|
||||
@@ -210,7 +211,9 @@ frappe.ui.form.on("Production Order", {
|
||||
frappe.ui.form.on("Production Order Item", {
|
||||
source_warehouse: function(frm, cdt, cdn) {
|
||||
var row = locals[cdt][cdn];
|
||||
if(row.source_warehouse) {
|
||||
if(!row.item_code) {
|
||||
frappe.throw(__("Please set the Item Code first"));
|
||||
} else if(row.source_warehouse) {
|
||||
frappe.call({
|
||||
"method": "erpnext.stock.utils.get_latest_stock_qty",
|
||||
args: {
|
||||
@@ -263,7 +266,7 @@ erpnext.production_order = {
|
||||
erpnext.production_order.stop_production_order(frm, "Resumed");
|
||||
}, __("Status"));
|
||||
}
|
||||
|
||||
|
||||
if(!frm.doc.skip_transfer){
|
||||
if ((flt(doc.material_transferred_for_manufacturing) < flt(doc.qty))
|
||||
&& frm.doc.status != 'Stopped') {
|
||||
@@ -272,7 +275,7 @@ erpnext.production_order = {
|
||||
erpnext.production_order.make_se(frm, 'Material Transfer for Manufacture');
|
||||
});
|
||||
start_btn.addClass('btn-primary');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!frm.doc.skip_transfer){
|
||||
@@ -291,8 +294,9 @@ erpnext.production_order = {
|
||||
} else {
|
||||
if ((flt(doc.produced_qty) < flt(doc.qty)) && frm.doc.status != 'Stopped') {
|
||||
frm.has_finish_btn = true;
|
||||
var finish_btn = frm.add_custom_button(__('Finish'),
|
||||
cur_frm.cscript['Update Finished Goods']);
|
||||
var finish_btn = frm.add_custom_button(__('Finish'), function() {
|
||||
erpnext.production_order.make_se(frm, 'Manufacture');
|
||||
});
|
||||
finish_btn.addClass('btn-primary');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1358,7 +1358,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-06-13 14:29:00.457874",
|
||||
"modified": "2017-07-10 14:29:00.457874",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Production Order",
|
||||
|
||||
@@ -50,8 +50,12 @@ class ProductionOrder(Document):
|
||||
|
||||
def validate_sales_order(self):
|
||||
if self.sales_order:
|
||||
so = frappe.db.sql("""select name, delivery_date, project from `tabSales Order`
|
||||
where name=%s and docstatus = 1""", self.sales_order, as_dict=1)
|
||||
so = frappe.db.sql("""
|
||||
select so.name, so_item.delivery_date, so.project
|
||||
from `tabSales Order` so, `tabSales Order Item` so_item
|
||||
where so.name=%s and so.name=so_item.parent
|
||||
and so.docstatus = 1 and so_item.item_code=%s
|
||||
""", (self.sales_order, self.production_item), as_dict=1)
|
||||
|
||||
if len(so):
|
||||
if not self.expected_delivery_date:
|
||||
@@ -510,6 +514,12 @@ def check_if_scrap_warehouse_mandatory(bom_no):
|
||||
|
||||
return res
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_production_order_ops(name):
|
||||
po = frappe.get_doc('Production Order', name)
|
||||
po.set_production_order_operations()
|
||||
po.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_stock_entry(production_order_id, purpose, qty=None):
|
||||
production_order = frappe.get_doc("Production Order", production_order_id)
|
||||
|
||||
@@ -353,7 +353,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-05-15 17:37:20.212361",
|
||||
"modified": "2017-07-10 17:37:20.212361",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Production Order Item",
|
||||
|
||||
@@ -8,3 +8,6 @@ from frappe.model.document import Document
|
||||
|
||||
class ProductionOrderItem(Document):
|
||||
pass
|
||||
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("Production Order Item", ["item_code", "source_warehouse"])
|
||||
@@ -0,0 +1,23 @@
|
||||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Workstation", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially('Workstation', [
|
||||
// insert a new Workstation
|
||||
() => frappe.tests.make([
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:workstation_name",
|
||||
@@ -12,11 +13,12 @@
|
||||
"editable_grid": 0,
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"fieldname": "description_and_warehouse",
|
||||
"fieldname": "description_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
@@ -25,7 +27,7 @@
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "",
|
||||
"label": "Description",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
@@ -41,6 +43,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -71,6 +74,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -102,65 +106,7 @@
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"fieldname": "holiday_list",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Holiday List",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Holiday List",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -190,6 +136,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -221,6 +168,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -252,6 +200,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -280,6 +229,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -311,6 +261,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -342,6 +293,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -373,6 +325,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -402,6 +355,7 @@
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
@@ -430,20 +384,52 @@
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"fieldname": "holiday_list",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Holiday List",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Holiday List",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "icon-wrench",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-02-17 17:26:57.238167",
|
||||
"modified": "2017-07-18 22:28:50.163219",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Workstation",
|
||||
@@ -470,11 +456,11 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 0,
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/* eslint-disable */
|
||||
frappe.listview_settings['Workstation'] = {
|
||||
// add_fields: ["status"],
|
||||
// filters:[["status","=", "Open"]]
|
||||
};
|
||||
@@ -413,4 +413,14 @@ execute:frappe.reload_doc('regional', 'doctype', 'gst_hsn_code')
|
||||
erpnext.patches.v8_1.removed_roles_from_gst_report_non_indian_account
|
||||
erpnext.patches.v8_1.gst_fixes #2017-07-06
|
||||
erpnext.patches.v8_0.update_production_orders
|
||||
erpnext.patches.v8_1.remove_sales_invoice_from_returned_serial_no
|
||||
erpnext.patches.v8_1.remove_sales_invoice_from_returned_serial_no
|
||||
erpnext.patches.v8_1.allow_invoice_copy_to_edit_after_submit
|
||||
erpnext.patches.v8_1.add_hsn_sac_codes
|
||||
erpnext.patches.v8_1.update_gst_state #17-07-2017
|
||||
erpnext.patches.v8_1.removed_report_support_hours
|
||||
erpnext.patches.v8_1.add_indexes_in_transaction_doctypes
|
||||
erpnext.patches.v8_3.set_restrict_to_domain_for_module_def
|
||||
erpnext.patches.v8_1.update_expense_claim_status
|
||||
erpnext.patches.v8_3.update_company_total_sales
|
||||
erpnext.patches.v8_1.set_delivery_date_in_so_item
|
||||
erpnext.patches.v8_5.fix_tax_breakup_for_non_invoice_docs
|
||||
@@ -51,7 +51,7 @@ def check_is_warehouse_associated_with_company():
|
||||
def make_warehouse_nestedset(company=None):
|
||||
validate_parent_account_for_warehouse(company)
|
||||
stock_account_group = get_stock_account_group(company.name)
|
||||
enable_perpetual_inventory = cint(erpnext.is_perpetual_inventory_enabled(company)) or 0
|
||||
enable_perpetual_inventory = cint(erpnext.is_perpetual_inventory_enabled(company.name)) or 0
|
||||
if not stock_account_group and enable_perpetual_inventory:
|
||||
return
|
||||
|
||||
|
||||
@@ -7,10 +7,11 @@ import frappe, erpnext
|
||||
def execute():
|
||||
frappe.reload_doctype("Account")
|
||||
|
||||
warehouses = frappe.db.sql_list("""select name, company from tabAccount
|
||||
warehouses = frappe.db.sql("""select name, company from tabAccount
|
||||
where account_type = 'Stock' and is_group = 0
|
||||
and (warehouse is null or warehouse = '')""", as_dict)
|
||||
and (warehouse is null or warehouse = '')""", as_dict=1)
|
||||
warehouses = [d.name for d in warehouses if erpnext.is_perpetual_inventory_enabled(d.company)]
|
||||
|
||||
if len(warehouses) > 0:
|
||||
warehouses = set_warehouse_for_stock_account(warehouses)
|
||||
if not warehouses:
|
||||
|
||||
@@ -11,6 +11,9 @@ def execute():
|
||||
for dt in ("assessment", "course", "fees"):
|
||||
frappe.reload_doc("schools", "doctype", dt)
|
||||
|
||||
for dt in ("domain", "has_domain", "domain_settings"):
|
||||
frappe.reload_doc("core", "doctype", dt)
|
||||
|
||||
frappe.reload_doc('website', 'doctype', 'portal_menu_item')
|
||||
|
||||
frappe.get_doc('Portal Settings').sync_menu()
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Copyright (c) 2017, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
# new field address_html is created in place of address field for the company's address in PR #8754 (without patch)
|
||||
# so here is the patch for moving the address details in the address doc
|
||||
company_list = []
|
||||
if 'address' in frappe.db.get_table_columns('Company'):
|
||||
company_list = frappe.db.sql('''select name, address from `tabCompany`
|
||||
where address is not null and address != ""''', as_dict=1)
|
||||
|
||||
for company in company_list:
|
||||
add_list = company.address.split(" ")
|
||||
if ',' in company.address:
|
||||
add_list = company.address.rpartition(',')
|
||||
elif ' ' in company.address:
|
||||
add_list = company.address.rpartition(' ')
|
||||
else:
|
||||
add_list = [company.address, None, company.address]
|
||||
|
||||
doc = frappe.get_doc({
|
||||
"doctype":"Address",
|
||||
"address_line1": add_list[0],
|
||||
"city": add_list[2],
|
||||
"links": [{
|
||||
"link_doctype": "Company",
|
||||
"link_name": company.name
|
||||
}]
|
||||
})
|
||||
doc.save()
|
||||
@@ -9,8 +9,8 @@ from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
def execute():
|
||||
# for converting student batch into student group
|
||||
for doctype in ["Student Group", "Student Group Student", "Student Group Instructor", "Student Attendance"]:
|
||||
frappe.reload_doc("schools", "doctype", doctype)
|
||||
for doctype in ["Student Group", "Student Group Student", "Student Group Instructor", "Student Attendance", "Student"]:
|
||||
frappe.reload_doc("schools", "doctype", frappe.scrub(doctype))
|
||||
|
||||
if frappe.db.table_exists("Student Batch"):
|
||||
student_batches = frappe.db.sql('''select name as student_group_name, student_batch_name as batch,
|
||||
|
||||
10
erpnext/patches/v8_1/add_hsn_sac_codes.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import frappe
|
||||
from erpnext.regional.india.setup import setup
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||
if not company:
|
||||
return
|
||||
|
||||
# call setup for india
|
||||
setup(patch=True)
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
for dt in ("Sales Order Item", "Purchase Order Item",
|
||||
"Material Request Item", "Production Order Item", "Packed Item"):
|
||||
frappe.get_doc("DocType", dt).run_module_method("on_doctype_update")
|
||||
@@ -0,0 +1,12 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
inv_copy_options = "ORIGINAL FOR RECIPIENT\nDUPLICATE FOR TRANSPORTER\nDUPLICATE FOR SUPPLIER\nTRIPLICATE FOR SUPPLIER"
|
||||
|
||||
frappe.db.sql("""update `tabCustom Field` set allow_on_submit=1, options=%s
|
||||
where fieldname='invoice_copy' and dt = 'Sales Invoice'
|
||||
""", inv_copy_options)
|
||||
|
||||
frappe.db.sql("""update `tabCustom Field` set read_only=1
|
||||
where fieldname='gst_state_number' and dt = 'Address'
|
||||
""")
|
||||
@@ -44,7 +44,7 @@ def add_custom_fields():
|
||||
],
|
||||
'Sales Invoice': [
|
||||
dict(fieldname='invoice_copy', label='Invoice Copy',
|
||||
fieldtype='Select', insert_after='project', print_hide=1,
|
||||
fieldtype='Select', insert_after='project', print_hide=1, allow_on_submit=1,
|
||||
options='ORIGINAL FOR RECIPIENT\nDUPLICATE FOR TRANSPORTER\nTRIPLICATE FOR SUPPLIER'),
|
||||
],
|
||||
'Sales Order Item': [hsn_sac_field],
|
||||
|
||||
14
erpnext/patches/v8_1/removed_report_support_hours.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# Copyright (c) 2017, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.db.sql(""" update `tabAuto Email Report` set report = %s
|
||||
where name = %s""", ('Support Hour Distribution', 'Support Hours'))
|
||||
|
||||
frappe.db.sql(""" update `tabCustom Role` set report = %s
|
||||
where report = %s""", ('Support Hour Distribution', 'Support Hours'))
|
||||
|
||||
frappe.delete_doc('Report', 'Support Hours')
|
||||
13
erpnext/patches/v8_1/set_delivery_date_in_so_item.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype("Sales Order")
|
||||
frappe.reload_doctype("Sales Order Item")
|
||||
|
||||
frappe.db.sql("""update `tabSales Order` set final_delivery_date = delivery_date where docstatus=1""")
|
||||
|
||||
frappe.db.sql("""
|
||||
update `tabSales Order` so, `tabSales Order Item` so_item
|
||||
set so_item.delivery_date = so.delivery_date
|
||||
where so.name = so_item.parent
|
||||
""")
|
||||
@@ -5,6 +5,7 @@ def execute():
|
||||
frappe.reload_doc('regional', 'doctype', 'gst_settings')
|
||||
frappe.reload_doc('regional', 'doctype', 'gst_hsn_code')
|
||||
frappe.reload_doc('stock', 'doctype', 'item')
|
||||
frappe.reload_doc("stock", "doctype", "customs_tariff_number")
|
||||
|
||||
for report_name in ('GST Sales Register', 'GST Purchase Register',
|
||||
'GST Itemised Sales Register', 'GST Itemised Purchase Register'):
|
||||
|
||||
23
erpnext/patches/v8_1/update_expense_claim_status.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Copyright (c) 2017, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype('Expense Claim')
|
||||
|
||||
for data in frappe.db.sql(""" select name from `tabExpense Claim`
|
||||
where (docstatus=1 and total_sanctioned_amount=0 and status = 'Paid') or
|
||||
(docstatus = 1 and approval_status = 'Rejected' and total_sanctioned_amount > 0)""", as_dict=1):
|
||||
doc = frappe.get_doc('Expense Claim', data.name)
|
||||
if doc.approval_status == 'Rejected':
|
||||
for d in doc.expenses:
|
||||
d.db_set("sanctioned_amount", 0, update_modified = False)
|
||||
doc.db_set("total_sanctioned_amount", 0, update_modified = False)
|
||||
|
||||
frappe.db.sql(""" delete from `tabGL Entry` where voucher_type = 'Expense Claim'
|
||||
and voucher_no = %s""", (doc.name))
|
||||
|
||||
doc.set_status()
|
||||
doc.db_set("status", doc.status, update_modified = False)
|
||||
14
erpnext/patches/v8_1/update_gst_state.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import frappe
|
||||
from erpnext.regional.india import states
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||
if not company:
|
||||
return
|
||||
|
||||
if not frappe.db.get_value("Custom Field", filters={'fieldname':'gst_state'}):
|
||||
return
|
||||
|
||||
frappe.db.sql("update `tabCustom Field` set options=%s where fieldname='gst_state'", '\n'.join(states))
|
||||
frappe.db.sql("update `tabAddress` set gst_state='Chhattisgarh' where gst_state='Chattisgarh'")
|
||||
frappe.db.sql("update `tabAddress` set gst_state_number='05' where gst_state='Uttarakhand'")
|
||||