Compare commits

...

114 Commits

Author SHA1 Message Date
Saurabh
332b4171c0 Merge branch 'hotfix' 2019-04-16 13:34:52 +05:30
Saurabh
e5544b8c86 bumped to version 11.1.21 2019-04-16 14:04:52 +06:00
sahil28297
38e5e7f616 Merge pull request #17246 from frappe/revert-17226-site_sync
Revert "feat(site_sync): return erpnext data in level"
2019-04-16 13:22:18 +05:30
sahil28297
a595346769 Revert "feat(site_sync): return erpnext data in level" 2019-04-16 13:21:24 +05:30
Nabin Hait
8dace802dc Merge pull request #17243 from hrwX/delivery_note_fix_v11
fix(Delivery Note): show get items even if note has been amended
2019-04-16 13:19:58 +05:30
Himanshu
ac6259dfe8 fix: let user delete the elements of items 2019-04-16 13:18:47 +05:30
Nabin Hait
f801cc953b Merge pull request #17152 from Alchez/hotfix-company-address-label
fix(selling): Add missing label to company address field
2019-04-16 12:45:40 +05:30
Nabin Hait
c117048fde Merge pull request #17226 from sahil28297/site_sync
feat(site_sync): return erpnext data in level
2019-04-16 12:42:45 +05:30
Himanshu Warekar
73fd508ccf fix: show get items even if note has been amended 2019-04-16 12:15:49 +05:30
Nabin Hait
734c32b970 Merge pull request #17234 from nabinhait/pr-to-pi
fix: Invoice against partially returned DN/PR
2019-04-16 09:46:24 +05:30
Nabin Hait
fa862e6814 Merge pull request #17236 from hrwX/remove_asset_permission_v11
fix(Asset): Remove user permission for employee in asset
2019-04-16 09:45:54 +05:30
Himanshu Warekar
cc581d21f0 Merge branch 'hotfix' of https://github.com/frappe/erpnext into remove_asset_permission_v11 2019-04-15 22:54:42 +05:30
Himanshu Warekar
dca60888ce fix: remove user permission for emp in asset 2019-04-15 22:52:50 +05:30
Nabin Hait
b2465c7a69 fix: Invoice against partially returned DN/PR 2019-04-15 21:02:16 +05:30
rohitwaghchaure
14477c7f51 Merge pull request #17161 from karthikeyan5/hotfix-woocommerce-fix
fix(woocommerce integration): 403 error and adding defaults
2019-04-15 19:34:54 +05:30
Nabin Hait
1024b55f99 Fixed merge conflict 2019-04-15 11:41:54 +05:30
Nabin Hait
9ba7b678fe fix: Bank reconciliation cleanup 2019-04-15 11:33:06 +05:30
Nabin Hait
a8c8e6b78a Merge pull request #16752 from nabinhait/limit-cond-fix
fix: Limit conditions while fetching payment entries
2019-04-15 10:19:12 +05:30
Nabin Hait
ce107086e7 Merge pull request #17083 from frappe/revert-16926-salary-slip-fix
Revert "fix(Salary Slip): Consider Leave without Pay for calculation"
2019-04-15 10:14:12 +05:30
Nabin Hait
49d1449d2b Merge branch 'hotfix' into revert-16926-salary-slip-fix 2019-04-15 10:13:31 +05:30
Nabin Hait
2f6789e54d Merge pull request #17228 from rohitwaghchaure/pos_not_working_if_user_can_access_more_than_one_company
fix: POS not working if user has access of multiple company
2019-04-15 10:12:51 +05:30
Nabin Hait
f2893e5701 Merge pull request #17215 from nabinhait/project-task-opt-tests
perf: Project task optimization
2019-04-15 10:11:16 +05:30
Nabin Hait
870410c9d5 Merge pull request #17180 from netchampfaris/duplicate-variant-check
fix: Validate variant attributes only if is_new
2019-04-15 10:09:12 +05:30
Nabin Hait
30bca30f20 Merge pull request #17185 from netchampfaris/allow-items-not-in-stock
feat: Allow items not in stock to be added in cart
2019-04-15 10:07:49 +05:30
Nabin Hait
80f1d5f63d Merge pull request #17217 from netchampfaris/item-price-packing-unit
fix: Set default value for Packing Unit as 0
2019-04-15 10:07:00 +05:30
Nabin Hait
76d4fa9f2b Merge pull request #17221 from nabinhait/supplier-sales-analytics
fix: supplier wise sales analytics report
2019-04-15 10:05:43 +05:30
Nabin Hait
e17c9d9978 Merge pull request #17225 from hrwX/naming_series
fix(Naming Series): Naming series
2019-04-15 10:05:17 +05:30
Rohit Waghchaure
548e93b2d3 fix: POS not working if user has access of multiple company 2019-04-14 19:39:11 +05:30
sahil28297
76eb9b32b3 Merge branch 'hotfix' into site_sync 2019-04-14 18:58:28 +05:30
Sahil Khan
0ac4cfa9b1 fix(site_sync): remove duplicate entry 2019-04-14 18:29:49 +05:30
Himanshu
dc34393b8a fix: allow braces for custom field names 2019-04-14 00:54:24 +05:30
Himanshu
023a865e1e Merge pull request #7 from frappe/hotfix
Hotfix
2019-04-14 00:49:41 +05:30
rohitwaghchaure
d2a7ec1add Merge pull request #17222 from Anurag810/pay_fix
fix: handle for party type member in payment entry(v11)
2019-04-13 00:10:30 +05:30
rohitwaghchaure
564ee5399c Merge pull request #17223 from rohitwaghchaure/user_permissions_are_not_working_for_stock_ledger
fix: user permissions are not working on stock ledger report
2019-04-13 00:09:58 +05:30
karthikeyan5
2518a2ab16 fix(woocommerce integration): travis fix 2019-04-12 19:35:07 +05:30
karthikeyan5
df3e8853ae fix(woocommerce integration): fix strange travis error
the patch was working locally. But, in was failing on travis. The strange thing was that the patch running in travis was looking for woocommerce_settings in the path 'frappe.core.doctype.woocommerce_settings.woocommerce_settings'
2019-04-12 19:35:07 +05:30
karthikeyan5
a0b7ff60b8 fix(woocommerce integration): possible travis fix
possible fix for travis patch error "Error: No module named woocommerce_settings.woocommerce_settings)"
2019-04-12 19:35:07 +05:30
karthikeyan5
97383716e6 fix(woocommerce integration): error in new-site
resolving "Could not find UOM: Nos" error in travis
2019-04-12 19:35:06 +05:30
karthikeyan5
f788117b3e fix(woocommerce integration): defaults in settings 2019-04-12 19:35:06 +05:30
karthikeyan5
6784335e2c fix(woocommerce integration): fixing 403 error 2019-04-12 19:35:06 +05:30
Rohit Waghchaure
c5c9dc5f6d fix: user permissions are not working on stock ledger report 2019-04-12 16:58:51 +05:30
Nabin Hait
819e24ddde fix: supplier wise sales analytics report 2019-04-12 15:11:11 +05:30
Anurag Mishra
313ed4feeb fix: handle for party type member in payment entry 2019-04-12 15:11:06 +05:30
Nabin Hait
5157fa9233 Merge pull request #17150 from nabinhait/ar-credit-note
fix: Show standalone credit note in Accounts receivable report
2019-04-12 14:18:17 +05:30
Faris Ansari
774b96495f fix: Set default value for Packing Unit as 0
The default value 1 assumes Items will be always packed in integer
quantities. This is not the usual case.
2019-04-12 12:38:04 +05:30
Nabin Hait
ea4c2c9e7d fix: task optimisation and test case fixes 2019-04-12 11:33:28 +05:30
Nabin Hait
b42bbf1b6f perf: Optimisation of project and task updation 2019-04-12 11:33:28 +05:30
Nabin Hait
c768febac6 Merge pull request #17206 from rohitwaghchaure/fix_pending_so_items_for_purchase_reques_report
fix: Pending SO Items For Purchase Request report not showing the so, requested and pending quantity correctly
2019-04-12 11:11:16 +05:30
Nabin Hait
0449d30423 Merge pull request #16976 from ESS-LLP/patient_hotfix
fix: Patient relation - patient link is not showing
2019-04-12 11:08:24 +05:30
Nabin Hait
f17dfb0ebe Merge pull request #17157 from Anurag810/vedmata-print-fixes
fix: Vedmata print fixes
2019-04-12 11:07:24 +05:30
Nabin Hait
13273412d1 Merge pull request #17207 from Anurag810/timesheet_report_amount_fixes
fix: timesheet report not showing total amount correctly
2019-04-12 11:05:12 +05:30
Nabin Hait
f198b1d032 Merge branch 'hotfix' into timesheet_report_amount_fixes 2019-04-12 11:05:03 +05:30
Nabin Hait
9512a43d44 Merge pull request #17187 from nabinhait/rounding-adjustment-gle
fix: Rounding Adjustment GL Entry
2019-04-12 11:00:29 +05:30
Anurag Mishra
50db128ff1 fix: timesheet report not showing total amount correctly 2019-04-11 16:07:38 +05:30
Rohit Waghchaure
5eaf7d0517 fix: Pending SO Items For Purchase Request not showing the so quantity correctly if so has duplicate items 2019-04-11 13:56:40 +05:30
Nabin Hait
760b01912a Merge branch 'hotfix' into limit-cond-fix 2019-04-11 11:47:48 +05:30
Nabin Hait
4c331206f1 Merge branch 'hotfix' into patient_hotfix 2019-04-11 11:46:53 +05:30
Nabin Hait
7fece8f431 Merge branch 'hotfix' into revert-16926-salary-slip-fix 2019-04-11 11:46:35 +05:30
Nabin Hait
bd7a165318 Merge branch 'hotfix' into ar-credit-note 2019-04-11 11:46:16 +05:30
Nabin Hait
4114365017 Merge branch 'hotfix' into hotfix-company-address-label 2019-04-11 11:46:08 +05:30
Nabin Hait
aace25ac2b Merge branch 'hotfix' into vedmata-print-fixes 2019-04-11 11:46:00 +05:30
Nabin Hait
697f1186c0 Merge branch 'hotfix' into duplicate-variant-check 2019-04-11 11:45:49 +05:30
Nabin Hait
55bee7a393 Merge branch 'hotfix' into allow-items-not-in-stock 2019-04-11 11:45:42 +05:30
Nabin Hait
c151b58acd Merge branch 'hotfix' into rounding-adjustment-gle 2019-04-11 11:45:36 +05:30
Sahil Khan
ef73452abe feat(sync_site): return erpnext data in levels 2019-04-10 15:29:49 +05:30
Nabin Hait
ff73090ad2 fix: Rounding Adjustment GL Entry 2019-04-09 19:24:54 +05:30
Faris Ansari
b63adcbac7 feat: Allow items not in stock to be added in cart 2019-04-09 18:41:31 +05:30
Faris Ansari
f492d5f61d fix: Validate variant attributes only if is_new 2019-04-09 15:19:10 +05:30
Anurag Mishra
4ac386d0fe fix: Removed Extra page on generating pdf in print formats 2019-04-08 11:43:24 +05:30
Anurag Mishra
4753bd4519 fix: UI on generating pdf in print format 2019-04-08 10:53:19 +05:30
Anurag Mishra
76815cf2be fix: removed before from accounts_controlle.pyr and fetch the gl from frontend 2019-04-06 12:07:40 +05:30
Rohan Bansal
936d147b4b fix(selling): Add missing label to company address field 2019-04-05 18:15:04 +05:30
Nabin Hait
20090306f6 fix: Show standalone credit note in Accounts receivable report 2019-04-05 18:06:07 +05:30
Anurag Mishra
a1a7beb12e fix: Print Auditing print format 2019-04-05 12:35:46 +05:30
Nabin Hait
3d6b51089c Revert "fix(Salary Slip): Consider Leave without Pay for calculation (#16926)"
This reverts commit 6343a697a2.
2019-04-01 11:09:55 +05:30
Nabin Hait
bf5ea691cf fixed merge conflict 2019-03-28 11:35:39 +05:30
Jamsheer
48e206d983 fix: Patient relation - patient link is not showing 2019-03-21 16:11:12 +05:30
Himanshu
bed6f4748e Merge pull request #6 from frappe/hotfix
Hotfix
2019-03-18 20:48:26 +05:30
Himanshu
ee2b523b31 Merge pull request #5 from frappe/hotfix
Hotfix
2019-03-09 00:05:50 +05:30
Nabin Hait
4ff2b0114f fix: Removed limit conditionas it does not make sense in get_outstanding function 2019-02-21 17:48:21 +05:30
Nabin Hait
e7cc6649eb fix: Limit conditions while fetching payment entries 2019-02-21 17:23:23 +05:30
Charles-Henri Decultot
4d19d344b6 Merge branch 'hotfix' into plaid_reconciliation 2019-02-19 11:05:00 +00:00
Himanshu
e7bc2beea0 Remove illegal character after break 2019-02-06 15:55:49 +05:30
Himanshu
cd416a3135 Merge pull request #4 from frappe/hotfix
Hotfix
2019-02-06 15:54:33 +05:30
Nabin Hait
2a0e8e24ec Merge branch 'staging-fixes' into plaid_reconciliation 2019-01-24 14:11:00 +05:30
Charles-Henri Decultot
c75300dc43 Purchase invoice modified date 2019-01-07 15:46:25 +00:00
Charles-Henri Decultot
8d36b362d1 Merge conflict resolution 2019-01-07 15:24:39 +00:00
Charles-Henri Decultot
4c57fae726 Codacy correction 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
2d1b5b0769 Codacy corrections 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
1d7646f31f Codacy corrections 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
1a19746904 Codacy corrections 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
e7fec6e659 Codacy corrections 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
c45e271b3e Add button to unlink bank account 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
422d483baf Move actions menu to standard menu 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
c56f771c81 UX corrections + additional tests 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
89923b84b1 UX enhancements 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
c936f07a1e Correct Travis error 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
aea2fbf82d Correct test case for Travis 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
7a1ea42271 Addition of test cases 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
58438f4e5b Duplicate query to avoid SQL injection 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
f6d18e81e9 Modify SQL queries and add a test case 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
e8f3050e27 Codacy corrections 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
cbe63ec418 Codacy corrections 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
94899981d3 Dev cleanup 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
6a4dae3a9d Codacy corrections + sql queries 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
57c6b49d1a Dev cleanup 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
eae7424984 Cleanup dev 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
e394cec194 Bank reconciliation dashboard 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
818492387a WIP 2019-01-07 15:21:57 +00:00
Charles-Henri Decultot
31cb24f48d Bank reconciliation WIP 2019-01-07 15:20:06 +00:00
Charles-Henri Decultot
590d8d3d3e Bank reconciliation wip 2019-01-07 15:20:06 +00:00
Charles-Henri Decultot
6025e498f2 Bank reconciliation wip 2019-01-07 15:20:06 +00:00
Charles-Henri Decultot
09cad814cd Reconciliation dashboard wip 2019-01-07 15:20:06 +00:00
Charles-Henri Decultot
c75a2b1eed Plaid integration 2019-01-07 15:20:06 +00:00
104 changed files with 15040 additions and 10223 deletions

View File

@@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
__version__ = '11.1.20'
__version__ = '11.1.21'
def get_default_company(user=None):
'''Get default company for user'''

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Account Subtype', {
refresh: function() {
}
});

View File

@@ -0,0 +1,134 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:account_subtype",
"beta": 0,
"creation": "2018-10-25 15:46:08.054586",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "account_subtype",
"fieldtype": "Data",
"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": "Account Subtype",
"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,
"translatable": 0,
"unique": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-10-25 15:47:03.841390",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account Subtype",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
}

View File

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class AccountSubtype(Document):
pass

View File

@@ -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: Account Subtype", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Account Subtype
() => frappe.tests.make('Account Subtype', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestAccountSubtype(unittest.TestCase):
pass

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Account Type', {
refresh: function() {
}
});

View File

@@ -0,0 +1,134 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:account_type",
"beta": 0,
"creation": "2018-10-25 15:45:45.789963",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "account_type",
"fieldtype": "Data",
"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": "Account Type",
"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,
"translatable": 0,
"unique": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-10-25 15:46:51.042604",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account Type",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
}

View File

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class AccountType(Document):
pass

View File

@@ -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: Account Type", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Account Type
() => frappe.tests.make('Account Type', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestAccountType(unittest.TestCase):
pass

View File

@@ -2,7 +2,29 @@
// For license information, please see license.txt
frappe.ui.form.on('Bank', {
onload: function(frm) {
add_fields_to_mapping_table(frm);
},
refresh: function(frm) {
add_fields_to_mapping_table(frm);
}
});
let add_fields_to_mapping_table = function (frm) {
let options = [];
frappe.model.with_doctype("Bank Transaction", function() {
let meta = frappe.get_meta("Bank Transaction");
meta.fields.forEach(value => {
if (!["Section Break", "Column Break"].includes(value.fieldtype)) {
options.push(value.fieldname);
}
});
});
frappe.meta.get_docfield("Bank Transaction Mapping", "bank_transaction_field",
frm.doc.name).options = options;
frm.fields_dict.bank_transaction_mapping.grid.refresh();
};

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
@@ -15,6 +16,7 @@
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -42,6 +44,134 @@
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "data_import_configuration_section",
"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": "Data Import Configuration",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bank_transaction_mapping",
"fieldtype": "Table",
"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": "Bank Transaction Mapping",
"length": 0,
"no_copy": 0,
"options": "Bank Transaction Mapping",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_4",
"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,
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "plaid_access_token",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Plaid Access Token",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
@@ -55,7 +185,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-04-07 17:00:21.246202",
"modified": "2018-11-27 16:12:13.938776",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank",
@@ -64,7 +194,6 @@
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
@@ -90,5 +219,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
"track_seen": 0,
"track_views": 0
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Bank Account', {
@@ -24,5 +24,13 @@ frappe.ui.form.on('Bank Account', {
else {
frappe.contacts.render_address_and_contact(frm);
}
if (frm.doc.integration_id) {
frm.add_custom_button(__("Unlink external integrations"), function() {
frappe.confirm(__("This action will unlink this account from any external service integrating ERPNext with your bank accounts. It cannot be undone. Are you certain ?"), function() {
frm.set_value("integration_id", "");
});
});
}
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,9 @@ class BankAccount(Document):
"""Load address and contacts in `__onload`"""
load_address_and_contact(self)
def autoname(self):
self.name = self.account_name + " - " + self.bank
def on_trash(self):
delete_contact_and_address('BankAccount', self.name)

View File

@@ -0,0 +1,14 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Bank Transaction', {
onload: function(frm) {
frm.set_query('payment_document', 'payment_entries', function() {
return {
"filters": {
"name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Expense Claim"]]
}
};
});
}
});

View File

@@ -0,0 +1,800 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "naming_series:",
"beta": 0,
"creation": "2018-10-22 18:19:02.784533",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "ACC-BTN-.YYYY.-",
"fetch_if_empty": 0,
"fieldname": "naming_series",
"fieldtype": "Select",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Series",
"length": 0,
"no_copy": 1,
"options": "ACC-BTN-.YYYY.-",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Date",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_2",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Settled",
"fetch_if_empty": 0,
"fieldname": "status",
"fieldtype": "Select",
"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": "Status",
"length": 0,
"no_copy": 0,
"options": "\nPending\nSettled",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "bank_account",
"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": "Bank Account",
"length": 0,
"no_copy": 0,
"options": "Bank Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fetch_from": "bank_account.company",
"fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_4",
"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,
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "debit",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Debit",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "credit",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Credit",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_7",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "currency",
"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": "Currency",
"length": 0,
"no_copy": 0,
"options": "Currency",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_10",
"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,
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "description",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Description",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_14",
"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,
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "reference_number",
"fieldtype": "Data",
"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": "Reference Number",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "transaction_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Transaction ID",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "payment_entries",
"fieldtype": "Table",
"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": "Payment Entries",
"length": 0,
"no_copy": 0,
"options": "Bank Transaction Payments",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_17",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"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": "Allocated Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "unallocated_amount",
"fieldtype": "Currency",
"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": "Unallocated Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from",
"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": "Amended From",
"length": 0,
"no_copy": 1,
"options": "Bank Transaction",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-03-22 10:52:04.540756",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 0,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "date",
"sort_order": "DESC",
"title_field": "bank_account",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
}

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.utils import flt
class BankTransaction(Document):
def after_insert(self):
self.unallocated_amount = abs(flt(self.credit) - flt(self.debit))
def on_update_after_submit(self):
allocated_amount = reduce(lambda x, y: flt(x) + flt(y), [x.allocated_amount for x in self.payment_entries])
if allocated_amount:
frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount))
frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit)) - flt(allocated_amount))
else:
frappe.db.set_value(self.doctype, self.name, "allocated_amount", 0)
frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit)))
self.reload()

