Compare commits

..

129 Commits

Author SHA1 Message Date
Pratik Vyas
14e0e58a7d Merge branch 'develop' 2014-10-17 15:04:05 +05:30
Pratik Vyas
5eb373f7b5 bumped to version 4.8.0 2014-10-17 15:34:05 +06:00
Nabin Hait
4595c30a7b Merge pull request #2322 from nabinhait/stock_reco
Partial payment reconciliation
2014-10-17 15:01:13 +05:30
Nabin Hait
fc2dd44694 Partial payment reconciliation. Fixes #1982 2014-10-17 14:57:20 +05:30
Pratik Vyas
053c54017e [minor] add admin password to travis test site 2014-10-17 14:16:10 +05:30
Pratik Vyas
af473d78f2 Merge branch 'develop' 2014-10-15 15:34:39 +05:30
Pratik Vyas
26d096aa87 bumped to version 4.7.2 2014-10-15 16:04:39 +06:00
Nabin Hait
476c613ba6 Merge pull request #2317 from nabinhait/stock_reco
repost stock utility
2014-10-15 15:33:50 +05:30
Nabin Hait
2348a5f592 repost stock utility 2014-10-15 15:31:33 +05:30
Pratik Vyas
9d8d045c66 Merge branch 'develop' 2014-10-15 14:44:35 +05:30
Pratik Vyas
63914dd55b bumped to version 4.7.1 2014-10-15 15:14:35 +06:00
Nabin Hait
93a8042f08 Merge pull request #2316 from nabinhait/stock_reco
Minor fix
2014-10-15 14:44:06 +05:30
Nabin Hait
b2f32dac1b Minor fix 2014-10-15 14:42:18 +05:30
Pratik Vyas
577a3acaac Merge branch 'develop' 2014-10-15 14:35:47 +05:30
Pratik Vyas
5e46ce8a50 bumped to version 4.7.0 2014-10-15 15:05:47 +06:00
Nabin Hait
3b7342b7b5 Merge pull request #2315 from nabinhait/stock_reco
Strip company in setup wizard
2014-10-15 13:59:07 +05:30
Nabin Hait
531077e504 Strip company in setup wizard 2014-10-15 13:55:37 +05:30
Nabin Hait
147918ed66 Merge pull request #2313 from nabinhait/stock_reco
Fix gl entries for stock transactions
2014-10-15 12:44:59 +05:30
Nabin Hait
9bbfca9226 Fix gl entries for stock transactions 2014-10-15 12:24:38 +05:30
Nabin Hait
f1a07ff105 Allow zero rate while reposting 2014-10-15 12:23:35 +05:30
Nabin Hait
fb6e434315 Guess valuation rate in case of negative stock 2014-10-15 11:34:40 +05:30
Rushabh Mehta
3811d96feb Merge pull request #2308 from devdesco-ceo/patch-1
Update README.md
2014-10-15 10:02:06 +05:30
Viktor Zhuromskyy
1017615d02 Update README.md 2014-10-14 10:12:10 -05:00
Pratik Vyas
056d627f46 Merge branch 'develop' 2014-10-14 17:34:01 +05:30
Pratik Vyas
663bfeacf4 bumped to version 4.6.2 2014-10-14 18:04:01 +06:00
Nabin Hait
e918ebd721 Merge pull request #2307 from nabinhait/stock_reco
Minor fixes
2014-10-14 17:16:32 +05:30
Nabin Hait
0dc18f0102 Minor fixes 2014-10-14 17:15:02 +05:30
Pratik Vyas
8ddc882a66 Merge branch 'develop' 2014-10-14 16:16:29 +05:30
Pratik Vyas
49365d0982 bumped to version 4.6.1 2014-10-14 16:46:29 +06:00
Nabin Hait
50c29c7d0f Merge pull request #2306 from nabinhait/stock_reco
Negative stock in perpetual inventory
2014-10-14 16:10:46 +05:30
Nabin Hait
bf492122f8 minor fix 2014-10-14 16:09:14 +05:30
Nabin Hait
38d0ed9f3a Negative stock allowed for perpetual inventory. Blocked in specific case 2014-10-14 16:08:19 +05:30
Pratik Vyas
248a65b37d Merge branch 'develop' 2014-10-14 12:37:07 +05:30
Pratik Vyas
78f86e9385 bumped to version 4.6.0 2014-10-14 13:07:07 +06:00
Nabin Hait
d9f3e0c275 Merge pull request #2297 from neilLasrado/bom-issue
Fix - BOM calculated wrong cost on update cost
2014-10-14 12:28:16 +05:30
Neil Trini Lasrado
c40451ee2e Patch Fixed 2014-10-14 12:14:39 +05:30
Neil Trini Lasrado
6644406185 Fix - BOM calculated wrong cost on update cost 2014-10-14 12:14:38 +05:30
Nabin Hait
b3a962e121 Merge pull request #2304 from nabinhait/stock_reco
Repost gl entries where mismatch with stock balance
2014-10-14 11:58:41 +05:30
Nabin Hait
8a28ccfa2f Repost gl entries where mismatch with stock balance 2014-10-14 11:41:56 +05:30
Nabin Hait
0a75fa09ef Merge pull request #2300 from nabinhait/stock_reco
Minor fixes
2014-10-13 11:52:00 +05:30
Nabin Hait
17a16eeaf4 Tets case fixed for payment tool 2014-10-13 11:43:14 +05:30
Nabin Hait
3d3f0bcf54 Minor fix in setup wizard 2014-10-13 11:32:41 +05:30
Nabin Hait
bc8b20ae3c Allocate entire advance amount if advance against SO/PO 2014-10-13 10:48:26 +05:30
Nabin Hait
daf344e5fd Precision fixed in batch-wise balance report 2014-10-13 10:48:26 +05:30
Pratik Vyas
7916792f99 Merge pull request #2298 from pdvyas/fix-mariadb
add my_config patch to travis
2014-10-12 19:39:52 +05:30
Pratik Vyas
27e37e68b2 add my_config patch to travis 2014-10-12 19:35:46 +05:30
Nabin Hait
a538f8a24a Merge pull request #2296 from nabinhait/stock_reco
Stock balance report and valuation fixes
2014-10-10 21:23:30 +05:30
Nabin Hait
70ec88b733 fixed test cases 2014-10-10 21:22:46 +05:30
Nabin Hait
4f0e5db216 Stock balance grid report deprecated and moved to server side 2014-10-10 20:54:57 +05:30
Nabin Hait
79ed124939 Update journal_voucher.py 2014-10-10 18:19:03 +05:30
Nabin Hait
c0a3cd603b Merge pull request #2290 from ankitjavalkarwork/accreport
Bug Fixes
2014-10-10 18:11:36 +05:30
Nabin Hait
7c6f990cf9 Minor fix for moving average 2014-10-10 18:03:27 +05:30
Nabin Hait
7820b171d3 Stock balance grid report deprecated and moved to server side 2014-10-10 18:03:07 +05:30
ankitjavalkarwork
0cf4cc283c Add Shipping Addr to Sales Invoice 2014-10-10 16:55:03 +05:30
ankitjavalkarwork
9a4b173b88 Add validation for stopped orders, advance payment in journal voucher 2014-10-10 16:28:41 +05:30
ankitjavalkarwork
6d83454237 Disallow Stopped Orders in Against Voucher table 2014-10-10 13:30:10 +05:30
ankitjavalkarwork
ff231b5e62 Allow advance JV payments in Accounts Receivable/Payable 2014-10-10 13:13:39 +05:30
Nabin Hait
4d74216147 Maintain negative stock balance if balance qty is negative 2014-10-09 19:25:19 +05:30
Nabin Hait
b7e5ad0a31 Merge pull request #2276 from ankitjavalkarwork/sopofield
Rearrange To/From, Recurring Type field for better UX
2014-10-09 11:58:13 +05:30
Pratik Vyas
e435592d64 [minor] Fix default website style patch (reload doc) 2014-10-09 11:22:12 +05:30
Nabin Hait
7ddde8dc3a Merge pull request #2278 from nabinhait/stock_reco
stock reco and repost vouchers
2014-10-08 18:38:54 +05:30
Nabin Hait
fce2881de6 minor fix 2014-10-08 18:38:27 +05:30
Nabin Hait
e96e83d557 stock reco and repost vouchers 2014-10-08 18:06:14 +05:30
ankitjavalkarwork
1b2944e871 Rearrange To/From, Recurring Type field for better UX 2014-10-08 14:24:58 +05:30
Rushabh Mehta
074e73a0dd [translations] updated via translator 2014-10-08 14:16:33 +05:30
Nabin Hait
8923801881 Update stock_entry.py 2014-10-08 13:20:31 +05:30
Nabin Hait
cfafe93391 Merge pull request #2274 from nabinhait/stock_reco
Stock reco
2014-10-08 11:03:03 +05:30
Nabin Hait
6c48ef781b Utility: Repost stock ledger entries and gl entries for all stock transactions 2014-10-08 11:02:18 +05:30
Nabin Hait
adeb976a1b Block negative stock in perpetual inventory 2014-10-08 11:02:18 +05:30
Nabin Hait
bb19b91ef9 stock reco fixes 2014-10-08 11:02:18 +05:30
Nabin Hait
bfa7f171bd Stock reconciliation sl entries 2014-10-08 11:02:18 +05:30
Nabin Hait
b96c014daf Stock Reconciliation logic simplified 2014-10-08 11:02:18 +05:30
Pratik Vyas
e0c83e22d9 Merge branch 'develop' 2014-10-07 17:05:32 +05:30
pdvyas
099ad0f5e1 bumped to version 4.5.2 2014-10-07 17:35:32 +06:00
Nabin Hait
cf9746dd84 Merge pull request #2270 from nabinhait/fix2
Voucher dynamic link in general ledger report
2014-10-07 11:33:40 +05:30
Nabin Hait
b70712dbba Voucher dynamic link in general ledger report 2014-10-07 11:32:54 +05:30
Rushabh Mehta
4c057fe693 Merge pull request #2269 from rmehta/fixes
[fix] packing slip
2014-10-07 11:06:08 +05:30
Nabin Hait
ee8ff51d60 Merge pull request #2261 from ankitjavalkarwork/sopofield
[#2253] Rearrange To/From fields and depend on is_recurring
2014-10-07 10:52:10 +05:30
Rushabh Mehta
9974b16c32 [fix] packing slip 2014-10-07 10:52:05 +05:30
ankitjavalkarwork
9e5f319d80 [#2253] Rearrange To/From fields and depend on is_recurring in SI,SO,PI,PO 2014-10-06 12:58:38 +05:30
Nabin Hait
4f614b4030 Merge pull request #2195 from neilLasrado/manufacture-and-repack
[fix] Issue #2183 - Manufacturing and repack seprated in stock entry
2014-09-30 15:11:05 +05:30
Nabin Hait
3d458e973e Merge pull request #2236 from neilLasrado/upstream/develop
Has Batch No field should be freezed #2023
2014-09-30 14:56:15 +05:30
Neil Trini Lasrado
3a19a71262 patch fixed 2014-09-30 14:48:38 +05:30
Neil Trini Lasrado
29d1a1c593 manufacturing and repack sepreted, test cases fixed, patch fixed 2014-09-30 14:07:56 +05:30
Neil Trini Lasrado
3b90de558f Has Batch No field should be freezed #2023 2014-09-30 12:57:32 +05:30
Nabin Hait
af7e31acb3 Merge pull request #2235 from adityaduggal/develop
Added the dynamic link field in the accounts receivable report.
2014-09-30 10:39:40 +05:30
Aditya Duggal
a2c9d35efb Added the dynamic link field. 2014-09-29 17:44:28 +05:30
Aditya Duggal
4c058f4056 Added the dynamic link and removed the link column 2014-09-29 17:41:19 +05:30
Pratik Vyas
ad67b84d43 Merge branch 'develop' 2014-09-29 15:40:04 +05:30
Pratik Vyas
4e81e4065b bumped to version 4.5.1 2014-09-29 16:10:04 +06:00
Nabin Hait
81332789cb Merge pull request #2232 from nabinhait/hotfix
Fix gl entries for stock transactions
2014-09-29 15:25:02 +05:30
Nabin Hait
82d7c0c9eb Patch: Fix gl entries for stock transactions 2014-09-29 15:07:51 +05:30
Nabin Hait
d60235e239 minor fix in warehouse-wise stock balance report 2014-09-29 11:36:06 +05:30
Nabin Hait
9b50b0a762 Fixes for item list view 2014-09-29 11:36:06 +05:30
Pratik Vyas
21e14c4c98 Merge branch 'develop' 2014-09-26 16:49:59 +05:30
Pratik Vyas
16edacebc7 bumped to version 4.5.0 2014-09-26 17:19:59 +06:00
Nabin Hait
75027b4d54 Merge pull request #2225 from nabinhait/hotfix
GL Entries for future stock voucher and multiple minor fixes
2014-09-26 14:48:56 +05:30
Nabin Hait
b0bd99266d Fix in landed cost voucher 2014-09-26 14:30:02 +05:30
Nabin Hait
b9e04815f8 Repost gl entries for future stock vouchers 2014-09-26 14:24:42 +05:30
Nabin Hait
18ccc27b1b Minor fix in naming naming series 2014-09-26 14:23:00 +05:30
Nabin Hait
95f1fe92e2 Maintenance visit search field issue fixed 2014-09-26 14:23:00 +05:30
Nabin Hait
b783f519ee Fixes in sales/purchase invoice trends report 2014-09-26 14:23:00 +05:30
Nabin Hait
996a1010cb Moved Installation note from tools to documents section 2014-09-26 14:23:00 +05:30
Anand Doshi
32a9dfd983 Merge branch 'sbkolate-develop' into develop 2014-09-22 13:19:24 +05:30
Anand Doshi
1394509343 Fixes for recurring document 2014-09-21 19:45:49 +05:30
Pratik Vyas
0f31c36b2c Merge branch 'develop' 2014-09-19 14:46:07 +05:30
Pratik Vyas
14ac8f71b7 bumped to version 4.4.2 2014-09-19 15:16:07 +06:00
Nabin Hait
6a92d51383 Merge pull request #2212 from nabinhait/hotfix
Fetch and validate advance entries in sales/purchase invoice
2014-09-19 14:41:20 +05:30
Nabin Hait
7c831c3fe5 Get advance in sales/purchase invoice 2014-09-19 14:31:49 +05:30
Nabin Hait
778ff463af Minor fix in authorization control 2014-09-19 11:39:47 +05:30
Nabin Hait
70a31d5402 Gantt view fix for translations 2014-09-19 11:18:10 +05:30
Nabin Hait
48f5fa69f3 Fetch and validate advance entries in sales/purchase invoice 2014-09-18 15:04:11 +05:30
Pratik Vyas
83a2d12cd2 Merge branch 'develop' 2014-09-18 14:19:15 +05:30
Pratik Vyas
e539297e53 bumped to version 4.4.1 2014-09-18 14:49:14 +06:00
Nabin Hait
bbc3d015a3 Update authorization_control.py 2014-09-18 14:09:20 +05:30
Nabin Hait
e4475c635d Merge pull request #2211 from nabinhait/hotfix
Escaped single quote in authorization control queries
2014-09-18 13:01:33 +05:30
Anand Doshi
8370cb3e71 Merge branch 'develop' of https://github.com/sbkolate/erpnext into sbkolate-develop 2014-09-18 12:06:05 +05:30
Nabin Hait
4073880ecf Escaped single quote in authorization control queries 2014-09-18 11:01:26 +05:30
Nabin Hait
763c7a56dc Merge pull request #2209 from nabinhait/hotfix
Minor fix in gross profit report
2014-09-17 14:02:51 +05:30
Nabin Hait
556fbc487d Minor fix in gross profit report 2014-09-17 12:13:44 +05:30
Sambhaji Kolate
f37d4337a4 fix test for purchase order 2014-09-15 13:37:46 +05:30
Sambhaji Kolate
525ab0a925 fix build 2014-09-15 12:57:58 +05:30
Sambhaji Kolate
930e7f5578 fix conflict 2014-09-15 12:43:48 +05:30
Sambhaji Kolate
d14e15d432 fix conflict 2014-09-15 12:31:44 +05:30
Sambhaji Kolate
4d3a18890b fix conflict 2014-09-15 12:20:11 +05:30
Sambhaji Kolate
b14401c320 change convert_to_recurring() to take recurring_id dynamicaly 2014-09-11 16:09:05 +05:30
Sambhaji Kolate
b2a3f2d386 some minor changes get fixed for PO/PI 2014-09-10 17:40:48 +05:30
Sambhaji Kolate
6b679c45df Updated purchase_invoice.json and purchase_order.json with some missed out changes 2014-09-10 13:57:45 +05:30
Sambhaji Kolate
e3d2643f2b Changes for Recurring PO/PI 2014-09-10 13:07:59 +05:30
110 changed files with 3942 additions and 3667 deletions

View File

@@ -14,6 +14,8 @@ install:
- sudo apt-get update
- sudo apt-get purge -y mysql-common
- sudo apt-get install mariadb-server mariadb-common libmariadbclient-dev
- ./ci/fix-mariadb.sh
- wget http://downloads.sourceforge.net/project/wkhtmltopdf/0.12.1/wkhtmltox-0.12.1_linux-precise-amd64.deb
- sudo dpkg -i wkhtmltox-0.12.1_linux-precise-amd64.deb
- CFLAGS=-O0 pip install git+https://github.com/frappe/frappe.git@develop

View File

@@ -4,7 +4,7 @@
Includes Accounting, Inventory, CRM, Sales, Purchase, Projects, HRMS. Built on Python / MariaDB.
ERPNext is built on [frappe](https://github.com/frappe/frappe)
ERPNext is built on [frappe](https://github.com/frappe/frappe) Python Framework.
- [User Guide](http://erpnext.org/user-guide.html)
- [Getting Help](http://erpnext.org/getting-help.html)
@@ -21,7 +21,7 @@ Use the bench, https://github.com/frappe/bench
1. go to "/login"
1. Administrator user name: "Administrator"
1. Administrator passowrd "admin"
1. Administrator password: "admin"
### Download and Install

11
ci/fix-mariadb.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
# stolen from http://cgit.drupalcode.org/octopus/commit/?id=db4f837
includedir=`mysql_config --variable=pkgincludedir`
thiscwd=`pwd`
_THIS_DB_VERSION=`mysql -V 2>&1 | tr -d "\n" | cut -d" " -f6 | awk '{ print $1}' | cut -d"-" -f1 | awk '{ print $1}' | sed "s/[\,']//g"`
if [ "$_THIS_DB_VERSION" = "5.5.40" ] && [ ! -e "$includedir-$_THIS_DB_VERSION-fixed.log" ] ; then
cd $includedir
sudo patch -p1 < $thiscwd/ci/my_config.h.patch &> /dev/null
sudo touch $includedir-$_THIS_DB_VERSION-fixed.log
fi

22
ci/my_config.h.patch Normal file
View File

@@ -0,0 +1,22 @@
diff -burp a/my_config.h b/my_config.h
--- a/my_config.h 2014-10-09 19:32:46.000000000 -0400
+++ b/my_config.h 2014-10-09 19:35:12.000000000 -0400
@@ -641,17 +641,4 @@
#define SIZEOF_TIME_T 8
/* #undef TIME_T_UNSIGNED */
-/*
- stat structure (from <sys/stat.h>) is conditionally defined
- to have different layout and size depending on the defined macros.
- The correct macro is defined in my_config.h, which means it MUST be
- included first (or at least before <features.h> - so, practically,
- before including any system headers).
-
- __GLIBC__ is defined in <features.h>
-*/
-#ifdef __GLIBC__
-#error <my_config.h> MUST be included first!
-#endif
-
#endif

View File

@@ -1 +1 @@
__version__ = '4.4.0'
__version__ = '4.8.0'

View File

@@ -124,6 +124,10 @@ def update_outstanding_amt(account, against_voucher_type, against_voucher, on_ca
from `tabGL Entry` where voucher_type = 'Journal Voucher' and voucher_no = %s
and account = %s and ifnull(against_voucher, '') = ''""",
(against_voucher, account))[0][0])
if not against_voucher_amount:
frappe.throw(_("Against Journal Voucher {0} is already adjusted against some other voucher")
.format(against_voucher))
bal = against_voucher_amount + bal
if against_voucher_amount < 0:
bal = -bal

View File

@@ -76,26 +76,36 @@ class JournalVoucher(AccountsController):
def validate_entries_for_advance(self):
for d in self.get('entries'):
if not d.is_advance and not d.against_voucher and \
not d.against_invoice and not d.against_jv:
if not (d.against_voucher and d.against_invoice and d.against_jv):
master_type = frappe.db.get_value("Account", d.account, "master_type")
if (master_type == 'Customer' and flt(d.credit) > 0) or \
(master_type == 'Supplier' and flt(d.debit) > 0):
msgprint(_("Row {0}: Please check 'Is Advance' against Account {1} if this \
is an advance entry.").format(d.idx, d.account))
if not d.is_advance:
msgprint(_("Row {0}: Please check 'Is Advance' against Account {1} if this is an advance entry.").format(d.idx, d.account))
elif (d.against_sales_order or d.against_purchase_order) and d.is_advance != "Yes":
frappe.throw(_("Row {0}: Payment against Sales/Purchase Order should always be marked as advance").format(d.idx))
def validate_against_jv(self):
for d in self.get('entries'):
if d.against_jv:
account_root_type = frappe.db.get_value("Account", d.account, "root_type")
if account_root_type == "Asset" and flt(d.debit) > 0:
frappe.throw(_("For {0}, only credit entries can be linked against another debit entry")
.format(d.account))
elif account_root_type == "Liability" and flt(d.credit) > 0:
frappe.throw(_("For {0}, only debit entries can be linked against another credit entry")
.format(d.account))
if d.against_jv == self.name:
frappe.throw(_("You can not enter current voucher in 'Against Journal Voucher' column"))
against_entries = frappe.db.sql("""select * from `tabJournal Voucher Detail`
where account = %s and docstatus = 1 and parent = %s
and ifnull(against_jv, '') = ''""", (d.account, d.against_jv), as_dict=True)
and ifnull(against_jv, '') = '' and ifnull(against_invoice, '') = ''
and ifnull(against_voucher, '') = ''""", (d.account, d.against_jv), as_dict=True)
if not against_entries:
frappe.throw(_("Journal Voucher {0} does not have account {1} or already matched")
frappe.throw(_("Journal Voucher {0} does not have account {1} or already matched against other voucher")
.format(d.against_jv, d.account))
else:
dr_or_cr = "debit" if d.credit > 0 else "credit"
@@ -152,7 +162,7 @@ class JournalVoucher(AccountsController):
and voucher_account != d.account:
frappe.throw(_("Row {0}: Account {1} does not match with {2} {3} account") \
.format(d.idx, d.account, doctype, field_dict.get(doctype)))
if against_field in ["against_sales_order", "against_purchase_order"]:
if voucher_account != account_master_name:
frappe.throw(_("Row {0}: Account {1} does not match with {2} {3} Name") \
@@ -164,7 +174,7 @@ class JournalVoucher(AccountsController):
def validate_against_invoice_fields(self, doctype, payment_against_voucher):
for voucher_no, payment_list in payment_against_voucher.items():
voucher_properties = frappe.db.get_value(doctype, voucher_no,
voucher_properties = frappe.db.get_value(doctype, voucher_no,
["docstatus", "outstanding_amount"])
if voucher_properties[0] != 1:
@@ -176,8 +186,8 @@ class JournalVoucher(AccountsController):
def validate_against_order_fields(self, doctype, payment_against_voucher):
for voucher_no, payment_list in payment_against_voucher.items():
voucher_properties = frappe.db.get_value(doctype, voucher_no,
["docstatus", "per_billed", "advance_paid", "grand_total"])
voucher_properties = frappe.db.get_value(doctype, voucher_no,
["docstatus", "per_billed", "status", "advance_paid", "grand_total"])
if voucher_properties[0] != 1:
frappe.throw(_("{0} {1} is not submitted").format(doctype, voucher_no))
@@ -185,7 +195,10 @@ class JournalVoucher(AccountsController):
if flt(voucher_properties[1]) >= 100:
frappe.throw(_("{0} {1} is fully billed").format(doctype, voucher_no))
if flt(voucher_properties[3]) < flt(voucher_properties[2]) + flt(sum(payment_list)):
if cstr(voucher_properties[2]) == "Stopped":
frappe.throw(_("{0} {1} is stopped").format(doctype, voucher_no))
if flt(voucher_properties[4]) < flt(voucher_properties[3]) + flt(sum(payment_list)):
frappe.throw(_("Advance paid against {0} {1} cannot be greater \
than Grand Total {2}").format(doctype, voucher_no, voucher_properties[3]))
@@ -528,9 +541,10 @@ def get_against_sales_invoice(doctype, txt, searchfield, start, page_len, filter
(filters["account"], "%%%s%%" % txt, start, page_len))
def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select jv.name, jv.posting_date, jv.user_remark
from `tabJournal Voucher` jv, `tabJournal Voucher Detail` jv_detail
where jv_detail.parent = jv.name and jv_detail.account = %s and jv.docstatus = 1
return frappe.db.sql("""select distinct jv.name, jv.posting_date, jv.user_remark
from `tabJournal Voucher` jv, `tabJournal Voucher Detail` jvd
where jvd.parent = jv.name and jvd.account = %s and jv.docstatus = 1
and (ifnull(jvd.against_invoice, '') = '' and ifnull(jvd.against_voucher, '') = '' and ifnull(jvd.against_jv, '') = '' )
and jv.%s like %s order by jv.name desc limit %s, %s""" %
("%s", searchfield, "%s", "%s", "%s"),
(filters["account"], "%%%s%%" % txt, start, page_len))

View File

@@ -19,9 +19,9 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
]
};
}
});
this.frm.set_query('bank_cash_account', function() {
if(!me.frm.doc.company) {
msgprint(__("Please select company first"));
@@ -35,12 +35,8 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
};
}
});
var help_content = '<i class="icon-hand-right"></i> ' + __("Note") + ':<br>'+
'<ul>' + __("If you are unable to match the exact amount, then amend your Journal Voucher and split rows such that payment amount match the invoice amount.") + '</ul>';
this.frm.set_value("reconcile_help", help_content);
},
get_unreconciled_entries: function() {
var me = this;
return this.frm.call({
@@ -48,12 +44,12 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
method: 'get_unreconciled_entries',
callback: function(r, rt) {
var invoices = [];
$.each(me.frm.doc.payment_reconciliation_invoices || [], function(i, row) {
if (row.invoice_number && !inList(invoices, row.invoice_number))
if (row.invoice_number && !inList(invoices, row.invoice_number))
invoices.push(row.invoice_number);
});
frappe.meta.get_docfield("Payment Reconciliation Payment", "invoice_number",
me.frm.doc.name).options = invoices.join("\n");
@@ -79,4 +75,4 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
$.extend(cur_frm.cscript, new erpnext.accounts.PaymentReconciliationController({frm: cur_frm}));
cur_frm.add_fetch('party_account', 'master_type', 'party_type')
cur_frm.add_fetch('party_account', 'master_type', 'party_type')

View File

@@ -118,19 +118,12 @@
"options": "Payment Reconciliation Invoice",
"permlevel": 0,
"read_only": 1
},
{
"fieldname": "reconcile_help",
"fieldtype": "Small Text",
"label": "",
"permlevel": 0,
"read_only": 1
}
],
"hide_toolbar": 1,
"icon": "icon-resize-horizontal",
"issingle": 1,
"modified": "2014-07-31 05:43:03.410832",
"modified": "2014-10-16 17:51:44.367107",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation",

View File

@@ -124,7 +124,7 @@ class PaymentReconciliation(Document):
dr_or_cr = "credit" if self.party_type == "Customer" else "debit"
lst = []
for e in self.get('payment_reconciliation_payments'):
if e.invoice_type and e.invoice_number:
if e.invoice_type and e.invoice_number and e.allocated_amount:
lst.append({
'voucher_no' : e.journal_voucher,
'voucher_detail_no' : e.voucher_detail_number,
@@ -134,7 +134,7 @@ class PaymentReconciliation(Document):
'is_advance' : e.is_advance,
'dr_or_cr' : dr_or_cr,
'unadjusted_amt' : flt(e.amount),
'allocated_amt' : flt(e.amount)
'allocated_amt' : flt(e.allocated_amount)
})
if lst:
@@ -162,18 +162,23 @@ class PaymentReconciliation(Document):
invoices_to_reconcile = []
for p in self.get("payment_reconciliation_payments"):
if p.invoice_type and p.invoice_number:
if p.invoice_type and p.invoice_number and p.allocated_amount:
invoices_to_reconcile.append(p.invoice_number)
if p.invoice_number not in unreconciled_invoices.get(p.invoice_type, {}):
frappe.throw(_("{0}: {1} not found in Invoice Details table")
.format(p.invoice_type, p.invoice_number))
if p.amount > unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number):
frappe.throw(_("Row {0}: Payment amount must be less than or equals to invoice outstanding amount. Please refer Note below.").format(p.idx))
if flt(p.allocated_amount) > flt(p.amount):
frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equals to JV amount {2}")
.format(p.idx, p.allocated_amount, p.amount))
if flt(p.allocated_amount) > unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number):
frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equals to invoice outstanding amount {2}")
.format(p.idx, p.allocated_amount, unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number)))
if not invoices_to_reconcile:
frappe.throw(_("Please select Invoice Type and Invoice Number in atleast one row"))
frappe.throw(_("Please select Allocated Amount, Invoice Type and Invoice Number in atleast one row"))
def check_condition(self, dr_or_cr):
cond = self.from_date and " and posting_date >= '" + self.from_date + "'" or ""

