Compare commits
148 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c59cd46391 | ||
|
|
dda239fd49 | ||
|
|
b86a6ce26a | ||
|
|
ee212e7bb5 | ||
|
|
4952c7b3b5 | ||
|
|
4e61536f50 | ||
|
|
f2886f152b | ||
|
|
b5c56f6cea | ||
|
|
80e95388f5 | ||
|
|
daed0b655a | ||
|
|
fbb994c731 | ||
|
|
10b155a486 | ||
|
|
ce6eda709b | ||
|
|
14e1d20df3 | ||
|
|
da8a02d56e | ||
|
|
16343683d9 | ||
|
|
2bdc017aff | ||
|
|
16e05c321c | ||
|
|
23d7919865 | ||
|
|
38265efc39 | ||
|
|
2d414706dc | ||
|
|
d85e4b0d46 | ||
|
|
45d03af8f6 | ||
|
|
73a3a2a131 | ||
|
|
73804580d4 | ||
|
|
259f9779db | ||
|
|
14e0e58a7d | ||
|
|
5eb373f7b5 | ||
|
|
4595c30a7b | ||
|
|
fc2dd44694 | ||
|
|
053c54017e | ||
|
|
af473d78f2 | ||
|
|
26d096aa87 | ||
|
|
476c613ba6 | ||
|
|
2348a5f592 | ||
|
|
9d8d045c66 | ||
|
|
63914dd55b | ||
|
|
93a8042f08 | ||
|
|
b2f32dac1b | ||
|
|
577a3acaac | ||
|
|
5e46ce8a50 | ||
|
|
3b7342b7b5 | ||
|
|
531077e504 | ||
|
|
147918ed66 | ||
|
|
9bbfca9226 | ||
|
|
f1a07ff105 | ||
|
|
fb6e434315 | ||
|
|
3811d96feb | ||
|
|
1017615d02 | ||
|
|
056d627f46 | ||
|
|
663bfeacf4 | ||
|
|
e918ebd721 | ||
|
|
0dc18f0102 | ||
|
|
8ddc882a66 | ||
|
|
49365d0982 | ||
|
|
50c29c7d0f | ||
|
|
bf492122f8 | ||
|
|
38d0ed9f3a | ||
|
|
248a65b37d | ||
|
|
78f86e9385 | ||
|
|
d9f3e0c275 | ||
|
|
c40451ee2e | ||
|
|
6644406185 | ||
|
|
b3a962e121 | ||
|
|
8a28ccfa2f | ||
|
|
0a75fa09ef | ||
|
|
17a16eeaf4 | ||
|
|
3d3f0bcf54 | ||
|
|
bc8b20ae3c | ||
|
|
daf344e5fd | ||
|
|
7916792f99 | ||
|
|
27e37e68b2 | ||
|
|
a538f8a24a | ||
|
|
70ec88b733 | ||
|
|
4f0e5db216 | ||
|
|
79ed124939 | ||
|
|
c0a3cd603b | ||
|
|
7c6f990cf9 | ||
|
|
7820b171d3 | ||
|
|
0cf4cc283c | ||
|
|
9a4b173b88 | ||
|
|
6d83454237 | ||
|
|
ff231b5e62 | ||
|
|
4d74216147 | ||
|
|
b7e5ad0a31 | ||
|
|
e435592d64 | ||
|
|
7ddde8dc3a | ||
|
|
fce2881de6 | ||
|
|
e96e83d557 | ||
|
|
1b2944e871 | ||
|
|
074e73a0dd | ||
|
|
8923801881 | ||
|
|
cfafe93391 | ||
|
|
6c48ef781b | ||
|
|
adeb976a1b | ||
|
|
bb19b91ef9 | ||
|
|
bfa7f171bd | ||
|
|
b96c014daf | ||
|
|
e0c83e22d9 | ||
|
|
099ad0f5e1 | ||
|
|
cf9746dd84 | ||
|
|
b70712dbba | ||
|
|
4c057fe693 | ||
|
|
ee8ff51d60 | ||
|
|
9974b16c32 | ||
|
|
9e5f319d80 | ||
|
|
4f614b4030 | ||
|
|
3d458e973e | ||
|
|
3a19a71262 | ||
|
|
29d1a1c593 | ||
|
|
3b90de558f | ||
|
|
af7e31acb3 | ||
|
|
a2c9d35efb | ||
|
|
4c058f4056 | ||
|
|
ad67b84d43 | ||
|
|
4e81e4065b | ||
|
|
81332789cb | ||
|
|
82d7c0c9eb | ||
|
|
d60235e239 | ||
|
|
9b50b0a762 | ||
|
|
21e14c4c98 | ||
|
|
16edacebc7 | ||
|
|
75027b4d54 | ||
|
|
b0bd99266d | ||
|
|
b9e04815f8 | ||
|
|
18ccc27b1b | ||
|
|
95f1fe92e2 | ||
|
|
b783f519ee | ||
|
|
996a1010cb | ||
|
|
32a9dfd983 | ||
|
|
1394509343 | ||
|
|
0f31c36b2c | ||
|
|
14ac8f71b7 | ||
|
|
6a92d51383 | ||
|
|
7c831c3fe5 | ||
|
|
778ff463af | ||
|
|
70a31d5402 | ||
|
|
48f5fa69f3 | ||
|
|
8370cb3e71 | ||
|
|
f37d4337a4 | ||
|
|
525ab0a925 | ||
|
|
930e7f5578 | ||
|
|
d14e15d432 | ||
|
|
4d3a18890b | ||
|
|
b14401c320 | ||
|
|
b2a3f2d386 | ||
|
|
6b679c45df | ||
|
|
e3d2643f2b |
@@ -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
|
||||
|
||||
@@ -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
11
ci/fix-mariadb.sh
Executable 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
22
ci/my_config.h.patch
Normal 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
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = '4.4.1'
|
||||
__version__ = '4.9.2'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -276,26 +289,25 @@ class JournalVoucher(AccountsController):
|
||||
|
||||
def set_print_format_fields(self):
|
||||
for d in self.get('entries'):
|
||||
result = frappe.db.get_value("Account", d.account,
|
||||
["account_type", "master_type"])
|
||||
acc = frappe.db.get_value("Account", d.account, ["account_type", "master_type"], as_dict=1)
|
||||
|
||||
if not result:
|
||||
continue
|
||||
if not acc: continue
|
||||
|
||||
account_type, master_type = result
|
||||
|
||||
if master_type in ['Supplier', 'Customer']:
|
||||
if acc.master_type in ['Supplier', 'Customer']:
|
||||
if not self.pay_to_recd_from:
|
||||
self.pay_to_recd_from = frappe.db.get_value(master_type,
|
||||
' - '.join(d.account.split(' - ')[:-1]),
|
||||
master_type == 'Customer' and 'customer_name' or 'supplier_name')
|
||||
self.pay_to_recd_from = frappe.db.get_value(acc.master_type, ' - '.join(d.account.split(' - ')[:-1]),
|
||||
acc.master_type == 'Customer' and 'customer_name' or 'supplier_name')
|
||||
if self.voucher_type in ["Credit Note", "Debit Note"]:
|
||||
self.set_total_amount(d.debit or d.credit)
|
||||
|
||||
if account_type in ['Bank', 'Cash']:
|
||||
company_currency = get_company_currency(self.company)
|
||||
amt = flt(d.debit) and d.debit or d.credit
|
||||
self.total_amount = fmt_money(amt, currency=company_currency)
|
||||
from frappe.utils import money_in_words
|
||||
self.total_amount_in_words = money_in_words(amt, company_currency)
|
||||
if acc.account_type in ['Bank', 'Cash']:
|
||||
self.set_total_amount(d.debit or d.credit)
|
||||
|
||||
def set_total_amount(self, amt):
|
||||
company_currency = get_company_currency(self.company)
|
||||
self.total_amount = fmt_money(amt, currency=company_currency)
|
||||
from frappe.utils import money_in_words
|
||||
self.total_amount_in_words = money_in_words(amt, company_currency)
|
||||
|
||||
def check_credit_days(self):
|
||||
date_diff = 0
|
||||
@@ -528,9 +540,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))
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -96,13 +96,14 @@ class PaymentReconciliation(Document):
|
||||
|
||||
payment_amount = payment_amount[0][0] if payment_amount else 0
|
||||
|
||||
if d.invoice_amount > payment_amount:
|
||||
if d.invoice_amount - payment_amount > 0.005:
|
||||
non_reconciled_invoices.append({
|
||||
'voucher_no': d.voucher_no,
|
||||
'voucher_type': d.voucher_type,
|
||||
'posting_date': d.posting_date,
|
||||
'invoice_amount': flt(d.invoice_amount),
|
||||
'outstanding_amount': d.invoice_amount - payment_amount})
|
||||
'outstanding_amount': flt(d.invoice_amount - payment_amount, 2)
|
||||
})
|
||||
|
||||
self.add_invoice_entries(non_reconciled_invoices)
|
||||
|
||||
@@ -124,7 +125,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 +135,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 +163,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 ""
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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" """)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
"doc_type": "Journal Voucher",
|
||||
"docstatus": 0,
|
||||
"doctype": "Print Format",
|
||||
"html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header -%}\n\n<div class=\"page-break\">\n {%- if not doc.get(\"print_heading\") and not doc.get(\"select_print_heading\") \n and doc.set(\"select_print_heading\", _(\"Credit Note\")) -%}{%- endif -%}\n {{ add_header(0, 1, doc, letter_head, no_letterhead) }}\n\n {%- for label, value in (\n (_(\"Credit To\"), doc.pay_to_recd_from),\n (_(\"Date\"), frappe.utils.formatdate(doc.voucher_date)),\n (_(\"Amount\"), \"<strong>\" + doc.total_amount + \"</strong><br>\" + (doc.total_amount_in_words or \"\") + \"<br>\"),\n (_(\"Remarks\"), doc.remark)\n ) -%}\n\n <div class=\"row\">\n <div class=\"col-sm-3\"><label class=\"text-right\">{{ label }}</label></div>\n <div class=\"col-sm-9\">{{ value }}</div>\n </div>\n\n {%- endfor -%}\n\n <hr>\n <br>\n <p class=\"strong\">\n {{ _(\"For\") }} {{ doc.company }},<br>\n <br>\n <br>\n <br>\n {{ _(\"Authorized Signatory\") }}\n </p>\n</div>\n\n\n",
|
||||
"html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header -%}\n\n<div class=\"page-break\">\n {%- if not doc.get(\"print_heading\") and not doc.get(\"select_print_heading\") \n and doc.set(\"select_print_heading\", _(\"Credit Note\")) -%}{%- endif -%}\n {{ add_header(0, 1, doc, letter_head, no_letterhead) }}\n\n {%- for label, value in (\n (_(\"Credit To\"), doc.pay_to_recd_from),\n (_(\"Date\"), frappe.utils.formatdate(doc.voucher_date)),\n (_(\"Amount\"), \"<strong>\" + frappe.utils.cstr(doc.total_amount) + \"</strong><br>\" + (doc.total_amount_in_words or \"\") + \"<br>\"),\n (_(\"Remarks\"), doc.remark)\n ) -%}\n\n <div class=\"row\">\n <div class=\"col-sm-3\"><label class=\"text-right\">{{ label }}</label></div>\n <div class=\"col-sm-9\">{{ value }}</div>\n </div>\n\n {%- endfor -%}\n\n <hr>\n <br>\n <p class=\"strong\">\n {{ _(\"For\") }} {{ doc.company }},<br>\n <br>\n <br>\n <br>\n {{ _(\"Authorized Signatory\") }}\n </p>\n</div>\n\n\n",
|
||||
"idx": 2,
|
||||
"modified": "2014-08-29 13:20:15.789533",
|
||||
"modified": "2014-10-17 17:20:02.740340",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Credit Note",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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.1"
|
||||
app_version = "4.9.2"
|
||||
|
||||
error_report_email = "support@erpnext.com"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
16
erpnext/patches/v4_2/recalculate_bom_cost.py
Normal file
16
erpnext/patches/v4_2/recalculate_bom_cost.py
Normal 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
|
||||
9
erpnext/patches/v4_2/seprate_manufacture_and_repack.py
Normal file
9
erpnext/patches/v4_2/seprate_manufacture_and_repack.py
Normal 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,"")="" """)
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"allow_import": 1,
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-04-03 16:38:41",
|
||||
"description": "Log of Activities performed by users against Tasks that can be used for tracking time, billing.",
|
||||
@@ -25,14 +25,6 @@
|
||||
"read_only": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "hours",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Hours",
|
||||
"permlevel": 0,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "to_time",
|
||||
"fieldtype": "Datetime",
|
||||
@@ -42,6 +34,14 @@
|
||||
"read_only": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "hours",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Hours",
|
||||
"permlevel": 0,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
@@ -151,7 +151,7 @@
|
||||
"icon": "icon-time",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"modified": "2014-08-04 05:23:15.740050",
|
||||
"modified": "2014-10-22 16:53:26.993828",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Time Log",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ class Quotation(SellingController):
|
||||
self.validate_order_type()
|
||||
self.validate_for_items()
|
||||
self.validate_uom_is_integer("stock_uom", "qty")
|
||||
self.quotation_to = "Customer" if self.customer else "Lead"
|
||||
self.validate_quotation_to()
|
||||
|
||||
def has_sales_order(self):
|
||||
return frappe.db.get_value("Sales Order Item", {"prevdoc_docname": self.name, "docstatus": 1})
|
||||
@@ -54,6 +54,13 @@ class Quotation(SellingController):
|
||||
if is_sales_item == 'No':
|
||||
frappe.throw(_("Item {0} must be Sales Item").format(d.item_code))
|
||||
|
||||
def validate_quotation_to(self):
|
||||
if self.customer:
|
||||
self.quotation_to = "Customer"
|
||||
self.lead = None
|
||||
elif self.lead:
|
||||
self.quotation_to = "Lead"
|
||||
|
||||
def update_opportunity(self):
|
||||
for opportunity in list(set([d.prevdoc_docname for d in self.get("quotation_details")])):
|
||||
if opportunity:
|
||||
@@ -139,8 +146,8 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
|
||||
return doclist
|
||||
|
||||
def _make_customer(source_name, ignore_permissions=False):
|
||||
quotation = frappe.db.get_value("Quotation", source_name, ["lead", "order_type"])
|
||||
if quotation and quotation[0]:
|
||||
quotation = frappe.db.get_value("Quotation", source_name, ["lead", "order_type", "customer"])
|
||||
if quotation and quotation[0] and not quotation[2]:
|
||||
lead_name = quotation[0]
|
||||
customer_name = frappe.db.get_value("Customer", {"lead_name": lead_name},
|
||||
["name", "customer_name"], as_dict=True)
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
@@ -587,16 +587,18 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
|
||||
});
|
||||
|
||||
frappe.ui.form.on(cur_frm.doctype,"project_name", function(frm) {
|
||||
frappe.call({
|
||||
method:'erpnext.projects.doctype.project.project.get_cost_center_name' ,
|
||||
args: { project_name: frm.doc.project_name },
|
||||
callback: function(r, rt) {
|
||||
if(!r.exc) {
|
||||
$.each(frm.doc[cur_frm.cscript.fname] || [], function(i, row) {
|
||||
frappe.model.set_value(row.doctype, row.name, "cost_center", r.message);
|
||||
msgprint(__("Cost Center For Item with Item Code '"+row.item_name+"' has been Changed to "+ r.message));
|
||||
})
|
||||
if(in_list(["Delivery Note", "Sales Invoice"], frm.doc.doctype)) {
|
||||
frappe.call({
|
||||
method:'erpnext.projects.doctype.project.project.get_cost_center_name' ,
|
||||
args: { project_name: frm.doc.project_name },
|
||||
callback: function(r, rt) {
|
||||
if(!r.exc) {
|
||||
$.each(frm.doc[cur_frm.cscript.fname] || [], function(i, row) {
|
||||
frappe.model.set_value(row.doctype, row.name, "cost_center", r.message);
|
||||
msgprint(__("Cost Center For Item with Item Code '"+row.item_name+"' has been Changed to "+ r.message));
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -40,7 +40,7 @@ class AuthorizationControl(TransactionBase):
|
||||
chk = 1
|
||||
add_cond1,add_cond2 = '',''
|
||||
if based_on == 'Itemwise Discount':
|
||||
add_cond1 += " and master_name = '"+cstr(item).replace("'", "\'")+"'"
|
||||
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""" %
|
||||
@@ -76,7 +76,7 @@ class AuthorizationControl(TransactionBase):
|
||||
add_cond = ''
|
||||
auth_value = av_dis
|
||||
|
||||
if val == 1: add_cond += " and system_user = '"+session['user'].replace("'", "\'")+"'"
|
||||
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,'') = ''"
|
||||
|
||||
@@ -85,7 +85,7 @@ class AuthorizationControl(TransactionBase):
|
||||
if doc_obj:
|
||||
if doc_obj.doctype == 'Sales Invoice': customer = doc_obj.customer
|
||||
else: customer = doc_obj.customer_name
|
||||
add_cond = " and master_name = '"+cstr(customer).replace("'", "\'")+"'"
|
||||
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):
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)):
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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"))
|
||||
14
erpnext/stock/doctype/batch/test_batch.py
Normal file
14
erpnext/stock/doctype/batch/test_batch.py
Normal 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"})):
|
||||
|
||||
@@ -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`"]
|
||||
};
|
||||
|
||||
@@ -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')
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
@@ -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 = ""
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -108,6 +108,6 @@
|
||||
],
|
||||
"posting_date": "2013-01-25",
|
||||
"posting_time": "17:14:24",
|
||||
"purpose": "Manufacture/Repack"
|
||||
"purpose": "Repack"
|
||||
}
|
||||
]
|
||||
@@ -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",
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"))
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
]
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Average "age" of an Item in a particular Warehouse based on First-in-first-out (FIFO).
|
||||
@@ -1 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
@@ -1,183 +0,0 @@
|
||||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
frappe.pages['stock-ageing'].onload = function(wrapper) {
|
||||
frappe.ui.make_app_page({
|
||||
parent: wrapper,
|
||||
title: __('Stock Ageing'),
|
||||
single_column: true
|
||||
});
|
||||
|
||||
new erpnext.StockAgeing(wrapper);
|
||||
|
||||
|
||||
wrapper.appframe.add_module_icon("Stock")
|
||||
|
||||
}
|
||||
|
||||
frappe.require("assets/erpnext/js/stock_grid_report.js");
|
||||
|
||||
erpnext.StockAgeing = erpnext.StockGridReport.extend({
|
||||
init: function(wrapper) {
|
||||
this._super({
|
||||
title: __("Stock Ageing"),
|
||||
page: wrapper,
|
||||
parent: $(wrapper).find('.layout-main'),
|
||||
appframe: wrapper.appframe,
|
||||
doctypes: ["Item", "Warehouse", "Stock Ledger Entry", "Item Group", "Brand", "Serial No"],
|
||||
})
|
||||
},
|
||||
setup_columns: function() {
|
||||
this.columns = [
|
||||
{id: "name", name: __("Item"), field: "name", width: 300,
|
||||
link_formatter: {
|
||||
open_btn: true,
|
||||
doctype: '"Item"'
|
||||
}},
|
||||
{id: "item_name", name: __("Item Name"), field: "item_name",
|
||||
width: 100, formatter: this.text_formatter},
|
||||
{id: "description", name: __("Description"), field: "description",
|
||||
width: 200, formatter: this.text_formatter},
|
||||
{id: "brand", name: __("Brand"), field: "brand", width: 100},
|
||||
{id: "average_age", name: __("Average Age"), field: "average_age",
|
||||
formatter: this.currency_formatter},
|
||||
{id: "earliest", name: __("Earliest"), field: "earliest",
|
||||
formatter: this.currency_formatter},
|
||||
{id: "latest", name: __("Latest"), field: "latest",
|
||||
formatter: this.currency_formatter},
|
||||
{id: "stock_uom", name: "UOM", field: "stock_uom", width: 100},
|
||||
];
|
||||
},
|
||||
filters: [
|
||||
{fieldtype:"Select", label: __("Warehouse"), link:"Warehouse",
|
||||
default_value: "Select Warehouse..."},
|
||||
{fieldtype:"Select", label: __("Brand"), link:"Brand",
|
||||
default_value: "Select Brand...", filter: function(val, item, opts) {
|
||||
return val == opts.default_value || item.brand == val;
|
||||
}, link_formatter: {filter_input: "brand"}},
|
||||
{fieldtype:"Select", label: __("Plot By"),
|
||||
options: ["Average Age", "Earliest", "Latest"]},
|
||||
{fieldtype:"Date", label: __("To Date")},
|
||||
{fieldtype:"Button", label: __("Refresh"), icon:"icon-refresh icon-white"},
|
||||
{fieldtype:"Button", label: __("Reset Filters"), icon: "icon-filter"}
|
||||
],
|
||||
setup_filters: function() {
|
||||
var me = this;
|
||||
this._super();
|
||||
this.trigger_refresh_on_change(["warehouse", "plot_by", "brand"]);
|
||||
this.show_zero_check();
|
||||
},
|
||||
init_filter_values: function() {
|
||||
this._super();
|
||||
this.filter_inputs.to_date.val(dateutil.obj_to_user(new Date()));
|
||||
},
|
||||
prepare_data: function() {
|
||||
var me = this;
|
||||
|
||||
if(!this.data) {
|
||||
me._data = frappe.report_dump.data["Item"];
|
||||
me.item_by_name = me.make_name_map(me._data);
|
||||
}
|
||||
|
||||
this.data = [].concat(this._data);
|
||||
|
||||
this.serialized_buying_rates = this.get_serialized_buying_rates();
|
||||
|
||||
$.each(this.data, function(i, d) {
|
||||
me.reset_item_values(d);
|
||||
});
|
||||
|
||||
this.prepare_balances();
|
||||
|
||||
// filter out brand
|
||||
this.data = $.map(this.data, function(d) {
|
||||
return me.apply_filter(d, "brand") ? d : null;
|
||||
});
|
||||
|
||||
// filter out rows with zero values
|
||||
this.data = $.map(this.data, function(d) {
|
||||
return me.apply_zero_filter(null, d, null, me) ? d : null;
|
||||
});
|
||||
},
|
||||
prepare_balances: function() {
|
||||
var me = this;
|
||||
var to_date = dateutil.str_to_obj(this.to_date);
|
||||
var data = frappe.report_dump.data["Stock Ledger Entry"];
|
||||
|
||||
this.item_warehouse = {};
|
||||
|
||||
for(var i=0, j=data.length; i<j; i++) {
|
||||
var sl = data[i];
|
||||
var posting_date = dateutil.str_to_obj(sl.posting_date);
|
||||
|
||||
if(me.is_default("warehouse") ? true : me.warehouse == sl.warehouse) {
|
||||
var wh = me.get_item_warehouse(sl.warehouse, sl.item_code);
|
||||
|
||||
// call diff to build fifo stack in item_warehouse
|
||||
var diff = me.get_value_diff(wh, sl, true);
|
||||
|
||||
if(posting_date > to_date)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$.each(me.data, function(i, item) {
|
||||
var full_fifo_stack = [];
|
||||
if(me.is_default("warehouse")) {
|
||||
$.each(me.item_warehouse[item.name] || {}, function(i, wh) {
|
||||
full_fifo_stack = full_fifo_stack.concat(wh.fifo_stack || [])
|
||||
});
|
||||
} else {
|
||||
full_fifo_stack = me.get_item_warehouse(me.warehouse, item.name).fifo_stack || [];
|
||||
}
|
||||
|
||||
var age_qty = total_qty = 0.0;
|
||||
var min_age = max_age = null;
|
||||
|
||||
$.each(full_fifo_stack, function(i, batch) {
|
||||
var batch_age = dateutil.get_diff(me.to_date, batch[2]);
|
||||
age_qty += batch_age * batch[0];
|
||||
total_qty += batch[0];
|
||||
max_age = Math.max(max_age, batch_age);
|
||||
if(min_age===null) min_age=batch_age;
|
||||
else min_age = Math.min(min_age, batch_age);
|
||||
});
|
||||
|
||||
item.average_age = total_qty.toFixed(2)==0.0 ? 0
|
||||
: (age_qty / total_qty).toFixed(2);
|
||||
item.earliest = max_age || 0.0;
|
||||
item.latest = min_age || 0.0;
|
||||
});
|
||||
|
||||
this.data = this.data.sort(function(a, b) {
|
||||
var sort_by = me.plot_by.replace(" ", "_").toLowerCase();
|
||||
return b[sort_by] - a[sort_by];
|
||||
});
|
||||
},
|
||||
get_plot_data: function() {
|
||||
var data = [];
|
||||
var me = this;
|
||||
|
||||
data.push({
|
||||
label: me.plot_by,
|
||||
data: $.map(me.data, function(item, idx) {
|
||||
return [[idx+1, item[me.plot_by.replace(" ", "_").toLowerCase() ]]]
|
||||
}),
|
||||
bars: {show: true},
|
||||
});
|
||||
|
||||
return data.length ? data : false;
|
||||
},
|
||||
get_plot_options: function() {
|
||||
var me = this;
|
||||
return {
|
||||
grid: { hoverable: true, clickable: true },
|
||||
xaxis: {
|
||||
ticks: $.map(me.data, function(item, idx) { return [[idx+1, item.name]] }),
|
||||
max: 15
|
||||
},
|
||||
series: { downsample: { threshold: 1000 } }
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"creation": "2012-09-21 20:15:14.000000",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"icon": "icon-table",
|
||||
"idx": 1,
|
||||
"modified": "2013-07-11 14:44:08.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "stock-ageing",
|
||||
"owner": "Administrator",
|
||||
"page_name": "stock-ageing",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Analytics"
|
||||
},
|
||||
{
|
||||
"role": "Material Manager"
|
||||
}
|
||||
],
|
||||
"standard": "Yes",
|
||||
"title": "Stock Ageing"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
Stock balances on a particular day, per warehouse.
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,39 @@ 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 = {}
|
||||
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(item_details[d.name].get("qty_after_transaction", 0))
|
||||
|
||||
if d.actual_qty > 0:
|
||||
fifo_queue.append([d.actual_qty, d.posting_date])
|
||||
else:
|
||||
@@ -52,7 +56,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 +65,14 @@ def get_fifo_queue(filters):
|
||||
batch[0] -= qty_to_pop
|
||||
qty_to_pop = 0
|
||||
|
||||
item_details[d.name]["qty_after_transaction"] = 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 +83,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 ""
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user