View File

@@ -0,0 +1,13 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.listview_settings['Bank Transaction'] = {
add_fields: ["unallocated_amount"],
get_indicator: function(doc) {
if(flt(doc.unallocated_amount)>0) {
return [__("Unreconciled"), "orange", "unallocated_amount,>,0"];
} else if(flt(doc.unallocated_amount)===0) {
return [__("Reconciled"), "green", "unallocated_amount,=,0"];
}
}
};

View File

@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import json
from frappe.utils import getdate
from frappe.utils.dateutils import parse_date
@frappe.whitelist()
def upload_bank_statement():
if getattr(frappe, "uploaded_file", None):
with open(frappe.uploaded_file, "rb") as upfile:
fcontent = upfile.read()
else:
from frappe.utils.file_manager import get_uploaded_content
fname, fcontent = get_uploaded_content()
if frappe.safe_encode(fname).lower().endswith("csv"):
from frappe.utils.csvutils import read_csv_content
rows = read_csv_content(fcontent, False)
elif frappe.safe_encode(fname).lower().endswith("xlsx"):
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
rows = read_xlsx_file_from_attached_file(fcontent=fcontent)
columns = rows[0]
rows.pop(0)
data = rows
return {"columns": columns, "data": data}
@frappe.whitelist()
def create_bank_entries(columns, data, bank_account):
header_map = get_header_mapping(columns, bank_account)
count = 0
for d in json.loads(data):
if all(item is None for item in d) is True:
continue
fields = {}
for key, value in header_map.iteritems():
fields.update({key: d[int(value)-1]})
bank_transaction = frappe.get_doc({
"doctype": "Bank Transaction"
})
bank_transaction.update(fields)
bank_transaction.date = getdate(parse_date(bank_transaction.date))
bank_transaction.bank_account = bank_account
bank_transaction.insert()
bank_transaction.submit()
count = count + 1
return count
def get_header_mapping(columns, bank_account):
mapping = get_bank_mapping(bank_account)
header_map = {}
for column in json.loads(columns):
if column["content"] in mapping:
header_map.update({mapping[column["content"]]: column["colIndex"]})
return header_map
def get_bank_mapping(bank_account):
bank_name = frappe.db.get_value("Bank Account", bank_account, "bank")
bank = frappe.get_doc("Bank", bank_name)
mapping = {row.file_field:row.bank_transaction_field for row in bank.bank_transaction_mapping}
return mapping

View File

@@ -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: Bank Transaction", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Bank Transaction
() => frappe.tests.make('Bank Transaction', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -0,0 +1,286 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
from erpnext.accounts.page.bank_reconciliation.bank_reconciliation import reconcile, get_linked_payments
test_dependencies = ["Item", "Cost Center"]
class TestBankTransaction(unittest.TestCase):
def setUp(self):
add_transactions()
add_payments()
def tearDown(self):
for bt in frappe.get_all("Bank Transaction"):
doc = frappe.get_doc("Bank Transaction", bt.name)
doc.cancel()
doc.delete()
# Delete directly in DB to avoid validation errors for countries not allowing deletion
frappe.db.sql("""delete from `tabPayment Entry Reference`""")
frappe.db.sql("""delete from `tabPayment Entry`""")
frappe.flags.test_bank_transactions_created = False
frappe.flags.test_payments_created = False
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
def test_linked_payments(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"))
linked_payments = get_linked_payments(bank_transaction.name)
self.assertTrue(linked_payments[0].party == "Conrad Electronic")
# This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment
def test_reconcile(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"))
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200))
reconcile(bank_transaction.name, "Payment Entry", payment.name)
unallocated_amount = frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount")
self.assertTrue(unallocated_amount == 0)
clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date")
self.assertTrue(clearance_date is not None)
# Check if ERPNext can correctly fetch a linked payment based on the party
def test_linked_payments_based_on_party(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"))
linked_payments = get_linked_payments(bank_transaction.name)
self.assertTrue(len(linked_payments)==1)
# Check if ERPNext can correctly filter a linked payments based on the debit/credit amount
def test_debit_credit_output(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"))
linked_payments = get_linked_payments(bank_transaction.name)
self.assertTrue(linked_payments[0].payment_type == "Pay")
# Check error if already reconciled
def test_already_reconciled(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"))
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200))
reconcile(bank_transaction.name, "Payment Entry", payment.name)
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"))
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200))
self.assertRaises(frappe.ValidationError, reconcile, bank_transaction=bank_transaction.name, payment_doctype="Payment Entry", payment_name=payment.name)
# Raise an error if creditor transaction vs creditor payment
def test_invalid_creditor_reconcilation(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio"))
payment = frappe.get_doc("Payment Entry", dict(party="Conrad Electronic", paid_amount=690))
self.assertRaises(frappe.ValidationError, reconcile, bank_transaction=bank_transaction.name, payment_doctype="Payment Entry", payment_name=payment.name)
# Raise an error if debitor transaction vs debitor payment
def test_invalid_debitor_reconcilation(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"))
payment = frappe.get_doc("Payment Entry", dict(party="Fayva", paid_amount=109080))
self.assertRaises(frappe.ValidationError, reconcile, bank_transaction=bank_transaction.name, payment_doctype="Payment Entry", payment_name=payment.name)
# Raise an error if debitor transaction vs debitor payment
def test_clear_sales_invoice(self):
bank_transaction = frappe.get_doc("Bank Transaction", dict(description="I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio"))
payment = frappe.get_doc("Sales Invoice", dict(customer="Fayva", status=["=", "Paid"]))
reconcile(bank_transaction.name, "Sales Invoice", payment.name)
self.assertEqual(frappe.db.get_value("Bank Transaction", bank_transaction.name, "unallocated_amount"), 0)
self.assertTrue(frappe.db.get_value("Sales Invoice Payment", dict(parent=payment.name), "clearance_date") is not None)
def add_transactions():
if frappe.flags.test_bank_transactions_created:
return
frappe.set_user("Administrator")
try:
frappe.get_doc({
"doctype": "Bank",
"bank_name":"Citi Bank",
}).insert()
except frappe.DuplicateEntryError:
pass
try:
frappe.get_doc({
"doctype": "Bank Account",
"account_name":"Checking Account",
"bank": "Citi Bank",
"account": "_Test Bank - _TC"
}).insert()
except frappe.DuplicateEntryError:
pass
doc = frappe.get_doc({
"doctype": "Bank Transaction",
"description":"1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G",
"date": "2018-10-23",
"debit": 1200,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank"
}).insert()
doc.submit()
doc = frappe.get_doc({
"doctype": "Bank Transaction",
"description":"1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G",
"date": "2018-10-23",
"debit": 1700,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank"
}).insert()
doc.submit()
doc = frappe.get_doc({
"doctype": "Bank Transaction",
"description":"Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic",
"date": "2018-10-26",
"debit": 690,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank"
}).insert()
doc.submit()
doc = frappe.get_doc({
"doctype": "Bank Transaction",
"description":"Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07",
"date": "2018-10-27",
"debit": 3900,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank"
}).insert()
doc.submit()
doc = frappe.get_doc({
"doctype": "Bank Transaction",
"description":"I2015000011 VD/000002514 ATWWXXX AT4701345000003510057 Bio",
"date": "2018-10-27",
"credit": 109080,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank"
}).insert()
doc.submit()
frappe.flags.test_bank_transactions_created = True
def add_payments():
if frappe.flags.test_payments_created:
return
frappe.set_user("Administrator")
try:
frappe.get_doc({
"doctype": "Supplier",
"supplier_group":"All Supplier Groups",
"supplier_type": "Company",
"supplier_name": "Conrad Electronic"
}).insert()
except frappe.DuplicateEntryError:
pass
pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Conrad Oct 18"
pe.reference_date = "2018-10-24"
pe.insert()
pe.submit()
try:
frappe.get_doc({
"doctype": "Supplier",
"supplier_group":"All Supplier Groups",
"supplier_type": "Company",
"supplier_name": "Mr G"
}).insert()
except frappe.DuplicateEntryError:
pass
pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1200)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Herr G Oct 18"
pe.reference_date = "2018-10-24"
pe.insert()
pe.submit()
pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Herr G Nov 18"
pe.reference_date = "2018-11-01"
pe.insert()
pe.submit()
try:
frappe.get_doc({
"doctype": "Supplier",
"supplier_group":"All Supplier Groups",
"supplier_type": "Company",
"supplier_name": "Poore Simon's"
}).insert()
except frappe.DuplicateEntryError:
pass
try:
frappe.get_doc({
"doctype": "Customer",
"customer_group":"All Customer Groups",
"customer_type": "Company",
"customer_name": "Poore Simon's"
}).insert()
except frappe.DuplicateEntryError:
pass
pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Poore Simon's Oct 18"
pe.reference_date = "2018-10-28"
pe.insert()
pe.submit()
si = create_sales_invoice(customer="Poore Simon's", qty=1, rate=3900)
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Poore Simon's Oct 18"
pe.reference_date = "2018-10-28"
pe.insert()
pe.submit()
try:
frappe.get_doc({
"doctype": "Customer",
"customer_group":"All Customer Groups",
"customer_type": "Company",
"customer_name": "Fayva"
}).insert()
except frappe.DuplicateEntryError:
pass
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080)
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Fayva Oct 18"
pe.reference_date = "2018-10-29"
pe.insert()
pe.submit()
company = frappe.db.get_single_value('Global Defaults', 'default_company')
frappe.get_doc({
"doctype": "Mode of Payment",
"name": "Cash"
}).append("accounts", {
"company": company,
"default_account": "_Test Bank - _TC"
}).save()
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_submit=1)
si.is_pos = 1
si.append("payments", {
"mode_of_payment": "Cash",
"account": "_Test Bank - _TC",
"amount": 109080
})
si.save()
si.submit()
frappe.flags.test_payments_created = True

View File

@@ -0,0 +1,107 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-10-24 15:24:56.713277",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bank_transaction_field",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Field in Bank Transaction",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "file_field",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Column in Bank File",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-10-24 15:24:56.713277",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction Mapping",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
}

View File

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class BankTransactionMapping(Document):
pass

View File

@@ -0,0 +1,141 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-11-28 08:55:40.815355",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "payment_document",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payment Document",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "payment_entry",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payment Entry",
"length": 0,
"no_copy": 0,
"options": "payment_document",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Allocated Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-12-06 10:57:02.635141",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction Payments",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"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,
"track_seen": 0,
"track_views": 0
}

View File

@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class BankTransactionPayments(Document):
pass

View File

@@ -536,9 +536,13 @@ class PaymentEntry(AccountsController):
@frappe.whitelist()
def get_outstanding_reference_documents(args):
if isinstance(args, string_types):
args = json.loads(args)
if args.get('party_type') == 'Member':
return
# confirm that Supplier is not blocked
if args.get('party_type') == 'Supplier':
supplier_status = get_supplier_block_status(args['party'])

View File

@@ -13,20 +13,20 @@ class PaymentReconciliation(Document):
def get_unreconciled_entries(self):
self.get_nonreconciled_payment_entries()
self.get_invoice_entries()
def get_nonreconciled_payment_entries(self):
self.check_mandatory_to_fetch()
payment_entries = self.get_payment_entries()
journal_entries = self.get_jv_entries()
self.add_payment_entries(payment_entries + journal_entries)
def get_payment_entries(self):
order_doctype = "Sales Order" if self.party_type=="Customer" else "Purchase Order"
payment_entries = get_advance_payment_entries(self.party_type, self.party,
payment_entries = get_advance_payment_entries(self.party_type, self.party,
self.receivable_payable_account, order_doctype, against_all_orders=True, limit=self.limit)
return payment_entries
def get_jv_entries(self):
@@ -36,12 +36,12 @@ class PaymentReconciliation(Document):
bank_account_condition = "t2.against_account like %(bank_cash_account)s" \
if self.bank_cash_account else "1=1"
limit_cond = "limit %s" % (self.limit or 1000)
limit_cond = "limit %s" % self.limit if self.limit else ""
journal_entries = frappe.db.sql("""
select
"Journal Entry" as reference_type, t1.name as reference_name,
t1.posting_date, t1.remark as remarks, t2.name as reference_row,
"Journal Entry" as reference_type, t1.name as reference_name,
t1.posting_date, t1.remark as remarks, t2.name as reference_row,
{dr_or_cr} as amount, t2.is_advance
from
`tabJournal Entry` t1, `tabJournal Entry Account` t2
@@ -49,8 +49,8 @@ class PaymentReconciliation(Document):
t1.name = t2.parent and t1.docstatus = 1 and t2.docstatus = 1
and t2.party_type = %(party_type)s and t2.party = %(party)s
and t2.account = %(account)s and {dr_or_cr} > 0
and (t2.reference_type is null or t2.reference_type = '' or
(t2.reference_type in ('Sales Order', 'Purchase Order')
and (t2.reference_type is null or t2.reference_type = '' or
(t2.reference_type in ('Sales Order', 'Purchase Order')
and t2.reference_name is not null and t2.reference_name != ''))
and (CASE
WHEN t1.voucher_type in ('Debit Note', 'Credit Note')
@@ -83,7 +83,10 @@ class PaymentReconciliation(Document):
condition = self.check_condition()
non_reconciled_invoices = get_outstanding_invoices(self.party_type, self.party,
self.receivable_payable_account, condition=condition, limit=self.limit)
self.receivable_payable_account, condition=condition)
if self.limit:
non_reconciled_invoices = non_reconciled_invoices[:self.limit]
self.add_invoice_entries(non_reconciled_invoices)
@@ -109,7 +112,7 @@ class PaymentReconciliation(Document):
self.validate_invoice()
dr_or_cr = ("credit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit_in_account_currency")
lst = []
for e in self.get('payments'):
if e.invoice_number and e.allocated_amount:
@@ -127,11 +130,11 @@ class PaymentReconciliation(Document):
'unadjusted_amount' : flt(e.amount),
'allocated_amount' : flt(e.allocated_amount)
}))
if lst:
from erpnext.accounts.utils import reconcile_against_document
reconcile_against_document(lst)
msgprint(_("Successfully Reconciled"))
self.get_unreconciled_entries()

View File

@@ -522,8 +522,13 @@ frappe.ui.form.on("Purchase Invoice", {
},
onload: function(frm) {
if(frm.doc.__onload && !frm.doc.__onload.supplier_tds) {
me.frm.set_df_property("apply_tds", "read_only", 1);
if(frm.doc.__onload) {
if(frm.doc.supplier) {
frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0;
}
if(!frm.doc.__onload.supplier_tds) {
frm.set_df_property("apply_tds", "read_only", 1);
}
}
erpnext.queries.setup_queries(frm, "Warehouse", function() {

File diff suppressed because it is too large Load Diff

View File

@@ -789,9 +789,8 @@ class PurchaseInvoice(BuyingController):
for d in self.items:
if d.project and d.project not in project_list:
project = frappe.get_doc("Project", d.project)
project.flags.dont_sync_tasks = True
project.update_purchase_costing()
project.save()
project.db_update()
project_list.append(d.project)
def validate_supplier_invoice(self):

View File

@@ -1022,9 +1022,8 @@ class SalesInvoice(SellingController):
def update_project(self):
if self.project:
project = frappe.get_doc("Project", self.project)
project.flags.dont_sync_tasks = True
project.update_billed_amount()
project.save()
project.db_update()
def verify_payment_amount_is_positive(self):

View File

@@ -1362,7 +1362,7 @@ class TestSalesInvoice(unittest.TestCase):
"included_in_print_rate": 1
})
si.save()
si.submit()
self.assertEqual(si.net_total, 19453.13)
self.assertEqual(si.grand_total, 24900)
self.assertEqual(si.total_taxes_and_charges, 5446.88)
@@ -1384,6 +1384,50 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_values[gle.account][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit)
def test_rounding_adjustment_2(self):
si = create_sales_invoice(rate=400, do_not_save=True)
for rate in [400, 600, 100]:
si.append("items", {
"item_code": "_Test Item",
"gst_hsn_code": "999800",
"warehouse": "_Test Warehouse - _TC",
"qty": 1,
"rate": rate,
"income_account": "Sales - _TC",
"cost_center": "_Test Cost Center - _TC"
})
for tax_account in ["_Test Account VAT - _TC", "_Test Account Service Tax - _TC"]:
si.append("taxes", {
"charge_type": "On Net Total",
"account_head": tax_account,
"description": tax_account,
"rate": 9,
"cost_center": "_Test Cost Center - _TC",
"included_in_print_rate": 1
})
si.save()
si.submit()
self.assertEqual(si.net_total, 1271.19)
self.assertEqual(si.grand_total, 1500)
self.assertEqual(si.total_taxes_and_charges, 228.82)
self.assertEqual(si.rounding_adjustment, -0.01)
expected_values = dict((d[0], d) for d in [
[si.debit_to, 1500, 0.0],
["_Test Account Service Tax - _TC", 0.0, 114.41],
["_Test Account VAT - _TC", 0.0, 114.41],
["Sales - _TC", 0.0, 1271.18]
])
gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc""", si.name, as_dict=1)
for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.account)
self.assertEqual(expected_values[gle.account][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit)
def test_sales_invoice_with_shipping_rule(self):
from erpnext.accounts.doctype.shipping_rule.test_shipping_rule \
import create_shipping_rule

View File

@@ -295,7 +295,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2019-03-06 15:58:37.839241",
"modified": "2019-03-19 14:54:56.524556",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Payment",

View File

@@ -135,9 +135,9 @@ def round_off_debit_credit(gl_map):
.format(gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff))
elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
make_round_off_gle(gl_map, debit_credit_diff)
make_round_off_gle(gl_map, debit_credit_diff, precision)
def make_round_off_gle(gl_map, debit_credit_diff):
def make_round_off_gle(gl_map, debit_credit_diff, precision):
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(gl_map[0].company)
round_off_account_exists = False
round_off_gle = frappe._dict()
@@ -150,6 +150,10 @@ def make_round_off_gle(gl_map, debit_credit_diff):
debit_credit_diff += flt(d.credit_in_account_currency)
round_off_account_exists = True
if round_off_account_exists and abs(debit_credit_diff) <= (1.0 / (10**precision)):
gl_map.remove(round_off_gle)
return
if not round_off_gle:
for k in ["voucher_type", "voucher_no", "company",
"posting_date", "remarks", "is_opening"]:

View File