View File

@@ -53,11 +53,20 @@
"label": "Column Break",
"permlevel": 0
},
{
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Allocated amount",
"permlevel": 0,
"precision": "",
"reqd": 1
},
{
"default": "Sales Invoice",
"fieldname": "invoice_type",
"fieldtype": "Select",
"in_list_view": 1,
"in_list_view": 0,
"label": "Invoice Type",
"options": "\nSales Invoice\nPurchase Invoice\nJournal Voucher",
"permlevel": 0,
@@ -95,7 +104,7 @@
}
],
"istable": 1,
"modified": "2014-07-21 16:53:56.206169",
"modified": "2014-10-16 17:40:54.040194",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation Payment",

View File

@@ -91,6 +91,7 @@ def get_orders_to_be_billed(party_type, party_name):
where
%s = %s
and docstatus = 1
and ifnull(status, "") != "Stopped"
and ifnull(grand_total, 0) > ifnull(advance_paid, 0)
and ifnull(per_billed, 0) < 100.0
""" % (voucher_type, 'customer' if party_type == "Customer" else 'supplier', '%s'),

View File

@@ -31,9 +31,10 @@ class TestPaymentTool(unittest.TestCase):
"customer": "_Test Customer 3"
})
jv_against_so1 = self.create_against_jv(jv_test_records[0], {
jv_against_so1 = self.create_against_jv(jv_test_records[0], {
"account": "_Test Customer 3 - _TC",
"against_sales_order": so1.name
"against_sales_order": so1.name,
"is_advance": "Yes"
})
@@ -42,10 +43,11 @@ class TestPaymentTool(unittest.TestCase):
"customer": "_Test Customer 3"
})
jv_against_so2 = self.create_against_jv(jv_test_records[0], {
jv_against_so2 = self.create_against_jv(jv_test_records[0], {
"account": "_Test Customer 3 - _TC",
"against_sales_order": so2.name,
"credit": 1000
"credit": 1000,
"is_advance": "Yes"
})
po = self.create_voucher(po_test_records[1], {
"supplier": "_Test Supplier 1"
@@ -54,20 +56,20 @@ class TestPaymentTool(unittest.TestCase):
#Create SI with partial outstanding
si1 = self.create_voucher(si_test_records[0], {
"customer": "_Test Customer 3",
"debit_to": "_Test Customer 3 - _TC"
"debit_to": "_Test Customer 3 - _TC"
})
jv_against_si1 = self.create_against_jv(jv_test_records[0], {
jv_against_si1 = self.create_against_jv(jv_test_records[0], {
"account": "_Test Customer 3 - _TC",
"against_invoice": si1.name
})
#Create SI with no outstanding
si2 = self.create_voucher(si_test_records[0], {
"customer": "_Test Customer 3",
"debit_to": "_Test Customer 3 - _TC"
"debit_to": "_Test Customer 3 - _TC"
})
jv_against_si2 = self.create_against_jv(jv_test_records[0], {
jv_against_si2 = self.create_against_jv(jv_test_records[0], {
"account": "_Test Customer 3 - _TC",
"against_invoice": si2.name,
"credit": 561.80
@@ -75,7 +77,7 @@ class TestPaymentTool(unittest.TestCase):
pi = self.create_voucher(pi_test_records[0], {
"supplier": "_Test Supplier 1",
"credit_to": "_Test Supplier 1 - _TC"
"credit_to": "_Test Supplier 1 - _TC"
})
#Create a dict containing properties and expected values
@@ -137,7 +139,7 @@ class TestPaymentTool(unittest.TestCase):
payment_tool_doc.set(k, v)
self.check_outstanding_vouchers(payment_tool_doc, args, expected_outstanding)
def check_outstanding_vouchers(self, doc, args, expected_outstanding):
from erpnext.accounts.doctype.payment_tool.payment_tool import get_outstanding_vouchers
@@ -161,7 +163,7 @@ class TestPaymentTool(unittest.TestCase):
new_jv = paytool.make_journal_voucher()
#Create a list of expected values as [party account, payment against, against_jv, against_invoice,
#Create a list of expected values as [party account, payment against, against_jv, against_invoice,
#against_voucher, against_sales_order, against_purchase_order]
expected_values = [
[paytool.party_account, 100.00, expected_outstanding.get("Journal Voucher")[0], None, None, None, None],
@@ -171,7 +173,7 @@ class TestPaymentTool(unittest.TestCase):
[paytool.party_account, 100.00, None, None, None, None, expected_outstanding.get("Purchase Order")[0]]
]
for jv_entry in new_jv.get("entries"):
for jv_entry in new_jv.get("entries"):
if paytool.party_account == jv_entry.get("account"):
row = [
jv_entry.get("account"),
@@ -183,11 +185,11 @@ class TestPaymentTool(unittest.TestCase):
jv_entry.get("against_purchase_order"),
]
self.assertTrue(row in expected_values)
self.assertEquals(new_jv.get("cheque_no"), paytool.reference_no)
self.assertEquals(new_jv.get("cheque_date"), paytool.reference_date)
def clear_table_entries(self):
frappe.db.sql("""delete from `tabGL Entry` where (account = "_Test Customer 3 - _TC" or account = "_Test Supplier 1 - _TC")""")
frappe.db.sql("""delete from `tabSales Order` where customer_name = "_Test Customer 3" """)
frappe.db.sql("""delete from `tabPurchase Order` where supplier_name = "_Test Supplier 1" """)
frappe.db.sql("""delete from `tabPurchase Order` where supplier_name = "_Test Supplier 1" """)

View File

@@ -232,7 +232,6 @@ cur_frm.fields_dict['entries'].grid.get_field('project_name').get_query = functi
}
}
cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
if(doc.select_print_heading){
// print heading

View File

@@ -752,12 +752,133 @@
"print_hide": 1,
"read_only": 0,
"reqd": 0
},
{
"depends_on": "eval:doc.docstatus<2",
"fieldname": "recurring_invoice",
"fieldtype": "Section Break",
"label": "Recurring Invoice",
"options": "icon-time",
"permlevel": 0,
"print_hide": 1
},
{
"fieldname": "column_break_77",
"fieldtype": "Column Break",
"permlevel": 0,
"print_hide": 1,
"width": "50%"
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.docstatus<2",
"description": "Check if recurring invoice, uncheck to stop recurring or put proper End Date",
"fieldname": "is_recurring",
"fieldtype": "Check",
"label": "Is Recurring",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
"description": "Select the period when the invoice will be generated automatically",
"fieldname": "recurring_type",
"fieldtype": "Select",
"label": "Recurring Type",
"no_copy": 1,
"options": "Monthly\nQuarterly\nHalf-yearly\nYearly",
"permlevel": 0,
"print_hide": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
"description": "Start date of current invoice's period",
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
"permlevel": 0
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
"description": "End date of current invoice's period",
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
"permlevel": 0
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
"description": "The day of the month on which auto invoice will be generated e.g. 05, 28 etc",
"fieldname": "repeat_on_day_of_month",
"fieldtype": "Int",
"label": "Repeat on Day of Month",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
"description": "The date on which recurring invoice will be stop",
"fieldname": "end_date",
"fieldtype": "Date",
"label": "End Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1
},
{
"fieldname": "column_break_82",
"fieldtype": "Column Break",
"permlevel": 0,
"print_hide": 1,
"width": "50%"
},
{
"depends_on": "eval:doc.is_recurring==1",
"description": "The date on which next invoice will be generated. It is generated on submit.",
"fieldname": "next_date",
"fieldtype": "Date",
"label": "Next Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},
{
"depends_on": "eval:doc.is_recurring==1",
"description": "The unique id for tracking all recurring invoices. It is generated on submit.",
"fieldname": "recurring_id",
"fieldtype": "Data",
"label": "Recurring Id",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
"description": "Enter email id separated by commas, invoice will be mailed automatically on particular date",
"fieldname": "notification_email_address",
"fieldtype": "Small Text",
"label": "Notification Email Address",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1
}
],
"icon": "icon-file-text",
"idx": 1,
"is_submittable": 1,
"modified": "2014-09-09 05:35:32.156763",
"modified": "2014-10-08 14:23:20.234176",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -4,11 +4,10 @@
from __future__ import unicode_literals
import frappe
from frappe.utils import cint, cstr, flt, formatdate
from frappe.utils import cint, cstr, formatdate, flt
from frappe import msgprint, _, throw
from erpnext.setup.utils import get_company_currency
import frappe.defaults
from erpnext.controllers.buying_controller import BuyingController
@@ -49,6 +48,7 @@ class PurchaseInvoice(BuyingController):
self.check_conversion_rate()
self.validate_credit_acc()
self.clear_unallocated_advances("Purchase Invoice Advance", "advance_allocation_details")
self.validate_advance_jv("advance_allocation_details", "purchase_order")
self.check_for_acc_head_of_supplier()
self.check_for_stopped_status()
self.validate_with_previous_doc()
@@ -80,7 +80,7 @@ class PurchaseInvoice(BuyingController):
def get_advances(self):
super(PurchaseInvoice, self).get_advances(self.credit_to,
"Purchase Invoice Advance", "advance_allocation_details", "debit")
"Purchase Invoice Advance", "advance_allocation_details", "debit", "purchase_order")
def check_active_purchase_items(self):
for d in self.get('entries'):
@@ -249,6 +249,8 @@ class PurchaseInvoice(BuyingController):
reconcile_against_document(lst)
def on_submit(self):
super(PurchaseInvoice, self).on_submit()
self.check_prev_docstatus()
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,

View File

@@ -231,4 +231,8 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertTrue(not frappe.db.sql("""select name from `tabJournal Voucher Detail`
where against_voucher=%s""", pi.name))
def test_recurring_invoice(self):
from erpnext.controllers.tests.test_recurring_document import test_recurring_document
test_recurring_document(self, test_records)
test_records = frappe.get_test_records('Purchase Invoice')

View File

@@ -399,37 +399,6 @@ cur_frm.cscript.on_submit = function(doc, cdt, cdn) {
})
}
cur_frm.cscript.is_recurring = function(doc, dt, dn) {
// set default values for recurring invoices
if(doc.is_recurring) {
var owner_email = doc.owner=="Administrator"
? frappe.user_info("Administrator").email
: doc.owner;
doc.notification_email_address = $.map([cstr(owner_email),
cstr(doc.contact_email)], function(v) { return v || null; }).join(", ");
doc.repeat_on_day_of_month = frappe.datetime.str_to_obj(doc.posting_date).getDate();
}
refresh_many(["notification_email_address", "repeat_on_day_of_month"]);
}
cur_frm.cscript.from_date = function(doc, dt, dn) {
// set to_date
if(doc.from_date) {
var recurring_type_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6,
'Yearly': 12};
var months = recurring_type_map[doc.recurring_type];
if(months) {
var to_date = frappe.datetime.add_months(doc.from_date,
months);
doc.to_date = frappe.datetime.add_days(to_date, -1);
refresh_field('to_date');
}
}
}
cur_frm.cscript.send_sms = function() {
frappe.require("assets/erpnext/js/sms_manager.js");
var sms_man = new SMSManager(cur_frm.doc);

View File

@@ -169,28 +169,25 @@
"search_index": 0
},
{
"allow_on_submit": 1,
"depends_on": "",
"description": "Start date of current invoice's period",
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From",
"no_copy": 1,
"fieldname": "shipping_address_name",
"fieldtype": "Link",
"hidden": 1,
"in_filter": 1,
"label": "Shipping Address Name",
"options": "Address",
"permlevel": 0,
"print_hide": 0,
"read_only": 0
"precision": "",
"print_hide": 1
},
{
"allow_on_submit": 1,
"depends_on": "",
"description": "End date of current invoice's period",
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To",
"no_copy": 1,
"fieldname": "shipping_address",
"fieldtype": "Small Text",
"hidden": 1,
"label": "Shipping Address",
"permlevel": 0,
"print_hide": 0,
"read_only": 0
"precision": "",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "currency_section",
@@ -1108,6 +1105,30 @@
"print_hide": 1,
"read_only": 0
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
"description": "Start date of current invoice's period",
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 0,
"read_only": 0
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
"description": "End date of current invoice's period",
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 0,
"read_only": 0
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
@@ -1120,17 +1141,6 @@
"print_hide": 1,
"read_only": 0
},
{
"depends_on": "eval:doc.is_recurring==1",
"description": "The date on which next invoice will be generated. It is generated on submit.\n",
"fieldname": "next_date",
"fieldtype": "Date",
"label": "Next Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.is_recurring==1",
@@ -1152,6 +1162,17 @@
"read_only": 0,
"width": "50%"
},
{
"depends_on": "eval:doc.is_recurring==1",
"description": "The date on which next invoice will be generated. It is generated on submit.\n",
"fieldname": "next_date",
"fieldtype": "Date",
"label": "Next Date",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"read_only": 1
},
{
"depends_on": "eval:doc.is_recurring==1",
"description": "The unique id for tracking all recurring invoices.\u00a0It is generated on submit.",
@@ -1192,7 +1213,7 @@
"icon": "icon-file-text",
"idx": 1,
"is_submittable": 1,
"modified": "2014-09-09 05:35:34.121045",
"modified": "2014-10-10 16:54:22.284284",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -4,18 +4,12 @@
from __future__ import unicode_literals
import frappe
import frappe.defaults
from frappe.utils import add_days, cint, cstr, date_diff, flt, getdate, nowdate, \
get_first_day, get_last_day
from frappe.model.naming import make_autoname
from frappe.utils import cint, cstr, flt
from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date
from erpnext.controllers.stock_controller import update_gl_entries_after
from frappe.model.mapper import get_mapped_doc
from erpnext.controllers.recurring_document import *
from erpnext.controllers.selling_controller import SellingController
form_grid_templates = {
@@ -56,6 +50,7 @@ class SalesInvoice(SellingController):
self.validate_debit_acc()
self.validate_fixed_asset_account()
self.clear_unallocated_advances("Sales Invoice Advance", "advance_adjustment_details")
self.validate_advance_jv("advance_adjustment_details", "sales_order")
self.add_remarks()
if cint(self.is_pos):
@@ -77,11 +72,12 @@ class SalesInvoice(SellingController):
self.set_against_income_account()
self.validate_c_form()
self.validate_time_logs_are_submitted()
validate_recurring_document(self)
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount",
"delivery_note_details")
def on_submit(self):
super(SalesInvoice, self).on_submit()
if cint(self.update_stock) == 1:
self.update_stock_ledger()
else:
@@ -104,7 +100,6 @@ class SalesInvoice(SellingController):
self.update_against_document_in_jv()
self.update_time_log_batch(self.name)
convert_to_recurring(self, "RECINV.#####", self.posting_date)
def before_cancel(self):
self.update_time_log_batch(None)
@@ -145,14 +140,6 @@ class SalesInvoice(SellingController):
'overflow_type': 'delivery'
})
def on_update_after_submit(self):
validate_recurring_document(self)
convert_to_recurring(self, "RECINV.#####", self.posting_date)
def before_recurring(self):
self.aging_date = None
self.due_date = None
def get_portal_page(self):
return "invoice" if self.docstatus==1 else None
@@ -222,7 +209,7 @@ class SalesInvoice(SellingController):
def get_advances(self):
super(SalesInvoice, self).get_advances(self.debit_to,
"Sales Invoice Advance", "advance_adjustment_details", "credit")
"Sales Invoice Advance", "advance_adjustment_details", "credit", "sales_order")
def get_company_abbr(self):
return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0]
@@ -486,9 +473,8 @@ class SalesInvoice(SellingController):
if repost_future_gle and cint(self.update_stock) \
and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")):
items, warehouse_account = self.get_items_and_warehouse_accounts()
update_gl_entries_after(self.posting_date, self.posting_time,
warehouse_account, items)
items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items)
def get_gl_entries(self, warehouse_account=None):
from erpnext.accounts.general_ledger import merge_similar_entries

View File

@@ -97,8 +97,7 @@ def validate_account_for_auto_accounting_for_stock(gl_map):
for entry in gl_map:
if entry.account in aii_accounts:
frappe.throw(_("Account: {0} can only be updated via \
Stock Transactions").format(entry.account), StockAccountInvalidTransaction)
frappe.throw(_("Account: {0} can only be updated via Stock Transactions").format(entry.account), StockAccountInvalidTransaction)
def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,

View File

@@ -30,7 +30,8 @@ def execute(filters=None):
data = []
for gle in entries:
if cstr(gle.against_voucher) == gle.voucher_no or not gle.against_voucher \
or [gle.against_voucher_type, gle.against_voucher] in entries_after_report_date:
or [gle.against_voucher_type, gle.against_voucher] in entries_after_report_date \
or (gle.against_voucher_type == "Purchase Order"):
voucher_details = voucher_detail_map.get(gle.voucher_type, {}).get(gle.voucher_no, {})
invoiced_amount = gle.credit > 0 and gle.credit or 0

View File

@@ -21,7 +21,7 @@ class AccountsReceivableReport(object):
def get_columns(self, customer_naming_by):
columns = [
_("Posting Date") + ":Date:80", _("Account") + ":Link/Account:150",
_("Voucher Type") + "::110", _("Voucher No") + "::120", "::30",
_("Voucher Type") + "::110", _("Voucher No") + ":Dynamic Link/Voucher Type:120",
_("Due Date") + ":Date:80",
_("Invoiced Amount") + ":Currency:100", _("Payment Received") + ":Currency:100",
_("Outstanding Amount") + ":Currency:100", _("Age") + ":Int:50", "0-30:Currency:100",
@@ -63,11 +63,6 @@ class AccountsReceivableReport(object):
row += [self.get_territory(gle.account), gle.remarks]
data.append(row)
for i in range(0, len(data)):
data[i].insert(4, """<a href="%s"><i class="icon icon-share" style="cursor: pointer;"></i></a>""" \
% ("/".join(["#Form", data[i][2], data[i][3]]),))
return data
def get_entries_after(self, report_date):
@@ -84,6 +79,9 @@ class AccountsReceivableReport(object):
return (
# advance
(not gle.against_voucher) or
# against sales order
(gle.against_voucher_type == "Sales Order") or
# sales invoice
(gle.against_voucher==gle.voucher_no and gle.debit > 0) or

View File

@@ -35,7 +35,7 @@ def validate_filters(filters, account_details):
def get_columns():
return [_("Posting Date") + ":Date:100", _("Account") + ":Link/Account:200", _("Debit") + ":Float:100",
_("Credit") + ":Float:100", _("Voucher Type") + "::120", _("Voucher No") + "::160", _("Link") + "::20",
_("Credit") + ":Float:100", _("Voucher Type") + "::120", _("Voucher No") + ":Dynamic Link/Voucher Type:160",
_("Against Account") + "::120", _("Cost Center") + ":Link/Cost Center:100", _("Remarks") + "::400"]
def get_result(filters, account_details):
@@ -162,15 +162,6 @@ def get_result_as_list(data):
for d in data:
result.append([d.get("posting_date"), d.get("account"), d.get("debit"),
d.get("credit"), d.get("voucher_type"), d.get("voucher_no"),
get_voucher_link(d.get("voucher_type"), d.get("voucher_no")),
d.get("against"), d.get("cost_center"), d.get("remarks")])
return result
def get_voucher_link(voucher_type, voucher_no):
icon = ""
if voucher_type and voucher_no:
icon = """<a href="%s"><i class="icon icon-share" style="cursor: pointer;">
</i></a>""" % ("/".join(["#Form", voucher_type, voucher_no]))
return icon

View File

@@ -9,29 +9,29 @@ from erpnext.stock.utils import get_buying_amount, get_sales_bom_buying_amount
def execute(filters=None):
if not filters: filters = {}
stock_ledger_entries = get_stock_ledger_entries(filters)
source = get_source_data(filters)
item_sales_bom = get_item_sales_bom()
columns = [__("Delivery Note/Sales Invoice") + "::120", _("Link") + "::30", _("Posting Date") + ":Date", _("Posting Time"),
columns = [_("Delivery Note/Sales Invoice") + "::120", _("Link") + "::30", _("Posting Date") + ":Date", _("Posting Time"),
_("Item Code") + ":Link/Item", _("Item Name"), _("Description"), _("Warehouse") + ":Link/Warehouse",
_("Qty") + ":Float", _("Selling Rate") + ":Currency", _("Avg. Buying Rate") + ":Currency",
_("Qty") + ":Float", _("Selling Rate") + ":Currency", _("Avg. Buying Rate") + ":Currency",
_("Selling Amount") + ":Currency", _("Buying Amount") + ":Currency",
_("Gross Profit") + ":Currency", _("Gross Profit %") + ":Percent", _("Project") + ":Link/Project"]
data = []
for row in source:
selling_amount = flt(row.base_amount)
item_sales_bom_map = item_sales_bom.get(row.parenttype, {}).get(row.name, frappe._dict())
if item_sales_bom_map.get(row.item_code):
buying_amount = get_sales_bom_buying_amount(row.item_code, row.warehouse,
buying_amount = get_sales_bom_buying_amount(row.item_code, row.warehouse,
row.parenttype, row.name, row.item_row, stock_ledger_entries, item_sales_bom_map)
else:
buying_amount = get_buying_amount(row.parenttype, row.name, row.item_row,
stock_ledger_entries.get((row.item_code, row.warehouse), []))
buying_amount = buying_amount > 0 and buying_amount or 0
gross_profit = selling_amount - buying_amount
@@ -39,41 +39,41 @@ def execute(filters=None):
gross_profit_percent = (gross_profit / selling_amount) * 100.0
else:
gross_profit_percent = 0.0
icon = """<a href="%s"><i class="icon icon-share" style="cursor: pointer;"></i></a>""" \
% ("/".join(["#Form", row.parenttype, row.name]),)
data.append([row.name, icon, row.posting_date, row.posting_time, row.item_code, row.item_name,
row.description, row.warehouse, row.qty, row.base_rate,
row.description, row.warehouse, row.qty, row.base_rate,
row.qty and (buying_amount / row.qty) or 0, row.base_amount, buying_amount,
gross_profit, gross_profit_percent, row.project])
return columns, data
def get_stock_ledger_entries(filters):
def get_stock_ledger_entries(filters):
query = """select item_code, voucher_type, voucher_no,
voucher_detail_no, posting_date, posting_time, stock_value,
warehouse, actual_qty as qty
from `tabStock Ledger Entry`"""
if filters.get("company"):
query += """ where company=%(company)s"""
query += " order by item_code desc, warehouse desc, posting_date desc, posting_time desc, name desc"
res = frappe.db.sql(query, filters, as_dict=True)
out = {}
for r in res:
if (r.item_code, r.warehouse) not in out:
out[(r.item_code, r.warehouse)] = []
out[(r.item_code, r.warehouse)].append(r)
return out
def get_item_sales_bom():
item_sales_bom = {}
for d in frappe.db.sql("""select parenttype, parent, parent_item,
item_code, warehouse, -1*qty as total_qty, parent_detail_docname
from `tabPacked Item` where docstatus=1""", as_dict=True):
@@ -81,7 +81,7 @@ def get_item_sales_bom():
frappe._dict()).setdefault(d.parent_item, []).append(d)
return item_sales_bom
def get_source_data(filters):
conditions = ""
if filters.get("company"):
@@ -90,9 +90,9 @@ def get_source_data(filters):
conditions += " and posting_date>=%(from_date)s"
if filters.get("to_date"):
conditions += " and posting_date<=%(to_date)s"
delivery_note_items = frappe.db.sql("""select item.parenttype, dn.name,
dn.posting_date, dn.posting_time, dn.project_name,
delivery_note_items = frappe.db.sql("""select item.parenttype, dn.name,
dn.posting_date, dn.posting_time, dn.project_name,
item.item_code, item.item_name, item.description, item.warehouse,
item.qty, item.base_rate, item.base_amount, item.name as "item_row",
timestamp(dn.posting_date, dn.posting_time) as posting_datetime
@@ -100,7 +100,7 @@ def get_source_data(filters):
where item.parent = dn.name and dn.docstatus = 1 %s
order by dn.posting_date desc, dn.posting_time desc""" % (conditions,), filters, as_dict=1)
sales_invoice_items = frappe.db.sql("""select item.parenttype, si.name,
sales_invoice_items = frappe.db.sql("""select item.parenttype, si.name,
si.posting_date, si.posting_time, si.project_name,
item.item_code, item.item_name, item.description, item.warehouse,
item.qty, item.base_rate, item.base_amount, item.name as "item_row",
@@ -109,9 +109,9 @@ def get_source_data(filters):
where item.parent = si.name and si.docstatus = 1 %s
and si.update_stock = 1
order by si.posting_date desc, si.posting_time desc""" % (conditions,), filters, as_dict=1)
source = delivery_note_items + sales_invoice_items
if len(source) > len(delivery_note_items):
source.sort(key=lambda d: d.posting_datetime, reverse=True)
return source
return source

View File

@@ -11,4 +11,4 @@ def execute(filters=None):
conditions = get_columns(filters, "Sales Invoice")
data = get_data(filters, conditions)
return conditions["columns"], data
return conditions["columns"], data

File diff suppressed because it is too large Load Diff

View File

@@ -162,6 +162,8 @@ class PurchaseOrder(BuyingController):
msgprint(_("Status of {0} {1} is now {2}").format(self.doctype, self.name, status))
def on_submit(self):
super(PurchaseOrder, self).on_submit()
purchase_controller = frappe.get_doc("Purchase Common")
self.update_prevdoc_status()

View File

@@ -107,7 +107,11 @@ class TestPurchaseOrder(unittest.TestCase):
po.get("po_details")[0].qty = 3.4
self.assertRaises(UOMMustBeIntegerError, po.insert)
def test_recurring_order(self):
from erpnext.controllers.tests.test_recurring_document import test_recurring_document
test_recurring_document(self, test_records)
test_dependencies = ["BOM"]
test_dependencies = ["BOM", "Item Price"]
test_records = frappe.get_test_records('Purchase Order')

View File

@@ -26,6 +26,11 @@ def get_data():
"name": "Purchase Receipt",
"description": _("Goods received from Suppliers."),
},
{
"type": "doctype",
"name": "Installation Note",
"description": _("Installation record for a Serial No.")
},
{
"type": "doctype",
"name": "Item",
@@ -57,11 +62,6 @@ def get_data():
"name": "Stock Reconciliation",
"description": _("Upload stock balance via csv.")
},
{
"type": "doctype",
"name": "Installation Note",
"description": _("Installation record for a Serial No.")
},
{
"type": "doctype",
"name": "Packing Slip",
@@ -142,10 +142,10 @@ def get_data():
"doctype": "Item",
},
{
"type": "page",
"name": "stock-balance",
"label": _("Stock Balance"),
"icon": "icon-table",
"type": "report",
"is_query_report": True,
"name": "Stock Balance",
"doctype": "Warehouse"
},
{
"type": "report",
@@ -170,13 +170,7 @@ def get_data():
"name": "stock-analytics",
"label": _("Stock Analytics"),
"icon": "icon-bar-chart"
},
{
"type": "report",
"is_query_report": True,
"name": "Warehouse-Wise Stock Balance",
"doctype": "Warehouse"
},
}
]
},
{

View File

@@ -4,12 +4,11 @@
from __future__ import unicode_literals
import frappe
from frappe import _, throw
from frappe.utils import add_days, cint, cstr, today, date_diff, flt, getdate, nowdate, \
get_first_day, get_last_day
from frappe.model.naming import make_autoname
from frappe.utils import cint, today, flt
from erpnext.setup.utils import get_company_currency, get_exchange_rate
from erpnext.accounts.utils import get_fiscal_year, validate_fiscal_year
from erpnext.utilities.transaction_base import TransactionBase
from erpnext.controllers.recurring_document import convert_to_recurring, validate_recurring_document
import json
class AccountsController(TransactionBase):
@@ -24,6 +23,24 @@ class AccountsController(TransactionBase):
self.validate_for_freezed_account()
if self.meta.get_field("is_recurring"):
validate_recurring_document(self)
def on_submit(self):
if self.meta.get_field("is_recurring"):
convert_to_recurring(self, self.get("posting_date") or self.get("transaction_date"))
def on_update_after_submit(self):
if self.meta.get_field("is_recurring"):
validate_recurring_document(self)
convert_to_recurring(self, self.get("posting_date") or self.get("transaction_date"))
def before_recurring(self):
self.fiscal_year = None
for fieldname in ("due_date", "aging_date"):
if self.meta.get_field(fieldname):
self.set(fieldname, None)
def set_missing_values(self, for_validate=False):
for fieldname in ["posting_date", "transaction_date"]:
if not self.get(fieldname) and self.meta.get_field(fieldname):
@@ -362,38 +379,67 @@ class AccountsController(TransactionBase):
frappe.db.sql("""delete from `tab%s` where parentfield=%s and parent = %s
and ifnull(allocated_amount, 0) = 0""" % (childtype, '%s', '%s'), (parentfield, self.name))
def get_advances(self, account_head, child_doctype, parentfield, dr_or_cr):
against_order_list = []
def get_advances(self, account_head, child_doctype, parentfield, dr_or_cr, against_order_field):
so_list = list(set([d.get(against_order_field) for d in self.get("entries") if d.get(against_order_field)]))
cond = ""
if so_list:
cond = "or (ifnull(t2.%s, '') in (%s))" % ("against_" + against_order_field, ', '.join(['%s']*len(so_list)))
res = frappe.db.sql("""
select
t1.name as jv_no, t1.remark, t2.%s as amount, t2.name as jv_detail_no, t2.%s as order_no
t1.name as jv_no, t1.remark, t2.%s as amount, t2.name as jv_detail_no, `against_%s` as against_order
from
`tabJournal Voucher` t1, `tabJournal Voucher Detail` t2
where
t1.name = t2.parent and t2.account = %s and t2.is_advance = 'Yes' and t1.docstatus = 1
and ifnull(t2.against_voucher, '') = ''
and ifnull(t2.against_invoice, '') = ''
and ifnull(t2.against_jv, '') = ''
and ((
ifnull(t2.against_voucher, '') = ''
and ifnull(t2.against_invoice, '') = ''
and ifnull(t2.against_jv, '') = ''
and ifnull(t2.against_sales_order, '') = ''
and ifnull(t2.against_purchase_order, '') = ''
) %s)
order by t1.posting_date""" %
(dr_or_cr, "against_sales_order" if dr_or_cr == "credit" \
else "against_purchase_order", '%s'),
account_head, as_dict= True)
if self.get("entries"):
for i in self.get("entries"):
against_order_list.append(i.sales_order if dr_or_cr == "credit" else i.purchase_order)
(dr_or_cr, against_order_field, '%s', cond),
tuple([account_head] + so_list), as_dict= True)
self.set(parentfield, [])
for d in res:
if not against_order_list or d.order_no in against_order_list:
self.append(parentfield, {
"doctype": child_doctype,
"journal_voucher": d.jv_no,
"jv_detail_no": d.jv_detail_no,
"remarks": d.remark,
"advance_amount": flt(d.amount),
"allocate_amount": 0
})
self.append(parentfield, {
"doctype": child_doctype,
"journal_voucher": d.jv_no,
"jv_detail_no": d.jv_detail_no,
"remarks": d.remark,
"advance_amount": flt(d.amount),
"allocated_amount": flt(d.amount) if d.against_order else 0
})
def validate_advance_jv(self, advance_table_fieldname, against_order_field):
order_list = list(set([d.get(against_order_field) for d in self.get("entries") if d.get(against_order_field)]))
if order_list:
account = self.get("debit_to" if self.doctype=="Sales Invoice" else "credit_to")
jv_against_order = frappe.db.sql("""select parent, %s as against_order
from `tabJournal Voucher Detail`
where docstatus=1 and account=%s and ifnull(is_advance, 'No') = 'Yes'
and ifnull(against_sales_order, '') in (%s)
group by parent, against_sales_order""" %
("against_" + against_order_field, '%s', ', '.join(['%s']*len(order_list))),
tuple([account] + order_list), as_dict=1)
if jv_against_order:
order_jv_map = {}
for d in jv_against_order:
order_jv_map.setdefault(d.against_order, []).append(d.parent)
advance_jv_against_si = [d.journal_voucher for d in self.get(advance_table_fieldname)]
for order, jv_list in order_jv_map.items():
for jv in jv_list:
if not advance_jv_against_si or jv not in advance_jv_against_si:
frappe.throw(_("Journal Voucher {0} is linked against Order {1}, hence it must be fetched as advance in Invoice as well.")
.format(jv, order))
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
from erpnext.controllers.status_updater import get_tolerance_for
@@ -421,7 +467,6 @@ class AccountsController(TransactionBase):
max_allowed_amt = flt(ref_amt * (100 + tolerance) / 100)
if total_billed_amt - max_allowed_amt > 0.01:
reduce_by = total_billed_amt - max_allowed_amt
frappe.throw(_("Cannot overbill for Item {0} in row {0} more than {1}. To allow overbilling, please set in Stock Settings").format(item.item_code, item.idx, max_allowed_amt))
def get_company_default(self, fieldname):

View File

@@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe
from frappe import _, msgprint
from frappe.utils import flt, rounded
from erpnext.setup.utils import get_company_currency
from erpnext.accounts.party import get_party_details
@@ -255,8 +256,6 @@ class BuyingController(StockController):
rm.required_qty = required_qty
rm.conversion_factor = item.conversion_factor
rm.rate = bom_item.rate
rm.amount = required_qty * flt(bom_item.rate)
rm.idx = rm_supplied_idx
if self.doctype == "Purchase Receipt":
@@ -267,7 +266,25 @@ class BuyingController(StockController):
rm_supplied_idx += 1
raw_materials_cost += required_qty * flt(bom_item.rate)
# get raw materials rate
if self.doctype == "Purchase Receipt":
from erpnext.stock.utils import get_incoming_rate
rm.rate = get_incoming_rate({
"item_code": bom_item.item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1 * required_qty,
"serial_no": rm.serial_no
})
if not rm.rate:
from erpnext.stock.stock_ledger import get_valuation_rate
rm.rate = get_valuation_rate(bom_item.item_code, self.supplier_warehouse)
else:
rm.rate = bom_item.rate
rm.amount = required_qty * flt(rm.rate)
raw_materials_cost += flt(rm.amount)
if self.doctype == "Purchase Receipt":
item.rm_supp_cost = raw_materials_cost

View File

@@ -2,15 +2,28 @@ from __future__ import unicode_literals
import frappe
import frappe.utils
import frappe.defaults
from frappe.utils import cint, cstr, getdate, nowdate, get_first_day, get_last_day
from frappe.utils import add_days, cint, cstr, date_diff, flt, getdate, nowdate, \
get_first_day, get_last_day, comma_and
from frappe.model.naming import make_autoname
from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date, get_party_details
from frappe.model.mapper import get_mapped_doc
month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
date_field_map = {
"Sales Order": "transaction_date",
"Sales Invoice": "posting_date",
"Purchase Order": "transaction_date",
"Purchase Invoice": "posting_date"
}
def create_recurring_documents():
manage_recurring_documents("Sales Order")
manage_recurring_documents("Sales Invoice")
manage_recurring_documents("Purchase Order")
manage_recurring_documents("Purchase Invoice")
def manage_recurring_documents(doctype, next_date=None, commit=True):
"""
@@ -19,10 +32,7 @@ def manage_recurring_documents(doctype, next_date=None, commit=True):
"""
next_date = next_date or nowdate()
if doctype == "Sales Order":
date_field = "transaction_date"
elif doctype == "Sales Invoice":
date_field = "posting_date"
date_field = date_field_map[doctype]
recurring_documents = frappe.db.sql("""select name, recurring_id
from `tab{}` where ifnull(is_recurring, 0)=1
@@ -51,7 +61,8 @@ def manage_recurring_documents(doctype, next_date=None, commit=True):
frappe.db.sql("update `tab%s` \
set is_recurring = 0 where name = %s" % (doctype, '%s'),
(ref_document))
notify_errors(ref_document, doctype, ref_wrapper.customer, ref_wrapper.owner)
notify_errors(ref_document, doctype, ref_wrapper.get("customer") or ref_wrapper.get("supplier"),
ref_wrapper.owner)
frappe.db.commit()
exception_list.append(frappe.get_traceback())
@@ -118,7 +129,7 @@ def send_notification(new_rv):
"fcontent": frappe.get_print_format(new_rv.doctype, new_rv.name, as_pdf=True)
}])
def notify_errors(doc, doctype, customer, owner):
def notify_errors(doc, doctype, party, owner):
from frappe.utils.user import get_system_managers
recipients = get_system_managers(only_name=True)
@@ -127,7 +138,7 @@ def notify_errors(doc, doctype, customer, owner):
message = frappe.get_template("templates/emails/recurring_document_failed.html").render({
"type": doctype,
"name": doc,
"customer": customer
"party": party
}))
assign_task_to_owner(doc, doctype, "Recurring Invoice Failed", recipients)
@@ -155,18 +166,18 @@ def validate_recurring_document(doc):
elif not (doc.from_date and doc.to_date):
throw(_("Period From and Period To dates mandatory for recurring %s") % doc.doctype)
def convert_to_recurring(doc, autoname, posting_date):
if doc.is_recurring:
if not doc.recurring_id:
frappe.db.set(doc, "recurring_id",
make_autoname(autoname))
#
def convert_to_recurring(doc, posting_date):
if doc.is_recurring:
if not doc.recurring_id:
frappe.db.set(doc, "recurring_id", doc.name)
set_next_date(doc, posting_date)
set_next_date(doc, posting_date)
elif doc.recurring_id:
frappe.db.sql("""update `tab%s`
set is_recurring = 0
where recurring_id = %s""" % (doc.doctype, '%s'), (doc.recurring_id))
elif doc.recurring_id:
frappe.db.sql("""update `tab%s` set is_recurring = 0
where recurring_id = %s""" % (doc.doctype, '%s'), (doc.recurring_id))
#
def validate_notification_email_id(doc):
if doc.notification_email_address:

View File

@@ -8,7 +8,7 @@ from frappe import msgprint, _
import frappe.defaults
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries
from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries, process_gl_map
class StockController(AccountsController):
def make_gl_entries(self, repost_future_gle=True):
@@ -16,20 +16,20 @@ class StockController(AccountsController):
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if cint(frappe.defaults.get_global_default("auto_accounting_for_stock")):
warehouse_account = self.get_warehouse_account()
warehouse_account = get_warehouse_account()
if self.docstatus==1:
gl_entries = self.get_gl_entries(warehouse_account)
make_gl_entries(gl_entries)
if repost_future_gle:
items, warehouse_account = self.get_items_and_warehouse_accounts(warehouse_account)
update_gl_entries_after(self.posting_date, self.posting_time,
warehouse_account, items)
items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items,
warehouse_account)
def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
default_cost_center=None):
from erpnext.accounts.general_ledger import process_gl_map
if not warehouse_account:
warehouse_account = get_warehouse_account()
@@ -88,10 +88,8 @@ class StockController(AccountsController):
return details
def get_items_and_warehouse_accounts(self, warehouse_account=None):
def get_items_and_warehouses(self):
items, warehouses = [], []
if not warehouse_account:
warehouse_account = get_warehouse_account()
if hasattr(self, "fname"):
item_doclist = self.get(self.fname)
@@ -117,86 +115,17 @@ class StockController(AccountsController):
if d.get("t_warehouse") and d.t_warehouse not in warehouses:
warehouses.append(d.t_warehouse)
warehouse_account = {wh: warehouse_account[wh] for wh in warehouses
if warehouse_account.get(wh)}
return items, warehouse_account
return items, warehouses
def get_stock_ledger_details(self):
stock_ledger = {}
for sle in frappe.db.sql("""select warehouse, stock_value_difference, voucher_detail_no
for sle in frappe.db.sql("""select warehouse, stock_value_difference,
voucher_detail_no, item_code, posting_date, actual_qty
from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s""",
(self.doctype, self.name), as_dict=True):
stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
return stock_ledger
def get_warehouse_account(self):
warehouse_account = dict(frappe.db.sql("""select master_name, name from tabAccount
where account_type = 'Warehouse' and ifnull(master_name, '') != ''"""))
return warehouse_account
def update_gl_entries_after(self, warehouse_account=None):
future_stock_vouchers = self.get_future_stock_vouchers()
gle = self.get_voucherwise_gl_entries(future_stock_vouchers)
if not warehouse_account:
warehouse_account = self.get_warehouse_account()
for voucher_type, voucher_no in future_stock_vouchers:
existing_gle = gle.get((voucher_type, voucher_no), [])
voucher_obj = frappe.get_doc(voucher_type, voucher_no)
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
if expected_gle:
matched = True
if existing_gle:
for entry in expected_gle:
for e in existing_gle:
if entry.account==e.account \
and entry.against_account==e.against_account\
and entry.cost_center==e.cost_center:
if entry.debit != e.debit or entry.credit != e.credit:
matched = False
break
else:
matched = False
if not matched:
self.delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(repost_future_gle=False)
else:
self.delete_gl_entries(voucher_type, voucher_no)
def get_future_stock_vouchers(self):
condition = ""
item_list = []
if getattr(self, "fname", None):
item_list = [d.item_code for d in self.get(self.fname)]
if item_list:
condition = "and item_code in ({})".format(", ".join(["%s"] * len(item_list)))
future_stock_vouchers = frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle
where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition}
order by timestamp(sle.posting_date, sle.posting_time) asc, name asc""".format(
condition=condition), tuple([self.posting_date, self.posting_date] + item_list),
as_list=True)
return future_stock_vouchers
def get_voucherwise_gl_entries(self, future_stock_vouchers):
gl_entries = {}
if future_stock_vouchers:
for d in frappe.db.sql("""select * from `tabGL Entry`
where posting_date >= %s and voucher_no in (%s)""" %
('%s', ', '.join(['%s']*len(future_stock_vouchers))),
tuple([self.posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1):
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
return gl_entries
def delete_gl_entries(self, voucher_type, voucher_no):
frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
def make_adjustment_entry(self, expected_gle, voucher_obj):
from erpnext.accounts.utils import get_stock_and_account_difference
account_list = [d.account for d in expected_gle]
@@ -287,15 +216,16 @@ class StockController(AccountsController):
return serialized_items
def update_gl_entries_after(posting_date, posting_time, warehouse_account=None, for_items=None):
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
warehouse_account=None):
def _delete_gl_entries(voucher_type, voucher_no):
frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
if not warehouse_account:
warehouse_account = get_warehouse_account()
future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time,
warehouse_account, for_items)
future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, for_warehouses, for_items)
gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date)
for voucher_type, voucher_no in future_stock_vouchers:
@@ -321,7 +251,7 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle):
break
return matched
def get_future_stock_vouchers(posting_date, posting_time, warehouse_account=None, for_items=None):
def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, for_items=None):
future_stock_vouchers = []
values = []
@@ -330,9 +260,9 @@ def get_future_stock_vouchers(posting_date, posting_time, warehouse_account=None
condition += " and item_code in ({})".format(", ".join(["%s"] * len(for_items)))
values += for_items
if warehouse_account:
condition += " and warehouse in ({})".format(", ".join(["%s"] * len(warehouse_account.keys())))
values += warehouse_account.keys()
if for_warehouses:
condition += " and warehouse in ({})".format(", ".join(["%s"] * len(for_warehouses)))
values += for_warehouses
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle

View File

@@ -2,12 +2,8 @@
# License: GNU General Public License v3. See license.txt
import frappe
import unittest, json, copy
from frappe.utils import flt
import frappe.permissions
from erpnext.accounts.utils import get_stock_and_account_difference
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
from erpnext.projects.doctype.time_log_batch.test_time_log_batch import *
from erpnext.controllers.recurring_document import date_field_map
def test_recurring_document(obj, test_records):
from frappe.utils import get_first_day, get_last_day, add_to_date, nowdate, getdate, add_days
@@ -27,20 +23,11 @@ def test_recurring_document(obj, test_records):
"to_date": get_last_day(today)
})
if base_doc.doctype == "Sales Order":
base_doc.update({
"transaction_date": today,
"delivery_date": add_days(today, 15)
})
elif base_doc.doctype == "Sales Invoice":
base_doc.update({
"posting_date": today
})
date_field = date_field_map[base_doc.doctype]
base_doc.set(date_field, today)
if base_doc.doctype == "Sales Order":
date_field = "transaction_date"
elif base_doc.doctype == "Sales Invoice":
date_field = "posting_date"
base_doc.set("delivery_date", add_days(today, 15))
# monthly
doc1 = frappe.copy_doc(base_doc)
@@ -128,7 +115,7 @@ def _test_recurring_document(obj, base_doc, date_field, first_and_last_day):
next_date = get_next_date(base_doc.get(date_field), no_of_months,
base_doc.repeat_on_day_of_month)
manage_recurring_documents(base_doc.doctype, next_date=next_date, commit=False)
recurred_documents = frappe.db.sql("""select name from `tab%s`

View File

@@ -4,7 +4,7 @@ app_publisher = "Web Notes Technologies Pvt. Ltd. and Contributors"
app_description = "Open Source Enterprise Resource Planning for Small and Midsized Organizations"
app_icon = "icon-th"
app_color = "#e74c3c"
app_version = "4.4.0"
app_version = "4.8.0"
error_report_email = "support@erpnext.com"

View File

@@ -115,6 +115,9 @@ class BOM(Document):
return rate
def update_cost(self):
if self.docstatus == 2:
return
for d in self.get("bom_materials"):
d.rate = self.get_bom_material_detail({
'item_code': d.item_code,
@@ -122,9 +125,10 @@ class BOM(Document):
'qty': d.qty
})["rate"]
if self.docstatus in (0, 1):
if self.docstatus == 1:
self.ignore_validate_update_after_submit = True
self.save()
self.calculate_cost()
self.save()
def get_bom_unitcost(self, bom_no):
bom = frappe.db.sql("""select name, total_variable_cost/quantity as unit_cost from `tabBOM`
@@ -269,29 +273,27 @@ class BOM(Document):
"""Calculate bom totals"""
self.calculate_op_cost()
self.calculate_rm_cost()
self.calculate_fixed_cost()
self.total_variable_cost = self.raw_material_cost + self.operating_cost
self.total_cost = self.total_variable_cost + self.total_fixed_cost
def calculate_op_cost(self):
"""Update workstation rate and calculates totals"""
total_op_cost = 0
total_op_cost, fixed_cost = 0, 0
for d in self.get('bom_operations'):
if d.workstation and not d.hour_rate:
d.hour_rate = frappe.db.get_value("Workstation", d.workstation, "hour_rate")
if d.workstation:
w = frappe.db.get_value("Workstation", d.workstation, ["hour_rate", "fixed_cycle_cost"])
if not d.hour_rate:
d.hour_rate = flt(w[0])
fixed_cost += flt(w[1])
if d.hour_rate and d.time_in_mins:
d.operating_cost = flt(d.hour_rate) * flt(d.time_in_mins) / 60.0
total_op_cost += flt(d.operating_cost)
self.operating_cost = total_op_cost
def calculate_fixed_cost(self):
"""Update workstation rate and calculates totals"""
fixed_cost = 0
for d in self.get('bom_operations'):
if d.workstation:
fixed_cost += flt(frappe.db.get_value("Workstation", d.workstation, "fixed_cycle_cost"))
self.total_fixed_cost = fixed_cost
def calculate_rm_cost(self):
"""Fetch RM rate as per today's valuation rate and calculate totals"""
total_rm_cost = 0

View File

@@ -96,7 +96,7 @@ cur_frm.cscript['Transfer Raw Materials'] = function() {
}
cur_frm.cscript['Update Finished Goods'] = function() {
cur_frm.cscript.make_se('Manufacture/Repack');
cur_frm.cscript.make_se('Manufacture');
}
cur_frm.fields_dict['production_item'].get_query = function(doc) {

View File

@@ -109,15 +109,15 @@
"permlevel": 0
},
{
"depends_on": "eval:doc.docstatus==1",
"description": "Automatically updated via Stock Entry of type Manufacture/Repack",
"fieldname": "produced_qty",
"fieldtype": "Float",
"label": "Manufactured Qty",
"no_copy": 1,
"oldfieldname": "produced_qty",
"oldfieldtype": "Currency",
"permlevel": 0,
"depends_on": "eval:doc.docstatus==1",
"description": "Automatically updated via Stock Entry of type Manufacture or Repack",
"fieldname": "produced_qty",
"fieldtype": "Float",
"label": "Manufactured Qty",
"no_copy": 1,
"oldfieldname": "produced_qty",
"oldfieldtype": "Currency",
"permlevel": 0,
"read_only": 1
},
{

View File

@@ -103,7 +103,7 @@ class ProductionOrder(Document):
status = "Submitted"
if stock_entries:
status = "In Process"
produced_qty = stock_entries.get("Manufacture/Repack")
produced_qty = stock_entries.get("Manufacture")
if flt(produced_qty) == flt(self.qty):
status = "Completed"
@@ -113,7 +113,7 @@ class ProductionOrder(Document):
def update_produced_qty(self):
produced_qty = frappe.db.sql("""select sum(fg_completed_qty)
from `tabStock Entry` where production_order=%s and docstatus=1
and purpose='Manufacture/Repack'""", self.name)
and purpose='Manufacture'""", self.name)
produced_qty = flt(produced_qty[0][0]) if produced_qty else 0
if produced_qty > self.qty:

View File

@@ -31,7 +31,7 @@ class TestProductionOrder(unittest.TestCase):
s.submit()
# from wip to fg
s = frappe.get_doc(make_stock_entry(pro_doc.name, "Manufacture/Repack", 4))
s = frappe.get_doc(make_stock_entry(pro_doc.name, "Manufacture", 4))
s.insert()
s.submit()
@@ -49,7 +49,7 @@ class TestProductionOrder(unittest.TestCase):
test_stock_entry.make_stock_entry("_Test Item", None, "_Test Warehouse - _TC", 100, 100)
test_stock_entry.make_stock_entry("_Test Item Home Desktop 100", None, "_Test Warehouse - _TC", 100, 100)
s = frappe.get_doc(make_stock_entry(pro_doc.name, "Manufacture/Repack", 7))
s = frappe.get_doc(make_stock_entry(pro_doc.name, "Manufacture", 7))
s.insert()
self.assertRaises(StockOverProductionError, s.submit)

View File

@@ -6,12 +6,12 @@
"doctype": "Report",
"idx": 1,
"is_standard": "Yes",
"modified": "2014-06-03 07:18:17.082436",
"modified": "2014-09-17 12:41:55.740299",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Issued Items Against Production Order",
"owner": "Administrator",
"query": "select\n ste.production_order as \"Production Order:Link/Production Order:120\",\n ste.posting_date as \"Issue Date:Date:140\",\n ste_item.item_code as \"Item Code:Link/Item:120\",\n\tste_item.description as \"Description::150\",\n\tste_item.transfer_qty as \"Qty:Float:100\",\n\tste_item.stock_uom as \"UOM:Link/UOM:80\",\n\tste_item.amount as \"Amount:Currency:120\",\n\tste_item.serial_no as \"Serial No:Link/Serial No:80\",\n\tste_item.s_warehouse as \"Source Warehouse:Link/Warehouse:120\",\n\tste_item.t_warehouse as \"Target Warehouse:Link/Warehouse:120\",\n\tpro.production_item as \"Finished Goods:Link/Item:120\", \n\tste.name as \"Stock Entry:Link/Stock Entry:120\"\nfrom\n\t`tabStock Entry` ste, `tabStock Entry Detail` ste_item, `tabProduction Order` pro\nwhere\n\tifnull(ste.production_order, '') != '' and ste.name = ste_item.parent \n\tand ste.production_order = pro.name and ste.docstatus = 1 \n\tand ste.purpose = 'Manufacture/Repack'\norder by ste.posting_date, ste.production_order, ste_item.item_code",
"query": "select\n ste.production_order as \"Production Order:Link/Production Order:120\",\n ste.posting_date as \"Issue Date:Date:140\",\n ste_item.item_code as \"Item Code:Link/Item:120\",\n\tste_item.description as \"Description::150\",\n\tste_item.transfer_qty as \"Qty:Float:100\",\n\tste_item.stock_uom as \"UOM:Link/UOM:80\",\n\tste_item.amount as \"Amount:Currency:120\",\n\tste_item.serial_no as \"Serial No:Link/Serial No:80\",\n\tste_item.s_warehouse as \"Source Warehouse:Link/Warehouse:120\",\n\tste_item.t_warehouse as \"Target Warehouse:Link/Warehouse:120\",\n\tpro.production_item as \"Finished Goods:Link/Item:120\", \n\tste.name as \"Stock Entry:Link/Stock Entry:120\"\nfrom\n\t`tabStock Entry` ste, `tabStock Entry Detail` ste_item, `tabProduction Order` pro\nwhere\n\tifnull(ste.production_order, '') != '' and ste.name = ste_item.parent \n\tand ste.production_order = pro.name and ste.docstatus = 1 \n\tand ste.purpose = 'Manufacture' or 'Repack'\norder by ste.posting_date, ste.production_order, ste_item.item_code",
"ref_doctype": "Production Order",
"report_name": "Issued Items Against Production Order",
"report_type": "Query Report"

View File

@@ -80,4 +80,10 @@ execute:frappe.delete_doc("DocType", "Landed Cost Wizard")
erpnext.patches.v4_2.default_website_style
erpnext.patches.v4_2.set_company_country
erpnext.patches.v4_2.update_sales_order_invoice_field_name
erpnext.patches.v4_2.cost_of_production_cycle
erpnext.patches.v4_2.cost_of_production_cycle
erpnext.patches.v4_2.seprate_manufacture_and_repack
execute:frappe.delete_doc("Report", "Warehouse-Wise Stock Balance")
execute:frappe.delete_doc("DocType", "Purchase Request")
execute:frappe.delete_doc("DocType", "Purchase Request Item")
erpnext.patches.v4_2.recalculate_bom_cost
erpnext.patches.v4_2.fix_gl_entries_for_stock_transactions

View File

@@ -2,6 +2,7 @@ import frappe
from frappe.templates.pages.style_settings import default_properties
def execute():
frappe.reload_doc('website', 'doctype', 'style_settings')
style_settings = frappe.get_doc("Style Settings", "Style Settings")
if not style_settings.apply_style:
style_settings.update(default_properties)

View File

@@ -0,0 +1,52 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import flt
def execute():
from erpnext.utilities.repost_stock import repost
repost(allow_zero_rate=True, only_actual=True)
warehouse_account = frappe.db.sql("""select name, master_name from tabAccount
where ifnull(account_type, '') = 'Warehouse'""")
if warehouse_account:
warehouses = [d[1] for d in warehouse_account]
accounts = [d[0] for d in warehouse_account]
stock_vouchers = frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle
where sle.warehouse in (%s)
order by sle.posting_date""" %
', '.join(['%s']*len(warehouses)), tuple(warehouses))
rejected = []
for voucher_type, voucher_no in stock_vouchers:
stock_bal = frappe.db.sql("""select sum(stock_value_difference) from `tabStock Ledger Entry`
where voucher_type=%s and voucher_no =%s and warehouse in (%s)""" %
('%s', '%s', ', '.join(['%s']*len(warehouses))), tuple([voucher_type, voucher_no] + warehouses))
account_bal = frappe.db.sql("""select ifnull(sum(ifnull(debit, 0) - ifnull(credit, 0)), 0)
from `tabGL Entry`
where voucher_type=%s and voucher_no =%s and account in (%s)
group by voucher_type, voucher_no""" %
('%s', '%s', ', '.join(['%s']*len(accounts))), tuple([voucher_type, voucher_no] + accounts))
if stock_bal and account_bal and abs(flt(stock_bal[0][0]) - flt(account_bal[0][0])) > 0.1:
try:
print voucher_type, voucher_no, stock_bal[0][0], account_bal[0][0]
frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
voucher = frappe.get_doc(voucher_type, voucher_no)
voucher.make_gl_entries(repost_future_gle=False)
frappe.db.commit()
except Exception, e:
print frappe.get_traceback()
rejected.append([voucher_type, voucher_no])
frappe.db.rollback()
print "Failed to repost: "
print rejected

View File

@@ -0,0 +1,16 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
for d in frappe.db.sql("select name from `tabBOM` where docstatus < 2"):
try:
document = frappe.get_doc('BOM', d[0])
if document.docstatus == 1:
document.ignore_validate_update_after_submit = True
document.calculate_cost()
document.save()
except:
pass

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.db.sql("""update `tabStock Entry` set purpose='Manufacture' where purpose='Manufacture/Repack' and ifnull(production_order,"")!="" """)
frappe.db.sql("""update `tabStock Entry` set purpose='Repack' where purpose='Manufacture/Repack' and ifnull(production_order,"")="" """)

View File

@@ -6,17 +6,17 @@ frappe.views.calendar["Task"] = {
"start": "exp_start_date",
"end": "exp_end_date",
"id": "name",
"title": __("subject"),
"title": "subject",
"allDay": "allDay"
},
gantt: true,
filters: [
{
"fieldtype": "Link",
"fieldname": "project",
"options": "Project",
"fieldtype": "Link",
"fieldname": "project",
"options": "Project",
"label": __("Project")
}
],
get_events_method: "erpnext.projects.doctype.task.task.get_events"
}
}