@@ -0,0 +1,572 @@
frappe.provide("erpnext.accounts");
frappe.pages['bank-reconciliation'].on_page_load = function(wrapper) {
new erpnext.accounts.bankReconciliation(wrapper);
}
erpnext.accounts.bankReconciliation = class BankReconciliation {
constructor(wrapper) {
this.page = frappe.ui.make_app_page({
parent: wrapper,
title: __("Bank Reconciliation"),
single_column: true
});
this.parent = wrapper;
this.page = this.parent.page;
this.check_plaid_status();
this.make();
}
make() {
const me = this;
me.$main_section = $(`<div class="reconciliation page-main-content"></div>`).appendTo(me.page.main);
const empty_state = __("Upload a bank statement, link or reconcile a bank account")
me.$main_section.append(`<div class="flex justify-center align-center text-muted"
style="height: 50vh; display: flex;"><h5 class="text-muted">${empty_state}</h5></div>`)
me.page.add_field({
fieldtype: 'Link',
label: __('Company'),
fieldname: 'company',
options: "Company",
onchange: function() {
console.log(this.value)
if (this.value) {
me.company = this.value;
} else {
me.company = null;
me.bank_account = null;
}
}
})
me.page.add_field({
fieldtype: 'Link',
label: __('Bank Account'),
fieldname: 'bank_account',
options: "Bank Account",
get_query: function() {
if(!me.company) {
frappe.throw(__("Please select company first"));
return
}
return {
filters: {
"company": me.company
}
}
},
onchange: function() {
if (this.value) {
me.bank_account = this.value;
me.add_actions();
} else {
me.bank_account = null;
me.page.hide_actions_menu();
}
}
})
}
check_plaid_status() {
const me = this;
frappe.db.get_value("Plaid Settings", "Plaid Settings", "enabled", (r) => {
if (r && r.enabled == "1") {
me.plaid_status = "active"
} else {
me.plaid_status = "inactive"
}
})
}
add_actions() {
const me = this;
me.page.show_menu()
me.page.add_menu_item(__("Upload a statement"), function() {
me.clear_page_content();
new erpnext.accounts.bankTransactionUpload(me);
}, true)
if (me.plaid_status==="active") {
me.page.add_menu_item(__("Synchronize this account"), function() {
me.clear_page_content();
new erpnext.accounts.bankTransactionSync(me);
}, true)
}
me.page.add_menu_item(__("Reconcile this account"), function() {
me.clear_page_content();
me.make_reconciliation_tool();
}, true)
}
clear_page_content() {
const me = this;
$(me.page.body).find('.frappe-list').remove();
me.$main_section.empty();
}
make_reconciliation_tool() {
const me = this;
frappe.model.with_doctype("Bank Transaction", () => {
erpnext.accounts.ReconciliationList = new erpnext.accounts.ReconciliationTool({
parent: me.parent,
doctype: "Bank Transaction"
});
})
}
}
erpnext.accounts.bankTransactionUpload = class bankTransactionUpload {
constructor(parent) {
this.parent = parent;
this.data = [];
const assets = [
"/assets/frappe/css/frappe-datatable.css",
"/assets/frappe/js/lib/clusterize.min.js",
"/assets/frappe/js/lib/Sortable.min.js",
"/assets/frappe/js/lib/frappe-datatable.js"
];
frappe.require(assets, () => {
this.make();
});
}
make() {
const me = this;
frappe.upload.make({
args: {
method: 'erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.upload_bank_statement',
allow_multiple: 0
},
no_socketio: true,
sample_url: "e.g. http://example.com/somefile.csv",
callback: function(attachment, r) {
if (!r.exc && r.message) {
me.data = r.message;
me.setup_transactions_dom();
me.create_datatable();
me.add_primary_action();
}
}
})
}
setup_transactions_dom() {
const me = this;
me.parent.$main_section.append(`<div class="transactions-table"></div>`)
}
create_datatable() {
try {
this.datatable = new DataTable('.transactions-table', {
columns: this.data.columns,
data: this.data.data
})
}
catch(err) {
let msg = __(`Your file could not be processed by ERPNext.
<br>It should be a standard CSV or XLSX file.
<br>The headers should be in the first row.`)
frappe.throw(msg)
}
}
add_primary_action() {
const me = this;
me.parent.page.set_primary_action(__("Submit"), function() {
me.add_bank_entries()
}, null, __("Creating bank entries..."))
}
add_bank_entries() {
const me = this;
frappe.xcall('erpnext.accounts.doctype.bank_transaction.bank_transaction_upload.create_bank_entries',
{columns: this.datatable.datamanager.columns, data: this.datatable.datamanager.data, bank_account: me.parent.bank_account}
).then((result) => {
let result_title = __("{0} bank transaction(s) created", [result])
let result_msg = `
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
<h5 class="text-muted">${result_title}</h5>
</div>`
me.parent.page.clear_primary_action();
me.parent.$main_section.empty();
me.parent.$main_section.append(result_msg);
frappe.show_alert({message:__("All bank transactions have been created"), indicator:'green'});
})
}
}
erpnext.accounts.bankTransactionSync = class bankTransactionSync {
constructor(parent) {
this.parent = parent;
this.data = [];
this.init_config()
}
init_config() {
const me = this;
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration')
.then(result => {
me.plaid_env = result.plaid_env;
me.plaid_public_key = result.plaid_public_key;
me.client_name = result.client_name;
me.sync_transactions()
})
}
sync_transactions() {
const me = this;
frappe.db.get_value("Bank Account", me.parent.bank_account, "bank", (v) => {
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions', {
bank: v['bank'],
bank_account: me.parent.bank_account,
freeze: true
})
.then((result) => {
let result_title = (result.length > 0) ? __("{0} bank transaction(s) created", [result.length]) : __("This bank account is already synchronized")
let result_msg = `
<div class="flex justify-center align-center text-muted" style="height: 50vh; display: flex;">
<h5 class="text-muted">${result_title}</h5>
</div>`
this.parent.$main_section.append(result_msg)
frappe.show_alert({message:__("Bank account '{0}' has been synchronized", [me.parent.bank_account]), indicator:'green'});
})
})
}
}
erpnext.accounts.ReconciliationTool = class ReconciliationTool extends frappe.views.BaseList {
constructor(opts) {
super(opts);
this.show();
}
setup_defaults() {
super.setup_defaults();
this.page_title = __("Bank Reconciliation");
this.doctype = 'Bank Transaction';
this.fields = ['date', 'description', 'debit', 'credit', 'currency']
}
setup_view() {
this.render_header();
}
setup_side_bar() {
//
}
make_standard_filters() {
//
}
freeze() {
this.$result.find('.list-count').html(`<span>${__('Refreshing')}...</span>`);
}
get_args() {
const args = super.get_args();
return Object.assign({}, args, {
...args.filters.push(["Bank Transaction", "docstatus", "=", 1],
["Bank Transaction", "unallocated_amount", ">", 0])
});
}
update_data(r) {
let data = r.message || [];
if (this.start === 0) {
this.data = data;
} else {
this.data = this.data.concat(data);
}
}
render() {
const me = this;
this.$result.find('.list-row-container').remove();
$('[data-fieldname="name"]').remove();
me.data.map((value) => {
const row = $('<div class="list-row-container">').data("data", value).appendTo(me.$result).get(0);
new erpnext.accounts.ReconciliationRow(row, value);
})
}
render_header() {
const me = this;
if ($(this.wrapper).find('.transaction-header').length === 0) {
me.$result.append(frappe.render_template("bank_transaction_header"));
}
}
}
erpnext.accounts.ReconciliationRow = class ReconciliationRow {
constructor(row, data) {
this.data = data;
this.row = row;
this.make();
this.bind_events();
}
make() {
$(this.row).append(frappe.render_template("bank_transaction_row", this.data))
}
bind_events() {
const me = this;
$(me.row).on('click', '.clickable-section', function() {
me.bank_entry = $(this).attr("data-name");
me.show_dialog($(this).attr("data-name"));
})
$(me.row).on('click', '.new-reconciliation', function() {
me.bank_entry = $(this).attr("data-name");
me.show_dialog($(this).attr("data-name"));
})
$(me.row).on('click', '.new-payment', function() {
me.bank_entry = $(this).attr("data-name");
me.new_payment();
})
$(me.row).on('click', '.new-invoice', function() {
me.bank_entry = $(this).attr("data-name");
me.new_invoice();
})
$(me.row).on('click', '.new-expense', function() {
me.bank_entry = $(this).attr("data-name");
me.new_expense();
})
}
new_payment() {
const me = this;
const paid_amount = me.data.credit > 0 ? me.data.credit : me.data.debit;
const payment_type = me.data.credit > 0 ? "Receive": "Pay";
const party_type = me.data.credit > 0 ? "Customer": "Supplier";
frappe.new_doc("Payment Entry", {"payment_type": payment_type, "paid_amount": paid_amount,
"party_type": party_type, "paid_from": me.data.bank_account})
}
new_invoice() {
const me = this;
const invoice_type = me.data.credit > 0 ? "Sales Invoice" : "Purchase Invoice";
frappe.new_doc(invoice_type)
}
new_expense() {
frappe.new_doc("Expense Claim")
}
show_dialog(data) {
const me = this;
frappe.db.get_value("Bank Account", me.data.bank_account, "account", (r) => {
me.gl_account = r.account;
})
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.get_linked_payments',
{bank_transaction: data, freeze:true, freeze_message:__("Finding linked payments")}
).then((result) => {
me.make_dialog(result)
})
}
make_dialog(data) {
const me = this;
me.selected_payment = null;
const fields = [
{
fieldtype: 'Section Break',
fieldname: 'section_break_1',
label: __('Automatic Reconciliation')
},
{
fieldtype: 'HTML',
fieldname: 'payment_proposals'
},
{
fieldtype: 'Section Break',
fieldname: 'section_break_2',
label: __('Search for a payment')
},
{
fieldtype: 'Link',
fieldname: 'payment_doctype',
options: 'DocType',
label: 'Payment DocType',
get_query: () => {
return {
filters : {
"name": ["in", ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Expense Claim"]]
}
}
},
},
{
fieldtype: 'Column Break',
fieldname: 'column_break_1',
},
{
fieldtype: 'Dynamic Link',
fieldname: 'payment_entry',
options: 'payment_doctype',
label: 'Payment Document',
get_query: () => {
let dt = this.dialog.fields_dict.payment_doctype.value;
if (dt === "Payment Entry") {
return {
query: "erpnext.accounts.page.bank_reconciliation.bank_reconciliation.payment_entry_query",
filters : {
"bank_account": this.data.bank_account,
"company": this.data.company
}
}
} else if (dt === "Journal Entry") {
return {
query: "erpnext.accounts.page.bank_reconciliation.bank_reconciliation.journal_entry_query",
filters : {
"bank_account": this.data.bank_account,
"company": this.data.company
}
}
} else if (dt === "Sales Invoice") {
return {
query: "erpnext.accounts.page.bank_reconciliation.bank_reconciliation.sales_invoices_query"
}
} else if (dt === "Purchase Invoice") {
return {
filters : [
["Purchase Invoice", "ifnull(clearance_date, '')", "=", ""],
["Purchase Invoice", "docstatus", "=", 1],
["Purchase Invoice", "company", "=", this.data.company]
]
}
} else if (dt === "Expense Claim") {
return {
filters : [
["Expense Claim", "ifnull(clearance_date, '')", "=", ""],
["Expense Claim", "docstatus", "=", 1],
["Expense Claim", "company", "=", this.data.company]
]
}
}
},
onchange: function() {
if (me.selected_payment !== this.value) {
me.selected_payment = this.value;
me.display_payment_details(this);
}
}
},
{
fieldtype: 'Section Break',
fieldname: 'section_break_3'
},
{
fieldtype: 'HTML',
fieldname: 'payment_details'
},
];
me.dialog = new frappe.ui.Dialog({
title: __("Choose a corresponding payment"),
fields: fields,
size: "large"
});
const proposals_wrapper = me.dialog.fields_dict.payment_proposals.$wrapper;
if (data && data.length > 0) {
proposals_wrapper.append(frappe.render_template("linked_payment_header"));
data.map(value => {
proposals_wrapper.append(frappe.render_template("linked_payment_row", value))
})
} else {
const empty_data_msg = __("ERPNext could not find any matching payment entry")
proposals_wrapper.append(`<div class="text-center"><h5 class="text-muted">${empty_data_msg}</h5></div>`)
}
$(me.dialog.body).on('click', '.reconciliation-btn', (e) => {
const payment_entry = $(e.target).attr('data-name');
const payment_doctype = $(e.target).attr('data-doctype');
frappe.xcall('erpnext.accounts.page.bank_reconciliation.bank_reconciliation.reconcile',
{bank_transaction: me.bank_entry, payment_doctype: payment_doctype, payment_name: payment_entry})
.then((result) => {
setTimeout(function(){
erpnext.accounts.ReconciliationList.refresh();
}, 2000);
me.dialog.hide();
})
})
me.dialog.show();
}
display_payment_details(event) {
const me = this;
if (event.value) {
let dt = me.dialog.fields_dict.payment_doctype.value;
me.dialog.fields_dict['payment_details'].$wrapper.empty();
frappe.db.get_doc(dt, event.value)
.then(doc => {
let displayed_docs = []
if (dt === "Payment Entry") {
doc.currency = doc.payment_type == "Receive" ? doc.paid_to_account_currency : doc.paid_from_account_currency;
displayed_docs.push(doc);
} else if (dt === "Journal Entry") {
doc.accounts.forEach(payment => {
if (payment.account === me.gl_account) {
payment.posting_date = doc.posting_date;
payment.party = doc.pay_to_recd_from;
payment.reference_no = doc.cheque_no;
payment.reference_date = doc.cheque_date;
payment.currency = payment.account_currency;
payment.paid_amount = payment.credit > 0 ? payment.credit : payment.debit;
payment.name = doc.name;
displayed_docs.push(payment);
}
})
} else if (dt === "Sales Invoice") {
doc.payments.forEach(payment => {
if (payment.clearance_date === null || payment.clearance_date === "") {
payment.posting_date = doc.posting_date;
payment.party = doc.customer;
payment.reference_no = doc.remarks;
payment.currency = doc.currency;
payment.paid_amount = payment.amount;
payment.name = doc.name;
displayed_docs.push(payment);
}
})
}
const details_wrapper = me.dialog.fields_dict.payment_details.$wrapper;
details_wrapper.append(frappe.render_template("linked_payment_header"));
displayed_docs.forEach(values => {
details_wrapper.append(frappe.render_template("linked_payment_row", values));
})
})
}
}
}

View File

@@ -0,0 +1,29 @@
{
"content": null,
"creation": "2018-11-24 12:03:14.646669",
"docstatus": 0,
"doctype": "Page",
"idx": 0,
"modified": "2018-11-24 12:03:14.646669",
"modified_by": "Administrator",
"module": "Accounts",
"name": "bank-reconciliation",
"owner": "Administrator",
"page_name": "bank-reconciliation",
"roles": [
{
"role": "System Manager"
},
{
"role": "Accounts Manager"
},
{
"role": "Accounts User"
}
],
"script": null,
"standard": "Yes",
"style": null,
"system_page": 0,
"title": "Bank Reconciliation"
}

View File

@@ -0,0 +1,385 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
import difflib
from frappe.utils import flt
from six import iteritems
from erpnext import get_company_currency
@frappe.whitelist()
def reconcile(bank_transaction, payment_doctype, payment_name):
transaction = frappe.get_doc("Bank Transaction", bank_transaction)
payment_entry = frappe.get_doc(payment_doctype, payment_name)
account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
gl_entry = frappe.get_doc("GL Entry", dict(account=account, voucher_type=payment_doctype, voucher_no=payment_name))
if transaction.unallocated_amount == 0:
frappe.throw(_("This bank transaction is already fully reconciled"))
if transaction.credit > 0 and gl_entry.credit > 0:
frappe.throw(_("The selected payment entry should be linked with a debtor bank transaction"))
if transaction.debit > 0 and gl_entry.debit > 0:
frappe.throw(_("The selected payment entry should be linked with a creditor bank transaction"))
add_payment_to_transaction(transaction, payment_entry, gl_entry)
clear_payment_entry(transaction, payment_entry, gl_entry)
return 'reconciled'
def add_payment_to_transaction(transaction, payment_entry, gl_entry):
transaction.append("payment_entries", {
"payment_document": payment_entry.doctype,
"payment_entry": payment_entry.name,
"allocated_amount": gl_entry.credit if gl_entry.credit > 0 else gl_entry.debit
})
transaction.save()
def clear_payment_entry(transaction, payment_entry, gl_entry):
linked_bank_transactions = frappe.db.sql("""
SELECT
bt.credit, bt.debit
FROM
`tabBank Transaction Payments` as btp
LEFT JOIN
`tabBank Transaction` as bt on btp.parent=bt.name
WHERE
btp.payment_document = %s
AND
btp.payment_entry = %s
AND
bt.docstatus = 1
""", (payment_entry.doctype, payment_entry.name), as_dict=True)
amount_cleared = (flt(linked_bank_transactions[0].credit) - flt(linked_bank_transactions[0].debit))
amount_to_be_cleared = (flt(gl_entry.debit) - flt(gl_entry.credit))
if payment_entry.doctype in ("Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"):
clear_simple_entry(amount_cleared, amount_to_be_cleared, payment_entry, transaction)
elif payment_entry.doctype == "Sales Invoice":
clear_sales_invoice(amount_cleared, amount_to_be_cleared, payment_entry, transaction)
def clear_simple_entry(amount_cleared, amount_to_be_cleared, payment_entry, transaction):
if amount_cleared >= amount_to_be_cleared:
frappe.db.set_value(payment_entry.doctype, payment_entry.name, "clearance_date", transaction.date)
def clear_sales_invoice(amount_cleared, amount_to_be_cleared, payment_entry, transaction):
if amount_cleared >= amount_to_be_cleared:
frappe.db.set_value("Sales Invoice Payment", dict(parenttype=payment_entry.doctype,
parent=payment_entry.name), "clearance_date", transaction.date)
@frappe.whitelist()
def get_linked_payments(bank_transaction):
transaction = frappe.get_doc("Bank Transaction", bank_transaction)
bank_account = frappe.db.get_values("Bank Account", transaction.bank_account, ["account", "company"], as_dict=True)
# Get all payment entries with a matching amount
amount_matching = check_matching_amount(bank_account[0].account, bank_account[0].company, transaction)
# Get some data from payment entries linked to a corresponding bank transaction
description_matching = get_matching_descriptions_data(bank_account[0].account, transaction)
if amount_matching:
return check_amount_vs_description(amount_matching, description_matching)
elif description_matching:
return sorted(description_matching, key = lambda x: x["posting_date"], reverse=True)
else:
return []
def check_matching_amount(bank_account, company, transaction):
payments = []
amount = transaction.credit if transaction.credit > 0 else transaction.debit
payment_type = "Receive" if transaction.credit > 0 else "Pay"
account_from_to = "paid_to" if transaction.credit > 0 else "paid_from"
currency_field = "paid_to_account_currency as currency" if transaction.credit > 0 else "paid_from_account_currency as currency"
payment_entries = frappe.get_all("Payment Entry", fields=["'Payment Entry' as doctype", "name", "paid_amount", "payment_type", "reference_no", "reference_date",
"party", "party_type", "posting_date", "{0}".format(currency_field)], filters=[["paid_amount", "like", "{0}%".format(amount)],
["docstatus", "=", "1"], ["payment_type", "=", payment_type], ["ifnull(clearance_date, '')", "=", ""], ["{0}".format(account_from_to), "=", "{0}".format(bank_account)]])
if transaction.credit > 0:
journal_entries = frappe.db.sql("""
SELECT
'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no,
je.pay_to_recd_from as party, je.cheque_date as reference_date, jea.debit_in_account_currency as paid_amount
FROM
`tabJournal Entry Account` as jea
JOIN
`tabJournal Entry` as je
ON
jea.parent = je.name
WHERE
(je.clearance_date is null or je.clearance_date='0000-00-00')
AND
jea.account = %s
AND
jea.debit_in_account_currency like %s
AND
je.docstatus = 1
""", (bank_account, amount), as_dict=True)
else:
journal_entries = frappe.db.sql("""
SELECT
'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no,
je.pay_to_recd_from as party, je.cheque_date as reference_date, jea.credit_in_account_currency as paid_amount
FROM
`tabJournal Entry Account` as jea
JOIN
`tabJournal Entry` as je
ON
jea.parent = je.name
WHERE
(je.clearance_date is null or je.clearance_date='0000-00-00')
AND
jea.account = %s
AND
jea.credit_in_account_currency like %s
AND
je.docstatus = 1
""", (bank_account, amount), as_dict=True)
if transaction.credit > 0:
sales_invoices = frappe.db.sql("""
SELECT
'Sales Invoice' as doctype, si.name, si.customer as party,
si.posting_date, sip.amount as paid_amount
FROM
`tabSales Invoice Payment` as sip
JOIN
`tabSales Invoice` as si
ON
sip.parent = si.name
WHERE
(sip.clearance_date is null or sip.clearance_date='0000-00-00')
AND
sip.account = %s
AND
sip.amount like %s
AND
si.docstatus = 1
""", (bank_account, amount), as_dict=True)
else:
sales_invoices = []
if transaction.debit > 0:
purchase_invoices = frappe.get_all("Purchase Invoice",
fields = ["'Purchase Invoice' as doctype", "name", "paid_amount", "supplier as party", "posting_date", "currency"],
filters=[
["paid_amount", "like", "{0}%".format(amount)],
["docstatus", "=", "1"],
["is_paid", "=", "1"],
["ifnull(clearance_date, '')", "=", ""],
["cash_bank_account", "=", "{0}".format(bank_account)]
]
)
mode_of_payments = [x["parent"] for x in frappe.db.get_list("Mode of Payment Account",
filters={"default_account": bank_account}, fields=["parent"])]
company_currency = get_company_currency(company)
expense_claims = frappe.get_all("Expense Claim",
fields=["'Expense Claim' as doctype", "name", "total_sanctioned_amount as paid_amount",
"employee as party", "posting_date", "'{0}' as currency".format(company_currency)],
filters=[
["total_sanctioned_amount", "like", "{0}%".format(amount)],
["docstatus", "=", "1"],
["is_paid", "=", "1"],
["ifnull(clearance_date, '')", "=", ""],
["mode_of_payment", "in", "{0}".format(tuple(mode_of_payments))]
]
)
else:
purchase_invoices = expense_claims = []
for data in [payment_entries, journal_entries, sales_invoices, purchase_invoices, expense_claims]:
if data:
payments.extend(data)
return payments
def get_matching_descriptions_data(bank_account, transaction):
if not transaction.description :
return []
bank_transactions = frappe.db.sql("""
SELECT
bt.name, bt.description, bt.date, btp.payment_document, btp.payment_entry
FROM
`tabBank Transaction` as bt
LEFT JOIN
`tabBank Transaction Payments` as btp
ON
bt.name = btp.parent
WHERE
bt.allocated_amount > 0
AND
bt.docstatus = 1
""", as_dict=True)
selection = []
for bank_transaction in bank_transactions:
if bank_transaction.description:
seq=difflib.SequenceMatcher(lambda x: x == " ", transaction.description, bank_transaction.description)
if seq.ratio() > 0.6:
bank_transaction["ratio"] = seq.ratio()
selection.append(bank_transaction)
document_types = set([x["payment_document"] for x in selection])
links = {}
for document_type in document_types:
links[document_type] = [x["payment_entry"] for x in selection if x["payment_document"]==document_type]
data = []
for key, value in iteritems(links):
if key == "Payment Entry":
data.extend(frappe.get_all("Payment Entry", filters=[["name", "in", value]], fields=["'Payment Entry' as doctype", "posting_date", "party", "reference_no", "reference_date", "paid_amount"]))
if key == "Journal Entry":
data.extend(frappe.get_all("Journal Entry", filters=[["name", "in", value]], fields=["'Journal Entry' as doctype", "posting_date", "paid_to_recd_from as party", "cheque_no as reference_no", "cheque_date as reference_date"]))
if key == "Sales Invoice":
data.extend(frappe.get_all("Sales Invoice", filters=[["name", "in", value]], fields=["'Sales Invoice' as doctype", "posting_date", "customer_name as party"]))
if key == "Purchase Invoice":
data.append(frappe.get_all("Purchase Invoice", filters=[["name", "in", value]], fields=["'Purchase Invoice' as doctype", "posting_date", "supplier_name as party"]))
if key == "Purchase Invoice":
data.append(frappe.get_all("Expense Claim", filters=[["name", "in", value]], fields=["'Expense Claim' as doctype", "posting_date", "employee_name as party"]))
return data
def check_amount_vs_description(amount_matching, description_matching):
result = []
if description_matching:
for am_match in amount_matching:
for des_match in description_matching:
if am_match["party"] == des_match["party"]:
if am_match not in result:
result.append(am_match)
continue
if hasattr(am_match, "reference_no") and hasattr(des_match, "reference_no"):
if difflib.SequenceMatcher(lambda x: x == " ", am_match["reference_no"], des_match["reference_no"]) > 70:
if am_match not in result:
result.append(am_match)
if result:
return sorted(result, key = lambda x: x["posting_date"], reverse=True)
else:
return sorted(amount_matching, key = lambda x: x["posting_date"], reverse=True)
else:
return sorted(amount_matching, key = lambda x: x["posting_date"], reverse=True)
def get_matching_transactions_payments(description_matching):
payments = [x["payment_entry"] for x in description_matching]
payment_by_ratio = {x["payment_entry"]: x["ratio"] for x in description_matching}
if payments:
reference_payment_list = frappe.get_all("Payment Entry", fields=["name", "paid_amount", "payment_type", "reference_no", "reference_date",
"party", "party_type", "posting_date", "paid_to_account_currency"], filters=[["name", "in", payments]])
return sorted(reference_payment_list, key=lambda x: payment_by_ratio[x["name"]])
else:
return []
def payment_entry_query(doctype, txt, searchfield, start, page_len, filters):
account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account")
if not account:
return
return frappe.db.sql("""
SELECT
name, party, paid_amount, received_amount, reference_no
FROM
`tabPayment Entry`
WHERE
(clearance_date is null or clearance_date='0000-00-00')
AND (paid_from = %(account)s or paid_to = %(account)s)
AND (name like %(txt)s or party like %(txt)s)
AND docstatus = 1
ORDER BY
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), name
LIMIT
%(start)s, %(page_len)s""",
{
'txt': "%%%s%%" % txt,
'_txt': txt.replace("%", ""),
'start': start,
'page_len': page_len,
'account': account
}
)
def journal_entry_query(doctype, txt, searchfield, start, page_len, filters):
account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account")
return frappe.db.sql("""
SELECT
jea.parent, je.pay_to_recd_from,
if(jea.debit_in_account_currency > 0, jea.debit_in_account_currency, jea.credit_in_account_currency)
FROM
`tabJournal Entry Account` as jea
LEFT JOIN
`tabJournal Entry` as je
ON
jea.parent = je.name
WHERE
(je.clearance_date is null or je.clearance_date='0000-00-00')
AND
jea.account = %(account)s
AND
(jea.parent like %(txt)s or je.pay_to_recd_from like %(txt)s)
AND
je.docstatus = 1
ORDER BY
if(locate(%(_txt)s, jea.parent), locate(%(_txt)s, jea.parent), 99999),
jea.parent
LIMIT
%(start)s, %(page_len)s""",
{
'txt': "%%%s%%" % txt,
'_txt': txt.replace("%", ""),
'start': start,
'page_len': page_len,
'account': account
}
)
def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""
SELECT
sip.parent, si.customer, sip.amount, sip.mode_of_payment
FROM
`tabSales Invoice Payment` as sip
LEFT JOIN
`tabSales Invoice` as si
ON
sip.parent = si.name
WHERE
(sip.clearance_date is null or sip.clearance_date='0000-00-00')
AND
(sip.parent like %(txt)s or si.customer like %(txt)s)
ORDER BY
if(locate(%(_txt)s, sip.parent), locate(%(_txt)s, sip.parent), 99999),
sip.parent
LIMIT
%(start)s, %(page_len)s""",
{
'txt': "%%%s%%" % txt,
'_txt': txt.replace("%", ""),
'start': start,
'page_len': page_len
}
)

View File

@@ -0,0 +1,21 @@
<div class="transaction-header">
<div class="level list-row list-row-head text-muted small">
<div class="col-sm-2 ellipsis hidden-xs">
{{ __("Date") }}
</div>
<div class="col-xs-11 col-sm-4 ellipsis list-subject">
{{ __("Description") }}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{{ __("Debit") }}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{{ __("Credit") }}
</div>
<div class="col-sm-1 ellipsis hidden-xs">
{{ __("Currency") }}
</div>
<div class="col-sm-1 ellipsis">
</div>
</div>
</div>

View File

@@ -0,0 +1,36 @@
<div class="list-row transaction-item">
<div>
<div class="clickable-section" data-name={{ name }}>
<div class="col-sm-2 ellipsis hidden-xs">
{%= frappe.datetime.str_to_user(date) %}
</div>
<div class="col-xs-8 col-sm-4 ellipsis list-subject">
{{ description }}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{%= format_currency(debit, currency) %}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{%= format_currency(credit, currency) %}
</div>
<div class="col-sm-1 ellipsis hidden-xs">
{{ currency }}
</div>
</div>
<div class="col-xs-3 col-sm-1">
<div class="btn-group">
<a class="dropdown-toggle btn btn-default btn-xs" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span>Actions </span>
<span class="caret"></span>
</a>
<ul class="dropdown-menu reports-dropdown" style="max-height: 300px; overflow-y: auto; right: 0px; left: auto;">
<li><a class="new-reconciliation" data-name={{ name }}>{{ __("Reconcile") }}</a></li>
<li class="divider"></li>
<li><a class="new-payment" data-name={{ name }}>{{ __("New Payment") }}</a></li>
<li><a class="new-invoice" data-name={{ name }}>{{ __("New Invoice") }}</a></li>
<li><a class="new-expense" data-name={{ name }}>{{ __("New Expense") }}</a></li>
</ul>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,21 @@
<div class="transaction-header">
<div class="level list-row list-row-head text-muted small">
<div class="col-xs-3 col-sm-2 ellipsis">
{{ __("Payment Name") }}
</div>
<div class="col-xs-3 col-sm-2 ellipsis">
{{ __("Reference Date") }}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{{ __("Amount") }}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{{ __("Party") }}
</div>
<div class="col-xs-3 col-sm-2 ellipsis">
{{ __("Reference Number") }}
</div>
<div class="col-xs-2 col-sm-2">
</div>
</div>
</div>

View File

@@ -0,0 +1,36 @@
<div class="list-row">
<div>
<div class="col-xs-3 col-sm-2 ellipsis">
{{ name }}
</div>
<div class="col-xs-3 col-sm-2 ellipsis">
{% if (typeof reference_date !== "undefined") %}
{%= frappe.datetime.str_to_user(reference_date) %}
{% else %}
{% if (typeof posting_date !== "undefined") %}
{%= frappe.datetime.str_to_user(posting_date) %}
{% endif %}
{% endif %}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{{ format_currency(paid_amount, currency) }}
</div>
<div class="col-sm-2 ellipsis hidden-xs">
{% if (typeof party !== "undefined") %}
{{ party }}
{% endif %}
</div>
<div class="col-xs-3 col-sm-2 ellipsis">
{% if (typeof reference_no !== "undefined") %}
{{ reference_no }}
{% else %}
{{ "" }}
{% endif %}
</div>
<div class="col-xs-2 col-sm-2">
<div class="text-right margin-bottom">
<button class="btn btn-primary btn-xs reconciliation-btn" data-doctype="{{ doctype }}" data-name="{{ name }}">{{ __("Reconcile") }}</button>
</div>
</div>
</div>
</div>

View File

@@ -6,17 +6,18 @@
</style>
<div class="page-break">
<div>
{% set gl = frappe.get_list(doctype="GL Entry", fields=["account", "party_type", "party", "debit", "credit", "remarks"], filters={"voucher_type": doc.doctype, "voucher_no": doc.name}) %}
{%- if not doc.get("print_heading") and not doc.get("select_print_heading")
and doc.set("select_print_heading", _("Payment Entry")) -%}{%- endif -%}
{{ add_header(0, 1, doc, letter_head, no_letterhead, print_settings) }}
<div class="row margin-bottom">
<div class="col-sm-6">
<div class="col-xs-6">
<table>
<tr><td><strong>Voucher No: </strong></td><td>{{ doc.name }}</td></tr>
</table>
</div>
<div>
<div class="col-xs-6">
<table>
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.formatdate(doc.creation) }}</td></tr>
</table>
@@ -30,53 +31,46 @@
<th>Party</th>
<th>Amount</th>
</tr>
<tr>
<td class="top-bottom" colspan="5"><strong>Debit</strong></td>
</tr>
{% for entries in gl %}
{% if entries.credit == 0.0 %}
<tr>
<td class="right top-bottom">{{ entries.account }}</td>
<td class="right left top-bottom">{{ entries.party_type }}</td>
<td class="right left top-bottom">{{ entries.party }}</td>
<td class="left top-bottom">{{ entries.debit }}</td>
</tr>
<tr>
<td class="top-bottom"colspan="4"><strong> Narration </strong><br>{{ entries.remarks }}</td>
</tr>
{% endif %}
{% endfor %}
<tr>
<td class="right" colspan="3" ><strong>Total (debit) </strong></td>
<td class="left" >{{ gl | sum(attribute='debit') }}</td>
</tr>
<tr>
<td class="top-bottom" colspan="5"><strong>Credit</strong></td>
</tr>
{% set total_credit = 0 -%}
{% for entries in doc.gl_entries %}
{% for entries in gl %}
{% if entries.debit == 0.0 %}
<tr>
<td class="right top-bottom">{{ entries.account }}</td>
<td class="right left top-bottom">{{ entries.party_type }}</td>
<td class="right left top-bottom">{{ entries.party }}</td>
<td class="left top-bottom">{{ entries.credit }}</td>
{% set total_credit = total_credit + entries.credit -%}
</tr>
<tr>
<td class="top-bottom" colspan="4"><strong> Narration </strong><br>{{ entries.remarks }}</td>
</tr>
{% endif %}
{% endfor %}
<tr>
<td class="right" colspan="3"><strong>Total (credit) </strong></td>
<td class="left" >{{total_credit}}</td>
<td class="left" >{{ gl | sum(attribute='credit') }}</td>
</tr>
{% endif %}
{% endfor %}
<tr>
<td class="top-bottom" colspan="4"> </td>
</tr>
<tr>
<td class="top-bottom" colspan="5"><strong>Debit</strong></td>
</tr>
{% set total_debit = 0 -%}
{% for entries in doc.gl_entries %}
{% if entries.credit == 0.0 %}
<tr>
<td class="right top-bottom">{{ entries.account }}</td>
<td class="right left top-bottom">{{ entries.party_type }}</td>
<td class="right left top-bottom">{{ entries.party }}</td>
{% set total_debit = total_debit + entries.debit -%}
<td class="left top-bottom">{{ entries.debit }}</td>
</tr>
<tr>
<td class="top-bottom"colspan="4"><strong> Narration </strong><br>{{ entries.remarks }}</td>
</tr>
<tr>
<td class="right" colspan="3" ><strong>Total (debit) </strong></td>
<td class="left" >{{total_debit}}</td>
</tr>
{% endif %}
{% endfor %}
</table>
<div>
</div>

View File

@@ -3,26 +3,25 @@
.table-bordered td.top-bottom {border-top: none !important;border-bottom: none !important;}
.table-bordered td.right{border-right: none !important;}
.table-bordered td.left{border-left: none !important;}
</style>
<div class="page-break">
<div>
{% set gl = frappe.get_list(doctype="GL Entry", fields=["account", "party_type", "party", "debit", "credit", "remarks"], filters={"voucher_type": doc.doctype, "voucher_no": doc.name}) %}
{%- if not doc.get("print_heading") and not doc.get("select_print_heading")
and doc.set("select_print_heading", _("Journal Entry")) -%}{%- endif -%}
{{ add_header(0, 1, doc, letter_head, no_letterhead, print_settings) }}
<div class="row margin-bottom">
<div class="col-sm-6">
<div class="row">
<div class="col-xs-6">
<table>
<tr><td><strong>Voucher No: </strong></td><td>{{ doc.name }}</td></tr>
<tr><td><strong>Voucher No: </strong></td><td>{{ doc.name }}</td></tr>
</table>
</div>
<div>
<div class="col-xs-6">
<table>
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.formatdate(doc.creation) }}</td></tr>
</table>
</div>
</div>
<div class="margin-top">
<div>
<table class="table table-bordered table-condensed">
<tr>
<th>Account</th>
@@ -30,47 +29,43 @@
<th>Party</th>
<th>Amount</th>
</tr>
<tr>
<td class="top-bottom" colspan="5"><strong>Debit</strong></td>
</tr>
{% for entries in gl %}
{% if entries.credit == 0.0 %}
<tr>
<td class="right top-bottom">{{ entries.account }}</td>
<td class="right left top-bottom">{{ entries.party_type }}</td>
<td class="right left top-bottom">{{ entries.party }}</td>
<td class="left top-bottom">{{ entries.debit }}</td>
</tr>
{% endif %}
{% endfor %}
<tr>
<td class="right" colspan="3" ><strong>Total (debit) </strong></td>
<td class="left" >{{ gl | sum(attribute='debit') }}</td>
</tr>
<tr>
<td class="top-bottom" colspan="5"><strong>Credit</strong></td>
</tr>
{% set total_credit = 0 -%}
{% for entries in doc.gl_entries %}
{% for entries in gl %}
{% if entries.debit == 0.0 %}
<tr>
<td class="right top-bottom">{{ entries.account }}</td>
<td class="right left top-bottom">{{ entries.party_type }}</td>
<td class="right left top-bottom">{{ entries.party }}</td>
<td class="left top-bottom">{{ entries.credit }}</td>
{% set total_credit = total_credit + entries.credit -%}
</tr>
<tr>
<td class="right" colspan="3"><strong>Total (credit) </strong></td>
<td class="left" >{{total_credit}}</td>
</tr>
{% endif %}
{% endfor %}
<tr>
<td class="top-bottom" colspan="4"> </td>
<td class="right" colspan="3"><strong>Total (credit) </strong></td>
<td class="left" >{{ gl | sum(attribute='credit') }}</td>
</tr>
<tr>
<td class="top-bottom" colspan="5"><strong>Debit</strong></td>
<td class="top-bottom" colspan="5"><b>Narration: </b>{{ gl[0].remarks }}</td>
</tr>
{% set total_debit = 0 -%}
{% for entries in doc.gl_entries %}
{% if entries.credit == 0.0 %}
<tr>
<td class="right top-bottom">{{ entries.account }}</td>
<td class="right left top-bottom">{{ entries.party_type }}</td>
<td class="right left top-bottom">{{ entries.party }}</td>
{% set total_debit = total_debit + entries.debit -%}
<td class="left top-bottom">{{ entries.debit }}</td>
</tr>
<tr>
<td class="right" colspan="3" ><strong>Total (debit) </strong></td>
<td class="left" >{{total_debit}}</td>
</tr>
{% endif %}
{% endfor %}
</table>
<div>
</div>

View File

@@ -1,10 +1,11 @@
{%- from "templates/print_formats/standard_macros.html" import add_header -%}
<div class="page-break">
<div>
{% set gl = frappe.get_list(doctype="GL Entry", fields=["account", "party_type", "party", "debit", "credit", "remarks"], filters={"voucher_type": doc.doctype, "voucher_no": doc.name}) %}
{%- if not doc.get("print_heading") and not doc.get("select_print_heading")
and doc.set("select_print_heading", _("Purchase Invoice")) -%}{%- endif -%}
{{ add_header(0, 1, doc, letter_head, no_letterhead, print_settings) }}
<div class="row margin-bottom">
<div class="col-sm-6">
<div class="col-xs-6">
<table>
<tr><td><strong>Supplier Name: </strong></td><td>{{ doc.supplier }}</td></tr>
<tr><td><strong>Due Date: </strong></td><td>{{ frappe.utils.formatdate(doc.due_date) }}</td></tr>
@@ -13,7 +14,7 @@
<tr><td><strong>Mobile no: </strong> </td><td>{{doc.contact_mobile}}</td></tr>
</table>
</div>
<div>
<div class="col-xs-6">
<table>
<tr><td><strong>Voucher No: </strong></td><td>{{ doc.name }}</td></tr>
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.formatdate(doc.creation) }}</td></tr>
@@ -49,21 +50,27 @@
</table>
</div>
<div class="row margin-bottom">
<div class="col-sm-6">
<div class="col-xs-6">
<table>
<tr><td><strong>Total Quantity: </strong></td><td>{{ doc.total_qty }}</td></tr>
<tr><td><strong>Total: </strong></td><td>{{doc.total}}</td></tr>
<tr><td><strong>Net Weight: </strong></td><td>{{ doc.total_net_weight }}</td></tr>
</table>
</div>
<div>
<div class="col-xs-6">
<table>
<tr><td><strong>Tax and Charges: </strong></td><td>{{doc.taxes_and_charges}}</td></tr>
{% for tax in doc.taxes %}
<tr><td><strong>{{ tax.account_head }}: </strong></td><td>{{ tax.tax_amount_after_discount_amount }}</td></tr>
{% if tax.tax_amount_after_discount_amount!= 0 %}
<tr><td><strong>{{ tax.account_head }}: </strong></td><td>{{ tax.tax_amount_after_discount_amount }}</td></tr>
{% endif %}
{% endfor %}
{% if doc.taxes_and_charges_added!= 0 %}
<tr><td><strong> Taxes and Charges Added: </strong></td><td>{{ doc.taxes_and_charges_added }}</td></tr>
{% endif %}
{% if doc.taxes_and_charges_deducted!= 0 %}
<tr><td><strong> Taxes and Charges Deducted: </strong></td><td>{{ doc.taxes_and_charges_deducted }}</td></tr>
{% endif %}
<tr><td><strong> Total Taxes and Charges: </strong></td><td>{{ doc.total_taxes_and_charges }}</td></tr>
<tr><td><strong> Net Payable: </strong></td><td>{{ doc.grand_total }}</td></tr>
</table>
@@ -76,17 +83,17 @@
<th>Account</th>
<th>Party Type</th>
<th>Party</th>
<th>Credit Amount</th>
<th>Debit Amount</th>
<th>Credit Amount</th>
</tr>
{% for entries in doc.gl_entries %}
{% for entries in gl %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ entries.account }}</td>
<td>{{ entries.party_type }}</td>
<td>{{ entries.party }}</td>
<td>{{ entries.credit }}</td>
<td>{{ entries.debit }}</td>
<td>{{ entries.credit }}</td>
</tr>
{% endfor %}
<tr>

View File

@@ -1,10 +1,11 @@
{%- from "templates/print_formats/standard_macros.html" import add_header -%}
<div class="page-break">
<div>
{% set gl = frappe.get_list(doctype="GL Entry", fields=["account", "party_type", "party", "debit", "credit", "remarks"], filters={"voucher_type": doc.doctype, "voucher_no": doc.name}) %}
{%- if not doc.get("print_heading") and not doc.get("select_print_heading")
and doc.set("select_print_heading", _("Sales Invoice")) -%}{%- endif -%}
{{ add_header(0, 1, doc, letter_head, no_letterhead, print_settings) }}
<div class="row margin-bottom">
<div class="col-sm-6">
<div class="col-xs-6">
<table>
<tr><td><strong>Customer Name: </strong></td><td>{{ doc.customer }}</td></tr>
<tr><td><strong>Due Date: </strong></td><td>{{ frappe.utils.formatdate(doc.due_date) }}</td></tr>
@@ -13,7 +14,7 @@
<tr><td><strong>Mobile no: </strong> </td><td>{{doc.contact_mobile}}</td></tr>
</table>
</div>
<div>
<div class="col-xs-6">
<table>
<tr><td><strong>Voucher No: </strong></td><td>{{ doc.name }}</td></tr>
<tr><td><strong>Date: </strong></td><td>{{ frappe.utils.formatdate(doc.creation) }}</td></tr>
@@ -45,18 +46,20 @@
</table>
</div>
<div class="row margin-bottom">
<div class="col-sm-6">
<div class="col-xs-6">
<table>
<tr><td><strong>Total Quantity: </strong></td><td>{{ doc.total_qty }}</td></tr>
<tr><td><strong>Total: </strong></td><td>{{doc.total}}</td></tr>
<tr><td><strong>Net Weight: </strong></td><td>{{ doc.total_net_weight }}</td></tr>
</table>
</div>
<div>
<div class="col-xs-6">
<table>
<tr><td><strong>Tax and Charges: </strong></td><td>{{doc.taxes_and_charges}}</td></tr>
{% for tax in doc.taxes %}
<tr><td><strong>{{ tax.account_head }}: </strong></td><td>{{ tax.tax_amount_after_discount_amount }}</td></tr>
{% if tax.tax_amount_after_discount_amount!= 0 %}
<tr><td><strong>{{ tax.account_head }}: </strong></td><td>{{ tax.tax_amount_after_discount_amount }}</td></tr>
{% endif %}
{% endfor %}
<tr><td><strong> Total Taxes and Charges: </strong></td><td>{{ doc.total_taxes_and_charges }}</td></tr>
<tr><td><strong> Net Payable: </strong></td><td>{{ doc.grand_total }}</td></tr>
@@ -70,17 +73,17 @@
<th>Account</th>
<th>Party Type</th>
<th>Party</th>
<th>Credit Amount</th>
<th>Debit Amount</th>
<th>Credit Amount</th>
</tr>
{% for entries in doc.gl_entries %}
{% for entries in gl %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ entries.account }}</td>
<td>{{ entries.party_type }}</td>
<td>{{ entries.party }}</td>
<td>{{ entries.credit }}</td>
<td>{{ entries.debit }}</td>
<td>{{ entries.credit }}</td>
</tr>
{% endfor %}
<tr>

View File

@@ -194,10 +194,9 @@ class ReceivablePayableReport(object):
self.payment_term_map = self.get_payment_term_detail(voucher_nos)
for gle in gl_entries_data:
if self.is_receivable_or_payable(gle, self.dr_or_cr, future_vouchers):
if self.is_receivable_or_payable(gle, self.dr_or_cr, future_vouchers, return_entries):
outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount(
gle,self.filters.report_date, self.dr_or_cr, return_entries)
temp_outstanding_amt = outstanding_amount
temp_credit_note_amt = credit_note_amount
@@ -377,7 +376,7 @@ class ReceivablePayableReport(object):
# returns a generator
return self.get_gl_entries(party_type, report_date)
def is_receivable_or_payable(self, gle, dr_or_cr, future_vouchers):
def is_receivable_or_payable(self, gle, dr_or_cr, future_vouchers, return_entries):
return (
# advance
(not gle.against_voucher) or
@@ -388,30 +387,37 @@ class ReceivablePayableReport(object):
# sales invoice/purchase invoice
(gle.against_voucher==gle.voucher_no and gle.get(dr_or_cr) > 0) or
# standalone credit notes
(gle.against_voucher==gle.voucher_no and gle.voucher_no in return_entries and not return_entries.get(gle.voucher_no)) or
# entries adjusted with future vouchers
((gle.against_voucher_type, gle.against_voucher) in future_vouchers)
)
def get_return_entries(self, party_type):
doctype = "Sales Invoice" if party_type=="Customer" else "Purchase Invoice"
return [d.name for d in frappe.get_all(doctype, filters={"is_return": 1, "docstatus": 1})]
return_entries = frappe._dict(frappe.get_all(doctype,
filters={"is_return": 1, "docstatus": 1}, fields=["name", "return_against"], as_list=1))
return return_entries
def get_outstanding_amount(self, gle, report_date, dr_or_cr, return_entries):
payment_amount, credit_note_amount = 0.0, 0.0
reverse_dr_or_cr = "credit" if dr_or_cr=="debit" else "debit"
for e in self.get_gl_entries_for(gle.party, gle.party_type, gle.voucher_type, gle.voucher_no):
if getdate(e.posting_date) <= report_date and e.name!=gle.name:
if getdate(e.posting_date) <= report_date \
and (e.name!=gle.name or (e.voucher_no in return_entries and not return_entries.get(e.voucher_no))):
amount = flt(e.get(reverse_dr_or_cr), self.currency_precision) - flt(e.get(dr_or_cr), self.currency_precision)
if e.voucher_no not in return_entries:
payment_amount += amount
else:
credit_note_amount += amount
outstanding_amount = (flt((flt(gle.get(dr_or_cr), self.currency_precision)
- flt(gle.get(reverse_dr_or_cr), self.currency_precision)
- payment_amount - credit_note_amount), self.currency_precision))
voucher_amount = flt(gle.get(dr_or_cr), self.currency_precision) - flt(gle.get(reverse_dr_or_cr), self.currency_precision)
if gle.voucher_no in return_entries and not return_entries.get(gle.voucher_no):
voucher_amount = 0
outstanding_amount = flt((voucher_amount - payment_amount - credit_note_amount), self.currency_precision)
credit_note_amount = flt(credit_note_amount, self.currency_precision)
return outstanding_amount, credit_note_amount, payment_amount

View File

@@ -157,7 +157,7 @@ def get_conditions(filters):
conditions += """ and exists(select name from `tabSales Invoice Item`
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s)"""
if filters.get("item_group"):
conditions += """ and exists(select name from `tabSales Invoice Item`
where parent=`tabSales Invoice`.name
@@ -171,7 +171,7 @@ def get_invoices(filters, additional_query_columns):
conditions = get_conditions(filters)
return frappe.db.sql("""
select name, posting_date, debit_to, project, customer,
select name, posting_date, debit_to, project, customer,
customer_name, owner, remarks, territory, tax_id, customer_group,
base_net_total, base_grand_total, base_rounded_total, outstanding_amount {0}
from `tabSales Invoice`

View File

@@ -615,7 +615,7 @@ def get_held_invoices(party_type, party):
return held_invoices
def get_outstanding_invoices(party_type, party, account, condition=None, limit=None):
def get_outstanding_invoices(party_type, party, account, condition=None):
outstanding_invoices = []
precision = frappe.get_precision("Sales Invoice", "outstanding_amount") or 2
@@ -628,7 +628,6 @@ def get_outstanding_invoices(party_type, party, account, condition=None, limit=N
invoice = 'Sales Invoice' if erpnext.get_party_account_type(party_type) == 'Receivable' else 'Purchase Invoice'
held_invoices = get_held_invoices(party_type, party)
limit_cond = "limit %s" % limit if limit else ""
invoice_list = frappe.db.sql("""
select
@@ -643,11 +642,10 @@ def get_outstanding_invoices(party_type, party, account, condition=None, limit=N
and (against_voucher = '' or against_voucher is null))
or (voucher_type not in ('Journal Entry', 'Payment Entry')))
group by voucher_type, voucher_no
order by posting_date, name {limit_cond}""".format(
order by posting_date, name""".format(
dr_or_cr=dr_or_cr,
invoice = invoice,
condition=condition or "",
limit_cond = limit_cond
condition=condition or ""
), {
"party_type": party_type,
"party": party,

File diff suppressed because it is too large Load Diff

View File

@@ -76,6 +76,14 @@ def get_data():
{
"type": "doctype",
"name": "Item",
},
{
"type": "doctype",
"name": "Bank",
},
{
"type": "doctype",
"name": "Bank Account",
}
]
},
@@ -135,6 +143,12 @@ def get_data():
"name": "Bank Reconciliation",
"description": _("Update bank payment dates with journals.")
},
{
"type": "page",
"label": _("Reconcile payments and bank transactions"),
"name": "bank-reconciliation",
"description": _("Link bank transactions with payments.")
},
{
"type": "doctype",
"label": _("Match Payments with Invoices"),

View File

@@ -35,6 +35,11 @@ def get_data():
"type": "doctype",
"name": "Amazon MWS Settings",
"description": _("Connect Amazon with ERPNext"),
},
{
"type": "doctype",
"name": "Plaid Settings",
"description": _("Connect your bank accounts to ERPNext"),
}
]
}