View File

@@ -24,7 +24,7 @@ var get_filters = function(){
{ "value": "Item Group", "label": __("Item Group") },
{ "value": "Supplier", "label": __("Supplier") },
{ "value": "Supplier Type", "label": __("Supplier Type") },
{ "value": "Supplier Type", "label": __("Project") }
{ "value": "Project", "label": __("Project") }
],
"default": "Item"
},

View File

@@ -25,7 +25,7 @@ var get_filters = function(){
{ "value": "Customer", "label": __("Customer") },
{ "value": "Customer Group", "label": __("Customer Group") },
{ "value": "Territory", "label": __("Territory") },
{ "value": "Supplier Type", "label": __("Project") }
{ "value": "Project", "label": __("Project") }
],
"default": "Item"
},

View File

@@ -138,9 +138,20 @@ erpnext.StockAnalytics = erpnext.StockGridReport.extend({
item.valuation_method : sys_defaults.valuation_method;
var is_fifo = valuation_method == "FIFO";
var diff = me.get_value_diff(wh, sl, is_fifo);
if(sl.voucher_type=="Stock Reconciliation") {
var diff = (sl.qty_after_transaction * sl.valuation_rate) - item.closing_qty_value;
wh.fifo_stack.push([sl.qty_after_transaction, sl.valuation_rate, sl.posting_date]);
wh.balance_qty = sl.qty_after_transaction;
wh.balance_value = sl.valuation_rate * sl.qty_after_transaction;
} else {
var diff = me.get_value_diff(wh, sl, is_fifo);
}
} else {
var diff = sl.qty;
if(sl.voucher_type=="Stock Reconciliation") {
var diff = sl.qty_after_transaction - item.closing_qty_value;
} else {
var diff = sl.qty;
}
}
if(posting_datetime < from_date) {
@@ -150,6 +161,8 @@ erpnext.StockAnalytics = erpnext.StockGridReport.extend({
} else {
break;
}
item.closing_qty_value += diff;
}
}
},

View File