View File

@@ -30,8 +30,9 @@ class AccountsController(TransactionBase):
return self.__company_currency
def onload(self):
self.get("__onload").make_payment_via_journal_entry \
= frappe.db.get_single_value('Accounts Settings', 'make_payment_via_journal_entry')
if self.get("__onload"):
self.get("__onload").make_payment_via_journal_entry \
= frappe.db.get_single_value('Accounts Settings', 'make_payment_via_journal_entry')
if self.is_new():
relevant_docs = ("Quotation", "Purchase Order", "Sales Order",
@@ -116,12 +117,6 @@ class AccountsController(TransactionBase):
self.validate_non_invoice_documents_schedule()
def before_print(self):
if self.doctype in ['Journal Entry', 'Payment Entry', 'Sales Invoice', 'Purchase Invoice']:
self.gl_entries = frappe.get_list("GL Entry", filters={
"voucher_type": self.doctype,
"voucher_no": self.name
}, fields=["account", "party_type", "party", "debit", "credit", "remarks"])
if self.doctype in ['Purchase Order', 'Sales Order', 'Sales Invoice', 'Purchase Invoice',
'Supplier Quotation', 'Purchase Receipt', 'Delivery Note', 'Quotation']:
if self.get("group_same_items"):
@@ -965,11 +960,11 @@ def get_advance_journal_entries(party_type, party, party_account, amount_field,
def get_advance_payment_entries(party_type, party, party_account, order_doctype,
order_list=None, include_unallocated=True, against_all_orders=False, limit=1000):
order_list=None, include_unallocated=True, against_all_orders=False, limit=None):
party_account_field = "paid_from" if party_type == "Customer" else "paid_to"
payment_type = "Receive" if party_type == "Customer" else "Pay"
payment_entries_against_order, unallocated_payment_entries = [], []
limit_cond = "limit %s" % (limit or 1000)
limit_cond = "limit %s" % limit if limit else ""
if order_list or against_all_orders:
if order_list:

View File

@@ -19,27 +19,24 @@ def verify_request():
frappe.get_request_header("X-Wc-Webhook-Signature") and \
not sig == bytes(frappe.get_request_header("X-Wc-Webhook-Signature").encode()):
frappe.throw(_("Unverified Webhook Data"))
frappe.set_user(woocommerce_settings.modified_by)
frappe.set_user(woocommerce_settings.creation_user)
@frappe.whitelist(allow_guest=True)
def order(data=None):
if not data:
verify_request()
def order():
woocommerce_settings = frappe.get_doc("Woocommerce Settings")
if frappe.flags.woocomm_test_order_data:
fd = frappe.flags.woocomm_test_order_data
event = "created"
if frappe.request and frappe.request.data:
elif frappe.request and frappe.request.data:
verify_request()
fd = json.loads(frappe.request.data)
elif data:
fd = data
event = frappe.get_request_header("X-Wc-Webhook-Event")
else:
return "success"
if not data:
event = frappe.get_request_header("X-Wc-Webhook-Event")
else:
event = "created"
if event == "created":
raw_billing_data = fd.get("billing")
customer_woo_com_email = raw_billing_data.get("email")
@@ -73,7 +70,7 @@ def order(data=None):
new_sales_order.po_no = fd.get("id")
new_sales_order.woocommerce_id = fd.get("id")
new_sales_order.naming_series = "SO-"
new_sales_order.naming_series = woocommerce_settings.sales_order_series or "SO-WOO-"
placed_order_date = created_date[0]
raw_date = datetime.datetime.strptime(placed_order_date, "%Y-%m-%d")
@@ -100,10 +97,10 @@ def order(data=None):
"item_name": found_item.item_name,
"description": found_item.item_name,
"delivery_date":order_delivery_date,
"uom": "Nos",
"uom": woocommerce_settings.uom or _("Nos"),
"qty": item.get("quantity"),
"rate": item.get("price"),
"warehouse": "Stores" + " - " + company_abbr
"warehouse": woocommerce_settings.warehouse or "Stores" + " - " + company_abbr
})
add_tax_details(new_sales_order,ordered_items_tax,"Ordered Item tax",0)
@@ -175,6 +172,7 @@ def link_customer_and_address(raw_billing_data,customer_status):
frappe.db.commit()
def link_item(item_data,item_status):
woocommerce_settings = frappe.get_doc("Woocommerce Settings")
if item_status == 0:
#Create Item
@@ -189,6 +187,7 @@ def link_item(item_data,item_status):
item.item_code = "woocommerce - " + str(item_data.get("product_id"))
item.woocommerce_id = str(item_data.get("product_id"))
item.item_group = "WooCommerce Products"
item.stock_uom = woocommerce_settings.uom or _("Nos")
item.save()
frappe.db.commit()
@@ -209,4 +208,4 @@ def add_tax_details(sales_order,price,desc,status):
"account_head": account_head_type,
"tax_amount": price,
"description": desc
})
})

View File

@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
import requests
from plaid import Client
from plaid.errors import APIError, ItemError
class PlaidConnector():
def __init__(self, access_token=None):
if not(frappe.conf.get("plaid_client_id") and frappe.conf.get("plaid_secret") and frappe.conf.get("plaid_public_key")):
frappe.throw(_("Please complete your Plaid API configuration before synchronizing your account"))
self.config = {
"plaid_client_id": frappe.conf.get("plaid_client_id"),
"plaid_secret": frappe.conf.get("plaid_secret"),
"plaid_public_key": frappe.conf.get("plaid_public_key"),
"plaid_env": frappe.conf.get("plaid_env")
}
self.client = Client(client_id=self.config["plaid_client_id"],
secret=self.config["plaid_secret"],
public_key=self.config["plaid_public_key"],
environment=self.config["plaid_env"]
)
self.access_token = access_token
def get_access_token(self, public_token):
if public_token is None:
frappe.log_error(_("Public token is missing for this bank"), _("Plaid public token error"))
response = self.client.Item.public_token.exchange(public_token)
access_token = response['access_token']
return access_token
def auth(self):
try:
self.client.Auth.get(self.access_token)
print("Authentication successful.....")
except ItemError as e:
if e.code == 'ITEM_LOGIN_REQUIRED':
pass
else:
pass
except APIError as e:
if e.code == 'PLANNED_MAINTENANCE':
pass
else:
pass
except requests.Timeout:
pass
except Exception as e:
print(e)
frappe.log_error(frappe.get_traceback(), _("Plaid authentication error"))
frappe.msgprint({"title": _("Authentication Failed"), "message":e, "raise_exception":1, "indicator":'red'})
def get_transactions(self, start_date, end_date, account_id=None):
try:
self.auth()
if account_id:
account_ids = [account_id]
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, account_ids=account_ids)
else:
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date)
transactions = response['transactions']
while len(transactions) < response['total_transactions']:
response = self.client.Transactions.get(self.access_token, start_date=start_date, end_date=end_date, offset=len(transactions))
transactions.extend(response['transactions'])
return transactions
except Exception:
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))

View File

@@ -0,0 +1,104 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.provide("erpnext.integrations");
frappe.ui.form.on('Plaid Settings', {
link_new_account: function(frm) {
new erpnext.integrations.plaidLink(frm);
}
});
erpnext.integrations.plaidLink = class plaidLink {
constructor(parent) {
this.frm = parent;
this.product = ["transactions", "auth"];
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
this.init_config();
}
init_config() {
const me = this;
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration')
.then(result => {
if (result !== "disabled") {
me.plaid_env = result.plaid_env;
me.plaid_public_key = result.plaid_public_key;
me.client_name = result.client_name;
me.init_plaid();
} else {
frappe.throw(__("Please save your document before adding a new account"));
}
});
}
init_plaid() {
const me = this;
me.loadScript(me.plaidUrl)
.then(() => {
me.onScriptLoaded(me);
})
.then(() => {
if (me.linkHandler) {
me.linkHandler.open();
}
})
.catch((error) => {
me.onScriptError(error);
});
}
loadScript(src) {
return new Promise(function (resolve, reject) {
if (document.querySelector('script[src="' + src + '"]')) {
resolve();
return;
}
const el = document.createElement('script');
el.type = 'text/javascript';
el.async = true;
el.src = src;
el.addEventListener('load', resolve);
el.addEventListener('error', reject);
el.addEventListener('abort', reject);
document.head.appendChild(el);
});
}
onScriptLoaded(me) {
me.linkHandler = window.Plaid.create({
clientName: me.client_name,
env: me.plaid_env,
key: me.plaid_public_key,
onSuccess: me.plaid_success,
product: me.product
});
}
onScriptError(error) {
frappe.msgprint('There was an issue loading the link-initialize.js script');
frappe.msgprint(error);
}
plaid_success(token, response) {
const me = this;
frappe.prompt({
fieldtype:"Link",
options: "Company",
label:__("Company"),
fieldname:"company",
reqd:1
}, (data) => {
me.company = data.company;
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_institution', {token: token, response: response})
.then((result) => {
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.add_bank_accounts', {response: response,
bank: result, company: me.company});
})
.then(() => {
frappe.show_alert({message:__("Bank accounts added"), indicator:'green'});
});
}, __("Select a company"), __("Continue"));
}
};

View File

@@ -0,0 +1,161 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-10-25 10:02:48.656165",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "enabled",
"fieldtype": "Check",
"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": "Enabled",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.enabled==1",
"fieldname": "automatic_sync",
"fieldtype": "Check",
"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": "Synchronize all accounts every hour",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:(doc.enabled==1)&&(!doc.__islocal)",
"fieldname": "link_new_account",
"fieldtype": "Button",
"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": "Link a new bank account",
"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,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2018-12-14 12:51:12.331395",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Plaid Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
}

View File

@@ -0,0 +1,198 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import json
from frappe import _
from frappe.model.document import Document
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_connector import PlaidConnector
from frappe.utils import getdate, formatdate, today, add_months
class PlaidSettings(Document):
pass
@frappe.whitelist()
def plaid_configuration():
if frappe.db.get_value("Plaid Settings", None, "enabled") == "1":
return {"plaid_public_key": frappe.conf.get("plaid_public_key") or None, "plaid_env": frappe.conf.get("plaid_env") or None, "client_name": frappe.local.site }
else:
return "disabled"
@frappe.whitelist()
def add_institution(token, response):
response = json.loads(response)
plaid = PlaidConnector()
access_token = plaid.get_access_token(token)
if not frappe.db.exists("Bank", response["institution"]["name"]):
try:
bank = frappe.get_doc({
"doctype": "Bank",
"bank_name": response["institution"]["name"],
"plaid_access_token": access_token
})
bank.insert()
except Exception:
frappe.throw(frappe.get_traceback())
else:
bank = frappe.get_doc("Bank", response["institution"]["name"])
bank.plaid_access_token = access_token
bank.save()
return bank
@frappe.whitelist()
def add_bank_accounts(response, bank, company):
response = json.loads(response) if not "accounts" in response else response
bank = json.loads(bank)
result = []
default_gl_account = get_default_bank_cash_account(company, "Bank")
if not default_gl_account:
frappe.throw(_("Please setup a default bank account for company {0}".format(company)))
for account in response["accounts"]:
acc_type = frappe.db.get_value("Account Type", account["type"])
if not acc_type:
add_account_type(account["type"])
acc_subtype = frappe.db.get_value("Account Subtype", account["subtype"])
if not acc_subtype:
add_account_subtype(account["subtype"])
if not frappe.db.exists("Bank Account", dict(integration_id=account["id"])):
try:
new_account = frappe.get_doc({
"doctype": "Bank Account",
"bank": bank["bank_name"],
"account": default_gl_account.account,
"account_name": account["name"],
"account_type": account["type"] or "",
"account_subtype": account["subtype"] or "",
"mask": account["mask"] or "",
"integration_id": account["id"],
"is_company_account": 1,
"company": company
})
new_account.insert()
result.append(new_account.name)
except frappe.UniqueValidationError:
frappe.msgprint(_("Bank account {0} already exists and could not be created again").format(new_account.account_name))
except Exception:
frappe.throw(frappe.get_traceback())
else:
result.append(frappe.db.get_value("Bank Account", dict(integration_id=account["id"]), "name"))
return result
def add_account_type(account_type):
try:
frappe.get_doc({
"doctype": "Account Type",
"account_type": account_type
}).insert()
except Exception:
frappe.throw(frappe.get_traceback())
def add_account_subtype(account_subtype):
try:
frappe.get_doc({
"doctype": "Account Subtype",
"account_subtype": account_subtype
}).insert()
except Exception:
frappe.throw(frappe.get_traceback())
@frappe.whitelist()
def sync_transactions(bank, bank_account):
last_sync_date = frappe.db.get_value("Bank Account", bank_account, "last_integration_date")
if last_sync_date:
start_date = formatdate(last_sync_date, "YYYY-MM-dd")
else:
start_date = formatdate(add_months(today(), -12), "YYYY-MM-dd")
end_date = formatdate(today(), "YYYY-MM-dd")
try:
transactions = get_transactions(bank=bank, bank_account=bank_account, start_date=start_date, end_date=end_date)
result = []
if transactions:
for transaction in transactions:
result.append(new_bank_transaction(transaction))
frappe.db.set_value("Bank Account", bank_account, "last_integration_date", getdate(end_date))
return result
except Exception:
frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error"))
def get_transactions(bank, bank_account=None, start_date=None, end_date=None):
access_token = None
if bank_account:
related_bank = frappe.db.get_values("Bank Account", bank_account, ["bank", "integration_id"], as_dict=True)
access_token = frappe.db.get_value("Bank", related_bank[0].bank, "plaid_access_token")
account_id = related_bank[0].integration_id
else:
access_token = frappe.db.get_value("Bank", bank, "plaid_access_token")
account_id = None
plaid = PlaidConnector(access_token)
transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id)
return transactions
def new_bank_transaction(transaction):
result = []
bank_account = frappe.db.get_value("Bank Account", dict(integration_id=transaction["account_id"]))
if float(transaction["amount"]) >= 0:
debit = float(transaction["amount"])
credit = 0
else:
debit = 0
credit = abs(float(transaction["amount"]))
status = "Pending" if transaction["pending"] == "True" else "Settled"
if not frappe.db.exists("Bank Transaction", dict(transaction_id=transaction["transaction_id"])):
try:
new_transaction = frappe.get_doc({
"doctype": "Bank Transaction",
"date": getdate(transaction["date"]),
"status": status,
"bank_account": bank_account,
"debit": debit,
"credit": credit,
"currency": transaction["iso_currency_code"],
"description": transaction["name"]
})
new_transaction.insert()
new_transaction.submit()
result.append(new_transaction.name)
except Exception:
frappe.throw(frappe.get_traceback())
return result
def automatic_synchronization():
settings = frappe.get_doc("Plaid Settings", "Plaid Settings")
if settings.enabled == 1 and settings.automatic_sync == 1:
plaid_accounts = frappe.get_all("Bank Account", filter={"integration_id": ["!=", ""]}, fields=["name", "bank"])
for plaid_account in plaid_accounts:
frappe.enqueue("erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.sync_transactions", bank=plaid_account.bank, bank_account=plaid_account.name)

View File

@@ -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: Plaid Settings", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Plaid Settings
() => frappe.tests.make('Plaid Settings', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
import frappe
from erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings import plaid_configuration, add_account_type, add_account_subtype, new_bank_transaction, add_bank_accounts
import json
from frappe.utils.response import json_handler
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
class TestPlaidSettings(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
for bt in frappe.get_all("Bank Transaction"):
doc = frappe.get_doc("Bank Transaction", bt.name)
doc.cancel()
doc.delete()
for ba in frappe.get_all("Bank Account"):
frappe.get_doc("Bank Account", ba.name).delete()
for at in frappe.get_all("Account Type"):
frappe.get_doc("Account Type", at.name).delete()
for ast in frappe.get_all("Account Subtype"):
frappe.get_doc("Account Subtype", ast.name).delete()
def test_plaid_disabled(self):
frappe.db.set_value("Plaid Settings", None, "enabled", 0)
self.assertTrue(plaid_configuration() == "disabled")
def test_add_account_type(self):
add_account_type("brokerage")
self.assertEqual(frappe.get_doc("Account Type", "brokerage").name, "brokerage")
def test_add_account_subtype(self):
add_account_subtype("loan")
self.assertEqual(frappe.get_doc("Account Subtype", "loan").name, "loan")
def test_default_bank_account(self):
if not frappe.db.exists("Bank", "Citi"):
frappe.get_doc({
"doctype": "Bank",
"bank_name": "Citi"
}).insert()
bank_accounts = {
'account': {
'subtype': 'checking',
'mask': '0000',
'type': 'depository',
'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
'name': 'Plaid Checking'
},
'account_id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
'link_session_id': 'db673d75-61aa-442a-864f-9b3f174f3725',
'accounts': [{
'type': 'depository',
'subtype': 'checking',
'mask': '0000',
'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
'name': 'Plaid Checking'
}],
'institution': {
'institution_id': 'ins_6',
'name': 'Citi'
}
}
bank = json.dumps(frappe.get_doc("Bank", "Citi").as_dict(), default=json_handler)
company = frappe.db.get_single_value('Global Defaults', 'default_company')
frappe.db.set_value("Company", company, "default_bank_account", None)
self.assertRaises(frappe.ValidationError, add_bank_accounts, response=bank_accounts, bank=bank, company=company)
def test_new_transaction(self):
if not frappe.db.exists("Bank", "Citi"):
frappe.get_doc({
"doctype": "Bank",
"bank_name": "Citi"
}).insert()
bank_accounts = {
'account': {
'subtype': 'checking',
'mask': '0000',
'type': 'depository',
'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
'name': 'Plaid Checking'
},
'account_id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
'link_session_id': 'db673d75-61aa-442a-864f-9b3f174f3725',
'accounts': [{
'type': 'depository',
'subtype': 'checking',
'mask': '0000',
'id': '6GbM6RRQgdfy3lAqGz4JUnpmR948WZFg8DjQK',
'name': 'Plaid Checking'
}],
'institution': {
'institution_id': 'ins_6',
'name': 'Citi'
}
}
bank = json.dumps(frappe.get_doc("Bank", "Citi").as_dict(), default=json_handler)
company = frappe.db.get_single_value('Global Defaults', 'default_company')
if frappe.db.get_value("Company", company, "default_bank_account") is None:
frappe.db.set_value("Company", company, "default_bank_account", get_default_bank_cash_account(company, "Cash").get("account"))
add_bank_accounts(bank_accounts, bank, company)
transactions = {
'account_owner': None,
'category': ['Food and Drink', 'Restaurants'],
'account_id': 'b4Jkp1LJDZiPgojpr1ansXJrj5Q6w9fVmv6ov',
'pending_transaction_id': None,
'transaction_id': 'x374xPa7DvUewqlR5mjNIeGK8r8rl3Sn647LM',
'unofficial_currency_code': None,
'name': 'INTRST PYMNT',
'transaction_type': 'place',
'amount': -4.22,
'location': {
'city': None,
'zip': None,
'store_number': None,
'lon': None,
'state': None,
'address': None,
'lat': None
},
'payment_meta': {
'reference_number': None,
'payer': None,
'payment_method': None,
'reason': None,
'payee': None,
'ppd_id': None,
'payment_processor': None,
'by_order_of': None
},
'date': '2017-12-22',
'category_id': '13005000',
'pending': False,
'iso_currency_code': 'USD'
}
new_bank_transaction(transactions)
self.assertTrue(len(frappe.get_all("Bank Transaction")) == 1)

View File

@@ -42,4 +42,15 @@ frappe.ui.form.on('Woocommerce Settings', {
frm.set_df_property("api_consumer_key", "reqd", frm.doc.enable_sync);
frm.set_df_property("api_consumer_secret", "reqd", frm.doc.enable_sync);
}
});
});
frappe.ui.form.on("Woocommerce Settings", "onload", function () {
frappe.call({
method: "erpnext.erpnext_integrations.doctype.woocommerce_settings.woocommerce_settings.get_series",
callback: function (r) {
$.each(r.message, function (key, value) {
set_field_options(key, value);
});
}
});
});

View File

@@ -122,3 +122,9 @@ def generate_secret():
woocommerce_settings = frappe.get_doc("Woocommerce Settings")
woocommerce_settings.secret = frappe.generate_hash()
woocommerce_settings.save()
@frappe.whitelist()
def get_series():
return {
"sales_order_series" : frappe.get_meta("Sales Order").get_options("naming_series") or "SO-WOO-",
}

View File