@@ -9,8 +9,8 @@ erpnext.StockGridReport = frappe.views.TreeGridReport.extend({
};
return this.item_warehouse[item][warehouse];
},
get_value_diff: function(wh, sl, is_fifo) {
get_value_diff: function(wh, sl, is_fifo) {
// value
if(sl.qty > 0) {
// incoming - rate is given
@@ -30,9 +30,9 @@ erpnext.StockGridReport = frappe.views.TreeGridReport.extend({
} else {
var value_diff = (rate * add_qty);
}
if(add_qty)
wh.fifo_stack.push([add_qty, sl.incoming_rate, sl.posting_date]);
wh.fifo_stack.push([add_qty, sl.incoming_rate, sl.posting_date]);
} else {
// called everytime for maintaining fifo stack
var fifo_value_diff = this.get_fifo_value_diff(wh, sl);
@@ -44,13 +44,13 @@ erpnext.StockGridReport = frappe.views.TreeGridReport.extend({
var value_diff = fifo_value_diff;
} else {
// average rate for weighted average
var rate = (wh.balance_qty.toFixed(2) == 0.00 ? 0 :
var rate = (wh.balance_qty.toFixed(2) == 0.00 ? 0 :
flt(wh.balance_value) / flt(wh.balance_qty));
// no change in value if negative qty
if((wh.balance_qty + sl.qty).toFixed(2) >= 0.00)
var value_diff = (rate * sl.qty);
else
else
var value_diff = -wh.balance_value;
}
}
@@ -58,7 +58,6 @@ erpnext.StockGridReport = frappe.views.TreeGridReport.extend({
// update balance (only needed in case of valuation)
wh.balance_qty += sl.qty;
wh.balance_value += value_diff;
return value_diff;
},
get_fifo_value_diff: function(wh, sl) {
@@ -66,19 +65,19 @@ erpnext.StockGridReport = frappe.views.TreeGridReport.extend({
var fifo_stack = (wh.fifo_stack || []).reverse();
var fifo_value_diff = 0.0;
var qty = -sl.qty;
for(var i=0, j=fifo_stack.length; i<j; i++) {
var batch = fifo_stack.pop();
if(batch[0] >= qty) {
batch[0] = batch[0] - qty;
fifo_value_diff += (qty * batch[1]);
qty = 0.0;
if(batch[0]) {
// batch still has qty put it back
fifo_stack.push(batch);
}
// all qty found
break;
} else {
@@ -87,35 +86,34 @@ erpnext.StockGridReport = frappe.views.TreeGridReport.extend({
qty = qty - batch[0];
}
}
// reset the updated stack
wh.fifo_stack = fifo_stack.reverse();
return -fifo_value_diff;
},
get_serialized_value_diff: function(sl) {
var me = this;
var value_diff = 0.0;
$.each(sl.serial_no.trim().split("\n"), function(i, sr) {
if(sr) {
value_diff += flt(me.serialized_buying_rates[sr.trim().toLowerCase()]);
}
});
return value_diff;
},
get_serialized_buying_rates: function() {
var serialized_buying_rates = {};
if (frappe.report_dump.data["Serial No"]) {
$.each(frappe.report_dump.data["Serial No"], function(i, sn) {
serialized_buying_rates[sn.name.toLowerCase()] = flt(sn.incoming_rate);
});
}
return serialized_buying_rates;
},
});
});

View File

@@ -155,7 +155,7 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({
project_name: item.project_name || me.frm.doc.project_name
}
},
callback: function(r) {
if(!r.exc) {
me.frm.script_manager.trigger("price_list_rate", cdt, cdn);
@@ -827,4 +827,35 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({
.appendTo($(this.frm.fields_dict.other_charges_calculation.wrapper).empty());
}
},
is_recurring: function() {
// set default values for recurring documents
if(this.frm.doc.is_recurring) {
var owner_email = this.frm.doc.owner=="Administrator"
? frappe.user_info("Administrator").email
: this.frm.doc.owner;
this.frm.doc.notification_email_address = $.map([cstr(owner_email),
cstr(this.frm.doc.contact_email)], function(v) { return v || null; }).join(", ");
this.frm.doc.repeat_on_day_of_month = frappe.datetime.str_to_obj(this.frm.doc.posting_date).getDate();
}
refresh_many(["notification_email_address", "repeat_on_day_of_month"]);
},
from_date: function() {
// set to_date
if(this.frm.doc.from_date) {
var recurring_type_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6,
'Yearly': 12};
var months = recurring_type_map[this.frm.doc.recurring_type];
if(months) {
var to_date = frappe.datetime.add_months(this.frm.doc.from_date,
months);
this.frm.doc.to_date = frappe.datetime.add_days(to_date, -1);
refresh_field('to_date');
}
}
}
});

View File

@@ -195,37 +195,6 @@ cur_frm.cscript.on_submit = function(doc, cdt, cdn) {
}
};
cur_frm.cscript.is_recurring = function(doc, dt, dn) {
// set default values for recurring orders
if(doc.is_recurring) {
var owner_email = doc.owner=="Administrator"
? frappe.user_info("Administrator").email
: doc.owner;
doc.notification_email_address = $.map([cstr(owner_email),
cstr(doc.contact_email)], function(v) { return v || null; }).join(", ");
doc.repeat_on_day_of_month = frappe.datetime.str_to_obj(doc.posting_date).getDate();
}
refresh_many(["notification_email_address", "repeat_on_day_of_month"]);
}
cur_frm.cscript.from_date = function(doc, dt, dn) {
// set to_date
if(doc.from_date) {
var recurring_type_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6,
'Yearly': 12};
var months = recurring_type_map[doc.recurring_type];
if(months) {
var to_date = frappe.datetime.add_months(doc.from_date,
months);
doc.to_date = frappe.datetime.add_days(to_date, -1);
refresh_field('to_date');
}
}
}
cur_frm.cscript.send_sms = function() {
frappe.require("assets/erpnext/js/sms_manager.js");
var sms_man = new SMSManager(cur_frm.doc);

File diff suppressed because it is too large Load Diff

View File

@@ -4,14 +4,10 @@
from __future__ import unicode_literals
import frappe
import frappe.utils
from frappe.utils import cstr, flt, getdate, comma_and
from frappe import _
from frappe.model.mapper import get_mapped_doc
from erpnext.controllers.recurring_document import convert_to_recurring, validate_recurring_document
from erpnext.controllers.selling_controller import SellingController
form_grid_templates = {
@@ -122,8 +118,6 @@ class SalesOrder(SellingController):
if not self.billing_status: self.billing_status = 'Not Billed'
if not self.delivery_status: self.delivery_status = 'Not Delivered'
validate_recurring_document(self)
def validate_warehouse(self):
from erpnext.stock.utils import validate_warehouse_company
@@ -157,6 +151,8 @@ class SalesOrder(SellingController):
doc.set_status(update=True)
def on_submit(self):
super(SalesOrder, self).on_submit()
self.update_stock_ledger(update_stock = 1)
self.check_credit(self.grand_total)
@@ -165,8 +161,6 @@ class SalesOrder(SellingController):
self.update_prevdoc_status('submit')
frappe.db.set(self, 'status', 'Submitted')
convert_to_recurring(self, "SO/REC/.#####", self.transaction_date)
def on_cancel(self):
# Cannot cancel stopped SO
@@ -255,11 +249,6 @@ class SalesOrder(SellingController):
def get_portal_page(self):
return "order" if self.docstatus==1 else None
def on_update_after_submit(self):
validate_recurring_document(self)
convert_to_recurring(self, "SO/REC/.#####", self.transaction_date)
@frappe.whitelist()
def make_material_request(source_name, target_doc=None):
def postprocess(source, doc):

View File

@@ -3,19 +3,11 @@
from __future__ import unicode_literals
import frappe
from frappe.utils import cstr, flt, has_common, make_esc, comma_or
from frappe.utils import cstr, flt, has_common, comma_or
from frappe import session, _
from erpnext.utilities.transaction_base import TransactionBase
class AuthorizationControl(TransactionBase):
# Get Names of all Approving Users and Roles
# -------------------------------------------
def get_appr_user_role(self, det, doctype_name, total, based_on, condition, item, company):
amt_list, appr_users, appr_roles = [], [], []
users, roles = '',''
@@ -24,10 +16,18 @@ class AuthorizationControl(TransactionBase):
amt_list.append(flt(x[0]))
max_amount = max(amt_list)
app_dtl = frappe.db.sql("select approving_user, approving_role from `tabAuthorization Rule` where transaction = %s and (value = %s or value > %s) and docstatus != 2 and based_on = %s and company = %s %s" % ('%s', '%s', '%s', '%s', '%s', condition), (doctype_name, flt(max_amount), total, based_on, company))
app_dtl = frappe.db.sql("""select approving_user, approving_role from `tabAuthorization Rule`
where transaction = %s and (value = %s or value > %s)
and docstatus != 2 and based_on = %s and company = %s %s""" %
('%s', '%s', '%s', '%s', '%s', condition),
(doctype_name, flt(max_amount), total, based_on, company))
if not app_dtl:
app_dtl = frappe.db.sql("select approving_user, approving_role from `tabAuthorization Rule` where transaction = %s and (value = %s or value > %s) and docstatus != 2 and based_on = %s and ifnull(company,'') = '' %s" % ('%s', '%s', '%s', '%s', condition), (doctype_name, flt(max_amount), total, based_on))
app_dtl = frappe.db.sql("""select approving_user, approving_role from `tabAuthorization Rule`
where transaction = %s and (value = %s or value > %s) and docstatus != 2
and based_on = %s and ifnull(company,'') = '' %s""" %
('%s', '%s', '%s', '%s', condition), (doctype_name, flt(max_amount), total, based_on))
for d in app_dtl:
if(d[0]): appr_users.append(d[0])
if(d[1]): appr_roles.append(d[1])
@@ -36,43 +36,56 @@ class AuthorizationControl(TransactionBase):
frappe.msgprint(_("Not authroized since {0} exceeds limits").format(_(based_on)))
frappe.throw(_("Can be approved by {0}").format(comma_or(appr_roles + appr_users)))
# Check if authorization rule is set specific to user
# ----------------------------------------------------
def validate_auth_rule(self, doctype_name, total, based_on, cond, company, item = ''):
chk = 1
add_cond1,add_cond2 = '',''
if based_on == 'Itemwise Discount':
add_cond1 += " and master_name = '"+cstr(item)+"'"
itemwise_exists = frappe.db.sql("select value from `tabAuthorization Rule` where transaction = %s and value <= %s and based_on = %s and company = %s and docstatus != 2 %s %s" % ('%s', '%s', '%s', '%s', cond, add_cond1), (doctype_name, total, based_on, company))
add_cond1 += " and master_name = '"+cstr(item).replace("'", "\\'")+"'"
itemwise_exists = frappe.db.sql("""select value from `tabAuthorization Rule`
where transaction = %s and value <= %s
and based_on = %s and company = %s and docstatus != 2 %s %s""" %
('%s', '%s', '%s', '%s', cond, add_cond1), (doctype_name, total, based_on, company))
if not itemwise_exists:
itemwise_exists = frappe.db.sql("select value from `tabAuthorization Rule` where transaction = %s and value <= %s and based_on = %s and ifnull(company,'') = '' and docstatus != 2 %s %s" % ('%s', '%s', '%s', cond, add_cond1), (doctype_name, total, based_on))
itemwise_exists = frappe.db.sql("""select value from `tabAuthorization Rule`
where transaction = %s and value <= %s and based_on = %s
and ifnull(company,'') = '' and docstatus != 2 %s %s""" %
('%s', '%s', '%s', cond, add_cond1), (doctype_name, total, based_on))
if itemwise_exists:
self.get_appr_user_role(itemwise_exists, doctype_name, total, based_on, cond+add_cond1, item,company)
chk = 0
if chk == 1:
if based_on == 'Itemwise Discount': add_cond2 += " and ifnull(master_name,'') = ''"
appr = frappe.db.sql("select value from `tabAuthorization Rule` where transaction = %s and value <= %s and based_on = %s and company = %s and docstatus != 2 %s %s" % ('%s', '%s', '%s', '%s', cond, add_cond2), (doctype_name, total, based_on, company))
if based_on == 'Itemwise Discount':
add_cond2 += " and ifnull(master_name,'') = ''"
appr = frappe.db.sql("""select value from `tabAuthorization Rule`
where transaction = %s and value <= %s and based_on = %s
and company = %s and docstatus != 2 %s %s""" %
('%s', '%s', '%s', '%s', cond, add_cond2), (doctype_name, total, based_on, company))
if not appr:
appr = frappe.db.sql("select value from `tabAuthorization Rule` where transaction = %s and value <= %s and based_on = %s and ifnull(company,'') = '' and docstatus != 2 %s %s"% ('%s', '%s', '%s', cond, add_cond2), (doctype_name, total, based_on))
appr = frappe.db.sql("""select value from `tabAuthorization Rule`
where transaction = %s and value <= %s and based_on = %s
and ifnull(company,'') = '' and docstatus != 2 %s %s""" %
('%s', '%s', '%s', cond, add_cond2), (doctype_name, total, based_on))
self.get_appr_user_role(appr, doctype_name, total, based_on, cond+add_cond2, item, company)
# Bifurcate Authorization based on type
# --------------------------------------
def bifurcate_based_on_type(self, doctype_name, total, av_dis, based_on, doc_obj, val, company):
add_cond = ''
auth_value = av_dis
if val == 1: add_cond += " and system_user = '"+session['user']+"'"
if val == 1: add_cond += " and system_user = '"+session['user'].replace("'", "\\'")+"'"
elif val == 2: add_cond += " and system_role IN %s" % ("('"+"','".join(frappe.user.get_roles())+"')")
else: add_cond += " and ifnull(system_user,'') = '' and ifnull(system_role,'') = ''"
if based_on == 'Grand Total': auth_value = total
elif based_on == 'Customerwise Discount':
if doc_obj:
if doc_obj.doctype == 'Sales Invoice': customer = doc_obj.customer
else: customer = doc_obj.customer_name
add_cond = " and master_name = '"+make_esc("'")(cstr(customer))+"'"
add_cond = " and master_name = '"+cstr(customer).replace("'", "\\'")+"'"
if based_on == 'Itemwise Discount':
if doc_obj:
for t in doc_obj.get(doc_obj.fname):
@@ -80,9 +93,6 @@ class AuthorizationControl(TransactionBase):
else:
self.validate_auth_rule(doctype_name, auth_value, based_on, add_cond, company)
# Check Approving Authority for transactions other than expense voucher and Appraisal
# -------------------------
def validate_approving_authority(self, doctype_name,company, total, doc_obj = ''):
av_dis = 0
if doc_obj:
@@ -94,11 +104,12 @@ class AuthorizationControl(TransactionBase):
if price_list_rate: av_dis = 100 - flt(base_rate * 100 / price_list_rate)
final_based_on = ['Grand Total','Average Discount','Customerwise Discount','Itemwise Discount']
# Individual User
# ================
# Check for authorization set for individual user
based_on = [x[0] for x in frappe.db.sql("select distinct based_on from `tabAuthorization Rule` where transaction = %s and system_user = %s and (company = %s or ifnull(company,'')='') and docstatus != 2", (doctype_name, session['user'], company))]
# Check for authorization set for individual user
based_on = [x[0] for x in frappe.db.sql("""select distinct based_on from `tabAuthorization Rule`
where transaction = %s and system_user = %s
and (company = %s or ifnull(company,'')='') and docstatus != 2""",
(doctype_name, session['user'], company))]
for d in based_on:
self.bifurcate_based_on_type(doctype_name, total, av_dis, d, doc_obj, 1, company)
@@ -107,8 +118,6 @@ class AuthorizationControl(TransactionBase):
for r in based_on:
if r in final_based_on and r != 'Itemwise Discount': final_based_on.remove(r)
# Specific Role
# ===============
# Check for authorization set on particular roles
based_on = [x[0] for x in frappe.db.sql("""select based_on
from `tabAuthorization Rule`
@@ -124,19 +133,24 @@ class AuthorizationControl(TransactionBase):
for r in based_on:
if r in final_based_on and r != 'Itemwise Discount': final_based_on.remove(r)
# Global Rule
# =============
# Check for global authorization
for g in final_based_on:
self.bifurcate_based_on_type(doctype_name, total, av_dis, g, doc_obj, 0, company)
#========================================================================================================================
# payroll related check
def get_value_based_rule(self,doctype_name,employee,total_claimed_amount,company):
val_lst =[]
val = frappe.db.sql("select value from `tabAuthorization Rule` where transaction=%s and (to_emp=%s or to_designation IN (select designation from `tabEmployee` where name=%s)) and ifnull(value,0)< %s and company = %s and docstatus!=2",(doctype_name,employee,employee,total_claimed_amount,company))
val = frappe.db.sql("""select value from `tabAuthorization Rule`
where transaction=%s and (to_emp=%s or
to_designation IN (select designation from `tabEmployee` where name=%s))
and ifnull(value,0)< %s and company = %s and docstatus!=2""",
(doctype_name,employee,employee,total_claimed_amount,company))
if not val:
val = frappe.db.sql("select value from `tabAuthorization Rule` where transaction=%s and (to_emp=%s or to_designation IN (select designation from `tabEmployee` where name=%s)) and ifnull(value,0)< %s and ifnull(company,'') = '' and docstatus!=2",(doctype_name, employee, employee, total_claimed_amount))
val = frappe.db.sql("""select value from `tabAuthorization Rule`
where transaction=%s and (to_emp=%s or
to_designation IN (select designation from `tabEmployee` where name=%s))
and ifnull(value,0)< %s and ifnull(company,'') = '' and docstatus!=2""",
(doctype_name, employee, employee, total_claimed_amount))
if val:
val_lst = [y[0] for y in val]
@@ -144,13 +158,23 @@ class AuthorizationControl(TransactionBase):
val_lst.append(0)
max_val = max(val_lst)
rule = frappe.db.sql("select name, to_emp, to_designation, approving_role, approving_user from `tabAuthorization Rule` where transaction=%s and company = %s and (to_emp=%s or to_designation IN (select designation from `tabEmployee` where name=%s)) and ifnull(value,0)= %s and docstatus!=2",(doctype_name,company,employee,employee,flt(max_val)), as_dict=1)
rule = frappe.db.sql("""select name, to_emp, to_designation, approving_role, approving_user
from `tabAuthorization Rule`
where transaction=%s and company = %s
and (to_emp=%s or to_designation IN (select designation from `tabEmployee` where name=%s))
and ifnull(value,0)= %s and docstatus!=2""",
(doctype_name,company,employee,employee,flt(max_val)), as_dict=1)
if not rule:
rule = frappe.db.sql("select name, to_emp, to_designation, approving_role, approving_user from `tabAuthorization Rule` where transaction=%s and ifnull(company,'') = '' and (to_emp=%s or to_designation IN (select designation from `tabEmployee` where name=%s)) and ifnull(value,0)= %s and docstatus!=2",(doctype_name,employee,employee,flt(max_val)), as_dict=1)
rule = frappe.db.sql("""select name, to_emp, to_designation, approving_role, approving_user
from `tabAuthorization Rule`
where transaction=%s and ifnull(company,'') = ''
and (to_emp=%s or to_designation IN (select designation from `tabEmployee` where name=%s))
and ifnull(value,0)= %s and docstatus!=2""",
(doctype_name,employee,employee,flt(max_val)), as_dict=1)
return rule
#---------------------------------------------------------------------------------------------------------------------
# related to payroll module only
def get_approver_name(self, doctype_name, total, doc_obj=''):
app_user=[]
@@ -159,11 +183,22 @@ class AuthorizationControl(TransactionBase):
if doc_obj:
if doctype_name == 'Expense Claim':
rule = self.get_value_based_rule(doctype_name,doc_obj.employee,doc_obj.total_claimed_amount, doc_obj.company)
rule = self.get_value_based_rule(doctype_name, doc_obj.employee,
doc_obj.total_claimed_amount, doc_obj.company)
elif doctype_name == 'Appraisal':
rule = frappe.db.sql("select name, to_emp, to_designation, approving_role, approving_user from `tabAuthorization Rule` where transaction=%s and (to_emp=%s or to_designation IN (select designation from `tabEmployee` where name=%s)) and company = %s and docstatus!=2",(doctype_name,doc_obj.employee, doc_obj.employee, doc_obj.company),as_dict=1)
rule = frappe.db.sql("""select name, to_emp, to_designation, approving_role, approving_user
from `tabAuthorization Rule` where transaction=%s
and (to_emp=%s or to_designation IN (select designation from `tabEmployee` where name=%s))
and company = %s and docstatus!=2""",
(doctype_name,doc_obj.employee, doc_obj.employee, doc_obj.company),as_dict=1)
if not rule:
rule = frappe.db.sql("select name, to_emp, to_designation, approving_role, approving_user from `tabAuthorization Rule` where transaction=%s and (to_emp=%s or to_designation IN (select designation from `tabEmployee` where name=%s)) and ifnull(company,'') = '' and docstatus!=2",(doctype_name,doc_obj.employee, doc_obj.employee),as_dict=1)
rule = frappe.db.sql("""select name, to_emp, to_designation, approving_role, approving_user
from `tabAuthorization Rule`
where transaction=%s and (to_emp=%s or
to_designation IN (select designation from `tabEmployee` where name=%s))
and ifnull(company,'') = '' and docstatus!=2""",
(doctype_name,doc_obj.employee, doc_obj.employee), as_dict=1)
if rule:
for m in rule:
@@ -171,7 +206,11 @@ class AuthorizationControl(TransactionBase):
if m['approving_user']:
app_specific_user.append(m['approving_user'])
elif m['approving_role']:
user_lst = [z[0] for z in frappe.db.sql("select distinct t1.name from `tabUser` t1, `tabUserRole` t2 where t2.role=%s and t2.parent=t1.name and t1.name !='Administrator' and t1.name != 'Guest' and t1.docstatus !=2",m['approving_role'])]
user_lst = [z[0] for z in frappe.db.sql("""select distinct t1.name
from `tabUser` t1, `tabUserRole` t2 where t2.role=%s
and t2.parent=t1.name and t1.name !='Administrator'
and t1.name != 'Guest' and t1.docstatus !=2""", m['approving_role'])]
for x in user_lst:
if not x in app_user:
app_user.append(x)

View File

@@ -48,11 +48,10 @@ class NamingSeries(Document):
# validate names
for i in options: self.validate_series_name(i)
if self.user_must_always_select:
if options and self.user_must_always_select:
options = [''] + options
default = ''
else:
default = options[0]
default = options[0] if options else ''
# update in property setter
prop_dict = {'options': "\n".join(options), 'default': default}

View File

@@ -71,16 +71,17 @@ def setup_account(args=None):
frappe.db.set_default('desktop:home_page', 'desktop')
website_maker(args.company_name, args.company_tagline, args.name)
website_maker(args.company_name.strip(), args.company_tagline, args.name)
create_logo(args)
frappe.clear_cache()
frappe.db.commit()
except:
traceback = frappe.get_traceback()
for hook in frappe.get_hooks("setup_wizard_exception"):
frappe.get_attr(hook)(traceback, args)
if args:
traceback = frappe.get_traceback()
for hook in frappe.get_hooks("setup_wizard_exception"):
frappe.get_attr(hook)(traceback, args)
raise
@@ -134,7 +135,7 @@ def create_fiscal_year_and_company(args):
frappe.get_doc({
"doctype":"Company",
'domain': args.get("industry"),
'company_name':args.get('company_name'),
'company_name':args.get('company_name').strip(),
'abbr':args.get('company_abbr'),
'default_currency':args.get('currency'),
'country': args.get('country'),
@@ -165,7 +166,7 @@ def set_defaults(args):
global_defaults.update({
'current_fiscal_year': args.curr_fiscal_year,
'default_currency': args.get('currency'),
'default_company':args.get('company_name'),
'default_company':args.get('company_name').strip(),
"country": args.get("country"),
})
@@ -284,7 +285,7 @@ def create_taxes(args):
try:
frappe.get_doc({
"doctype":"Account",
"company": args.get("company_name"),
"company": args.get("company_name").strip(),
"parent_account": _("Duties and Taxes") + " - " + args.get("company_abbr"),
"account_name": args.get("tax_" + str(i)),
"group_or_ledger": "Ledger",
@@ -344,7 +345,7 @@ def create_customers(args):
"customer_type": "Company",
"customer_group": _("Commercial"),
"territory": args.get("country"),
"company": args.get("company_name")
"company": args.get("company_name").strip()
}).insert()
if args.get("customer_contact_" + str(i)):
@@ -364,7 +365,7 @@ def create_suppliers(args):
"doctype":"Supplier",
"supplier_name": supplier,
"supplier_type": _("Local"),
"company": args.get("company_name")
"company": args.get("company_name").strip()
}).insert()
if args.get("supplier_contact_" + str(i)):

View File

@@ -78,7 +78,8 @@ data_map = {
"Stock Ledger Entry": {
"columns": ["name", "posting_date", "posting_time", "item_code", "warehouse",
"actual_qty as qty", "voucher_type", "voucher_no", "project",
"ifnull(incoming_rate,0) as incoming_rate", "stock_uom", "serial_no"],
"ifnull(incoming_rate,0) as incoming_rate", "stock_uom", "serial_no",
"qty_after_transaction", "valuation_rate"],
"order_by": "posting_date, posting_time, name",
"links": {
"item_code": ["Item", "name"],

View File

@@ -3,8 +3,15 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
class Batch(Document):
pass
def validate(self):
self.item_has_batch_enabled()
def item_has_batch_enabled(self):
has_batch_no = frappe.db.get_value("Item",self.item,"has_batch_no")
if has_batch_no =='No':
frappe.throw(_("The selected item cannot have Batch"))

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
from frappe.exceptions import ValidationError
import unittest
class TestBatch(unittest.TestCase):
def test_item_has_batch_enabled(self):
self.assertRaises(ValidationError, frappe.get_doc({
"doctype": "Batch",
"name": "_test Batch",
"item": "_Test Item"
}).save)

View File

@@ -11,27 +11,27 @@ class Bin(Document):
def validate(self):
if self.get("__islocal") or not self.stock_uom:
self.stock_uom = frappe.db.get_value('Item', self.item_code, 'stock_uom')
self.validate_mandatory()
self.projected_qty = flt(self.actual_qty) + flt(self.ordered_qty) + \
flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty)
def validate_mandatory(self):
qf = ['actual_qty', 'reserved_qty', 'ordered_qty', 'indented_qty']
for f in qf:
if (not getattr(self, f, None)) or (not self.get(f)):
if (not getattr(self, f, None)) or (not self.get(f)):
self.set(f, 0.0)
def update_stock(self, args):
self.update_qty(args)
if args.get("actual_qty"):
if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation":
from erpnext.stock.stock_ledger import update_entries_after
if not args.get("posting_date"):
args["posting_date"] = nowdate()
# update valuation and qty after transaction for post dated entry
update_entries_after({
"item_code": self.item_code,
@@ -39,21 +39,34 @@ class Bin(Document):
"posting_date": args.get("posting_date"),
"posting_time": args.get("posting_time")
})
def update_qty(self, args):
# update the stock values (for current quantities)
self.actual_qty = flt(self.actual_qty) + flt(args.get("actual_qty"))
if args.get("voucher_type")=="Stock Reconciliation":
if args.get('is_cancelled') == 'No':
self.actual_qty = args.get("qty_after_transaction")
else:
qty_after_transaction = frappe.db.get_value("""select qty_after_transaction
from `tabStock Ledger Entry`
where item_code=%s and warehouse=%s
and not (voucher_type='Stock Reconciliation' and voucher_no=%s)
order by posting_date desc limit 1""",
(self.item_code, self.warehouse, args.get('voucher_no')))
self.actual_qty = flt(qty_after_transaction[0][0]) if qty_after_transaction else 0.0
else:
self.actual_qty = flt(self.actual_qty) + flt(args.get("actual_qty"))
self.ordered_qty = flt(self.ordered_qty) + flt(args.get("ordered_qty"))
self.reserved_qty = flt(self.reserved_qty) + flt(args.get("reserved_qty"))
self.indented_qty = flt(self.indented_qty) + flt(args.get("indented_qty"))
self.planned_qty = flt(self.planned_qty) + flt(args.get("planned_qty"))
self.projected_qty = flt(self.actual_qty) + flt(self.ordered_qty) + \
flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty)
self.save()
def get_first_sle(self):
sle = frappe.db.sql("""
select * from `tabStock Ledger Entry`
@@ -62,4 +75,4 @@ class Bin(Document):
order by timestamp(posting_date, posting_time) asc, name asc
limit 1
""", (self.item_code, self.warehouse), as_dict=1)
return sle and sle[0] or None
return sle and sle[0] or None

View File

@@ -245,7 +245,7 @@ class DeliveryNote(SellingController):
sl_entries = []
for d in self.get_item_list():
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" \
and d.warehouse:
and d.warehouse and flt(d['qty']):
self.update_reserved_qty(d)
sl_entries.append(self.get_sl_entries(d, {

View File

@@ -19,7 +19,7 @@ cur_frm.cscript.refresh = function(doc) {
cur_frm.cscript.edit_prices_button();
if (!doc.__islocal && doc.is_stock_item == 'Yes') {
cur_frm.toggle_enable(['has_serial_no', 'is_stock_item', 'valuation_method'],
cur_frm.toggle_enable(['has_serial_no', 'is_stock_item', 'valuation_method', 'has_batch_no'],
(doc.__onload && doc.__onload.sle_exists=="exists") ? false : true);
}
@@ -185,4 +185,4 @@ cur_frm.cscript.image = function() {
else {
msgprint(__("You may need to update: {0}", [frappe.meta.get_docfield(cur_frm.doc.doctype, "description_html").label]));
}
}
}

View File

@@ -187,13 +187,14 @@ class Item(WebsiteGenerator):
def cant_change(self):
if not self.get("__islocal"):
vals = frappe.db.get_value("Item", self.name,
["has_serial_no", "is_stock_item", "valuation_method"], as_dict=True)
["has_serial_no", "is_stock_item", "valuation_method", "has_batch_no"], as_dict=True)
if vals and ((self.is_stock_item == "No" and vals.is_stock_item == "Yes") or
vals.has_serial_no != self.has_serial_no or
vals.has_batch_no != self.has_batch_no or
cstr(vals.valuation_method) != cstr(self.valuation_method)):
if self.check_if_sle_exists() == "exists":
frappe.throw(_("As there are existing stock transactions for this item, you can not change the values of 'Has Serial No', 'Is Stock Item' and 'Valuation Method'"))
frappe.throw(_("As there are existing stock transactions for this item, you can not change the values of 'Has Serial No', 'Has Batch No', 'Is Stock Item' and 'Valuation Method'"))
def validate_item_type_for_reorder(self):
if self.re_order_level or len(self.get("item_reorder", {"material_request_type": "Purchase"})):

View File

@@ -1,5 +1,5 @@
frappe.listview_settings['Item'] = {
add_fields: ["item_name", "stock_uom", "item_group", "image",
"is_stock_item", "is_sales_item", "is_purchase_item",
"is_manufactured_item", "show_in_website"]
add_fields: ["`tabItem`.`item_name`", "`tabItem`.`stock_uom`", "`tabItem`.`item_group`", "`tabItem`.`image`",
"`tabItem`.`is_stock_item`", "`tabItem`.`is_sales_item`", "`tabItem`.`is_purchase_item`",
"`tabItem`.`is_manufactured_item`", "`tabItem`.`show_in_website`"]
};

View File

@@ -9,6 +9,6 @@ class TestItem(unittest.TestCase):
def test_duplicate_item(self):
from erpnext.stock.doctype.item_price.item_price import ItemPriceDuplicateItem
doc = frappe.copy_doc(test_records[0])
self.assertRaises(ItemPriceDuplicateItem, doc.insert)
self.assertRaises(ItemPriceDuplicateItem, doc.save)
test_records = frappe.get_test_records('Item Price')

View File

@@ -16,5 +16,11 @@
"item_code": "_Test Item 2",
"price_list": "_Test Price List Rest of the World",
"price_list_rate": 20
},
{
"doctype": "Item Price",
"item_code": "_Test Item Home Desktop 100",
"price_list": "_Test Price List",
"price_list_rate": 1000
}
]

View File

@@ -15,7 +15,7 @@ class LandedCostVoucher(Document):
self.set("landed_cost_items", [])
for pr in self.get("landed_cost_purchase_receipts"):
pr_items = frappe.db.sql("""select pr_item.item_code, pr_item.description,
pr_item.qty, pr_item.rate, pr_item.amount, pr_item.name
pr_item.qty, pr_item.base_rate, pr_item.base_amount, pr_item.name
from `tabPurchase Receipt Item` pr_item where parent = %s
and exists(select name from tabItem where name = pr_item.item_code and is_stock_item = 'Yes')""",
pr.purchase_receipt, as_dict=True)
@@ -25,8 +25,8 @@ class LandedCostVoucher(Document):
item.item_code = d.item_code
item.description = d.description
item.qty = d.qty
item.rate = d.rate
item.amount = d.amount
item.rate = d.base_rate
item.amount = d.base_amount
item.purchase_receipt = pr.purchase_receipt
item.purchase_receipt_item = d.name
@@ -97,10 +97,10 @@ class LandedCostVoucher(Document):
# update stock & gl entries for cancelled state of PR
pr.docstatus = 2
pr.update_stock()
pr.update_stock_ledger()
pr.make_gl_entries_on_cancel()
# update stock & gl entries for submit state of PR
pr.docstatus = 1
pr.update_stock()
pr.update_stock_ledger()
pr.make_gl_entries()

View File

@@ -162,8 +162,7 @@ def item_details(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond
return frappe.db.sql("""select name, item_name, description from `tabItem`
where name in ( select item_code FROM `tabDelivery Note Item`
where parent= %s
and ifnull(qty, 0) > ifnull(packed_qty, 0))
where parent= %s)
and %s like "%s" %s
limit %s, %s """ % ("%s", searchfield, "%s",
get_match_cond(doctype), "%s", "%s"),

View File

@@ -130,7 +130,7 @@ class PurchaseReceipt(BuyingController):
if not d.prevdoc_docname:
frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code))
def update_stock(self):
def update_stock_ledger(self):
sl_entries = []
stock_items = self.get_stock_items()
@@ -234,7 +234,7 @@ class PurchaseReceipt(BuyingController):
self.update_ordered_qty()
self.update_stock()
self.update_stock_ledger()
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
update_serial_nos_after_submit(self, "purchase_receipt_details")
@@ -267,7 +267,7 @@ class PurchaseReceipt(BuyingController):
self.update_ordered_qty()
self.update_stock()
self.update_stock_ledger()
self.update_prevdoc_status()
pc_obj.update_last_purchase_rate(self, 0)

View File

@@ -95,7 +95,7 @@ class TestPurchaseReceipt(unittest.TestCase):
pr.insert()
self.assertEquals(len(pr.get("pr_raw_material_details")), 2)
self.assertEquals(pr.get("purchase_receipt_details")[0].rm_supp_cost, 70000.0)
self.assertEquals(pr.get("purchase_receipt_details")[0].rm_supp_cost, 20750.0)
def test_serial_no_supplier(self):
@@ -151,6 +151,6 @@ def set_perpetual_inventory(enable=1):
accounts_settings.save()
test_dependencies = ["BOM"]
test_dependencies = ["BOM", "Item Price"]
test_records = frappe.get_test_records('Purchase Receipt')

View File

@@ -120,7 +120,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
clean_up: function() {
// Clear Production Order record from locals, because it is updated via Stock Entry
if(this.frm.doc.production_order &&
this.frm.doc.purpose == "Manufacture/Repack") {
this.frm.doc.purpose == "Manufacture") {
frappe.model.remove_from_locals("Production Order",
this.frm.doc.production_order);
}
@@ -162,7 +162,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
},
toggle_enable_bom: function() {
this.frm.toggle_enable("bom_no", !this.frm.doc.production_order);
this.frm.toggle_enable("bom_no", this.frm.doc.purpose!="Manufacture");
},
get_doctype_docname: function() {
@@ -339,6 +339,8 @@ cur_frm.cscript.toggle_related_fields = function(doc) {
cur_frm.fields_dict["mtn_details"].grid.set_column_disp("s_warehouse", !disable_from_warehouse);
cur_frm.fields_dict["mtn_details"].grid.set_column_disp("t_warehouse", !disable_to_warehouse);
cur_frm.cscript.toggle_enable_bom();
if(doc.purpose == 'Purchase Return') {
doc.customer = doc.customer_name = doc.customer_address =
doc.delivery_note_no = doc.sales_invoice_no = null;
@@ -351,6 +353,8 @@ cur_frm.cscript.toggle_related_fields = function(doc) {
doc.delivery_note_no = doc.sales_invoice_no = doc.supplier =
doc.supplier_name = doc.supplier_address = doc.purchase_receipt_no = null;
}
}
cur_frm.fields_dict['production_order'].get_query = function(doc) {
@@ -457,4 +461,5 @@ cur_frm.fields_dict.customer.get_query = function(doc, cdt, cdn) {
cur_frm.fields_dict.supplier.get_query = function(doc, cdt, cdn) {
return { query: "erpnext.controllers.queries.supplier_query" }
}
cur_frm.add_fetch('production_order', 'total_fixed_cost', 'total_fixed_cost');
cur_frm.add_fetch('production_order', 'total_fixed_cost', 'total_fixed_cost');
cur_frm.add_fetch('bom_no', 'total_fixed_cost', 'total_fixed_cost');

File diff suppressed because it is too large Load Diff

View File

@@ -43,7 +43,7 @@ class StockEntry(StockController):
self.validate_uom_is_integer("uom", "qty")
self.validate_uom_is_integer("stock_uom", "transfer_qty")
self.validate_warehouse(pro_obj)
self.validate_production_order(pro_obj)
self.validate_production_order()
self.get_stock_and_rate()
self.validate_incoming_rate()
self.validate_bom()
@@ -54,6 +54,7 @@ class StockEntry(StockController):
self.validate_valuation_rate()
self.set_total_amount()
def on_submit(self):
self.update_stock_ledger()
@@ -74,7 +75,7 @@ class StockEntry(StockController):
def validate_purpose(self):
valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer",
"Manufacture/Repack", "Subcontract", "Sales Return", "Purchase Return"]
"Manufacture", "Repack", "Subcontract", "Sales Return", "Purchase Return"]
if self.purpose not in valid_purposes:
frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes)))
@@ -137,7 +138,7 @@ class StockEntry(StockController):
if self.purpose in target_mandatory and not d.t_warehouse:
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
if self.purpose == "Manufacture/Repack":
if self.purpose in ["Manufacture", "Repack"]:
if validate_for_manufacture_repack:
if d.bom_no:
d.s_warehouse = None
@@ -156,14 +157,11 @@ class StockEntry(StockController):
if cstr(d.s_warehouse) == cstr(d.t_warehouse):
frappe.throw(_("Source and target warehouse cannot be same for row {0}").format(d.idx))
def validate_production_order(self, pro_obj=None):
if not pro_obj:
if self.production_order:
pro_obj = frappe.get_doc('Production Order', self.production_order)
else:
return
if self.purpose == "Manufacture/Repack":
def validate_production_order(self):
if self.purpose == "Manufacture":
# check if production order is entered
if not self.production_order:
frappe.throw(_("Production order number is mandatory for stock entry purpose manufacture"))
# check for double entry
self.check_duplicate_entry_for_production_order()
elif self.purpose != "Material Transfer":
@@ -192,7 +190,7 @@ class StockEntry(StockController):
+ self.production_order + ":" + ", ".join(other_ste), DuplicateEntryForProductionOrderError)
def validate_valuation_rate(self):
if self.purpose == "Manufacture/Repack":
if self.purpose in ["Manufacture", "Repack"]:
valuation_at_source, valuation_at_target = 0, 0
for d in self.get("mtn_details"):
if d.s_warehouse and not d.t_warehouse:
@@ -248,7 +246,7 @@ class StockEntry(StockController):
raw_material_cost += flt(d.amount)
# set incoming rate for fg item
if self.purpose == "Manufacture/Repack":
if self.purpose in ["Manufacture", "Repack"]:
number_of_fg_items = len([t.t_warehouse for t in self.get("mtn_details") if t.t_warehouse])
for d in self.get("mtn_details"):
if d.bom_no or (d.t_warehouse and number_of_fg_items == 1):
@@ -391,7 +389,7 @@ class StockEntry(StockController):
pro_doc = frappe.get_doc("Production Order", self.production_order)
_validate_production_order(pro_doc)
pro_doc.run_method("update_status")
if self.purpose == "Manufacture/Repack":
if self.purpose == "Manufacture":
pro_doc.run_method("update_produced_qty")
self.update_planned_qty(pro_doc)
@@ -463,20 +461,20 @@ class StockEntry(StockController):
def get_items(self):
self.set('mtn_details', [])
self.validate_production_order()
pro_obj = None
if self.production_order:
# common validations
pro_obj = frappe.get_doc('Production Order', self.production_order)
if pro_obj:
self.validate_production_order(pro_obj)
self.bom_no = pro_obj.bom_no
else:
# invalid production order
self.production_order = None
if self.bom_no:
if self.purpose in ["Material Issue", "Material Transfer", "Manufacture/Repack",
if self.purpose in ["Material Issue", "Material Transfer", "Manufacture", "Repack",
"Subcontract"]:
if self.production_order and self.purpose == "Material Transfer":
item_dict = self.get_pending_raw_materials(pro_obj)
@@ -493,7 +491,7 @@ class StockEntry(StockController):
self.add_to_stock_entry_detail(item_dict)
# add finished good item to Stock Entry Detail table -- along with bom_no
if self.production_order and self.purpose == "Manufacture/Repack":
if self.production_order and self.purpose == "Manufacture":
item = frappe.db.get_value("Item", pro_obj.production_item, ["item_name",
"description", "stock_uom", "expense_account", "buying_cost_center"], as_dict=1)
self.add_to_stock_entry_detail({
@@ -509,7 +507,7 @@ class StockEntry(StockController):
}
}, bom_no=pro_obj.bom_no)
elif self.purpose in ["Material Receipt", "Manufacture/Repack"]:
elif self.purpose in ["Material Receipt", "Repack"]:
if self.purpose=="Material Receipt":
self.from_warehouse = ""

View File

@@ -6,7 +6,8 @@
"Material Issue": "icon-arrow-right",
"Material Receipt": "icon-arrow-left",
"Material Transfer": "icon-resize-horizontal",
"Manufacture/Repack": "icon-wrench",
"Manufacture": "icon-wrench",
"Repack": "icon-wrench",
"Sales Return": "icon-warning-sign",
"Purchase Return": "icon-warning-sign",
"Subcontract": "icon-truck"

View File

@@ -108,6 +108,6 @@
],
"posting_date": "2013-01-25",
"posting_time": "17:14:24",
"purpose": "Manufacture/Repack"
"purpose": "Repack"
}
]

View File

@@ -9,14 +9,64 @@ from erpnext.stock.doctype.serial_no.serial_no import *
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import StockFreezeError
def get_sle(**args):
condition, values = "", []
for key, value in args.iteritems():
condition += " and " if condition else " where "
condition += "`{0}`=%s".format(key)
values.append(value)
return frappe.db.sql("""select * from `tabStock Ledger Entry` %s
order by timestamp(posting_date, posting_time) desc, name desc limit 1"""% condition,
values, as_dict=1)
def make_zero(item_code, warehouse):
sle = get_sle(item_code = item_code, warehouse = warehouse)
qty = sle[0].qty_after_transaction if sle else 0
if qty < 0:
make_stock_entry(item_code, None, warehouse, abs(qty), incoming_rate=10)
elif qty > 0:
make_stock_entry(item_code, warehouse, None, qty, incoming_rate=10)
class TestStockEntry(unittest.TestCase):
def tearDown(self):
frappe.set_user("Administrator")
set_perpetual_inventory(0)
if hasattr(self, "old_default_company"):
frappe.db.set_default("company", self.old_default_company)
def test_fifo(self):
frappe.db.set_default("allow_negative_stock", 1)
item_code = "_Test Item 2"
warehouse = "_Test Warehouse - _TC"
make_zero(item_code, warehouse)
make_stock_entry(item_code, None, warehouse, 1, incoming_rate=10)
sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
self.assertEqual([[1, 10]], eval(sle.stock_queue))
# negative qty
make_zero(item_code, warehouse)
make_stock_entry(item_code, warehouse, None, 1, incoming_rate=10)
sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
self.assertEqual([[-1, 10]], eval(sle.stock_queue))
# further negative
make_stock_entry(item_code, warehouse, None, 1)
sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
self.assertEqual([[-2, 10]], eval(sle.stock_queue))
# move stock to positive
make_stock_entry(item_code, None, warehouse, 3, incoming_rate=10)
sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
self.assertEqual([[1, 10]], eval(sle.stock_queue))
frappe.db.set_default("allow_negative_stock", 0)
def test_auto_material_request(self):
frappe.db.sql("""delete from `tabMaterial Request Item`""")
frappe.db.sql("""delete from `tabMaterial Request`""")
@@ -821,19 +871,19 @@ class TestStockEntry(unittest.TestCase):
se = frappe.copy_doc(test_records[0]).insert()
self.assertRaises (StockFreezeError, se.submit)
frappe.db.set_value("Stock Settings", None, "stock_frozen_upto_days", 0)
def test_production_order(self):
bom_no = frappe.db.get_value("BOM", {"item": "_Test FG Item 2",
bom_no = frappe.db.get_value("BOM", {"item": "_Test FG Item 2",
"is_default": 1, "docstatus": 1})
production_order = frappe.new_doc("Production Order")
production_order.update({
"company": "_Test Company",
"fg_warehouse": "_Test Warehouse 1 - _TC",
"production_item": "_Test FG Item 2",
"fg_warehouse": "_Test Warehouse 1 - _TC",
"production_item": "_Test FG Item 2",
"bom_no": bom_no,
"qty": 1.0,
"stock_uom": "Nos",
"stock_uom": "Nos",
"wip_warehouse": "_Test Warehouse - _TC"
})
production_order.insert()
@@ -843,7 +893,7 @@ class TestStockEntry(unittest.TestCase):
stock_entry = frappe.new_doc("Stock Entry")
stock_entry.update({
"purpose": "Manufacture/Repack",
"purpose": "Manufacture",
"production_order": production_order.name,
"bom_no": bom_no,
"fg_completed_qty": "1",

View File

@@ -44,11 +44,14 @@ class StockLedgerEntry(Document):
formatdate(self.posting_date), self.posting_time))
def validate_mandatory(self):
mandatory = ['warehouse','posting_date','voucher_type','voucher_no','actual_qty','company']
mandatory = ['warehouse','posting_date','voucher_type','voucher_no','company']
for k in mandatory:
if not self.get(k):
frappe.throw(_("{0} is required").format(self.meta.get_label(k)))
if self.voucher_type != "Stock Reconciliation" and not self.actual_qty:
frappe.throw(_("Actual Qty is mandatory"))
def validate_item(self):
item_det = frappe.db.sql("""select name, has_batch_no, docstatus, is_stock_item
from tabItem where name=%s""", self.item_code, as_dict=True)[0]

View File

@@ -1,5 +1,5 @@
{
"allow_copy": 1,
"allow_copy": 1,
"autoname": "SR/.######",
"creation": "2013-03-28 10:35:31",
"description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.",
@@ -7,6 +7,7 @@
"doctype": "DocType",
"fields": [
{
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
"in_filter": 0,
@@ -118,7 +119,7 @@
"idx": 1,
"is_submittable": 1,
"max_attachments": 1,
"modified": "2014-05-26 03:05:54.024413",
"modified": "2014-10-07 12:43:52.825575",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation",

View File

@@ -16,13 +16,11 @@ class StockReconciliation(StockController):
self.head_row = ["Item Code", "Warehouse", "Quantity", "Valuation Rate"]
def validate(self):
self.entries = []
self.validate_data()
self.validate_expense_account()
def on_submit(self):
self.insert_stock_ledger_entries()
self.update_stock_ledger()
self.make_gl_entries()
def on_cancel(self):
@@ -126,10 +124,9 @@ class StockReconciliation(StockController):
except Exception, e:
self.validation_messages.append(_("Row # ") + ("%d: " % (row_num)) + cstr(e))
def insert_stock_ledger_entries(self):
def update_stock_ledger(self):
""" find difference between current and expected entries
and create stock ledger entries based on the difference"""
from erpnext.stock.utils import get_valuation_method
from erpnext.stock.stock_ledger import get_previous_sle
row_template = ["item_code", "warehouse", "qty", "valuation_rate"]
@@ -141,105 +138,27 @@ class StockReconciliation(StockController):
for row_num, row in enumerate(data[data.index(self.head_row)+1:]):
row = frappe._dict(zip(row_template, row))
row["row_num"] = row_num
previous_sle = get_previous_sle({
"item_code": row.item_code,
"warehouse": row.warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time
})
# check valuation rate mandatory
if row.qty not in ["", None] and not row.valuation_rate and \
flt(previous_sle.get("qty_after_transaction")) <= 0:
frappe.throw(_("Valuation Rate required for Item {0}").format(row.item_code))
if row.qty in ("", None) or row.valuation_rate in ("", None):
previous_sle = get_previous_sle({
"item_code": row.item_code,
"warehouse": row.warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time
})
change_in_qty = row.qty not in ["", None] and \
(flt(row.qty) - flt(previous_sle.get("qty_after_transaction")))
if row.qty in ("", None):
row.qty = previous_sle.get("qty_after_transaction")
change_in_rate = row.valuation_rate not in ["", None] and \
(flt(row.valuation_rate) - flt(previous_sle.get("valuation_rate")))
if row.valuation_rate in ("", None):
row.valuation_rate = previous_sle.get("valuation_rate")
if get_valuation_method(row.item_code) == "Moving Average":
self.sle_for_moving_avg(row, previous_sle, change_in_qty, change_in_rate)
# if row.qty and not row.valuation_rate:
# frappe.throw(_("Valuation Rate required for Item {0}").format(row.item_code))
else:
self.sle_for_fifo(row, previous_sle, change_in_qty, change_in_rate)
self.insert_entries(row)
def sle_for_moving_avg(self, row, previous_sle, change_in_qty, change_in_rate):
"""Insert Stock Ledger Entries for Moving Average valuation"""
def _get_incoming_rate(qty, valuation_rate, previous_qty, previous_valuation_rate):
if previous_valuation_rate == 0:
return flt(valuation_rate)
else:
if valuation_rate in ["", None]:
valuation_rate = previous_valuation_rate
return (qty * valuation_rate - previous_qty * previous_valuation_rate) \
/ flt(qty - previous_qty)
if change_in_qty:
# if change in qty, irrespective of change in rate
incoming_rate = _get_incoming_rate(flt(row.qty), flt(row.valuation_rate),
flt(previous_sle.get("qty_after_transaction")), flt(previous_sle.get("valuation_rate")))
row["voucher_detail_no"] = "Row: " + cstr(row.row_num) + "/Actual Entry"
self.insert_entries({"actual_qty": change_in_qty, "incoming_rate": incoming_rate}, row)
elif change_in_rate and flt(previous_sle.get("qty_after_transaction")) > 0:
# if no change in qty, but change in rate
# and positive actual stock before this reconciliation
incoming_rate = _get_incoming_rate(
flt(previous_sle.get("qty_after_transaction"))+1, flt(row.valuation_rate),
flt(previous_sle.get("qty_after_transaction")),
flt(previous_sle.get("valuation_rate")))
# +1 entry
row["voucher_detail_no"] = "Row: " + cstr(row.row_num) + "/Valuation Adjustment +1"
self.insert_entries({"actual_qty": 1, "incoming_rate": incoming_rate}, row)
# -1 entry
row["voucher_detail_no"] = "Row: " + cstr(row.row_num) + "/Valuation Adjustment -1"
self.insert_entries({"actual_qty": -1}, row)
def sle_for_fifo(self, row, previous_sle, change_in_qty, change_in_rate):
"""Insert Stock Ledger Entries for FIFO valuation"""
previous_stock_queue = json.loads(previous_sle.get("stock_queue") or "[]")
previous_stock_qty = sum((batch[0] for batch in previous_stock_queue))
previous_stock_value = sum((batch[0] * batch[1] for batch in \
previous_stock_queue))
def _insert_entries():
if previous_stock_queue != [[row.qty, row.valuation_rate]]:
# make entry as per attachment
if flt(row.qty):
row["voucher_detail_no"] = "Row: " + cstr(row.row_num) + "/Actual Entry"
self.insert_entries({"actual_qty": row.qty,
"incoming_rate": flt(row.valuation_rate)}, row)
# Make reverse entry
if previous_stock_qty:
row["voucher_detail_no"] = "Row: " + cstr(row.row_num) + "/Reverse Entry"
self.insert_entries({"actual_qty": -1 * previous_stock_qty,
"incoming_rate": previous_stock_qty < 0 and
flt(row.valuation_rate) or 0}, row)
if change_in_qty:
if row.valuation_rate in ["", None]:
# dont want change in valuation
if previous_stock_qty > 0:
# set valuation_rate as previous valuation_rate
row.valuation_rate = previous_stock_value / flt(previous_stock_qty)
_insert_entries()
elif change_in_rate and previous_stock_qty > 0:
# if no change in qty, but change in rate
# and positive actual stock before this reconciliation
row.qty = previous_stock_qty
_insert_entries()
def insert_entries(self, opts, row):
def insert_entries(self, row):
"""Insert Stock Ledger Entries"""
args = frappe._dict({
"doctype": "Stock Ledger Entry",
@@ -251,16 +170,13 @@ class StockReconciliation(StockController):
"voucher_no": self.name,
"company": self.company,
"stock_uom": frappe.db.get_value("Item", row.item_code, "stock_uom"),
"voucher_detail_no": row.voucher_detail_no,
"fiscal_year": self.fiscal_year,
"is_cancelled": "No"
"is_cancelled": "No",
"qty_after_transaction": row.qty,
"valuation_rate": row.valuation_rate
})
args.update(opts)
self.make_sl_entries([args])
# append to entries
self.entries.append(args)
def delete_and_repost_sle(self):
""" Delete Stock Ledger Entries related to this voucher
and repost future Stock Ledger Entries"""
@@ -295,7 +211,7 @@ class StockReconciliation(StockController):
if not self.expense_account:
msgprint(_("Please enter Expense Account"), raise_exception=1)
elif not frappe.db.sql("""select * from `tabStock Ledger Entry`"""):
elif not frappe.db.sql("""select name from `tabStock Ledger Entry` limit 1"""):
if frappe.db.get_value("Account", self.expense_account, "report_type") == "Profit and Loss":
frappe.throw(_("Difference Account must be a 'Liability' type account, since this Stock Reconciliation is an Opening Entry"))

View File

@@ -28,7 +28,7 @@ class TestStockReconciliation(unittest.TestCase):
[20, "", "2012-12-26", "12:05", 16000, 15, 18000],
[10, 2000, "2012-12-26", "12:10", 20000, 5, 6000],
[1, 1000, "2012-12-01", "00:00", 1000, 11, 13200],
[0, "", "2012-12-26", "12:10", 0, -5, 0]
[0, "", "2012-12-26", "12:10", 0, -5, -6000]
]
for d in input_data:
@@ -63,16 +63,16 @@ class TestStockReconciliation(unittest.TestCase):
input_data = [
[50, 1000, "2012-12-26", "12:00", 50000, 45, 48000],
[5, 1000, "2012-12-26", "12:00", 5000, 0, 0],
[15, 1000, "2012-12-26", "12:00", 15000, 10, 12000],
[15, 1000, "2012-12-26", "12:00", 15000, 10, 11500],
[25, 900, "2012-12-26", "12:00", 22500, 20, 22500],
[20, 500, "2012-12-26", "12:00", 10000, 15, 18000],
[50, 1000, "2013-01-01", "12:00", 50000, 65, 68000],
[5, 1000, "2013-01-01", "12:00", 5000, 20, 23000],
["", 1000, "2012-12-26", "12:05", 15000, 10, 12000],
["", 1000, "2012-12-26", "12:05", 15000, 10, 11500],
[20, "", "2012-12-26", "12:05", 18000, 15, 18000],
[10, 2000, "2012-12-26", "12:10", 20000, 5, 6000],
[1, 1000, "2012-12-01", "00:00", 1000, 11, 13200],
[0, "", "2012-12-26", "12:10", 0, -5, 0]
[10, 2000, "2012-12-26", "12:10", 20000, 5, 7600],
[1, 1000, "2012-12-01", "00:00", 1000, 11, 12512.73],
[0, "", "2012-12-26", "12:10", 0, -5, -5142.86]
]

View File

@@ -6,18 +6,17 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import cint
from frappe.model.document import Document
class StockSettings(Document):
def validate(self):
for key in ["item_naming_by", "item_group", "stock_uom",
"allow_negative_stock"]:
for key in ["item_naming_by", "item_group", "stock_uom", "allow_negative_stock"]:
frappe.db.set_default(key, self.get(key, ""))
from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series
set_by_naming_series("Item", "item_code",
set_by_naming_series("Item", "item_code",
self.get("item_naming_by")=="Naming Series", hide_name_field=True)
stock_frozen_limit = 356
@@ -25,3 +24,5 @@ class StockSettings(Document):
if submitted_stock_frozen > stock_frozen_limit:
self.stock_frozen_upto_days = stock_frozen_limit
frappe.msgprint (_("`Freeze Stocks Older Than` should be smaller than %d days.") %stock_frozen_limit)

View File

@@ -1 +0,0 @@
Stock balances on a particular day, per warehouse.

View File

@@ -1,181 +0,0 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.require("assets/erpnext/js/stock_analytics.js");
frappe.pages['stock-balance'].onload = function(wrapper) {
frappe.ui.make_app_page({
parent: wrapper,
title: __('Stock Balance'),
single_column: true
});
new erpnext.StockBalance(wrapper);
wrapper.appframe.add_module_icon("Stock");
}
erpnext.StockBalance = erpnext.StockAnalytics.extend({
init: function(wrapper) {
this._super(wrapper, {
title: __("Stock Balance"),
doctypes: ["Item", "Item Group", "Warehouse", "Stock Ledger Entry", "Brand",
"Stock Entry", "Project", "Serial No"],
});
},
setup_columns: function() {
this.columns = [
{id: "name", name: __("Item"), field: "name", width: 300,
formatter: this.tree_formatter},
{id: "item_name", name: __("Item Name"), field: "item_name", width: 100},
{id: "description", name: __("Description"), field: "description", width: 200,
formatter: this.text_formatter},
{id: "brand", name: __("Brand"), field: "brand", width: 100},
{id: "stock_uom", name: __("UOM"), field: "stock_uom", width: 100},
{id: "opening_qty", name: __("Opening Qty"), field: "opening_qty", width: 100,
formatter: this.currency_formatter},
{id: "inflow_qty", name: __("In Qty"), field: "inflow_qty", width: 100,
formatter: this.currency_formatter},
{id: "outflow_qty", name: __("Out Qty"), field: "outflow_qty", width: 100,
formatter: this.currency_formatter},
{id: "closing_qty", name: __("Closing Qty"), field: "closing_qty", width: 100,
formatter: this.currency_formatter},
{id: "opening_value", name: __("Opening Value"), field: "opening_value", width: 100,
formatter: this.currency_formatter},
{id: "inflow_value", name: __("In Value"), field: "inflow_value", width: 100,
formatter: this.currency_formatter},
{id: "outflow_value", name: __("Out Value"), field: "outflow_value", width: 100,
formatter: this.currency_formatter},
{id: "closing_value", name: __("Closing Value"), field: "closing_value", width: 100,
formatter: this.currency_formatter},
{id: "valuation_rate", name: __("Valuation Rate"), field: "valuation_rate", width: 100,
formatter: this.currency_formatter},
];
},
filters: [
{fieldtype:"Select", label: __("Brand"), link:"Brand", fieldname: "brand",
default_value: __("Select Brand..."), filter: function(val, item, opts) {
return val == opts.default_value || item.brand == val || item._show;
}, link_formatter: {filter_input: "brand"}},
{fieldtype:"Select", label: __("Warehouse"), link:"Warehouse", fieldname: "warehouse",
default_value: __("Select Warehouse..."), filter: function(val, item, opts, me) {
return me.apply_zero_filter(val, item, opts, me);
}},
{fieldtype:"Select", label: __("Project"), link:"Project", fieldname: "project",
default_value: __("Select Project..."), filter: function(val, item, opts, me) {
return me.apply_zero_filter(val, item, opts, me);
}, link_formatter: {filter_input: "project"}},
{fieldtype:"Date", label: __("From Date"), fieldname: "from_date"},
{fieldtype:"Label", label: __("To")},
{fieldtype:"Date", label: __("To Date"), fieldname: "to_date"},
{fieldtype:"Button", label: __("Refresh"), icon:"icon-refresh icon-white"},
{fieldtype:"Button", label: __("Reset Filters"), icon: "icon-filter"}
],
setup_plot_check: function() {
return;
},
prepare_data: function() {
this.stock_entry_map = this.make_name_map(frappe.report_dump.data["Stock Entry"], "name");
this._super();
},
prepare_balances: function() {
var me = this;
var from_date = dateutil.str_to_obj(this.from_date);
var to_date = dateutil.str_to_obj(this.to_date);
var data = frappe.report_dump.data["Stock Ledger Entry"];
this.item_warehouse = {};
this.serialized_buying_rates = this.get_serialized_buying_rates();
for(var i=0, j=data.length; i<j; i++) {
var sl = data[i];
var sl_posting_date = dateutil.str_to_obj(sl.posting_date);
if((me.is_default("warehouse") ? true : me.warehouse == sl.warehouse) &&
(me.is_default("project") ? true : me.project == sl.project)) {
var item = me.item_by_name[sl.item_code];
var wh = me.get_item_warehouse(sl.warehouse, sl.item_code);
var valuation_method = item.valuation_method ?
item.valuation_method : sys_defaults.valuation_method;
var is_fifo = valuation_method == "FIFO";
var qty_diff = sl.qty;
var value_diff = me.get_value_diff(wh, sl, is_fifo);
if(sl_posting_date < from_date) {
item.opening_qty += qty_diff;
item.opening_value += value_diff;
} else if(sl_posting_date <= to_date) {
var ignore_inflow_outflow = this.is_default("warehouse")
&& sl.voucher_type=="Stock Entry"
&& this.stock_entry_map[sl.voucher_no].purpose=="Material Transfer";
if(!ignore_inflow_outflow) {
if(qty_diff < 0) {
item.outflow_qty += Math.abs(qty_diff);
} else {
item.inflow_qty += qty_diff;
}
if(value_diff < 0) {
item.outflow_value += Math.abs(value_diff);
} else {
item.inflow_value += value_diff;
}
item.closing_qty += qty_diff;
item.closing_value += value_diff;
}
} else {
break;
}
}
}
// opening + diff = closing
// adding opening, since diff already added to closing
$.each(me.item_by_name, function(key, item) {
item.closing_qty += item.opening_qty;
item.closing_value += item.opening_value;
// valuation rate
if(!item.is_group && flt(item.closing_qty) > 0)
item.valuation_rate = flt(item.closing_value) / flt(item.closing_qty);
else item.valuation_rate = 0.0
});
},
update_groups: function() {
var me = this;
$.each(this.data, function(i, item) {
// update groups
if(!item.is_group && me.apply_filter(item, "brand")) {
var parent = me.parent_map[item.name];
while(parent) {
parent_group = me.item_by_name[parent];
$.each(me.columns, function(c, col) {
if (col.formatter == me.currency_formatter && col.field != "valuation_rate") {
parent_group[col.field] = flt(parent_group[col.field]) + flt(item[col.field]);
}
});
// show parent if filtered by brand
if(item.brand == me.brand)
parent_group._show = true;
parent = me.parent_map[parent];
}
}
});
},
get_plot_data: function() {
return;
}
});

View File

@@ -1,23 +0,0 @@
{
"creation": "2012-12-27 18:57:47.000000",
"docstatus": 0,
"doctype": "Page",
"icon": "icon-table",
"idx": 1,
"modified": "2013-07-11 14:44:15.000000",
"modified_by": "Administrator",
"module": "Stock",
"name": "stock-balance",
"owner": "Administrator",
"page_name": "stock-balance",
"roles": [
{
"role": "Material Manager"
},
{
"role": "Analytics"
}
],
"standard": "Yes",
"title": "Stock Balance"
}

View File

@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import flt
from frappe.utils import flt, cint
def execute(filters=None):
if not filters: filters = {}
@@ -57,6 +57,7 @@ def get_stock_ledger_entries(filters):
conditions, as_dict=1)
def get_item_warehouse_batch_map(filters):
float_precision = cint(frappe.db.get_default("float_precision")) or 3
sle = get_stock_ledger_entries(filters)
iwb_map = {}
@@ -67,14 +68,14 @@ def get_item_warehouse_batch_map(filters):
}))
qty_dict = iwb_map[d.item_code][d.warehouse][d.batch_no]
if d.posting_date < filters["from_date"]:
qty_dict.opening_qty += flt(d.actual_qty)
qty_dict.opening_qty += flt(d.actual_qty, float_precision)
elif d.posting_date >= filters["from_date"] and d.posting_date <= filters["to_date"]:
if flt(d.actual_qty) > 0:
qty_dict.in_qty += flt(d.actual_qty)
qty_dict.in_qty += flt(d.actual_qty, float_precision)
else:
qty_dict.out_qty += abs(flt(d.actual_qty))
qty_dict.out_qty += abs(flt(d.actual_qty, float_precision))
qty_dict.bal_qty += flt(d.actual_qty)
qty_dict.bal_qty += flt(d.actual_qty, float_precision)
return iwb_map

View File

@@ -4,10 +4,10 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import date_diff
from frappe.utils import date_diff, flt
def execute(filters=None):
columns = get_columns()
item_details = get_fifo_queue(filters)
to_date = filters["to_date"]
@@ -16,35 +16,40 @@ def execute(filters=None):
fifo_queue = item_dict["fifo_queue"]
details = item_dict["details"]
if not fifo_queue: continue
average_age = get_average_age(fifo_queue, to_date)
earliest_age = date_diff(to_date, fifo_queue[0][1])
latest_age = date_diff(to_date, fifo_queue[-1][1])
data.append([item, details.item_name, details.description, details.item_group,
data.append([item, details.item_name, details.description, details.item_group,
details.brand, average_age, earliest_age, latest_age, details.stock_uom])
return columns, data
def get_average_age(fifo_queue, to_date):
batch_age = age_qty = total_qty = 0.0
for batch in fifo_queue:
batch_age = date_diff(to_date, batch[1])
age_qty += batch_age * batch[0]
total_qty += batch[0]
return (age_qty / total_qty) if total_qty else 0.0
def get_columns():
return [_("Item Code") + ":Link/Item:100", _("Item Name") + "::100", _("Description") + "::200",
_("Item Group") + ":Link/Item Group:100", _("Brand") + ":Link/Brand:100", _("Average Age") + ":Float:100",
return [_("Item Code") + ":Link/Item:100", _("Item Name") + "::100", _("Description") + "::200",
_("Item Group") + ":Link/Item Group:100", _("Brand") + ":Link/Brand:100", _("Average Age") + ":Float:100",
_("Earliest") + ":Int:80", _("Latest") + ":Int:80", _("UOM") + ":Link/UOM:100"]
def get_fifo_queue(filters):
item_details = {}
prev_qty = 0.0
for d in get_stock_ledger_entries(filters):
item_details.setdefault(d.name, {"details": d, "fifo_queue": []})
fifo_queue = item_details[d.name]["fifo_queue"]
if d.voucher_type == "Stock Reconciliation":
d.actual_qty = flt(d.qty_after_transaction) - flt(prev_qty)
if d.actual_qty > 0:
fifo_queue.append([d.actual_qty, d.posting_date])
else:
@@ -52,7 +57,7 @@ def get_fifo_queue(filters):
while qty_to_pop:
batch = fifo_queue[0] if fifo_queue else [0, None]
if 0 < batch[0] <= qty_to_pop:
# if batch qty > 0
# if batch qty > 0
# not enough or exactly same qty in current batch, clear batch
qty_to_pop -= batch[0]
fifo_queue.pop(0)
@@ -61,12 +66,14 @@ def get_fifo_queue(filters):
batch[0] -= qty_to_pop
qty_to_pop = 0
prev_qty = d.qty_after_transaction
return item_details
def get_stock_ledger_entries(filters):
return frappe.db.sql("""select
item.name, item.item_name, item_group, brand, description, item.stock_uom,
actual_qty, posting_date
return frappe.db.sql("""select
item.name, item.item_name, item_group, brand, description, item.stock_uom,
actual_qty, posting_date, voucher_type, qty_after_transaction
from `tabStock Ledger Entry` sle,
(select name, item_name, description, stock_uom, brand, item_group
from `tabItem` {item_conditions}) item
@@ -77,19 +84,19 @@ def get_stock_ledger_entries(filters):
order by posting_date, posting_time, sle.name"""\
.format(item_conditions=get_item_conditions(filters),
sle_conditions=get_sle_conditions(filters)), filters, as_dict=True)
def get_item_conditions(filters):
conditions = []
if filters.get("item_code"):
conditions.append("item_code=%(item_code)s")
if filters.get("brand"):
conditions.append("brand=%(brand)s")
return "where {}".format(" and ".join(conditions)) if conditions else ""
def get_sle_conditions(filters):
conditions = []
if filters.get("warehouse"):
conditions.append("warehouse=%(warehouse)s")
return "and {}".format(" and ".join(conditions)) if conditions else ""
return "and {}".format(" and ".join(conditions)) if conditions else ""

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors and contributors
// For license information, please see license.txt
frappe.query_reports["Warehouse-Wise Stock Balance"] = {
frappe.query_reports["Stock Balance"] = {
"filters": [
{
"fieldname":"from_date",
@@ -18,4 +18,4 @@ frappe.query_reports["Warehouse-Wise Stock Balance"] = {
"default": frappe.datetime.get_today()
}
]
}
}

View File

@@ -1,16 +1,17 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2013-06-05 11:00:31",
"creation": "2014-10-10 17:58:11.577901",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 1,
"is_standard": "Yes",
"modified": "2014-06-03 07:18:17.384923",
"modified": "2014-10-10 17:58:11.577901",
"modified_by": "Administrator",
"module": "Stock",
"name": "Warehouse-Wise Stock Balance",
"name": "Stock Balance",
"owner": "Administrator",
"ref_doctype": "Stock Ledger Entry",
"report_name": "Warehouse-Wise Stock Balance",
"report_name": "Stock Balance",
"report_type": "Script Report"
}

View File

@@ -58,10 +58,10 @@ def get_conditions(filters):
#get all details
def get_stock_ledger_entries(filters):
conditions = get_conditions(filters)
return frappe.db.sql("""select item_code, warehouse, posting_date,
actual_qty, valuation_rate, stock_uom, company
return frappe.db.sql("""select item_code, warehouse, posting_date, actual_qty, valuation_rate,
stock_uom, company, voucher_type, qty_after_transaction, stock_value_difference
from `tabStock Ledger Entry`
where docstatus < 2 %s order by item_code, warehouse""" %
where docstatus < 2 %s order by posting_date, posting_time, name""" %
conditions, as_dict=1)
def get_item_warehouse_map(filters):
@@ -71,30 +71,36 @@ def get_item_warehouse_map(filters):
for d in sle:
iwb_map.setdefault(d.company, {}).setdefault(d.item_code, {}).\
setdefault(d.warehouse, frappe._dict({\
"opening_qty": 0.0, "opening_val": 0.0,
"in_qty": 0.0, "in_val": 0.0,
"out_qty": 0.0, "out_val": 0.0,
"bal_qty": 0.0, "bal_val": 0.0,
"opening_qty": 0.0, "opening_val": 0.0,
"in_qty": 0.0, "in_val": 0.0,
"out_qty": 0.0, "out_val": 0.0,
"bal_qty": 0.0, "bal_val": 0.0,
"val_rate": 0.0, "uom": None
}))
qty_dict = iwb_map[d.company][d.item_code][d.warehouse]
qty_dict.uom = d.stock_uom
if d.voucher_type == "Stock Reconciliation":
qty_diff = flt(d.qty_after_transaction) - qty_dict.bal_qty
else:
qty_diff = flt(d.actual_qty)
value_diff = flt(d.stock_value_difference)
if d.posting_date < filters["from_date"]:
qty_dict.opening_qty += flt(d.actual_qty)
qty_dict.opening_val += flt(d.actual_qty * d.valuation_rate)
qty_dict.opening_qty += qty_diff
qty_dict.opening_val += value_diff
elif d.posting_date >= filters["from_date"] and d.posting_date <= filters["to_date"]:
qty_dict.val_rate = d.valuation_rate
if flt(d.actual_qty) > 0:
qty_dict.in_qty += flt(d.actual_qty)
qty_dict.in_val += flt(d.actual_qty * d.valuation_rate)
if qty_diff > 0:
qty_dict.in_qty += qty_diff
qty_dict.in_val += value_diff
else:
qty_dict.out_qty += abs(flt(d.actual_qty))
qty_dict.out_val += flt(abs(flt(d.actual_qty)) * d.valuation_rate)
qty_dict.out_qty += abs(qty_diff)
qty_dict.out_val += abs(value_diff)
qty_dict.bal_qty += flt(d.actual_qty)
qty_dict.bal_val += flt(d.actual_qty * d.valuation_rate)
qty_dict.bal_qty += qty_diff
qty_dict.bal_val += value_diff
return iwb_map

View File

@@ -13,16 +13,13 @@ def execute(filters=None):
data = []
for sle in sl_entries:
item_detail = item_details[sle.item_code]
voucher_link_icon = """<a href="%s"><i class="icon icon-share"
style="cursor: pointer;"></i></a>""" \
% ("/".join(["#Form", sle.voucher_type, sle.voucher_no]),)
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,
voucher_link_icon, sle.batch_no, sle.serial_no, sle.company])
sle.batch_no, sle.serial_no, sle.company])
return columns, data
@@ -31,7 +28,7 @@ def get_columns():
_("Brand") + ":Link/Brand:100", _("Description") + "::200", _("Warehouse") + ":Link/Warehouse:100",
_("Stock UOM") + ":Link/UOM:100", _("Qty") + ":Float:50", _("Balance Qty") + ":Float:100",
_("Incoming Rate") + ":Currency:110", _("Valuation Rate") + ":Currency:110", _("Balance Value") + ":Currency:110",
_("Voucher Type") + "::110", _("Voucher #") + "::100", _("Link") + "::30", _("Batch") + ":Link/Batch:100",
_("Voucher Type") + "::110", _("Voucher #") + ":Dynamic Link/Voucher Type:100", _("Batch") + ":Link/Batch:100",
_("Serial #") + ":Link/Serial No:100", _("Company") + ":Link/Company:100"]
def get_stock_ledger_entries(filters):

View File

@@ -27,7 +27,7 @@ def make_sl_entries(sl_entries, is_amended=None):
if sle.get('is_cancelled') == 'Yes':
sle['actual_qty'] = -flt(sle['actual_qty'])
if sle.get("actual_qty"):
if sle.get("actual_qty") or sle.voucher_type=="Stock Reconciliation":
sle_id = make_entry(sle)
args = sle.copy()
@@ -36,9 +36,9 @@ def make_sl_entries(sl_entries, is_amended=None):
"is_amended": is_amended
})
update_bin(args)
if cancel:
delete_cancelled_entry(sl_entries[0].get('voucher_type'),
sl_entries[0].get('voucher_no'))
delete_cancelled_entry(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no'))
def set_as_cancel(voucher_type, voucher_no):
frappe.db.sql("""update `tabStock Ledger Entry` set is_cancelled='Yes',
@@ -58,7 +58,7 @@ def delete_cancelled_entry(voucher_type, voucher_no):
frappe.db.sql("""delete from `tabStock Ledger Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
def update_entries_after(args, verbose=1):
def update_entries_after(args, allow_zero_rate=False, verbose=1):
"""
update valution rate and qty after transaction
from the current time-bucket onwards
@@ -83,7 +83,6 @@ def update_entries_after(args, verbose=1):
entries_to_fix = get_sle_after_datetime(previous_sle or \
{"item_code": args["item_code"], "warehouse": args["warehouse"]}, for_update=True)
valuation_method = get_valuation_method(args["item_code"])
stock_value_difference = 0.0
@@ -95,21 +94,30 @@ def update_entries_after(args, verbose=1):
qty_after_transaction += flt(sle.actual_qty)
continue
if sle.serial_no:
valuation_rate = get_serialized_values(qty_after_transaction, sle, valuation_rate)
elif valuation_method == "Moving Average":
valuation_rate = get_moving_average_values(qty_after_transaction, sle, valuation_rate)
else:
valuation_rate = get_fifo_values(qty_after_transaction, sle, stock_queue)
qty_after_transaction += flt(sle.actual_qty)
qty_after_transaction += flt(sle.actual_qty)
else:
if sle.voucher_type=="Stock Reconciliation":
valuation_rate = sle.valuation_rate
qty_after_transaction = sle.qty_after_transaction
stock_queue = [[qty_after_transaction, valuation_rate]]
else:
if valuation_method == "Moving Average":
valuation_rate = get_moving_average_values(qty_after_transaction, sle, valuation_rate, allow_zero_rate)
else:
valuation_rate = get_fifo_values(qty_after_transaction, sle, stock_queue, allow_zero_rate)
qty_after_transaction += flt(sle.actual_qty)
# get stock value
if sle.serial_no:
stock_value = qty_after_transaction * valuation_rate
elif valuation_method == "Moving Average":
stock_value = (qty_after_transaction > 0) and \
(qty_after_transaction * valuation_rate) or 0
stock_value = qty_after_transaction * valuation_rate
else:
stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in stock_queue))
@@ -243,69 +251,72 @@ def get_serialized_values(qty_after_transaction, sle, valuation_rate):
return valuation_rate
def get_moving_average_values(qty_after_transaction, sle, valuation_rate):
def get_moving_average_values(qty_after_transaction, sle, valuation_rate, allow_zero_rate):
incoming_rate = flt(sle.incoming_rate)
actual_qty = flt(sle.actual_qty)
if not incoming_rate:
# In case of delivery/stock issue in_rate = 0 or wrong incoming rate
incoming_rate = valuation_rate
if flt(sle.actual_qty) > 0:
if qty_after_transaction < 0 and not valuation_rate:
# if negative stock, take current valuation rate as incoming rate
valuation_rate = incoming_rate
elif qty_after_transaction < 0:
# if negative stock, take current valuation rate as incoming rate
valuation_rate = incoming_rate
new_stock_qty = abs(qty_after_transaction) + actual_qty
new_stock_value = (abs(qty_after_transaction) * valuation_rate) + (actual_qty * incoming_rate)
new_stock_qty = qty_after_transaction + actual_qty
new_stock_value = qty_after_transaction * valuation_rate + actual_qty * incoming_rate
if new_stock_qty:
valuation_rate = new_stock_value / flt(new_stock_qty)
elif not valuation_rate and qty_after_transaction <= 0:
valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, allow_zero_rate)
if new_stock_qty > 0 and new_stock_value > 0:
valuation_rate = new_stock_value / flt(new_stock_qty)
elif new_stock_qty <= 0:
valuation_rate = 0.0
return abs(flt(valuation_rate))
# NOTE: val_rate is same as previous entry if new stock value is negative
return valuation_rate
def get_fifo_values(qty_after_transaction, sle, stock_queue):
def get_fifo_values(qty_after_transaction, sle, stock_queue, allow_zero_rate):
incoming_rate = flt(sle.incoming_rate)
actual_qty = flt(sle.actual_qty)
if not stock_queue:
stock_queue.append([0, 0])
if actual_qty > 0:
if not stock_queue:
stock_queue.append([0, 0])
if stock_queue[-1][0] > 0:
stock_queue.append([actual_qty, incoming_rate])
else:
qty = stock_queue[-1][0] + actual_qty
stock_queue[-1] = [qty, qty > 0 and incoming_rate or 0]
if qty == 0:
stock_queue.pop(-1)
else:
stock_queue[-1] = [qty, incoming_rate]
else:
incoming_cost = 0
qty_to_pop = abs(actual_qty)
while qty_to_pop:
if not stock_queue:
stock_queue.append([0, 0])
stock_queue.append([0, get_valuation_rate(sle.item_code, sle.warehouse, allow_zero_rate)
if qty_after_transaction <= 0 else 0])
batch = stock_queue[0]
if 0 < batch[0] <= qty_to_pop:
# if batch qty > 0
# not enough or exactly same qty in current batch, clear batch
incoming_cost += flt(batch[0]) * flt(batch[1])
qty_to_pop -= batch[0]
if qty_to_pop >= batch[0]:
# consume current batch
qty_to_pop = qty_to_pop - batch[0]
stock_queue.pop(0)
if not stock_queue and qty_to_pop:
# stock finished, qty still remains to be withdrawn
# negative stock, keep in as a negative batch
stock_queue.append([-qty_to_pop, batch[1]])
break
else:
# all from current batch
incoming_cost += flt(qty_to_pop) * flt(batch[1])
batch[0] -= qty_to_pop
# qty found in current batch
# consume it and exit
batch[0] = batch[0] - qty_to_pop
qty_to_pop = 0
stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in stock_queue))
stock_qty = sum((flt(batch[0]) for batch in stock_queue))
valuation_rate = stock_qty and (stock_value / flt(stock_qty)) or 0
valuation_rate = (stock_value / flt(stock_qty)) if stock_qty else 0
return valuation_rate
return abs(valuation_rate)
def _raise_exceptions(args, verbose=1):
deficiency = min(e["diff"] for e in _exceptions)
@@ -337,3 +348,26 @@ def get_previous_sle(args, for_update=False):
"timestamp(posting_date, posting_time) <= timestamp(%(posting_date)s, %(posting_time)s)"],
"desc", "limit 1", for_update=for_update)
return sle and sle[0] or {}
def get_valuation_rate(item_code, warehouse, allow_zero_rate=False):
last_valuation_rate = frappe.db.sql("""select valuation_rate
from `tabStock Ledger Entry`
where item_code = %s and warehouse = %s
and ifnull(valuation_rate, 0) > 0
order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse))
if not last_valuation_rate:
last_valuation_rate = frappe.db.sql("""select valuation_rate
from `tabStock Ledger Entry`
where item_code = %s and ifnull(valuation_rate, 0) > 0
order by posting_date desc, posting_time desc, name desc limit 1""", item_code)
valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
if not valuation_rate:
valuation_rate = frappe.db.get_value("Item Price", {"item_code": item_code, "buying": 1}, "price_list_rate")
if not allow_zero_rate and not valuation_rate and cint(frappe.db.get_value("Accounts Settings", None, "auto_accounting_for_stock")):
frappe.throw(_("Purchase rate for item: {0} not found, which is required to book accounting entry (expense). Please mention item price against a buying price list.").format(item_code))
return valuation_rate

View File

@@ -5,7 +5,6 @@ import frappe
from frappe import _
import json
from frappe.utils import flt, cstr, nowdate, add_days, cint
from frappe.defaults import get_global_default
from frappe.utils.email_lib import sendmail
from erpnext.accounts.utils import get_fiscal_year, FiscalYearError
@@ -94,7 +93,7 @@ def get_valuation_method(item_code):
"""get valuation method from item or default"""
val_method = frappe.db.get_value('Item', item_code, 'valuation_method')
if not val_method:
val_method = get_global_default('valuation_method') or "FIFO"
val_method = frappe.db.get_value("Stock Settings", None, "valuation_method") or "FIFO"
return val_method
def get_fifo_rate(previous_stock_queue, qty):

View File

@@ -279,7 +279,7 @@
"icon": "icon-file-text",
"idx": 1,
"is_submittable": 1,
"modified": "2014-06-23 07:55:49.200714",
"modified": "2014-09-26 11:37:41.026433",
"modified_by": "Administrator",
"module": "Support",
"name": "Maintenance Visit",
@@ -301,7 +301,7 @@
"write": 1
}
],
"search_fields": "status,maintenance_type,customer,customer_name, address,mntc_date,company,fiscal_year",
"search_fields": "status,maintenance_type,customer,customer_name,mntc_date,company,fiscal_year",
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@@ -1,6 +1,6 @@
<h2>Recurring {{ type }} Failed</h2>
<p>An error occured while creating recurring {{ type }} <b>{{ name }}</b> for <b>{{ customer }}</b>.</p>
<p>An error occured while creating recurring {{ type }} <b>{{ name }}</b> for <b>{{ party }}</b>.</p>
<p>This could be because of some invalid email ids in the {{ type }}.</p>
<p>To stop sending repetitive error notifications from the system, we have unchecked
"Convert into Recurring" field in the {{ type }} {{ name }}.</p>

View File

@@ -34,7 +34,7 @@
"<a href=""#Sales Browser/Territory"">Add / Edit</a>","<a href=""#Sales Browser/Territory""> إضافة / تحرير < / A>"
"<h4>Default Template</h4><p>Uses <a href=""http://jinja.pocoo.org/docs/templates/"">Jinja Templating</a> and all the fields of Address (including Custom Fields if any) will be available</p><pre><code>{{ address_line1 }}&lt;br&gt;{% if address_line2 %}{{ address_line2 }}&lt;br&gt;{% endif -%}{{ city }}&lt;br&gt;{% if state %}{{ state }}&lt;br&gt;{% endif -%}{% if pincode %} PIN: {{ pincode }}&lt;br&gt;{% endif -%}{{ country }}&lt;br&gt;{% if phone %}Phone: {{ phone }}&lt;br&gt;{% endif -%}{% if fax %}Fax: {{ fax }}&lt;br&gt;{% endif -%}{% if email_id %}Email: {{ email_id }}&lt;br&gt;{% endif -%}</code></pre>","<h4> افتراضي قالب </ H4> <p> ويستخدم <a href=""http://jinja.pocoo.org/docs/templates/""> جنجا القولبة </ a> و كافة الحقول من العنوان ( بما في ذلك الحقول المخصصة إن وجدت) وسوف تكون متاحة </ P> <PRE> على <code> {{}} address_line1 <BR> {٪ إذا address_line2٪} {{}} address_line2 <BR> { ENDIF٪ -٪} {{المدينة}} <BR> {٪ إذا الدولة٪} {{الدولة}} {<BR>٪ ENDIF -٪} {٪ إذا كان الرقم السري٪} PIN: {{}} الرقم السري {<BR>٪ ENDIF -٪} {{البلد}} <BR> {٪ إذا كان الهاتف٪} الهاتف: {{هاتف}} {<BR> ENDIF٪ -٪} {٪ إذا الفاكس٪} فاكس: {{}} الفاكس <BR> {٪ ENDIF -٪} {٪٪ إذا email_id} البريد الإلكتروني: {{}} email_id <BR> ؛ {٪ ENDIF -٪} </ رمز> </ قبل>"
A Customer Group exists with same name please change the Customer name or rename the Customer Group,يوجد مجموعة العملاء مع نفس الاسم الرجاء تغيير اسم العميل أو إعادة تسمية المجموعة العملاء
A Customer exists with same name,العملاء من وجود نفس الاسم مع
A Customer exists with same name,يوجد في قائمة العملاء عميل بنفس الاسم
A Lead with this email id should exist,وينبغي أن يكون هذا المعرف الرصاص مع البريد الإلكتروني موجود
A Product or Service,منتج أو خدمة
A Supplier exists with same name,وهناك مورد موجود مع نفس الاسم
@@ -47,7 +47,7 @@ Absent,غائب
Acceptance Criteria,معايير القبول
Accepted,مقبول
Accepted + Rejected Qty must be equal to Received quantity for Item {0},يجب أن يكون مقبول مرفوض + الكمية مساوية ل كمية تلقى القطعة ل {0}
Accepted Quantity,قبلت الكمية
Accepted Quantity,كمية مقبولة
Accepted Warehouse,قبلت مستودع
Account,حساب
Account Balance,رصيد حسابك
@@ -65,12 +65,12 @@ Account with child nodes cannot be converted to ledger,حساب مع العقد
Account with existing transaction can not be converted to group.,حساب مع الصفقة الحالية لا يمكن تحويلها إلى المجموعة.
Account with existing transaction can not be deleted,حساب مع الصفقة الحالية لا يمكن حذف
Account with existing transaction cannot be converted to ledger,حساب مع الصفقة الحالية لا يمكن تحويلها إلى دفتر الأستاذ
Account {0} cannot be a Group,حساب {0} لا يمكن أن تكون المجموعة
Account {0} does not belong to Company {1},حساب {0} لا تنتمي إلى شركة {1}
Account {0} cannot be a Group,حساب {0} لا يمكن أن يكون مجموعة
Account {0} does not belong to Company {1},حساب {0} لا ينتمي إلى شركة {1}
Account {0} does not belong to company: {1},حساب {0} لا تنتمي إلى الشركة: {1}
Account {0} does not exist,حساب {0} غير موجود
Account {0} has been entered more than once for fiscal year {1},حساب {0} تم إدخال أكثر من مرة للعام المالي {1}
Account {0} is frozen,حساب {0} يتم تجميد
Account {0} is frozen,حساب {0} مجمد
Account {0} is inactive,حساب {0} غير نشط
Account {0} is not valid,حساب {0} غير صالح
Account {0} must be of type 'Fixed Asset' as Item {1} is an Asset Item,"حساب {0} يجب أن تكون من النوع ' الأصول الثابتة ""كما البند {1} هو البند الأصول"
@@ -85,7 +85,7 @@ Accounting,المحاسبة
"Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.",قيد محاسبي المجمدة تصل إلى هذا التاريخ، لا أحد يمكن أن تفعل / تعديل إدخال باستثناء دور المحددة أدناه.
Accounting journal entries.,المحاسبة إدخالات دفتر اليومية.
Accounts,حسابات
Accounts Browser,حسابات متصفح
Accounts Browser,متصفح الحسابات
Accounts Frozen Upto,حسابات مجمدة لغاية
Accounts Payable,ذمم دائنة
Accounts Receivable,حسابات القبض
@@ -95,13 +95,13 @@ Active: Will extract emails from ,نشط: سيتم استخراج رسائل ا
Activity,نشاط
Activity Log,سجل النشاط
Activity Log:,النشاط المفتاح:
Activity Type,النشاط نوع
Activity Type,نوع النشاط
Actual,فعلي
Actual Budget,الميزانية الفعلية
Actual Completion Date,تاريخ الإنتهاء الفعلي
Actual Date,تاريخ الفعلية
Actual Date,التاريخ الفعلي
Actual End Date,تاريخ الإنتهاء الفعلي
Actual Invoice Date,الفعلي تاريخ الفاتورة
Actual Invoice Date,التاريخ الفعلي للفاتورة
Actual Posting Date,تاريخ النشر الفعلي
Actual Qty,الكمية الفعلية
Actual Qty (at source/target),الكمية الفعلية (في المصدر / الهدف)
@@ -112,7 +112,7 @@ Actual Start Date,تاريخ البدء الفعلي
Add,إضافة
Add / Edit Taxes and Charges,إضافة / تعديل الضرائب والرسوم
Add Child,إضافة الطفل
Add Serial No,إضافة رقم المسلسل
Add Serial No,إضافة رقم تسلسلي
Add Taxes,إضافة الضرائب
Add Taxes and Charges,إضافة الضرائب والرسوم
Add or Deduct,إضافة أو خصم
@@ -131,7 +131,7 @@ Address Line 2,العنوان سطر 2
Address Template,قالب عنوان
Address Title,عنوان عنوان
Address Title is mandatory.,عنوان عنوانها إلزامية.
Address Type,عنوان نوع
Address Type,نوع العنوان
Address master.,عنوان رئيسي.
Administrative Expenses,المصاريف الإدارية
Administrative Officer,موظف إداري
@@ -217,7 +217,7 @@ Amount to Bill,تصل إلى بيل
An Customer exists with same name,موجود على العملاء مع نفس الاسم
"An Item Group exists with same name, please change the item name or rename the item group",وجود فريق المدينة مع نفس الاسم، الرجاء تغيير اسم العنصر أو إعادة تسمية المجموعة البند
"An item exists with same name ({0}), please change the item group name or rename the item",عنصر موجود مع نفس الاسم ( {0} ) ، الرجاء تغيير اسم المجموعة البند أو إعادة تسمية هذا البند
Analyst,المحلل
Analyst,محلل
Annual,سنوي
Another Period Closing Entry {0} has been made after {1},دخول أخرى الفترة الإنتهاء {0} أحرز بعد {1}
Another Salary Structure {0} is active for employee {0}. Please make its status 'Inactive' to proceed.,"هيكل الرواتب أخرى {0} نشطة للموظف {0} . يرجى التأكد مكانتها ""غير نشطة "" والمضي قدما."
@@ -266,15 +266,15 @@ Atleast one of the Selling or Buying must be selected,يجب تحديد الاق
Atleast one warehouse is mandatory,واحدة على الاقل مستودع إلزامي
Attach Image,إرفاق صورة
Attach Letterhead,نعلق رأسية
Attach Logo,نعلق شعار
Attach Your Picture,نعلق صورتك
Attach Logo,إرفاق صورة الشعار/العلامة التجارية
Attach Your Picture,إرفاق صورتك
Attendance,الحضور
Attendance Date,تاريخ الحضور
Attendance Details,تفاصيل الحضور
Attendance From Date,الحضور من تاريخ
Attendance From Date and Attendance To Date is mandatory,الحضور من التسجيل والحضور إلى تاريخ إلزامي
Attendance To Date,الحضور إلى تاريخ
Attendance can not be marked for future dates,لا يمكن أن تكون علامة لحضور تواريخ مستقبلية
Attendance can not be marked for future dates,لا يمكن أن ىكون تاريخ الحضور تاريخ مستقبلي
Attendance for employee {0} is already marked,الحضور للموظف {0} تم وضع علامة بالفعل
Attendance record.,سجل الحضور.
Authorization Control,إذن التحكم
@@ -287,13 +287,13 @@ Automatically extract Job Applicants from a mail box ,
Automatically extract Leads from a mail box e.g.,استخراج الشراء تلقائيا من صندوق البريد على سبيل المثال
Automatically updated via Stock Entry of type Manufacture/Repack,تحديثها تلقائيا عن طريق إدخال الأسهم الصنع نوع / أعد حزم
Automotive,السيارات
Autoreply when a new mail is received,عندما رد تلقائي تلقي بريد جديد
Autoreply when a new mail is received,رد تلقائي عند تلقي بريد جديد
Available,متاح
Available Qty at Warehouse,الكمية المتاحة في مستودع
Available Stock for Packing Items,الأسهم المتاحة للتعبئة وحدات
"Available in BOM, Delivery Note, Purchase Invoice, Production Order, Purchase Order, Purchase Receipt, Sales Invoice, Sales Order, Stock Entry, Timesheet",المتاحة في BOM ، تسليم مذكرة ، شراء الفاتورة ، ترتيب الإنتاج، طلب شراء ، شراء استلام ، فاتورة المبيعات ، ترتيب المبيعات ، اسهم الدخول و الجدول الزمني
Average Age,متوسط ​​العمر
Average Commission Rate,متوسط سعر جنة
Average Commission Rate,متوسط العمولة
Average Discount,متوسط ​​الخصم
Awesome Products,المنتجات رهيبة
Awesome Services,خدمات رهيبة
@@ -331,9 +331,9 @@ Bank Clearance Summary,بنك ملخص التخليص
Bank Draft,البنك مشروع
Bank Name,اسم البنك
Bank Overdraft Account,حساب السحب على المكشوف المصرفي
Bank Reconciliation,البنك المصالحة
Bank Reconciliation Detail,البنك المصالحة تفاصيل
Bank Reconciliation Statement,بيان التسويات المصرفية
Bank Reconciliation,تسوية البنك
Bank Reconciliation Detail,تفاصيل تسوية البنك
Bank Reconciliation Statement,بيان تسوية البنك
Bank Voucher,البنك قسيمة
Bank/Cash Balance,بنك / النقد وما في حكمه
Banking,مصرفي
@@ -405,8 +405,8 @@ Bundle items at time of sale.,حزمة البنود في وقت البيع.
Business Development Manager,مدير تطوير الأعمال
Buying,شراء
Buying & Selling,شراء وبيع
Buying Amount,شراء المبلغ
Buying Settings,شراء إعدادات
Buying Amount,مبلغ الشراء
Buying Settings,إعدادات الشراء
"Buying must be checked, if Applicable For is selected as {0}",يجب أن يتم التحقق الشراء، إذا تم تحديد مطبق للك {0}
C-Form,نموذج C-
C-Form Applicable,C-نموذج قابل للتطبيق
@@ -1025,7 +1025,7 @@ Extract Emails,استخراج رسائل البريد الإلكتروني
FCFS Rate,FCFS قيم
Failed: ,فشل:
Family Background,الخلفية العائلية
Fax,بالفاكس
Fax,فاكس
Features Setup,ميزات الإعداد
Feed,أطعم
Feed Type,إطعام نوع
@@ -1041,7 +1041,7 @@ Financial / accounting year.,المالية / المحاسبة العام.
Financial Analytics,تحليلات مالية
Financial Services,الخدمات المالية
Financial Year End Date,تاريخ نهاية السنة المالية
Financial Year Start Date,السنة المالية تاريخ بدء
Financial Year Start Date,تاريخ بدء السنة المالية
Finished Goods,السلع تامة الصنع
First Name,الاسم الأول
First Responded On,أجاب أولا على
@@ -1059,7 +1059,7 @@ Food,غذاء
For Company,لشركة
For Employee,لموظف
For Employee Name,لاسم الموظف
For Price List,ل ائحة الأسعار
For Price List,لائحة الأسعار
For Production,للإنتاج
For Reference Only.,للإشارة فقط.
For Sales Invoice,لفاتورة المبيعات
@@ -1823,7 +1823,7 @@ Notify by Email on creation of automatic Material Request,إبلاغ عن طري
Number Format,عدد تنسيق
Offer Date,عرض التسجيل
Office,مكتب
Office Equipments,معدات المكاتب
Office Equipments,أدوات المكتب
Office Maintenance Expenses,مصاريف صيانة المكاتب
Office Rent,مكتب للإيجار
Old Parent,العمر الرئيسي
@@ -1840,7 +1840,7 @@ Open Production Orders,أوامر مفتوحة الانتاج
Open Tickets,تذاكر مفتوحة
Opening (Cr),افتتاح (الكروم )
Opening (Dr),افتتاح ( الدكتور )
Opening Date,فتح تاريخ
Opening Date,تاريخ الفتح
Opening Entry,فتح دخول
Opening Qty,فتح الكمية
Opening Time,يفتح من الساعة
@@ -1854,7 +1854,7 @@ Operation {0} is repeated in Operations Table,عملية {0} يتكرر في ج
Operation {0} not present in Operations Table,عملية {0} غير موجودة في جدول العمليات
Operations,عمليات
Opportunity,فرصة
Opportunity Date,الفرصة تاريخ
Opportunity Date,تاريخ الفرصة
Opportunity From,فرصة من
Opportunity Item,فرصة السلعة
Opportunity Items,فرصة الأصناف
@@ -1914,9 +1914,9 @@ Packing Slip,زلة التعبئة
Packing Slip Item,التعبئة الإغلاق زلة
Packing Slip Items,التعبئة عناصر زلة
Packing Slip(s) cancelled,زلة التعبئة (ق ) إلغاء
Page Break,الصفحة استراحة
Page Name,الصفحة اسم
Paid Amount,دفع المبلغ
Page Break,فاصل الصفحة
Page Name,اسم الصفحة
Paid Amount,المبلغ المدفوع
Paid amount + Write Off Amount can not be greater than Grand Total,المبلغ المدفوع + شطب المبلغ لا يمكن أن يكون أكبر من المجموع الكلي
Pair,زوج
Parameter,المعلمة
@@ -3128,7 +3128,7 @@ Users with this role are allowed to create / modify accounting entry before froz
Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts,يسمح للمستخدمين مع هذا الدور لضبط الحسابات المجمدة و إنشاء / تعديل القيود المحاسبية على حسابات مجمدة
Utilities,خدمات
Utility Expenses,مصاريف فائدة
Valid For Territories,صالحة للالأقاليم
Valid For Territories,صالحة للأقاليم
Valid From,صالحة من
Valid Upto,صالحة لغاية
Valid for Territories,صالحة للالأقاليم
@@ -3237,7 +3237,7 @@ Write Off Voucher,شطب قسيمة
Wrong Template: Unable to find head row.,قالب الخطأ: تعذر العثور على صف الرأس.
Year,عام
Year Closed,مغلق العام
Year End Date,نهاية التاريخ العام
Year End Date,تاريخ نهاية العام
Year Name,اسم العام
Year Start Date,تاريخ بدء العام
Year of Passing,اجتياز سنة
@@ -3261,8 +3261,8 @@ You have entered duplicate items. Please rectify and try again.,لقد دخلت
You may need to update: {0},قد تحتاج إلى تحديث : {0}
You must Save the form before proceeding,يجب حفظ النموذج قبل الشروع
Your Customer's TAX registration numbers (if applicable) or any general information,عميلك أرقام التسجيل الضريبي (إن وجدت) أو أي معلومات عامة
Your Customers,الزبائن
Your Login Id,تسجيل الدخول اسم المستخدم الخاص بك
Your Customers,العملاء
Your Login Id,تسجيل الدخول - اسم المستخدم الخاص بك
Your Products or Services,المنتجات أو الخدمات الخاصة بك
Your Suppliers,لديك موردون
Your email address,عنوان البريد الإلكتروني الخاص بك
1 and year: والسنة:
34 <a href="#Sales Browser/Territory">Add / Edit</a> <a href="#Sales Browser/Territory"> إضافة / تحرير < / A>
35 <h4>Default Template</h4><p>Uses <a href="http://jinja.pocoo.org/docs/templates/">Jinja Templating</a> and all the fields of Address (including Custom Fields if any) will be available</p><pre><code>{{ address_line1 }}&lt;br&gt;{% if address_line2 %}{{ address_line2 }}&lt;br&gt;{% endif -%}{{ city }}&lt;br&gt;{% if state %}{{ state }}&lt;br&gt;{% endif -%}{% if pincode %} PIN: {{ pincode }}&lt;br&gt;{% endif -%}{{ country }}&lt;br&gt;{% if phone %}Phone: {{ phone }}&lt;br&gt;{% endif -%}{% if fax %}Fax: {{ fax }}&lt;br&gt;{% endif -%}{% if email_id %}Email: {{ email_id }}&lt;br&gt;{% endif -%}</code></pre> <h4> افتراضي قالب </ H4> <p> ويستخدم <a href="http://jinja.pocoo.org/docs/templates/"> جنجا القولبة </ a> و كافة الحقول من العنوان ( بما في ذلك الحقول المخصصة إن وجدت) وسوف تكون متاحة </ P> <PRE> على <code> {{}} address_line1 <BR> {٪ إذا address_line2٪} {{}} address_line2 <BR> { ENDIF٪ -٪} {{المدينة}} <BR> {٪ إذا الدولة٪} {{الدولة}} {<BR>٪ ENDIF -٪} {٪ إذا كان الرقم السري٪} PIN: {{}} الرقم السري {<BR>٪ ENDIF -٪} {{البلد}} <BR> {٪ إذا كان الهاتف٪} الهاتف: {{هاتف}} {<BR> ENDIF٪ -٪} {٪ إذا الفاكس٪} فاكس: {{}} الفاكس <BR> {٪ ENDIF -٪} {٪٪ إذا email_id} البريد الإلكتروني: {{}} email_id <BR> ؛ {٪ ENDIF -٪} </ رمز> </ قبل>
36 A Customer Group exists with same name please change the Customer name or rename the Customer Group يوجد مجموعة العملاء مع نفس الاسم الرجاء تغيير اسم العميل أو إعادة تسمية المجموعة العملاء
37 A Customer exists with same name العملاء من وجود نفس الاسم مع يوجد في قائمة العملاء عميل بنفس الاسم
38 A Lead with this email id should exist وينبغي أن يكون هذا المعرف الرصاص مع البريد الإلكتروني موجود
39 A Product or Service منتج أو خدمة
40 A Supplier exists with same name وهناك مورد موجود مع نفس الاسم
47 Acceptance Criteria معايير القبول
48 Accepted مقبول
49 Accepted + Rejected Qty must be equal to Received quantity for Item {0} يجب أن يكون مقبول مرفوض + الكمية مساوية ل كمية تلقى القطعة ل {0}
50 Accepted Quantity قبلت الكمية كمية مقبولة
51 Accepted Warehouse قبلت مستودع
52 Account حساب
53 Account Balance رصيد حسابك
65 Account with existing transaction can not be converted to group. حساب مع الصفقة الحالية لا يمكن تحويلها إلى المجموعة.
66 Account with existing transaction can not be deleted حساب مع الصفقة الحالية لا يمكن حذف
67 Account with existing transaction cannot be converted to ledger حساب مع الصفقة الحالية لا يمكن تحويلها إلى دفتر الأستاذ
68 Account {0} cannot be a Group حساب {0} لا يمكن أن تكون المجموعة حساب {0} لا يمكن أن يكون مجموعة
69 Account {0} does not belong to Company {1} حساب {0} لا تنتمي إلى شركة {1} حساب {0} لا ينتمي إلى شركة {1}
70 Account {0} does not belong to company: {1} حساب {0} لا تنتمي إلى الشركة: {1}
71 Account {0} does not exist حساب {0} غير موجود
72 Account {0} has been entered more than once for fiscal year {1} حساب {0} تم إدخال أكثر من مرة للعام المالي {1}
73 Account {0} is frozen حساب {0} يتم تجميد حساب {0} مجمد
74 Account {0} is inactive حساب {0} غير نشط
75 Account {0} is not valid حساب {0} غير صالح
76 Account {0} must be of type 'Fixed Asset' as Item {1} is an Asset Item حساب {0} يجب أن تكون من النوع ' الأصول الثابتة "كما البند {1} هو البند الأصول
85 Accounting entry frozen up to this date, nobody can do / modify entry except role specified below. قيد محاسبي المجمدة تصل إلى هذا التاريخ، لا أحد يمكن أن تفعل / تعديل إدخال باستثناء دور المحددة أدناه.
86 Accounting journal entries. المحاسبة إدخالات دفتر اليومية.
87 Accounts حسابات
88 Accounts Browser حسابات متصفح متصفح الحسابات
89 Accounts Frozen Upto حسابات مجمدة لغاية
90 Accounts Payable ذمم دائنة
91 Accounts Receivable حسابات القبض
95 Activity نشاط
96 Activity Log سجل النشاط
97 Activity Log: النشاط المفتاح:
98 Activity Type النشاط نوع نوع النشاط
99 Actual فعلي
100 Actual Budget الميزانية الفعلية
101 Actual Completion Date تاريخ الإنتهاء الفعلي
102 Actual Date تاريخ الفعلية التاريخ الفعلي
103 Actual End Date تاريخ الإنتهاء الفعلي
104 Actual Invoice Date الفعلي تاريخ الفاتورة التاريخ الفعلي للفاتورة
105 Actual Posting Date تاريخ النشر الفعلي
106 Actual Qty الكمية الفعلية
107 Actual Qty (at source/target) الكمية الفعلية (في المصدر / الهدف)
112 Add إضافة
113 Add / Edit Taxes and Charges إضافة / تعديل الضرائب والرسوم
114 Add Child إضافة الطفل
115 Add Serial No إضافة رقم المسلسل إضافة رقم تسلسلي
116 Add Taxes إضافة الضرائب
117 Add Taxes and Charges إضافة الضرائب والرسوم
118 Add or Deduct إضافة أو خصم
131 Address Template قالب عنوان
132 Address Title عنوان عنوان
133 Address Title is mandatory. عنوان عنوانها إلزامية.
134 Address Type عنوان نوع نوع العنوان
135 Address master. عنوان رئيسي.
136 Administrative Expenses المصاريف الإدارية
137 Administrative Officer موظف إداري
217 An Customer exists with same name موجود على العملاء مع نفس الاسم
218 An Item Group exists with same name, please change the item name or rename the item group وجود فريق المدينة مع نفس الاسم، الرجاء تغيير اسم العنصر أو إعادة تسمية المجموعة البند
219 An item exists with same name ({0}), please change the item group name or rename the item عنصر موجود مع نفس الاسم ( {0} ) ، الرجاء تغيير اسم المجموعة البند أو إعادة تسمية هذا البند
220 Analyst المحلل محلل
221 Annual سنوي
222 Another Period Closing Entry {0} has been made after {1} دخول أخرى الفترة الإنتهاء {0} أحرز بعد {1}
223 Another Salary Structure {0} is active for employee {0}. Please make its status 'Inactive' to proceed. هيكل الرواتب أخرى {0} نشطة للموظف {0} . يرجى التأكد مكانتها "غير نشطة " والمضي قدما.
266 Atleast one warehouse is mandatory واحدة على الاقل مستودع إلزامي
267 Attach Image إرفاق صورة
268 Attach Letterhead نعلق رأسية
269 Attach Logo نعلق شعار إرفاق صورة الشعار/العلامة التجارية
270 Attach Your Picture نعلق صورتك إرفاق صورتك
271 Attendance الحضور
272 Attendance Date تاريخ الحضور
273 Attendance Details تفاصيل الحضور
274 Attendance From Date الحضور من تاريخ
275 Attendance From Date and Attendance To Date is mandatory الحضور من التسجيل والحضور إلى تاريخ إلزامي
276 Attendance To Date الحضور إلى تاريخ
277 Attendance can not be marked for future dates لا يمكن أن تكون علامة لحضور تواريخ مستقبلية لا يمكن أن ىكون تاريخ الحضور تاريخ مستقبلي
278 Attendance for employee {0} is already marked الحضور للموظف {0} تم وضع علامة بالفعل
279 Attendance record. سجل الحضور.
280 Authorization Control إذن التحكم
287 Automatically extract Leads from a mail box e.g. استخراج الشراء تلقائيا من صندوق البريد على سبيل المثال
288 Automatically updated via Stock Entry of type Manufacture/Repack تحديثها تلقائيا عن طريق إدخال الأسهم الصنع نوع / أعد حزم
289 Automotive السيارات
290 Autoreply when a new mail is received عندما رد تلقائي تلقي بريد جديد رد تلقائي عند تلقي بريد جديد
291 Available متاح
292 Available Qty at Warehouse الكمية المتاحة في مستودع
293 Available Stock for Packing Items الأسهم المتاحة للتعبئة وحدات
294 Available in BOM, Delivery Note, Purchase Invoice, Production Order, Purchase Order, Purchase Receipt, Sales Invoice, Sales Order, Stock Entry, Timesheet المتاحة في BOM ، تسليم مذكرة ، شراء الفاتورة ، ترتيب الإنتاج، طلب شراء ، شراء استلام ، فاتورة المبيعات ، ترتيب المبيعات ، اسهم الدخول و الجدول الزمني
295 Average Age متوسط ​​العمر
296 Average Commission Rate متوسط ​​سعر جنة متوسط ​​العمولة
297 Average Discount متوسط ​​الخصم
298 Awesome Products المنتجات رهيبة
299 Awesome Services خدمات رهيبة
331 Bank Draft البنك مشروع
332 Bank Name اسم البنك
333 Bank Overdraft Account حساب السحب على المكشوف المصرفي
334 Bank Reconciliation البنك المصالحة تسوية البنك
335 Bank Reconciliation Detail البنك المصالحة تفاصيل تفاصيل تسوية البنك
336 Bank Reconciliation Statement بيان التسويات المصرفية بيان تسوية البنك
337 Bank Voucher البنك قسيمة
338 Bank/Cash Balance بنك / النقد وما في حكمه
339 Banking مصرفي
405 Business Development Manager مدير تطوير الأعمال
406 Buying شراء
407 Buying & Selling شراء وبيع
408 Buying Amount شراء المبلغ مبلغ الشراء
409 Buying Settings شراء إعدادات إعدادات الشراء
410 Buying must be checked, if Applicable For is selected as {0} يجب أن يتم التحقق الشراء، إذا تم تحديد مطبق للك {0}
411 C-Form نموذج C-
412 C-Form Applicable C-نموذج قابل للتطبيق
1025 FCFS Rate FCFS قيم
1026 Failed: فشل:
1027 Family Background الخلفية العائلية
1028 Fax بالفاكس فاكس
1029 Features Setup ميزات الإعداد
1030 Feed أطعم
1031 Feed Type إطعام نوع
1041 Financial Analytics تحليلات مالية
1042 Financial Services الخدمات المالية
1043 Financial Year End Date تاريخ نهاية السنة المالية
1044 Financial Year Start Date السنة المالية تاريخ بدء تاريخ بدء السنة المالية
1045 Finished Goods السلع تامة الصنع
1046 First Name الاسم الأول
1047 First Responded On أجاب أولا على
1059 For Company لشركة
1060 For Employee لموظف
1061 For Employee Name لاسم الموظف
1062 For Price List ل ائحة الأسعار لائحة الأسعار
1063 For Production للإنتاج
1064 For Reference Only. للإشارة فقط.
1065 For Sales Invoice لفاتورة المبيعات
1823 Number Format عدد تنسيق
1824 Offer Date عرض التسجيل
1825 Office مكتب
1826 Office Equipments معدات المكاتب أدوات المكتب
1827 Office Maintenance Expenses مصاريف صيانة المكاتب
1828 Office Rent مكتب للإيجار
1829 Old Parent العمر الرئيسي
1840 Open Tickets تذاكر مفتوحة
1841 Opening (Cr) افتتاح (الكروم )
1842 Opening (Dr) افتتاح ( الدكتور )
1843 Opening Date فتح تاريخ تاريخ الفتح
1844 Opening Entry فتح دخول
1845 Opening Qty فتح الكمية
1846 Opening Time يفتح من الساعة
1854 Operation {0} not present in Operations Table عملية {0} غير موجودة في جدول العمليات
1855 Operations عمليات
1856 Opportunity فرصة
1857 Opportunity Date الفرصة تاريخ تاريخ الفرصة
1858 Opportunity From فرصة من
1859 Opportunity Item فرصة السلعة
1860 Opportunity Items فرصة الأصناف
1914 Packing Slip Item التعبئة الإغلاق زلة
1915 Packing Slip Items التعبئة عناصر زلة
1916 Packing Slip(s) cancelled زلة التعبئة (ق ) إلغاء
1917 Page Break الصفحة استراحة فاصل الصفحة
1918 Page Name الصفحة اسم اسم الصفحة
1919 Paid Amount دفع المبلغ المبلغ المدفوع
1920 Paid amount + Write Off Amount can not be greater than Grand Total المبلغ المدفوع + شطب المبلغ لا يمكن أن يكون أكبر من المجموع الكلي
1921 Pair زوج
1922 Parameter المعلمة
3128 Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts يسمح للمستخدمين مع هذا الدور لضبط الحسابات المجمدة و إنشاء / تعديل القيود المحاسبية على حسابات مجمدة
3129 Utilities خدمات
3130 Utility Expenses مصاريف فائدة
3131 Valid For Territories صالحة للالأقاليم صالحة للأقاليم
3132 Valid From صالحة من
3133 Valid Upto صالحة لغاية
3134 Valid for Territories صالحة للالأقاليم
3237 Wrong Template: Unable to find head row. قالب الخطأ: تعذر العثور على صف الرأس.
3238 Year عام
3239 Year Closed مغلق العام
3240 Year End Date نهاية التاريخ العام تاريخ نهاية العام
3241 Year Name اسم العام
3242 Year Start Date تاريخ بدء العام
3243 Year of Passing اجتياز سنة
3261 You may need to update: {0} قد تحتاج إلى تحديث : {0}
3262 You must Save the form before proceeding يجب حفظ النموذج قبل الشروع
3263 Your Customer's TAX registration numbers (if applicable) or any general information عميلك أرقام التسجيل الضريبي (إن وجدت) أو أي معلومات عامة
3264 Your Customers الزبائن العملاء
3265 Your Login Id تسجيل الدخول اسم المستخدم الخاص بك تسجيل الدخول - اسم المستخدم الخاص بك
3266 Your Products or Services المنتجات أو الخدمات الخاصة بك
3267 Your Suppliers لديك موردون
3268 Your email address عنوان البريد الإلكتروني الخاص بك

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