@@ -123,13 +123,13 @@ var btn_invoice_registration = function (frm) {
frappe.ui.form.on('Patient Relation', {
patient_relation_add: function(frm){
frm.fields_dict['patient_relation'].grid.get_field('patient').get_query = function(frm){
frm.fields_dict['patient_relation'].grid.get_field('patient').get_query = function(doc){
var patient_list = [];
if(!frm.doc.__islocal) patient_list.push(frm.doc.name);
$.each(frm.doc.patient_relation, function(idx, val){
if(!doc.__islocal) patient_list.push(doc.name);
$.each(doc.patient_relation, function(idx, val){
if (val.patient) patient_list.push(val.patient);
});
return { filters: [['Patient', 'name', 'not in', patient_list]] };
};
}
});
});

View File

@@ -229,6 +229,7 @@ scheduler_events = {
'erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails',
"erpnext.accounts.doctype.subscription.subscription.process_all",
"erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details",
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
"erpnext.projects.doctype.project.project.hourly_reminder",
"erpnext.projects.doctype.project.project.collect_project_status"
],

View File

@@ -39,6 +39,7 @@ class TestEmployeeOnboarding(unittest.TestCase):
# complete the task
project = frappe.get_doc('Project', onboarding.project)
project.load_tasks()
project.tasks[0].status = 'Closed'
project.save()

View File

@@ -10,9 +10,9 @@ test_dependencies = ["Employee Onboarding"]
class TestEmployeeSeparation(unittest.TestCase):
def test_employee_separation(self):
employee = get_employee()
employee = frappe.db.get_value("Employee", {"status": "Active"})
separation = frappe.new_doc('Employee Separation')
separation.employee = employee.name
separation.employee = employee
separation.company = '_Test Company'
separation.append('activities', {
'activity_name': 'Deactivate Employee',
@@ -23,7 +23,4 @@ class TestEmployeeSeparation(unittest.TestCase):
separation.submit()
self.assertEqual(separation.docstatus, 1)
separation.cancel()
self.assertEqual(separation.project, "")
def get_employee():
return frappe.get_doc('Employee', {'employee_name': 'Test Researcher'})
self.assertEqual(separation.project, "")

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@ class ExpenseApproverIdentityError(frappe.ValidationError): pass
class ExpenseClaim(AccountsController):
def onload(self):
self.get("__onload").make_payment_via_journal_entry = frappe.db.get_single_value('Accounts Settings',
self.get("__onload").make_payment_via_journal_entry = frappe.db.get_single_value('Accounts Settings',
'make_payment_via_journal_entry')
def validate(self):
@@ -103,7 +103,7 @@ class ExpenseClaim(AccountsController):
self.validate_account_details()
payable_amount = flt(self.total_sanctioned_amount) - flt(self.total_advance_amount)
# payable entry
if payable_amount:
gl_entry.append(
@@ -233,7 +233,7 @@ class ExpenseClaim(AccountsController):
expense.default_account = get_expense_claim_account(expense.expense_type, self.company)["account"]
def update_reimbursed_amount(doc):
amt = frappe.db.sql("""select ifnull(sum(debit_in_account_currency), 0) as amt
amt = frappe.db.sql("""select ifnull(sum(debit_in_account_currency), 0) as amt
from `tabGL Entry` where against_voucher_type = 'Expense Claim' and against_voucher = %s
and party = %s """, (doc.name, doc.employee) ,as_dict=1)[0].amt
@@ -288,7 +288,7 @@ def get_expense_claim_account(expense_claim_type, company):
if not account:
frappe.throw(_("Please set default account in Expense Claim Type {0}")
.format(expense_claim_type))
return {
"account": account
}
@@ -301,9 +301,9 @@ def get_advances(employee, advance_id=None):
condition = 'name="{0}"'.format(frappe.db.escape(advance_id))
return frappe.db.sql("""
select
select
name, posting_date, paid_amount, claimed_amount, advance_account
from
from
`tabEmployee Advance`
where {0}
""".format(condition), as_dict=1)

View File

@@ -13,19 +13,23 @@ test_dependencies = ['Employee']
class TestExpenseClaim(unittest.TestCase):
def test_total_expense_claim_for_project(self):
frappe.db.sql("""delete from `tabTask` where project = "_Test Project 1" """)
frappe.db.sql("""delete from `tabProject Task` where parent = "_Test Project 1" """)
frappe.db.sql("""delete from `tabProject` where name = "_Test Project 1" """)
frappe.db.sql("delete from `tabExpense Claim` where project='_Test Project 1'")
frappe.get_doc({
"project_name": "_Test Project 1",
"doctype": "Project",
"tasks" :
[{ "title": "_Test Project Task 1", "status": "Open" }]
}).save()
task = frappe.get_doc({
"doctype": "Task",
"subject": "_Test Project Task 1",
"project": "_Test Project 1"
}).save()
task_name = frappe.db.get_value("Task", {"project": "_Test Project 1"})
payable_account = get_payable_account("Wind Power LLC")
make_expense_claim(payable_account, 300, 200, "Wind Power LLC","Travel Expenses - WP", "_Test Project 1", task_name)
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200)
@@ -103,9 +107,10 @@ def get_payable_account(company):
return frappe.get_cached_value('Company', company, 'default_payable_account')
def make_expense_claim(payable_account,claim_amount, sanctioned_amount, company, account, project=None, task_name=None):
employee = frappe.db.get_value("Employee", {"status": "Active"})
expense_claim = frappe.get_doc({
"doctype": "Expense Claim",
"employee": "_T-Employee-00001",
"employee": employee,
"payable_account": payable_account,
"approval_status": "Approved",
"company": company,

View File

@@ -441,7 +441,7 @@ class SalarySlip(TransactionBase):
def calculate_net_pay(self):
if self.salary_structure:
self.calculate_component_amounts()
disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total"))
precision = frappe.defaults.get_global_default("currency_precision")
self.total_deduction = 0
@@ -452,10 +452,10 @@ class SalarySlip(TransactionBase):
self.set_loan_repayment()
self.net_pay = (flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))) * flt(self.payment_days / self.total_working_days)
self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
self.rounded_total = rounded(self.net_pay,
self.precision("net_pay") if disable_rounded_total else 0)
if self.net_pay < 0:
frappe.throw(_("Net Pay cannnot be negative"))

View File

@@ -589,4 +589,5 @@ erpnext.patches.v11_0.remove_barcodes_field_from_copy_fields_to_variants
erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019
erpnext.patches.v11_0.make_italian_localization_fields # 26-03-2019
erpnext.patches.v11_1.make_job_card_time_logs
erpnext.patches.v11_1.set_variant_based_on
erpnext.patches.v11_1.set_variant_based_on
erpnext.patches.v11_1.woocommerce_set_creation_user

View File

@@ -0,0 +1,10 @@
from __future__ import unicode_literals
import frappe
def execute():
woocommerce_setting_enable_sync = frappe.db.sql("SELECT t.value FROM tabSingles t WHERE doctype = 'Woocommerce Settings' AND field = 'enable_sync'", as_dict=True)
if len(woocommerce_setting_enable_sync) and woocommerce_setting_enable_sync[0].value == '1':
frappe.db.sql("""UPDATE tabSingles
SET value = (SELECT t.value FROM tabSingles t WHERE doctype = 'Woocommerce Settings' AND field = 'modified_by')
WHERE doctype = 'Woocommerce Settings'
AND field = 'creation_user';""")

View File

@@ -30,11 +30,13 @@ class Project(Document):
self.update_costing()
def __setup__(self):
def before_print(self):
self.onload()
def load_tasks(self):
"""Load `tasks` from the database"""
project_task_custom_fields = frappe.get_all("Custom Field", {"dt": "Project Task"}, "fieldname")
self.tasks = []
for task in self.get_tasks():
task_map = {
@@ -47,7 +49,7 @@ class Project(Document):
"task_weight": task.task_weight
}
self.map_custom_fields(task, task_map)
self.map_custom_fields(task, task_map, project_task_custom_fields)
self.append("tasks", task_map)
@@ -149,7 +151,7 @@ class Project(Document):
"task_weight": t.task_weight
})
self.map_custom_fields(t, task)
self.map_custom_fields(t, task, custom_fields)
task.flags.ignore_links = True
task.flags.from_project = True
@@ -173,10 +175,6 @@ class Project(Document):
for t in frappe.get_all("Task", ["name"], {"project": self.name, "name": ("not in", task_names)}):
self.deleted_task_list.append(t.name)
def update_costing_and_percentage_complete(self):
self.update_percent_complete()
self.update_costing()
def is_row_updated(self, row, existing_task_data, fields):
if self.get("__islocal") or not existing_task_data: return True
@@ -186,10 +184,8 @@ class Project(Document):
if row.get(field) != d.get(field):
return True
def map_custom_fields(self, source, target):
project_task_custom_fields = frappe.get_all("Custom Field", {"dt": "Project Task"}, "fieldname")
for field in project_task_custom_fields:
def map_custom_fields(self, source, target, custom_fields):
for field in custom_fields:
target.update({
field.fieldname: source.get(field.fieldname)
})
@@ -197,8 +193,6 @@ class Project(Document):
def update_project(self):
self.update_percent_complete()
self.update_costing()
self.flags.dont_sync_tasks = True
self.save(ignore_permissions=True)
def after_insert(self):
if self.sales_order:
@@ -233,6 +227,7 @@ class Project(Document):
self.status = "Completed"
elif not self.status == "Cancelled":
self.status = "Open"
self.db_update()
def update_costing(self):
from_time_sheet = frappe.db.sql("""select
@@ -246,7 +241,7 @@ class Project(Document):
from_expense_claim = frappe.db.sql("""select
sum(total_sanctioned_amount) as total_sanctioned_amount
from `tabExpense Claim` where project = %s
and docstatus = 1""", self.name, as_dict=1)[0]
and docstatus = 1""", self.name, as_dict=1, debug=1)[0]
self.actual_start_date = from_time_sheet.start_date
self.actual_end_date = from_time_sheet.end_date
@@ -260,6 +255,7 @@ class Project(Document):
self.update_sales_amount()
self.update_billed_amount()
self.calculate_gross_margin()
self.db_update()
def calculate_gross_margin(self):
expense_amount = (flt(self.total_costing_amount) + flt(self.total_expense_claim)
@@ -313,7 +309,7 @@ class Project(Document):
def on_update(self):
self.delete_task()
self.load_tasks()
self.update_costing_and_percentage_complete()
self.update_project()
self.update_dependencies_on_duplicated_project()
def delete_task(self):

View File

@@ -74,24 +74,17 @@ def get_data(filters):
if time_start <= from_date and time_end >= from_date:
total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity,
time_end, from_date, total_hours, total_billable_hours, total_amount)
billable_hours_worked += total_billable_hours
hours_worked += total_hours
working_cost += total_amount
elif time_start >= from_date and time_end >= to_date:
elif time_start <= to_date and time_end >= to_date:
total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity,
to_date, time_start, total_hours, total_billable_hours, total_amount)
billable_hours_worked += total_billable_hours
hours_worked += total_hours
working_cost += total_amount
elif time_start >= from_date and time_end <= to_date:
total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity,
time_end, time_start, total_hours, total_billable_hours, total_amount)
billable_hours_worked += total_billable_hours
hours_worked += total_hours
working_cost += total_amount
hours_worked += total_hours
billable_hours_worked += total_billable_hours
working_cost += total_amount
row = {
"employee": entries.employee,
@@ -101,7 +94,6 @@ def get_data(filters):
"total_hours": total_hours,
"amount": total_amount
}
if entries_exists:
data.append(row)
entries_exists = False

View File

@@ -1,58 +1,58 @@
{
"css/erpnext.css": [
"public/less/erpnext.less",
"public/less/hub.less"
],
"css/marketplace.css": [
"public/less/hub.less"
],
"js/erpnext-web.min.js": [
"public/js/website_utils.js",
"public/js/shopping_cart.js"
],
"css/erpnext.css": [
"public/less/erpnext.less",
"public/less/hub.less"
],
"css/marketplace.css": [
"public/less/hub.less"
],
"js/erpnext-web.min.js": [
"public/js/website_utils.js",
"public/js/shopping_cart.js"
],
"css/erpnext-web.css": [
"public/less/website.less"
],
"js/marketplace.min.js": [
"public/js/hub/marketplace.js"
],
"js/erpnext.min.js": [
"public/js/conf.js",
"public/js/utils.js",
"public/js/queries.js",
"public/js/sms_manager.js",
"public/js/utils/party.js",
"public/js/templates/address_list.html",
"public/js/templates/contact_list.html",
"public/js/controllers/stock_controller.js",
"public/js/payment/payments.js",
"public/js/controllers/taxes_and_totals.js",
"public/js/controllers/transaction.js",
"public/js/pos/pos.html",
"public/js/pos/pos_bill_item.html",
"public/js/pos/pos_bill_item_new.html",
"public/js/pos/pos_selected_item.html",
"public/js/pos/pos_item.html",
"public/js/pos/pos_tax_row.html",
"public/js/pos/customer_toolbar.html",
"public/js/pos/pos_invoice_list.html",
"public/js/payment/pos_payment.html",
"public/js/payment/payment_details.html",
"public/js/templates/item_selector.html",
"js/marketplace.min.js": [
"public/js/hub/marketplace.js"
],
"js/erpnext.min.js": [
"public/js/conf.js",
"public/js/utils.js",
"public/js/queries.js",
"public/js/sms_manager.js",
"public/js/utils/party.js",
"public/js/templates/address_list.html",
"public/js/templates/contact_list.html",
"public/js/controllers/stock_controller.js",
"public/js/payment/payments.js",
"public/js/controllers/taxes_and_totals.js",
"public/js/controllers/transaction.js",
"public/js/pos/pos.html",
"public/js/pos/pos_bill_item.html",
"public/js/pos/pos_bill_item_new.html",
"public/js/pos/pos_selected_item.html",
"public/js/pos/pos_item.html",
"public/js/pos/pos_tax_row.html",
"public/js/pos/customer_toolbar.html",
"public/js/pos/pos_invoice_list.html",
"public/js/payment/pos_payment.html",
"public/js/payment/payment_details.html",
"public/js/templates/item_selector.html",
"public/js/templates/employees_to_mark_attendance.html",
"public/js/utils/item_selector.js",
"public/js/help_links.js",
"public/js/agriculture/ternary_plot.js",
"public/js/templates/item_quick_entry.html",
"public/js/utils/item_quick_entry.js",
"public/js/utils/item_selector.js",
"public/js/help_links.js",
"public/js/agriculture/ternary_plot.js",
"public/js/templates/item_quick_entry.html",
"public/js/utils/item_quick_entry.js",
"public/js/utils/customer_quick_entry.js",
"public/js/education/student_button.html",
"public/js/education/assessment_result_tool.html",
"public/js/hub/hub_factory.js"
],
"js/item-dashboard.min.js": [
"stock/dashboard/item_dashboard.html",
"stock/dashboard/item_dashboard_list.html",
"stock/dashboard/item_dashboard.js"
]
"public/js/education/student_button.html",
"public/js/education/assessment_result_tool.html",
"public/js/hub/hub_factory.js"
],
"js/item-dashboard.min.js": [
"stock/dashboard/item_dashboard.html",
"stock/dashboard/item_dashboard_list.html",
"stock/dashboard/item_dashboard.js"
]
}

View File

@@ -25,10 +25,10 @@
.app-icon-svg {
display: inline-block;
margin: auto;
text-align: center;
border-radius: 16px;
cursor: pointer;
margin: auto;
text-align: center;
border-radius: 16px;
cursor: pointer;
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.15);
}
@@ -458,4 +458,4 @@ body[data-route="pos"] {
.list-item_content {
padding-right: 45px;
}
}
}

View File

@@ -21,6 +21,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "customer_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -53,6 +54,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break0",
"fieldtype": "Column Break",
"hidden": 0,
@@ -86,6 +88,7 @@
"collapsible": 0,
"columns": 0,
"default": "{customer_name}",
"fetch_if_empty": 0,
"fieldname": "title",
"fieldtype": "Data",
"hidden": 1,
@@ -119,6 +122,7 @@
"collapsible": 0,
"columns": 0,
"default": "",
"fetch_if_empty": 0,
"fieldname": "naming_series",
"fieldtype": "Select",
"hidden": 0,
@@ -153,6 +157,7 @@
"bold": 1,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "customer",
"fieldtype": "Link",
"hidden": 0,
@@ -188,6 +193,7 @@
"collapsible": 0,
"columns": 0,
"fetch_from": "customer.customer_name",
"fetch_if_empty": 0,
"fieldname": "customer_name",
"fieldtype": "Data",
"hidden": 0,
@@ -222,6 +228,7 @@
"columns": 0,
"default": "Sales",
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "order_type",
"fieldtype": "Select",
"hidden": 0,
@@ -256,6 +263,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break1",
"fieldtype": "Column Break",
"hidden": 0,
@@ -288,6 +296,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 1,
@@ -324,6 +333,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
@@ -360,6 +370,7 @@
"collapsible": 0,
"columns": 0,
"default": "Today",
"fetch_if_empty": 0,
"fieldname": "transaction_date",
"fieldtype": "Date",
"hidden": 0,
@@ -394,6 +405,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "delivery_date",
"fieldtype": "Date",
"hidden": 0,
@@ -428,6 +440,7 @@
"columns": 0,
"depends_on": "",
"description": "",
"fetch_if_empty": 0,
"fieldname": "po_no",
"fieldtype": "Data",
"hidden": 0,
@@ -464,6 +477,7 @@
"columns": 0,
"depends_on": "eval:doc.po_no",
"description": "",
"fetch_if_empty": 0,
"fieldname": "po_date",
"fieldtype": "Date",
"hidden": 0,
@@ -498,6 +512,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "tax_id",
"fieldtype": "Data",
"hidden": 0,
@@ -533,6 +548,7 @@
"collapsible_depends_on": "",
"columns": 0,
"depends_on": "customer",
"fetch_if_empty": 0,
"fieldname": "contact_info",
"fieldtype": "Section Break",
"hidden": 0,
@@ -565,6 +581,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "customer_address",
"fieldtype": "Link",
"hidden": 0,
@@ -597,6 +614,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "address_display",
"fieldtype": "Small Text",
"hidden": 0,
@@ -628,6 +646,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "contact_person",
"fieldtype": "Link",
"hidden": 0,
@@ -660,6 +679,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "contact_display",
"fieldtype": "Small Text",
"hidden": 0,
@@ -691,6 +711,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "contact_mobile",
"fieldtype": "Small Text",
"hidden": 0,
@@ -722,6 +743,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "contact_email",
"fieldtype": "Data",
"hidden": 1,
@@ -754,38 +776,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company_address_display",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "company_address",
"fieldtype": "Link",
"hidden": 0,
@@ -795,7 +786,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Company Address",
"label": "Company Address Name",
"length": 0,
"no_copy": 0,
"options": "Address",
@@ -819,6 +810,40 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "company_address_display",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Company Address",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "col_break46",
"fieldtype": "Column Break",
"hidden": 0,
@@ -850,6 +875,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "shipping_address_name",
"fieldtype": "Link",
"hidden": 0,
@@ -882,6 +908,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "shipping_address",
"fieldtype": "Small Text",
"hidden": 0,
@@ -914,6 +941,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "customer_group",
"fieldtype": "Link",
"hidden": 1,
@@ -947,6 +975,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "territory",
"fieldtype": "Link",
"hidden": 0,
@@ -979,6 +1008,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "currency_and_price_list",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1011,6 +1041,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "currency",
"fieldtype": "Link",
"hidden": 0,
@@ -1047,6 +1078,7 @@
"collapsible": 0,
"columns": 0,
"description": "Rate at which customer's currency is converted to company's base currency",
"fetch_if_empty": 0,
"fieldname": "conversion_rate",
"fieldtype": "Float",
"hidden": 0,
@@ -1082,6 +1114,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break2",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1113,6 +1146,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "selling_price_list",
"fieldtype": "Link",
"hidden": 0,
@@ -1148,6 +1182,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "price_list_currency",
"fieldtype": "Link",
"hidden": 0,
@@ -1181,6 +1216,7 @@
"collapsible": 0,
"columns": 0,
"description": "Rate at which Price list currency is converted to company's base currency",
"fetch_if_empty": 0,
"fieldname": "plc_conversion_rate",
"fieldtype": "Float",
"hidden": 0,
@@ -1214,6 +1250,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "ignore_pricing_rule",
"fieldtype": "Check",
"hidden": 0,
@@ -1245,6 +1282,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "sec_warehouse",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1276,6 +1314,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "set_warehouse",
"fieldtype": "Link",
"hidden": 0,
@@ -1309,6 +1348,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "items_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1342,6 +1382,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "scan_barcode",
"fieldtype": "Data",
"hidden": 0,
@@ -1374,6 +1415,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "items",
"fieldtype": "Table",
"hidden": 0,
@@ -1408,6 +1450,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_31",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1438,6 +1481,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_33a",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1468,6 +1512,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_qty",
"fieldtype": "Float",
"hidden": 0,
@@ -1500,6 +1545,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_total",
"fieldtype": "Currency",
"hidden": 0,
@@ -1533,6 +1579,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_net_total",
"fieldtype": "Currency",
"hidden": 0,
@@ -1568,6 +1615,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_33",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1598,6 +1646,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total",
"fieldtype": "Currency",
"hidden": 0,
@@ -1631,6 +1680,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "net_total",
"fieldtype": "Currency",
"hidden": 0,
@@ -1663,6 +1713,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_net_weight",
"fieldtype": "Float",
"hidden": 0,
@@ -1695,6 +1746,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "taxes_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1728,6 +1780,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "taxes_and_charges",
"fieldtype": "Link",
"hidden": 0,
@@ -1762,6 +1815,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_38",
"fieldtype": "Column Break",
"hidden": 0,
@@ -1792,6 +1846,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "shipping_rule",
"fieldtype": "Link",
"hidden": 0,
@@ -1825,6 +1880,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_40",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1855,6 +1911,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "taxes",
"fieldtype": "Table",
"hidden": 0,
@@ -1889,6 +1946,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "sec_tax_breakup",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1921,6 +1979,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "other_charges_calculation",
"fieldtype": "Text",
"hidden": 0,
@@ -1953,6 +2012,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_43",
"fieldtype": "Section Break",
"hidden": 0,
@@ -1983,6 +2043,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_total_taxes_and_charges",
"fieldtype": "Currency",
"hidden": 0,
@@ -2018,6 +2079,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_46",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2049,6 +2111,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_taxes_and_charges",
"fieldtype": "Currency",
"hidden": 0,
@@ -2081,6 +2144,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "loyalty_points_redemption",
"fieldtype": "Section Break",
"hidden": 1,
@@ -2113,6 +2177,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "loyalty_points",
"fieldtype": "Int",
"hidden": 1,
@@ -2145,6 +2210,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "loyalty_amount",
"fieldtype": "Currency",
"hidden": 1,
@@ -2178,6 +2244,7 @@
"collapsible": 1,
"collapsible_depends_on": "discount_amount",
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_48",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2211,6 +2278,7 @@
"collapsible": 0,
"columns": 0,
"default": "Grand Total",
"fetch_if_empty": 0,
"fieldname": "apply_discount_on",
"fieldtype": "Select",
"hidden": 0,
@@ -2244,6 +2312,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_discount_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -2277,6 +2346,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_50",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2308,6 +2378,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "additional_discount_percentage",
"fieldtype": "Float",
"hidden": 0,
@@ -2341,6 +2412,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "discount_amount",
"fieldtype": "Currency",
"hidden": 0,
@@ -2373,6 +2445,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "totals",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2406,6 +2479,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_grand_total",
"fieldtype": "Currency",
"hidden": 0,
@@ -2441,6 +2515,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_rounding_adjustment",
"fieldtype": "Currency",
"hidden": 0,
@@ -2474,6 +2549,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "base_rounded_total",
"fieldtype": "Currency",
"hidden": 0,
@@ -2510,6 +2586,7 @@
"collapsible": 0,
"columns": 0,
"description": "In Words will be visible once you save the Sales Order.",
"fetch_if_empty": 0,
"fieldname": "base_in_words",
"fieldtype": "Data",
"hidden": 0,
@@ -2544,6 +2621,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break3",
"fieldtype": "Column Break",
"hidden": 0,
@@ -2576,6 +2654,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "grand_total",
"fieldtype": "Currency",
"hidden": 0,
@@ -2612,6 +2691,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "rounding_adjustment",
"fieldtype": "Currency",
"hidden": 0,
@@ -2645,6 +2725,7 @@
"bold": 1,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "rounded_total",
"fieldtype": "Currency",
"hidden": 0,
@@ -2680,6 +2761,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "in_words",
"fieldtype": "Data",
"hidden": 0,
@@ -2714,6 +2796,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "advance_paid",
"fieldtype": "Currency",
"hidden": 0,
@@ -2748,6 +2831,7 @@
"collapsible_depends_on": "packed_items",
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "packing_list",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2781,6 +2865,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "packed_items",
"fieldtype": "Table",
"hidden": 0,
@@ -2815,6 +2900,7 @@
"collapsible": 0,
"collapsible_depends_on": "",
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "payment_schedule_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2847,6 +2933,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "payment_terms_template",
"fieldtype": "Link",
"hidden": 0,
@@ -2880,6 +2967,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "payment_schedule",
"fieldtype": "Table",
"hidden": 0,
@@ -2914,6 +3002,7 @@
"collapsible": 1,
"collapsible_depends_on": "terms",
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "terms_section_break",
"fieldtype": "Section Break",
"hidden": 0,
@@ -2947,6 +3036,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "tc_name",
"fieldtype": "Link",
"hidden": 0,
@@ -2981,6 +3071,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "terms",
"fieldtype": "Text Editor",
"hidden": 0,
@@ -3015,6 +3106,7 @@
"collapsible": 1,
"collapsible_depends_on": "project",
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "more_info",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3049,6 +3141,7 @@
"collapsible": 0,
"columns": 0,
"description": "Track this Sales Order against any Project",
"fetch_if_empty": 0,
"fieldname": "project",
"fieldtype": "Link",
"hidden": 0,
@@ -3083,6 +3176,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "party_account_currency",
"fieldtype": "Link",
"hidden": 1,
@@ -3116,6 +3210,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_77",
"fieldtype": "Column Break",
"hidden": 0,
@@ -3147,6 +3242,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "source",
"fieldtype": "Link",
"hidden": 0,
@@ -3182,6 +3278,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "campaign",
"fieldtype": "Link",
"hidden": 0,
@@ -3216,6 +3313,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "printing_details",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3248,6 +3346,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "language",
"fieldtype": "Data",
"hidden": 0,
@@ -3280,6 +3379,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "letter_head",
"fieldtype": "Link",
"hidden": 0,
@@ -3314,6 +3414,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break4",
"fieldtype": "Column Break",
"hidden": 0,
@@ -3346,6 +3447,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "select_print_heading",
"fieldtype": "Link",
"hidden": 0,
@@ -3380,6 +3482,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "group_same_items",
"fieldtype": "Check",
"hidden": 0,
@@ -3412,6 +3515,7 @@
"bold": 0,
"collapsible": 1,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_78",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3446,6 +3550,7 @@
"collapsible": 0,
"columns": 0,
"default": "Draft",
"fetch_if_empty": 0,
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
@@ -3481,6 +3586,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "delivery_status",
"fieldtype": "Select",
"hidden": 1,
@@ -3515,6 +3621,7 @@
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"description": "% of materials delivered against this Sales Order",
"fetch_if_empty": 0,
"fieldname": "per_delivered",
"fieldtype": "Percent",
"hidden": 0,
@@ -3549,6 +3656,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_81",
"fieldtype": "Column Break",
"hidden": 0,
@@ -3581,6 +3689,7 @@
"columns": 0,
"depends_on": "eval:!doc.__islocal",
"description": "% of materials billed against this Sales Order",
"fetch_if_empty": 0,
"fieldname": "per_billed",
"fieldtype": "Percent",
"hidden": 0,
@@ -3615,6 +3724,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "billing_status",
"fieldtype": "Select",
"hidden": 1,
@@ -3648,6 +3758,7 @@
"collapsible": 1,
"collapsible_depends_on": "commission_rate",
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "sales_team_section_break",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3681,6 +3792,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "sales_partner",
"fieldtype": "Link",
"hidden": 0,
@@ -3716,6 +3828,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break7",
"fieldtype": "Column Break",
"hidden": 0,
@@ -3747,6 +3860,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "commission_rate",
"fieldtype": "Float",
"hidden": 0,
@@ -3781,6 +3895,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "total_commission",
"fieldtype": "Currency",
"hidden": 0,
@@ -3816,6 +3931,7 @@
"collapsible": 1,
"collapsible_depends_on": "sales_team",
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break1",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3847,6 +3963,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "sales_team",
"fieldtype": "Table",
"hidden": 0,
@@ -3881,6 +3998,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "subscription_section",
"fieldtype": "Section Break",
"hidden": 0,
@@ -3915,6 +4033,7 @@
"columns": 0,
"depends_on": "",
"description": "",
"fetch_if_empty": 0,
"fieldname": "from_date",
"fieldtype": "Date",
"hidden": 0,
@@ -3948,6 +4067,7 @@
"columns": 0,
"depends_on": "",
"description": "",
"fetch_if_empty": 0,
"fieldname": "to_date",
"fieldtype": "Date",
"hidden": 0,
@@ -3979,6 +4099,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_108",
"fieldtype": "Column Break",
"hidden": 0,
@@ -4010,6 +4131,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "auto_repeat",
"fieldtype": "Link",
"hidden": 0,
@@ -4044,6 +4166,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.auto_repeat",
"fetch_if_empty": 0,
"fieldname": "update_auto_repeat_reference",
"fieldtype": "Button",
"hidden": 0,
@@ -4081,7 +4204,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-01-09 16:51:47.917329",
"modified": "2019-04-05 03:44:46.037178",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",

View File

@@ -202,9 +202,8 @@ class SalesOrder(SellingController):
if self.project:
project = frappe.get_doc("Project", self.project)
project.flags.dont_sync_tasks = True
project.update_sales_amount()
project.save()
project.db_update()
def check_credit_limit(self):
# if bypass credit limit check is set to true (1) at sales order level,

View File

@@ -54,8 +54,16 @@ erpnext.pos.PointOfSale = class PointOfSale {
this.prepare_menu();
this.set_online_status();
},
() => this.setup_company(),
() => this.make_new_invoice(),
() => {
if(!this.frm.doc.company) {
this.setup_company()
.then((company) => {
this.frm.doc.company = company;
this.get_pos_profile();
});
}
},
() => {
frappe.dom.unfreeze();
},
@@ -63,6 +71,22 @@ erpnext.pos.PointOfSale = class PointOfSale {
]);
}
get_pos_profile() {
return frappe.xcall("erpnext.stock.get_item_details.get_pos_profile",
{'company': this.frm.doc.company})
.then((r) => {
if(r) {
this.frm.doc.pos_profile = r.name;
this.set_pos_profile_data()
.then(() => {
this.on_change_pos_profile();
});
} else {
this.raise_exception_for_pos_profile();
}
});
}
set_online_status() {
this.connection_status = false;
this.page.set_indicator(__("Offline"), "grey");
@@ -77,6 +101,11 @@ erpnext.pos.PointOfSale = class PointOfSale {
});
}
raise_exception_for_pos_profile() {
setTimeout(() => frappe.set_route('List', 'POS Profile'), 2000);
frappe.throw(__("POS Profile is required to use Point-of-Sale"));
}
prepare_dom() {
this.wrapper.append(`
<div class="pos">
@@ -447,7 +476,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
setup_company() {
return new Promise(resolve => {
if(!frappe.sys_defaults.company) {
if(!this.frm.doc.company) {
frappe.prompt({fieldname:"company", options: "Company", fieldtype:"Link",
label: __("Select Company"), reqd: 1}, (data) => {
this.company = data.company;
@@ -487,6 +516,10 @@ erpnext.pos.PointOfSale = class PointOfSale {
return new Promise(resolve => {
if (this.frm) {
this.frm = get_frm(this.frm);
if(this.company) {
this.frm.doc.company = this.company;
}
resolve();
} else {
frappe.model.with_doctype(doctype, () => {
@@ -503,6 +536,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
frm.refresh(name);
frm.doc.items = [];
frm.doc.is_pos = 1;
return frm;
}
}
@@ -512,6 +546,10 @@ erpnext.pos.PointOfSale = class PointOfSale {
this.frm.doc.company = this.company;
}
if (!this.frm.doc.company) {
return;
}
return new Promise(resolve => {
return this.frm.call({
doc: this.frm.doc,
@@ -520,8 +558,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
if(!r.exc) {
if (!this.frm.doc.pos_profile) {
frappe.dom.unfreeze();
setTimeout(() => frappe.set_route('List', 'POS Profile'), 2000);
frappe.throw(__("POS Profile is required to use Point-of-Sale"));
this.raise_exception_for_pos_profile();
}
this.frm.script_manager.trigger("update_stock");
frappe.model.set_default_values(this.frm.doc);

View File

@@ -47,9 +47,8 @@ def get_columns():
},
{
"label": _("Material Request"),
"options": "Material Request",
"fieldname": "material_request",
"fieldtype": "Link",
"fieldtype": "Data",
"width": 140
},
{
@@ -116,33 +115,43 @@ def get_data():
{"sales_order_item": ("!=",""), "docstatus": 1},
["parent", "qty", "sales_order", "item_code"])
grouped_records = {}
materials_request_dict = {}
for record in mr_records:
grouped_records.setdefault(record.sales_order, []).append(record)
key = (record.sales_order, record.item_code)
if key not in materials_request_dict:
materials_request_dict.setdefault(key, {
'qty': 0,
'material_requests': [record.parent]
})
details = materials_request_dict.get(key)
details['qty'] += record.qty
if record.parent not in details.get('material_requests'):
details['material_requests'].append(record.parent)
pending_so=[]
for so in sales_order_entry:
# fetch all the material request records for a sales order item
mr_list = grouped_records.get(so.name) or [{}]
mr_item_record = ([mr for mr in mr_list if mr.get('item_code') == so.item_code] or [{}])
key = (so.name, so.item_code)
materials_request = materials_request_dict.get(key) or {}
for mr in mr_item_record:
# check for pending sales order
if cint(so.net_qty) > cint(mr.get('qty')):
so_record = {
"item_code": so.item_code,
"item_name": so.item_name,
"description": so.description,
"sales_order_no": so.name,
"date": so.transaction_date,
"material_request": cstr(mr.get('parent')),
"customer": so.customer,
"territory": so.territory,
"so_qty": so.net_qty,
"requested_qty": cint(mr.get('qty')),
"pending_qty": so.net_qty - cint(mr.get('qty')),
"company": so.company
}
pending_so.append(so_record)
# check for pending sales order
if cint(so.net_qty) > cint(materials_request.get('qty')):
so_record = {
"item_code": so.item_code,
"item_name": so.item_name,
"description": so.description,
"sales_order_no": so.name,
"date": so.transaction_date,
"material_request": ','.join(materials_request.get('material_requests', [])),
"customer": so.customer,
"territory": so.territory,
"so_qty": so.net_qty,
"requested_qty": cint(materials_request.get('qty')),
"pending_qty": so.net_qty - cint(materials_request.get('qty')),
"company": so.company
}
pending_so.append(so_record)
return pending_so

View File

@@ -135,7 +135,7 @@ class NamingSeries(Document):
def validate_series_name(self, n):
import re
if not re.match("^[\w\- /.#]*$", n, re.UNICODE):
if not (re.match("^[\w\- /.#]*$", n, re.UNICODE) or re.match("\{(.*?)\}", n, re.UNICODE)):
throw(_('Special Characters except "-", "#", "." and "/" not allowed in naming series'))
def get_options(self, arg=None):

View File

@@ -51,7 +51,9 @@ def get_cart_quotation(doc=None):
@frappe.whitelist()
def place_order():
quotation = _get_cart_quotation()
quotation.company = frappe.db.get_value("Shopping Cart Settings", None, "company")
cart_settings = frappe.db.get_value("Shopping Cart Settings", None,
["company", "allow_items_not_in_stock"], as_dict=1)
quotation.company = cart_settings.company
if not quotation.get("customer_address"):
throw(_("{0} is required").format(_(quotation.meta.get_label("customer_address"))))
@@ -64,14 +66,16 @@ def place_order():
from erpnext.selling.doctype.quotation.quotation import _make_sales_order
sales_order = frappe.get_doc(_make_sales_order(quotation.name, ignore_permissions=True))
for item in sales_order.get("items"):
item.reserved_warehouse, is_stock_item = frappe.db.get_value("Item",
item.item_code, ["website_warehouse", "is_stock_item"])
if is_stock_item:
item_stock = get_qty_in_stock(item.item_code, "website_warehouse")
if item.qty > item_stock.stock_qty[0][0]:
throw(_("Only {0} in stock for item {1}").format(item_stock.stock_qty[0][0], item.item_code))
if not cart_settings.allow_items_not_in_stock:
for item in sales_order.get("items"):
item.reserved_warehouse, is_stock_item = frappe.db.get_value("Item",
item.item_code, ["website_warehouse", "is_stock_item"])
if is_stock_item:
item_stock = get_qty_in_stock(item.item_code, "website_warehouse")
if item.qty > item_stock.stock_qty[0][0]:
throw(_("Only {0} in stock for item {1}").format(item_stock.stock_qty[0][0], item.item_code))
sales_order.flags.ignore_permissions = True
sales_order.insert()

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
@@ -19,6 +20,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "enabled",
"fieldtype": "Check",
"hidden": 0,
@@ -51,6 +53,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "display_settings",
"fieldtype": "Section Break",
"hidden": 0,
@@ -84,6 +87,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "show_attachments",
"fieldtype": "Check",
"hidden": 0,
@@ -118,6 +122,7 @@
"columns": 0,
"depends_on": "eval:doc.enabled==0",
"description": "",
"fetch_if_empty": 0,
"fieldname": "show_price",
"fieldtype": "Check",
"hidden": 0,
@@ -150,6 +155,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
@@ -181,6 +187,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "show_stock_availability",
"fieldtype": "Check",
"hidden": 0,
@@ -214,6 +221,7 @@
"collapsible": 0,
"columns": 0,
"depends_on": "show_stock_availability",
"fetch_if_empty": 0,
"fieldname": "show_quantity_in_website",
"fieldtype": "Check",
"hidden": 0,
@@ -246,6 +254,40 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "allow_items_not_in_stock",
"fieldtype": "Check",
"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": "Allow items not in stock to be added to cart",
"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,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_2",
"fieldtype": "Section Break",
"hidden": 0,
@@ -276,6 +318,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
@@ -309,6 +352,7 @@
"collapsible": 0,
"columns": 0,
"description": "Prices will not be shown if Price List is not set",
"fetch_if_empty": 0,
"fieldname": "price_list",
"fieldtype": "Link",
"hidden": 0,
@@ -342,6 +386,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
@@ -373,6 +418,7 @@
"collapsible": 0,
"columns": 0,
"description": "",
"fetch_if_empty": 0,
"fieldname": "default_customer_group",
"fieldtype": "Link",
"hidden": 0,
@@ -405,6 +451,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "quotation_series",
"fieldtype": "Select",
"hidden": 0,
@@ -437,6 +484,7 @@
"collapsible": 1,
"collapsible_depends_on": "eval:doc.enable_checkout",
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_8",
"fieldtype": "Section Break",
"hidden": 0,
@@ -469,6 +517,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "enable_checkout",
"fieldtype": "Check",
"hidden": 0,
@@ -503,6 +552,7 @@
"columns": 0,
"default": "Orders",
"description": "After payment completion redirect user to selected page.",
"fetch_if_empty": 0,
"fieldname": "payment_success_url",
"fieldtype": "Select",
"hidden": 0,
@@ -536,6 +586,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_11",
"fieldtype": "Column Break",
"hidden": 0,
@@ -567,6 +618,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "payment_gateway_account",
"fieldtype": "Link",
"hidden": 0,
@@ -605,8 +657,8 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2018-05-31 03:11:58.911732",
"modified_by": "sushant@digithinkit.com",
"modified": "2019-04-09 18:29:43.270862",
"modified_by": "Administrator",
"module": "Shopping Cart",
"name": "Shopping Cart Settings",
"owner": "Administrator",
@@ -637,5 +689,6 @@
"show_name_in_global_search": 0,
"sort_order": "ASC",
"track_changes": 0,
"track_seen": 0
}
"track_seen": 0,
"track_views": 0
}

View File

@@ -101,6 +101,30 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
refresh: function(doc, dt, dn) {
var me = this;
this._super();
if ((!doc.is_return) && (doc.status!="Closed" || doc.is_new())) {
if (this.frm.doc.docstatus===0) {
this.frm.add_custom_button(__('Sales Order'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
source_doctype: "Sales Order",
target: me.frm,
setters: {
customer: me.frm.doc.customer || undefined,
},
get_query_filters: {
docstatus: 1,
status: ["!=", "Closed"],
per_delivered: ["<", 99.99],
company: me.frm.doc.company,
project: me.frm.doc.project || undefined,
}
})
}, __("Get items from"));
}
}
if (!doc.is_return && doc.status!="Closed") {
if(flt(doc.per_installed, 2) < 100 && doc.docstatus==1)
this.frm.add_custom_button(__('Installation Note'), function() {
@@ -127,27 +151,6 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
if (!doc.__islocal && doc.docstatus==1) {
this.frm.page.set_inner_btn_group_as_primary(__("Make"));
}
if (this.frm.doc.docstatus===0) {
this.frm.add_custom_button(__('Sales Order'),
function() {
erpnext.utils.map_current_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
source_doctype: "Sales Order",
target: me.frm,
setters: {
customer: me.frm.doc.customer || undefined,
},
get_query_filters: {
docstatus: 1,
status: ["!=", "Closed"],
per_delivered: ["<", 99.99],
company: me.frm.doc.company,
project: me.frm.doc.project || undefined,
}
})
}, __("Get items from"));
}
}
if (doc.docstatus==1) {

View File

@@ -391,19 +391,7 @@ def get_invoiced_qty_map(delivery_note):
return invoiced_qty_map
def get_returned_qty_map_against_so(sales_orders):
"""returns a map: {so_detail: returned_qty}"""
returned_qty_map = {}
for name, returned_qty in frappe.get_all('Sales Order Item', fields = ["name", "returned_qty"],
filters = {'parent': ('in', sales_orders), 'docstatus': 1}, as_list=1):
if not returned_qty_map.get(name):
returned_qty_map[name] = 0
returned_qty_map[name] += returned_qty
return returned_qty_map
def get_returned_qty_map_against_dn(delivery_note):
def get_returned_qty_map(delivery_note):
"""returns a map: {so_detail: returned_qty}"""
returned_qty_map = frappe._dict(frappe.db.sql("""select dn_item.item_code, sum(abs(dn_item.qty)) as qty
from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn
@@ -420,8 +408,7 @@ def get_returned_qty_map_against_dn(delivery_note):
def make_sales_invoice(source_name, target_doc=None):
doc = frappe.get_doc('Delivery Note', source_name)
sales_orders = [d.against_sales_order for d in doc.items]
returned_qty_map_against_so = get_returned_qty_map_against_so(sales_orders)
returned_qty_map_against_dn = get_returned_qty_map_against_dn(source_name)
returned_qty_map = get_returned_qty_map(source_name)
invoiced_qty_map = get_invoiced_qty_map(source_name)
def set_missing_values(source, target):
@@ -442,17 +429,16 @@ def make_sales_invoice(source_name, target_doc=None):
def update_item(source_doc, target_doc, source_parent):
target_doc.qty, returned_qty = get_pending_qty(source_doc)
if not source_doc.so_detail:
returned_qty_map_against_dn[source_doc.item_code] = returned_qty
returned_qty_map[source_doc.item_code] = returned_qty
if source_doc.serial_no and source_parent.per_billed > 0:
target_doc.serial_no = get_delivery_note_serial_no(source_doc.item_code,
target_doc.qty, source_parent.name)
def get_pending_qty(item_row):
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) - returned_qty_map_against_so.get(item_row.so_detail, 0)
returned_qty = flt(returned_qty_map_against_dn.get(item_row.item_code, 0))
if not item_row.so_detail:
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
returned_qty = flt(returned_qty_map.get(item_row.item_code, 0))
if returned_qty:
if returned_qty >= pending_qty:
pending_qty = 0
returned_qty -= pending_qty

View File

@@ -655,7 +655,7 @@ class TestDeliveryNote(unittest.TestCase):
si = make_sales_invoice(dn.name)
self.assertEquals(si.items[0].qty, 1)
def test_make_sales_invoice_from_dn_with_returned_qty_against_dn(self):
def test_make_sales_invoice_from_dn_with_returned_qty_duplicate_items(self):
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
dn = create_delivery_note(qty=8, do_not_submit=True)

View File

@@ -767,7 +767,7 @@ class Item(WebsiteGenerator):
attributes.append(d.attribute)
def validate_variant_attributes(self):
if self.variant_of and self.variant_based_on == 'Item Attribute':
if self.is_new() and self.variant_of and self.variant_based_on == 'Item Attribute':
args = {}
for d in self.attributes:
if cstr(d.attribute_value).strip() == '':

File diff suppressed because it is too large Load Diff

View File

@@ -407,10 +407,7 @@ def update_billed_amount_based_on_po(po_detail, update_modified=True):
def make_purchase_invoice(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
doc = frappe.get_doc('Purchase Receipt', source_name)
purchase_orders = [d.purchase_order for d in doc.items]
returned_qty_map_against_po = get_returned_qty_map_against_po(purchase_orders)
returned_qty_map_against_pr = get_returned_qty_map_against_pr(source_name)
returned_qty_map = get_returned_qty_map(source_name)
invoiced_qty_map = get_invoiced_qty_map(source_name)
def set_missing_values(source, target):
@@ -419,19 +416,18 @@ def make_purchase_invoice(source_name, target_doc=None):
doc = frappe.get_doc(target)
doc.ignore_pricing_rule = 1
doc.run_method("onload")
doc.run_method("set_missing_values")
doc.run_method("calculate_taxes_and_totals")
def update_item(source_doc, target_doc, source_parent):
target_doc.qty, returned_qty = get_pending_qty(source_doc)
if not source_doc.purchase_order_item:
returned_qty_map_against_pr[source_doc.item_code] = returned_qty
returned_qty_map[source_doc.item_code] = returned_qty
def get_pending_qty(item_row):
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) \
- returned_qty_map_against_po.get(item_row.purchase_order_item, 0)
returned_qty = flt(returned_qty_map_against_pr.get(item_row.item_code, 0))
if not item_row.purchase_order_item:
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
returned_qty = flt(returned_qty_map.get(item_row.item_code, 0))
if returned_qty:
if returned_qty >= pending_qty:
pending_qty = 0
returned_qty -= pending_qty
@@ -484,19 +480,7 @@ def get_invoiced_qty_map(purchase_receipt):
return invoiced_qty_map
def get_returned_qty_map_against_po(purchase_orders):
"""returns a map: {so_detail: returned_qty}"""
returned_qty_map = {}
for name, returned_qty in frappe.get_all('Purchase Order Item', fields = ["name", "returned_qty"],
filters = {'parent': ('in', purchase_orders), 'docstatus': 1}, as_list=1):
if not returned_qty_map.get(name):
returned_qty_map[name] = 0
returned_qty_map[name] += returned_qty
return returned_qty_map
def get_returned_qty_map_against_pr(purchase_receipt):
def get_returned_qty_map(purchase_receipt):
"""returns a map: {so_detail: returned_qty}"""
returned_qty_map = frappe._dict(frappe.db.sql("""select pr_item.item_code, sum(abs(pr_item.qty)) as qty
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr

View File

@@ -418,7 +418,7 @@ class TestPurchaseReceipt(unittest.TestCase):
pi = make_purchase_invoice(pr.name)
self.assertEquals(pi.items[0].qty, 3)
def test_make_purchase_invoice_from_dn_with_returned_qty_against_dn(self):
def test_make_purchase_invoice_from_pr_with_returned_qty_duplicate_items(self):
pr1 = make_purchase_receipt(qty=8, do_not_submit=True)
pr1.append("items", {
"item_code": "_Test Item",

View File

@@ -22,12 +22,8 @@ def execute(filters=None):
for sle in sl_entries:
item_detail = item_details[sle.item_code]
data.append([sle.date, sle.item_code, item_detail.item_name, item_detail.item_group,
item_detail.brand, item_detail.description, sle.warehouse,
item_detail.stock_uom, sle.actual_qty, sle.qty_after_transaction,
(sle.incoming_rate if sle.actual_qty > 0 else 0.0),
sle.valuation_rate, sle.stock_value, sle.voucher_type, sle.voucher_no,
sle.batch_no, sle.serial_no, sle.project, sle.company])
sle.update(item_detail)
data.append(sle)
if include_uom:
conversion_factors.append(item_detail.conversion_factor)

Some files were not shown because too many files have changed in this diff Show More