Merge branch 'develop' into develop

This commit is contained in:
Nabin Hait 2019-09-05 14:59:16 +05:30 committed by GitHub
commit eb2ab3bf76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 3915 additions and 6199 deletions

View File

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

View File

@ -8,7 +8,7 @@ def get_data():
'fieldname': 'bank',
'transactions': [
{
'label': _('Bank Deatils'),
'label': _('Bank Details'),
'items': ['Bank Account', 'Bank Guarantee']
}
]

View File

@ -11,32 +11,32 @@ from erpnext.buying.doctype.purchase_order.test_purchase_order import create_pur
from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
class TestBudget(unittest.TestCase):
class TestBudget(unittest.TestCase):
def test_monthly_budget_crossed_ignore(self):
set_total_expense_zero("2013-02-28", "Cost Center")
budget = make_budget(budget_against="Cost Center")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True)
self.assertTrue(frappe.db.get_value("GL Entry",
{"voucher_type": "Journal Entry", "voucher_no": jv.name}))
budget.cancel()
def test_monthly_budget_crossed_stop1(self):
set_total_expense_zero("2013-02-28", "Cost Center")
budget = make_budget(budget_against="Cost Center")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-02-28")
self.assertRaises(BudgetError, jv.submit)
budget.load_from_db()
budget.cancel()
@ -46,7 +46,7 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-03-02")
@ -117,14 +117,14 @@ class TestBudget(unittest.TestCase):
set_total_expense_zero("2013-02-28", "Project")
budget = make_budget(budget_against="Project")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date="2013-02-28")
self.assertRaises(BudgetError, jv.submit)
budget.load_from_db()
budget.cancel()
@ -132,31 +132,31 @@ class TestBudget(unittest.TestCase):
set_total_expense_zero("2013-02-28", "Cost Center")
budget = make_budget(budget_against="Cost Center")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 150000, "_Test Cost Center - _TC", posting_date="2013-03-28")
self.assertRaises(BudgetError, jv.submit)
budget.cancel()
def test_yearly_budget_crossed_stop2(self):
set_total_expense_zero("2013-02-28", "Project")
budget = make_budget(budget_against="Project")
jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 150000, "_Test Cost Center - _TC", project="_Test Project", posting_date="2013-03-28")
self.assertRaises(BudgetError, jv.submit)
budget.cancel()
def test_monthly_budget_on_cancellation1(self):
set_total_expense_zero("2013-02-28", "Cost Center")
budget = make_budget(budget_against="Cost Center")
jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True)
@ -170,9 +170,9 @@ class TestBudget(unittest.TestCase):
{"voucher_type": "Journal Entry", "voucher_no": jv2.name}))
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
self.assertRaises(BudgetError, jv1.cancel)
budget.load_from_db()
budget.cancel()
@ -180,7 +180,7 @@ class TestBudget(unittest.TestCase):
set_total_expense_zero("2013-02-28", "Project")
budget = make_budget(budget_against="Project")
jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True, project="_Test Project")
@ -194,16 +194,16 @@ class TestBudget(unittest.TestCase):
{"voucher_type": "Journal Entry", "voucher_no": jv2.name}))
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
self.assertRaises(BudgetError, jv1.cancel)
budget.load_from_db()
budget.cancel()
def test_monthly_budget_against_group_cost_center(self):
set_total_expense_zero("2013-02-28", "Cost Center")
set_total_expense_zero("2013-02-28", "Cost Center", "_Test Cost Center 2 - _TC")
budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
@ -211,7 +211,7 @@ class TestBudget(unittest.TestCase):
"_Test Bank - _TC", 40000, "_Test Cost Center 2 - _TC", posting_date="2013-02-28")
self.assertRaises(BudgetError, jv.submit)
budget.load_from_db()
budget.cancel()
@ -239,8 +239,6 @@ class TestBudget(unittest.TestCase):
budget.cancel()
jv.cancel()
frappe.delete_doc('Journal Entry', jv.name)
frappe.delete_doc('Cost Center', cost_center)
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
if budget_against_field == "Project":
@ -256,7 +254,7 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again
"budget_against_field": budget_against_field,
"budget_against": budget_against
}))
if existing_expense:
if budget_against_field == "Cost Center":
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
@ -281,13 +279,13 @@ def make_budget(**args):
frappe.db.sql("delete from `tabBudget Account` where parent = %(name)s", d)
budget = frappe.new_doc("Budget")
if budget_against == "Project":
budget.project = "_Test Project"
else:
budget.cost_center =cost_center or "_Test Cost Center - _TC"
budget.fiscal_year = "_Test Fiscal Year 2013"
budget.monthly_distribution = "_Test Distribution"
budget.company = "_Test Company"
@ -299,7 +297,7 @@ def make_budget(**args):
"account": "_Test Account Cost for Goods Sold - _TC",
"budget_amount": 100000
})
if args.applicable_on_material_request:
budget.applicable_on_material_request = 1
budget.action_if_annual_budget_exceeded_on_mr = args.action_if_annual_budget_exceeded_on_mr or 'Warn'

View File

@ -49,7 +49,6 @@ class TestLoyaltyProgram(unittest.TestCase):
# cancel and delete
for d in [si_redeem, si_original]:
d.cancel()
frappe.delete_doc('Sales Invoice', d.name)
def test_loyalty_points_earned_multiple_tier(self):
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty")
@ -91,7 +90,6 @@ class TestLoyaltyProgram(unittest.TestCase):
# cancel and delete
for d in [si_redeem, si_original]:
d.cancel()
frappe.delete_doc('Sales Invoice', d.name)
def test_cancel_sales_invoice(self):
''' cancelling the sales invoice should cancel the earned points'''
@ -143,7 +141,6 @@ class TestLoyaltyProgram(unittest.TestCase):
d.cancel()
except frappe.TimestampMismatchError:
frappe.get_doc('Sales Invoice', d.name).cancel()
frappe.delete_doc('Sales Invoice', d.name)
def test_loyalty_points_for_dashboard(self):
doc = frappe.get_doc('Customer', 'Test Loyalty Customer')

View File

@ -947,10 +947,15 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
paid_amount = abs(outstanding_amount)
if bank_amount:
received_amount = bank_amount
else:
received_amount = paid_amount * doc.conversion_rate
else:
received_amount = abs(outstanding_amount)
if bank_amount:
paid_amount = bank_amount
else:
# if party account currency and bank currency is different then populate paid amount as well
paid_amount = received_amount * doc.conversion_rate
pe = frappe.new_doc("Payment Entry")
pe.payment_type = payment_type

View File

@ -1,7 +1,6 @@
cur_frm.add_fetch("payment_gateway", "payment_account", "payment_account")
cur_frm.add_fetch("payment_gateway", "payment_gateway", "payment_gateway")
cur_frm.add_fetch("payment_gateway", "message", "message")
cur_frm.add_fetch("payment_gateway", "payment_url_message", "payment_url_message")
cur_frm.add_fetch("payment_gateway_account", "payment_account", "payment_account")
cur_frm.add_fetch("payment_gateway_account", "payment_gateway", "payment_gateway")
cur_frm.add_fetch("payment_gateway_account", "message", "message")
frappe.ui.form.on("Payment Request", "onload", function(frm, dt, dn){
if (frm.doc.reference_doctype) {

View File

@ -227,8 +227,8 @@ def get_contacts(customers):
customers = [frappe._dict({'name': customers})]
for data in customers:
contact = frappe.db.sql(""" select email_id, phone, mobile_no from `tabContact`
where is_primary_contact =1 and name in
contact = frappe.db.sql(""" select email_id, phone from `tabContact`
where is_primary_contact=1 and name in
(select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s
and parenttype = 'Contact')""", data.name, as_dict=1)
if contact:

View File

@ -818,7 +818,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_gl_entries[i][2], gle.credit)
si.cancel()
frappe.delete_doc('Sales Invoice', si.name)
gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)

View File

@ -48,7 +48,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
#delete invoices to avoid clashing
for d in invoices:
d.cancel()
frappe.delete_doc("Purchase Invoice", d.name)
def test_single_threshold_tds(self):
invoices = []
@ -83,7 +82,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
# delete invoices to avoid clashing
for d in invoices:
d.cancel()
frappe.delete_doc("Purchase Invoice", d.name)
def test_single_threshold_tds_with_previous_vouchers(self):
invoices = []
@ -102,7 +100,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
# delete invoices to avoid clashing
for d in invoices:
d.cancel()
frappe.delete_doc("Purchase Invoice", d.name)
def create_purchase_invoice(**args):
# return sales invoice doc object

View File

@ -816,7 +816,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
contact = me.contacts[data.name];
if(reg.test(data.name.toLowerCase())
|| reg.test(data.customer_name.toLowerCase())
|| (contact && reg.test(contact["mobile_no"]))
|| (contact && reg.test(contact["phone"]))
|| (data.customer_group && reg.test(data.customer_group.toLowerCase()))){
return data;
@ -834,7 +833,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
if(contact && !c['phone']) {
c["phone"] = contact["phone"];
c["email_id"] = contact["email_id"];
c["mobile_no"] = contact["mobile_no"];
}
me.customers_mapper.push({
@ -844,10 +842,9 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
customer_group: c.customer_group,
territory: c.territory,
phone: contact ? contact["phone"] : '',
mobile_no: contact ? contact["mobile_no"] : '',
email_id: contact ? contact["email_id"] : '',
searchtext: ['customer_name', 'customer_group', 'name', 'value',
'label', 'email_id', 'phone', 'mobile_no']
'label', 'email_id', 'phone']
.map(key => c[key]).join(' ')
.toLowerCase()
});
@ -1121,7 +1118,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
if (key) {
return $.grep(this.items_list, function (item) {
if (search_status) {
if (in_list(me.batch_no_data[item.item_code], me.search_item.$input.val())) {
if (me.batch_no_data[item.item_code] &&
in_list(me.batch_no_data[item.item_code], me.search_item.$input.val())) {
search_status = false;
return me.item_batch_no[item.item_code] = me.search_item.$input.val()
} else if (me.serial_no_data[item.item_code]
@ -1129,7 +1127,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
search_status = false;
me.item_serial_no[item.item_code] = [me.search_item.$input.val(), me.serial_no_data[item.item_code][me.search_item.$input.val()]]
return true
} else if (in_list(me.barcode_data[item.item_code], me.search_item.$input.val())) {
} else if (me.barcode_data[item.item_code] &&
in_list(me.barcode_data[item.item_code], me.search_item.$input.val())) {
search_status = false;
return true;
} else if (reg.test(item.item_code.toLowerCase()) || (item.description && reg.test(item.description.toLowerCase())) ||

View File

@ -365,7 +365,7 @@ def validate_due_date(posting_date, due_date, party_type, party, company=None, b
.format(formatdate(default_due_date)))
@frappe.whitelist()
def get_address_tax_category(tax_category, billing_address=None, shipping_address=None):
def get_address_tax_category(tax_category=None, billing_address=None, shipping_address=None):
addr_tax_category_from = frappe.db.get_single_value("Accounts Settings", "determine_address_tax_category_from")
if addr_tax_category_from == "Shipping Address":
if shipping_address:

View File

@ -40,7 +40,7 @@
</div>
</div>
{% if(filters.show_pdc_in_print) { %}
{% if(filters.show_future_payments) { %}
{% var balance_row = data.slice(-1).pop();
var range1 = report.columns[11].label;
var range2 = report.columns[12].label;
@ -122,22 +122,22 @@
<th style="width: 10%">{%= __("Date") %}</th>
<th style="width: 4%">{%= __("Age (Days)") %}</th>
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %}
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
<th style="width: 14%">{%= __("Reference") %}</th>
<th style="width: 10%">{%= __("Sales Person") %}</th>
{% } else { %}
<th style="width: 24%">{%= __("Reference") %}</th>
{% } %}
{% if(!filters.show_pdc_in_print) { %}
{% if(!filters.show_future_payments) { %}
<th style="width: 20%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
{% } %}
<th style="width: 10%; text-align: right">{%= __("Invoiced Amount") %}</th>
{% if(!filters.show_pdc_in_print) { %}
{% if(!filters.show_future_payments) { %}
<th style="width: 10%; text-align: right">{%= __("Paid Amount") %}</th>
<th style="width: 10%; text-align: right">{%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %}</th>
{% } %}
<th style="width: 10%; text-align: right">{%= __("Outstanding Amount") %}</th>
{% if(filters.show_pdc_in_print) { %}
{% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %}
<th style="width: 12%">{%= __("Customer LPO No.") %}</th>
{% } %}
@ -162,18 +162,18 @@
<td>{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td>
<td style="text-align: right">{%= data[i][__("Age (Days)")] %}</td>
<td>
{% if(!filters.show_pdc_in_print) { %}
{% if(!filters.show_future_payments) { %}
{%= data[i]["voucher_type"] %}
<br>
{% } %}
{%= data[i]["voucher_no"] %}
</td>
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %}
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
<td>{%= data[i]["sales_person"] %}</td>
{% } %}
{% if(!filters.show_pdc_in_print) { %}
{% if(!filters.show_future_payments) { %}
<td>
{% if(!(filters.customer || filters.supplier)) { %}
{%= data[i][__("Customer")] || data[i][__("Supplier")] %}
@ -195,7 +195,7 @@
<td style="text-align: right">
{%= format_currency(data[i]["invoiced_amount"], data[i]["currency"]) %}</td>
{% if(!filters.show_pdc_in_print) { %}
{% if(!filters.show_future_payments) { %}
<td style="text-align: right">
{%= format_currency(data[i]["paid_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">
@ -204,7 +204,7 @@
<td style="text-align: right">
{%= format_currency(data[i]["outstanding_amount"], data[i]["currency"]) %}</td>
{% if(filters.show_pdc_in_print) { %}
{% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %}
<td style="text-align: right">
{%= data[i]["po_no"] %}</td>
@ -215,10 +215,10 @@
{% } %}
{% } else { %}
<td></td>
{% if(!filters.show_pdc_in_print) { %}
{% if(!filters.show_future_payments) { %}
<td></td>
{% } %}
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %}
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
<td></td>
{% } %}
<td></td>
@ -226,7 +226,7 @@
<td style="text-align: right">
{%= format_currency(data[i]["invoiced_amount"], data[i]["currency"] ) %}</td>
{% if(!filters.show_pdc_in_print) { %}
{% if(!filters.show_future_payments) { %}
<td style="text-align: right">
{%= format_currency(data[i]["paid_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= report.report_name === "Accounts Receivable" ? format_currency(data[i]["credit_note"], data[i]["currency"]) : format_currency(data[i]["debit_note"], data[i]["currency"]) %} </td>
@ -234,7 +234,7 @@
<td style="text-align: right">
{%= format_currency(data[i]["outstanding_amount"], data[i]["currency"]) %}</td>
{% if(filters.show_pdc_in_print) { %}
{% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %}
<td style="text-align: right">
{%= data[i][__("Customer LPO")] %}</td>

View File

@ -130,13 +130,18 @@ frappe.query_reports["Accounts Receivable"] = {
"fieldtype": "Check",
},
{
"fieldname":"show_pdc_in_print",
"label": __("Show PDC in Print"),
"fieldname":"show_future_payments",
"label": __("Show Future Payments"),
"fieldtype": "Check",
},
{
"fieldname":"show_sales_person_in_print",
"label": __("Show Sales Person in Print"),
"fieldname":"show_delivery_notes",
"label": __("Show Delivery Notes"),
"fieldtype": "Check",
},
{
"fieldname":"show_sales_person",
"label": __("Show Sales Person"),
"fieldtype": "Check",
},
{

View File

@ -14,33 +14,44 @@ class TestAccountsReceivable(unittest.TestCase):
filters = {
'company': '_Test Company 2',
'based_on_payment_terms': 1
'based_on_payment_terms': 1,
'report_date': today(),
'range1': 30,
'range2': 60,
'range3': 90,
'range4': 120
}
# check invoice grand total and invoiced column's value for 3 payment terms
name = make_sales_invoice()
report = execute(filters)
expected_data = [[100,30], [100,50], [100,20]]
expected_data = [[100, 30], [100, 50], [100, 20]]
self.assertEqual(expected_data[0], report[1][0][7:9])
self.assertEqual(expected_data[1], report[1][1][7:9])
self.assertEqual(expected_data[2], report[1][2][7:9])
for i in range(3):
row = report[1][i-1]
self.assertEqual(expected_data[i-1], [row.invoice_grand_total, row.invoiced])
# check invoice grand total, invoiced, paid and outstanding column's value after payment
make_payment(name)
report = execute(filters)
expected_data_after_payment = [[100,50], [100,20]]
expected_data_after_payment = [[100, 50, 10, 40], [100, 20, 0, 20]]
self.assertEqual(expected_data_after_payment[0], report[1][0][7:9])
self.assertEqual(expected_data_after_payment[1], report[1][1][7:9])
for i in range(2):
row = report[1][i-1]
self.assertEqual(expected_data_after_payment[i-1],
[row.invoice_grand_total, row.invoiced, row.paid, row.outstanding])
# check invoice grand total, invoiced, paid and outstanding column's value after credit note
make_credit_note(name)
report = execute(filters)
expected_data_after_credit_note = [[100,100,30,100,-30]]
self.assertEqual(expected_data_after_credit_note[0], report[1][0][7:12])
expected_data_after_credit_note = [100, 0, 0, 40, -40]
row = report[1][0]
self.assertEqual(expected_data_after_credit_note,
[row.invoice_grand_total, row.invoiced, row.paid, row.credit_note, row.outstanding])
def make_sales_invoice():
frappe.set_user("Administrator")
@ -64,7 +75,7 @@ def make_sales_invoice():
return si.name
def make_payment(docname):
pe = get_payment_entry("Sales Invoice", docname, bank_account="Cash - _TC2", party_amount=30)
pe = get_payment_entry("Sales Invoice", docname, bank_account="Cash - _TC2", party_amount=40)
pe.paid_from = "Debtors - _TC2"
pe.insert()
pe.submit()

View File

@ -3,236 +3,11 @@
from __future__ import unicode_literals
import frappe
from frappe import _, scrub
from frappe.utils import flt
from frappe import _
from frappe.utils import flt, cint
from erpnext.accounts.party import get_partywise_advanced_payment_amount
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
from six import iteritems
from six.moves import zip
class AccountsReceivableSummary(ReceivablePayableReport):
def run(self, args):
party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1])
return self.get_columns(party_naming_by, args), self.get_data(party_naming_by, args)
def get_columns(self, party_naming_by, args):
columns = [_(args.get("party_type")) + ":Link/" + args.get("party_type") + ":200"]
if party_naming_by == "Naming Series":
columns += [ args.get("party_type") + " Name::140"]
credit_debit_label = "Credit Note Amt" if args.get('party_type') == 'Customer' else "Debit Note Amt"
columns += [{
"label": _("Advance Amount"),
"fieldname": "advance_amount",
"fieldtype": "Currency",
"options": "currency",
"width": 100
},{
"label": _("Total Invoiced Amt"),
"fieldname": "total_invoiced_amt",
"fieldtype": "Currency",
"options": "currency",
"width": 100
},
{
"label": _("Total Paid Amt"),
"fieldname": "total_paid_amt",
"fieldtype": "Currency",
"options": "currency",
"width": 100
}]
columns += [
{
"label": _(credit_debit_label),
"fieldname": scrub(credit_debit_label),
"fieldtype": "Currency",
"options": "currency",
"width": 140
},
{
"label": _("Total Outstanding Amt"),
"fieldname": "total_outstanding_amt",
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _("0-" + str(self.filters.range1)),
"fieldname": scrub("0-" + str(self.filters.range1)),
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _(str(self.filters.range1) + "-" + str(self.filters.range2)),
"fieldname": scrub(str(self.filters.range1) + "-" + str(self.filters.range2)),
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _(str(self.filters.range2) + "-" + str(self.filters.range3)),
"fieldname": scrub(str(self.filters.range2) + "-" + str(self.filters.range3)),
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _(str(self.filters.range3) + "-" + str(self.filters.range4)),
"fieldname": scrub(str(self.filters.range3) + "-" + str(self.filters.range4)),
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _(str(self.filters.range4) + _("-Above")),
"fieldname": scrub(str(self.filters.range4) + _("-Above")),
"fieldtype": "Currency",
"options": "currency",
"width": 160
}
]
if args.get("party_type") == "Customer":
columns += [{
"label": _("Territory"),
"fieldname": "territory",
"fieldtype": "Link",
"options": "Territory",
"width": 80
},
{
"label": _("Customer Group"),
"fieldname": "customer_group",
"fieldtype": "Link",
"options": "Customer Group",
"width": 80
},
{
"label": _("Sales Person"),
"fieldtype": "Data",
"fieldname": "sales_person",
"width": 120,
}]
if args.get("party_type") == "Supplier":
columns += [{
"label": _("Supplier Group"),
"fieldname": "supplier_group",
"fieldtype": "Link",
"options": "Supplier Group",
"width": 80
}]
columns.append({
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
"width": 80
})
return columns
def get_data(self, party_naming_by, args):
data = []
partywise_total = self.get_partywise_total(party_naming_by, args)
partywise_advance_amount = get_partywise_advanced_payment_amount(args.get("party_type"),
self.filters.get("report_date")) or {}
for party, party_dict in iteritems(partywise_total):
row = [party]
if party_naming_by == "Naming Series":
row += [self.get_party_name(args.get("party_type"), party)]
row += [partywise_advance_amount.get(party, 0)]
paid_amt = 0
if party_dict.paid_amt > 0:
paid_amt = flt(party_dict.paid_amt - partywise_advance_amount.get(party, 0))
row += [
party_dict.invoiced_amt, paid_amt, party_dict.credit_amt, party_dict.outstanding_amt,
party_dict.range1, party_dict.range2, party_dict.range3, party_dict.range4, party_dict.range5
]
if args.get("party_type") == "Customer":
row += [self.get_territory(party), self.get_customer_group(party), ", ".join(set(party_dict.sales_person))]
if args.get("party_type") == "Supplier":
row += [self.get_supplier_group(party)]
row.append(party_dict.currency)
data.append(row)
return data
def get_partywise_total(self, party_naming_by, args):
party_total = frappe._dict()
for d in self.get_voucherwise_data(party_naming_by, args):
party_total.setdefault(d.party,
frappe._dict({
"invoiced_amt": 0,
"paid_amt": 0,
"credit_amt": 0,
"outstanding_amt": 0,
"range1": 0,
"range2": 0,
"range3": 0,
"range4": 0,
"range5": 0,
"sales_person": []
})
)
for k in list(party_total[d.party]):
if k not in ["currency", "sales_person"]:
party_total[d.party][k] += flt(d.get(k, 0))
party_total[d.party].currency = d.currency
if d.sales_person:
party_total[d.party].sales_person.append(d.sales_person)
return party_total
def get_voucherwise_data(self, party_naming_by, args):
voucherwise_data = ReceivablePayableReport(self.filters).run(args)[1]
cols = ["posting_date", "party"]
if party_naming_by == "Naming Series":
cols += ["party_name"]
if args.get("party_type") == 'Customer':
cols += ["contact"]
cols += ["voucher_type", "voucher_no", "due_date"]
if args.get("party_type") == "Supplier":
cols += ["bill_no", "bill_date"]
cols += ["invoiced_amt", "paid_amt", "credit_amt",
"outstanding_amt", "age", "range1", "range2", "range3", "range4", "range5", "currency", "pdc/lc_date", "pdc/lc_ref",
"pdc/lc_amount"]
if args.get("party_type") == "Supplier":
cols += ["supplier_group", "remarks"]
if args.get("party_type") == "Customer":
cols += ["po_no", "do_no", "territory", "customer_group", "sales_person", "remarks"]
return self.make_data_dict(cols, voucherwise_data)
def make_data_dict(self, cols, data):
data_dict = []
for d in data:
data_dict.append(frappe._dict(zip(cols, d)))
return data_dict
def execute(filters=None):
args = {
@ -241,3 +16,119 @@ def execute(filters=None):
}
return AccountsReceivableSummary(filters).run(args)
class AccountsReceivableSummary(ReceivablePayableReport):
def run(self, args):
self.party_type = args.get('party_type')
self.party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1])
self.get_columns()
self.get_data(args)
return self.columns, self.data
def get_data(self, args):
self.data = []
self.receivables = ReceivablePayableReport(self.filters).run(args)[1]
self.get_party_total(args)
party_advance_amount = get_partywise_advanced_payment_amount(self.party_type,
self.filters.report_date) or {}
for party, party_dict in iteritems(self.party_total):
row = frappe._dict()
row.party = party
if self.party_naming_by == "Naming Series":
row.party_name = frappe.get_cached_value(self.party_type, party, [self.party_type + "_name"])
row.update(party_dict)
# Advance against party
row.advance = party_advance_amount.get(party, 0)
# In AR/AP, advance shown in paid columns,
# but in summary report advance shown in separate column
row.paid -= row.advance
self.data.append(row)
def get_party_total(self, args):
self.party_total = frappe._dict()
for d in self.receivables:
self.init_party_total(d)
# Add all amount columns
for k in list(self.party_total[d.party]):
if k not in ["currency", "sales_person"]:
self.party_total[d.party][k] += d.get(k, 0.0)
# set territory, customer_group, sales person etc
self.set_party_details(d)
def init_party_total(self, row):
self.party_total.setdefault(row.party, frappe._dict({
"invoiced": 0.0,
"paid": 0.0,
"credit_note": 0.0,
"outstanding": 0.0,
"range1": 0.0,
"range2": 0.0,
"range3": 0.0,
"range4": 0.0,
"range5": 0.0,
"sales_person": []
}))
def set_party_details(self, row):
self.party_total[row.party].currency = row.currency
for key in ('territory', 'customer_group', 'supplier_group'):
if row.get(key):
self.party_total[row.party][key] = row.get(key)
if row.sales_person:
self.party_total[row.party].sales_person.append(row.sales_person)
def get_columns(self):
self.columns = []
self.add_column(label=_(self.party_type), fieldname='party',
fieldtype='Link', options=self.party_type, width=180)
if self.party_naming_by == "Naming Series":
self.add_column(_('{0} Name').format(self.party_type),
fieldname = 'party_name', fieldtype='Data')
credit_debit_label = "Credit Note" if self.party_type == 'Customer' else "Debit Note"
self.add_column(_('Advance Amount'), fieldname='advance')
self.add_column(_('Invoiced Amount'), fieldname='invoiced')
self.add_column(_('Paid Amount'), fieldname='paid')
self.add_column(_(credit_debit_label), fieldname='credit_note')
self.add_column(_('Outstanding Amount'), fieldname='outstanding')
self.setup_ageing_columns()
if self.party_type == "Customer":
self.add_column(label=_('Territory'), fieldname='territory', fieldtype='Link',
options='Territory')
self.add_column(label=_('Customer Group'), fieldname='customer_group', fieldtype='Link',
options='Customer Group')
if self.filters.show_sales_person:
self.add_column(label=_('Sales Person'), fieldname='sales_person', fieldtype='Data')
else:
self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link',
options='Supplier Group')
self.add_column(label=_('Currency'), fieldname='currency', fieldtype='Link',
options='Currency', width=80)
def setup_ageing_columns(self):
for i, label in enumerate(["0-{range1}".format(range1=self.filters["range1"]),
"{range1}-{range2}".format(range1=cint(self.filters["range1"])+ 1, range2=self.filters["range2"]),
"{range2}-{range3}".format(range2=cint(self.filters["range2"])+ 1, range3=self.filters["range3"]),
"{range3}-{range4}".format(range3=cint(self.filters["range3"])+ 1, range4=self.filters["range4"]),
"{range4}-{above}".format(range4=cint(self.filters["range4"])+ 1, above=_("Above"))]):
self.add_column(label=label, fieldname='range' + str(i+1))

View File

@ -456,8 +456,6 @@ class TestAsset(unittest.TestCase):
self.assertEqual(gle, expected_gle)
si.cancel()
frappe.delete_doc("Sales Invoice", si.name)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
def test_asset_expected_value_after_useful_life(self):

View File

@ -0,0 +1,6 @@
# Version 12.1.0 Release Notes
### Stock
1. [Pick List](https://erpnext.com/docs/user/manual/en/stock/pick-list)
2. [Refactored Accounts Receivable Reports](https://erpnext.com/docs/user/manual/en/accounts/accounting-reports#2-accounting-statements)

View File

@ -28,7 +28,7 @@ class CallLog(Document):
self.trigger_call_popup()
def trigger_call_popup(self):
scheduled_employees = get_scheduled_employees_for_popup(self.to)
scheduled_employees = get_scheduled_employees_for_popup(self.medium)
employee_emails = get_employees_with_number(self.to)
# check if employees with matched number are scheduled to receive popup
@ -73,6 +73,10 @@ def set_caller_information(doc, state):
# contact_name or lead_name
display_name_field = '{}_name'.format(fieldname)
# Contact now has all the nos saved in child table
if doc.doctype == 'Contact':
numbers = [d.phone for d in doc.phone_nos]
for number in numbers:
number = strip_number(number)
if not number: continue

View File

@ -30,6 +30,12 @@ def get_data():
"onboard": 1,
"dependencies": ["Item"],
},
{
"type": "doctype",
"name": "Pick List",
"onboard": 1,
"dependencies": ["Item"],
},
{
"type": "doctype",
"name": "Delivery Trip"
@ -329,5 +335,5 @@ def get_data():
}
]
},
]

View File

@ -263,7 +263,7 @@ class AccountsController(TransactionBase):
if self.get("is_subcontracted"):
args["is_subcontracted"] = self.is_subcontracted
ret = get_item_details(args, self)
ret = get_item_details(args, self, overwrite_warehouse=False)
for fieldname, value in ret.items():
if item.meta.get_field(fieldname) and value is not None:

View File

@ -394,8 +394,8 @@ class SellingController(StockController):
elif self.doctype == "Delivery Note":
e = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or '']
f = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice]
elif self.doctype == "Sales Order":
e = [d.item_code, d.description, d.warehouse, d.batch_no or '']
elif self.doctype in ["Sales Order", "Quotation"]:
e = [d.item_code, d.description, d.warehouse, '']
f = [d.item_code, d.description]
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:

View File

@ -88,7 +88,7 @@ def get_status(start_date, end_date):
end_date = getdate(end_date)
now_date = getdate(nowdate())
return "Active" if start_date < now_date < end_date else "Inactive"
return "Active" if start_date <= now_date <= end_date else "Inactive"
def update_status_for_contracts():

View File

@ -45,15 +45,16 @@ class TestOpportunity(unittest.TestCase):
# create new customer and create new contact against 'new.opportunity@example.com'
customer = make_customer(opp_doc.party_name).insert(ignore_permissions=True)
frappe.get_doc({
contact = frappe.get_doc({
"doctype": "Contact",
"email_id": new_lead_email_id,
"first_name": "_Test Opportunity Customer",
"links": [{
"link_doctype": "Customer",
"link_name": customer.name
}]
}).insert(ignore_permissions=True)
})
contact.add_email(new_lead_email_id)
contact.insert(ignore_permissions=True)
opp_doc = frappe.get_doc(args).insert(ignore_permissions=True)
self.assertTrue(opp_doc.party_name)

View File

@ -3,7 +3,6 @@
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
import requests
import frappe

View File

@ -69,33 +69,10 @@ def validate_service_item(item, msg):
def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None):
fields = ["name", "first_name", "mobile_phone"]
match_conditions = build_match_conditions("Healthcare Practitioner")
match_conditions = "and {}".format(match_conditions) if match_conditions else ""
if filters:
filter_conditions = get_filters_cond(doctype, filters, [])
match_conditions += "{}".format(filter_conditions)
filters = {
'name': ("like", "%%%s%%" % txt)
}
return frappe.db.sql("""select %s from `tabHealthcare Practitioner` where docstatus < 2
and (%s like %s or first_name like %s)
and active = 1
{match_conditions}
order by
case when name like %s then 0 else 1 end,
case when first_name like %s then 0 else 1 end,
name, first_name limit %s, %s""".format(
match_conditions=match_conditions) %
(
", ".join(fields),
frappe.db.escape(searchfield),
"%s", "%s", "%s", "%s", "%s", "%s"
),
(
"%%%s%%" % frappe.db.escape(txt),
"%%%s%%" % frappe.db.escape(txt),
"%%%s%%" % frappe.db.escape(txt),
"%%%s%%" % frappe.db.escape(txt),
start,
page_len
)
)
return frappe.get_all("Healthcare Practitioner", fields = fields,
filters = filters, start=start, page_length=page_len, order_by="name, first_name", as_list=1)

View File

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import cint, cstr, getdate
from frappe.utils import cint, cstr, getdate, flt
import dateutil
from frappe.model.naming import set_name_by_naming_series
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account,get_income_account,send_registration_sms
@ -64,7 +64,7 @@ class Patient(Document):
def invoice_patient_registration(self):
frappe.db.set_value("Patient", self.name, "disabled", 0)
send_registration_sms(self)
if(frappe.get_value("Healthcare Settings", None, "registration_fee")>0):
if(flt(frappe.get_value("Healthcare Settings", None, "registration_fee"))>0):
company = frappe.defaults.get_user_default('company')
if not company:
company = frappe.db.get_value("Global Defaults", None, "default_company")

View File

@ -11,7 +11,7 @@ from frappe.utils import getdate, date_diff
class AdditionalSalary(Document):
def before_insert(self):
if frappe.db.exists("Additional Salary", {"employee": self.employee, "salary_component": self.salary_component,
"amount": self.amount, "payroll_date": self.payroll_date, "company": self.company}):
"amount": self.amount, "payroll_date": self.payroll_date, "company": self.company, "docstatus": 1}):
frappe.throw(_("Additional Salary Component Exists."))

View File

@ -2,7 +2,21 @@
// For license information, please see license.txt
frappe.ui.form.on('Employee Incentive', {
refresh: function(frm) {
setup: function(frm) {
frm.set_query("employee", function() {
return {
filters: {
"status": "Active"
}
};
});
frm.set_query("salary_component", function() {
return {
filters: {
"type": "Earning"
}
};
});
}
});

View File

@ -1,330 +1,130 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "HR-EINV-.YY.-.MM.-.#####",
"beta": 0,
"creation": "2018-04-13 16:13:43.404546",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"autoname": "HR-EINV-.YY.-.MM.-.#####",
"creation": "2018-04-13 16:13:43.404546",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"employee",
"incentive_amount",
"payroll_date",
"salary_component",
"amended_from",
"column_break_5",
"employee_name",
"department",
"additional_salary"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "employee",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Employee",
"length": 0,
"no_copy": 0,
"options": "Employee",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "employee",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Employee",
"options": "Employee",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "incentive_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Incentive Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "incentive_amount",
"fieldtype": "Currency",
"label": "Incentive Amount",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "payroll_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payroll Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "payroll_date",
"fieldtype": "Date",
"label": "Payroll Date",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"options": "Employee Incentive",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Employee Incentive",
"print_hide": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Employee Name",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"label": "Employee Name",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.department",
"fieldname": "department",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Department",
"length": 0,
"no_copy": 0,
"options": "Department",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fetch_from": "employee.department",
"fieldname": "department",
"fieldtype": "Link",
"label": "Department",
"options": "Department",
"read_only": 1
},
{
"fieldname": "additional_salary",
"fieldtype": "Link",
"label": "Additional Salary",
"no_copy": 1,
"options": "Additional Salary",
"read_only": 1
},
{
"fieldname": "salary_component",
"fieldtype": "Link",
"label": "Salary Component",
"options": "Salary Component",
"reqd": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 16:15:51.811149",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Incentive",
"name_case": "",
"owner": "Administrator",
],
"is_submittable": 1,
"modified": "2019-09-03 16:48:16.822252",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Incentive",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"share": 1,
"submit": 1,
"write": 1
},
},
{
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Employee",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 0
},
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Employee",
"share": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"share": 1,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "employee_name",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
],
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "employee_name",
"track_changes": 1
}

View File

@ -7,4 +7,39 @@ import frappe
from frappe.model.document import Document
class EmployeeIncentive(Document):
pass
def on_submit(self):
company = frappe.db.get_value('Employee', self.employee, 'company')
additional_salary = frappe.db.exists('Additional Salary', {
'employee': self.employee,
'salary_component': self.salary_component,
'payroll_date': self.payroll_date,
'company': company,
'docstatus': 1
})
if not additional_salary:
additional_salary = frappe.new_doc('Additional Salary')
additional_salary.employee = self.employee
additional_salary.salary_component = self.salary_component
additional_salary.amount = self.incentive_amount
additional_salary.payroll_date = self.payroll_date
additional_salary.company = company
additional_salary.submit()
self.db_set('additional_salary', additional_salary.name)
else:
incentive_added = frappe.db.get_value('Additional Salary', additional_salary, 'amount') + self.incentive_amount
frappe.db.set_value('Additional Salary', additional_salary, 'amount', incentive_added)
self.db_set('additional_salary', additional_salary)
def on_cancel(self):
if self.additional_salary:
incentive_removed = frappe.db.get_value('Additional Salary', self.additional_salary, 'amount') - self.incentive_amount
if incentive_removed == 0:
frappe.get_doc('Additional Salary', self.additional_salary).cancel()
else:
frappe.db.set_value('Additional Salary', self.additional_salary, 'amount', incentive_removed)
self.db_set('additional_salary', '')

View File

@ -45,7 +45,6 @@ class TestExpenseClaim(unittest.TestCase):
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 700)
expense_claim2.cancel()
frappe.delete_doc("Expense Claim", expense_claim2.name)
self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200)
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200)

View File

@ -95,26 +95,25 @@ def process_expired_allocation():
'expire_carry_forwarded_leaves_after_days': (">", 0)
}, fieldname=['name'])
if leave_type_records:
leave_type = [record[0] for record in leave_type_records]
leave_type = [record[0] for record in leave_type_records]
expired_allocation = frappe.db.sql_list("""SELECT name
FROM `tabLeave Ledger Entry`
WHERE
`transaction_type`='Leave Allocation'
AND `is_expired`=1""")
expired_allocation = frappe.db.sql_list("""SELECT name
FROM `tabLeave Ledger Entry`
WHERE
`transaction_type`='Leave Allocation'
AND `is_expired`=1""")
expire_allocation = frappe.get_all("Leave Ledger Entry",
fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'],
filters={
'to_date': ("<", today()),
'transaction_type': 'Leave Allocation',
'transaction_name': ('not in', expired_allocation)
},
or_filters={
'is_carry_forward': 0,
'leave_type': ('in', leave_type)
})
expire_allocation = frappe.get_all("Leave Ledger Entry",
fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'],
filters={
'to_date': ("<", today()),
'transaction_type': 'Leave Allocation',
'transaction_name': ('not in', expired_allocation)
},
or_filters={
'is_carry_forward': 0,
'leave_type': ('in', leave_type)
})
if expire_allocation:
create_expiry_ledger_entry(expire_allocation)

View File

@ -30,7 +30,7 @@ class LoanApplication(Document):
monthly_interest_rate = flt(self.rate_of_interest) / (12 *100)
if monthly_interest_rate:
min_repayment_amount = self.loan_amount*monthly_interest_rate
if self.repayment_amount - min_repayment_amount <= 0:
if (self.repayment_amount - min_repayment_amount) <= 0:
frappe.throw(_("Repayment Amount must be greater than " \
+ str(flt(min_repayment_amount, 2))))
self.repayment_periods = math.ceil((math.log(self.repayment_amount) -
@ -58,10 +58,13 @@ def make_loan(source_name, target_doc = None):
doclist = get_mapped_doc("Loan Application", source_name, {
"Loan Application": {
"doctype": "Loan",
"field_map": {
"repayment_amount": "monthly_repayment_amount"
},
"validation": {
"docstatus": ["=", 1]
}
}
}, target_doc)
return doclist
return doclist

View File

@ -10,8 +10,13 @@ frappe.ui.form.on('Retention Bonus', {
}
};
});
},
refresh: function(frm) {
frm.set_query("salary_component", function() {
return {
filters: {
"type": "Earning"
}
};
});
}
});

View File

@ -1,415 +1,165 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "HR-RTB-.YYYY.-.#####",
"beta": 0,
"creation": "2018-05-13 14:59:42.038964",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"allow_import": 1,
"allow_rename": 1,
"autoname": "HR-RTB-.YYYY.-.#####",
"creation": "2018-05-13 14:59:42.038964",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company",
"employee",
"bonus_payment_date",
"bonus_amount",
"salary_component",
"amended_from",
"column_break_6",
"employee_name",
"department",
"date_of_joining",
"additional_salary"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "employee",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Employee",
"length": 0,
"no_copy": 0,
"options": "Employee",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "employee",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Employee",
"options": "Employee",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bonus_payment_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Bonus Payment Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "bonus_payment_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Bonus Payment Date",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bonus_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Bonus Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "bonus_amount",
"fieldtype": "Currency",
"label": "Bonus Amount",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"options": "Retention Bonus",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Retention Bonus",
"print_hide": 1,
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_6",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_6",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Employee Name",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"label": "Employee Name",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.department",
"fieldname": "department",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Department",
"length": 0,
"no_copy": 0,
"options": "Department",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fetch_from": "employee.department",
"fieldname": "department",
"fieldtype": "Link",
"label": "Department",
"options": "Department",
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.date_of_joining",
"fieldname": "date_of_joining",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Date of Joining",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"fetch_from": "employee.date_of_joining",
"fieldname": "date_of_joining",
"fieldtype": "Data",
"label": "Date of Joining",
"read_only": 1
},
{
"fieldname": "additional_salary",
"fieldtype": "Link",
"label": "Additional Salary",
"no_copy": 1,
"options": "Additional Salary",
"read_only": 1
},
{
"fieldname": "salary_component",
"fieldtype": "Link",
"label": "Salary Component",
"options": "Salary Component",
"reqd": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 16:15:38.710684",
"modified_by": "Administrator",
"module": "HR",
"name": "Retention Bonus",
"name_case": "",
"owner": "Administrator",
],
"is_submittable": 1,
"modified": "2019-09-03 16:47:24.210422",
"modified_by": "Administrator",
"module": "HR",
"name": "Retention Bonus",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"write": 1
},
},
{
"amend": 0,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR Manager",
"share": 1,
"submit": 1,
"write": 1
},
},
{
"amend": 0,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"share": 1,
"submit": 1,
"write": 1
},
},
{
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Employee",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 0
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Employee",
"share": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -10,7 +10,42 @@ from frappe.utils import getdate
class RetentionBonus(Document):
def validate(self):
if frappe.get_value("Employee", self.employee, "status") == "Left":
frappe.throw(_("Cannot create Retention Bonus for left Employees"))
if frappe.get_value('Employee', self.employee, 'status') == 'Left':
frappe.throw(_('Cannot create Retention Bonus for left Employees'))
if getdate(self.bonus_payment_date) < getdate():
frappe.throw(_("Bonus Payment Date cannot be a past date"))
frappe.throw(_('Bonus Payment Date cannot be a past date'))
def on_submit(self):
company = frappe.db.get_value('Employee', self.employee, 'company')
additional_salary = frappe.db.exists('Additional Salary', {
'employee': self.employee,
'salary_component': self.salary_component,
'payroll_date': self.bonus_payment_date,
'company': company,
'docstatus': 1
})
if not additional_salary:
additional_salary = frappe.new_doc('Additional Salary')
additional_salary.employee = self.employee
additional_salary.salary_component = self.salary_component
additional_salary.amount = self.bonus_amount
additional_salary.payroll_date = self.bonus_payment_date
additional_salary.company = company
additional_salary.submit()
self.db_set('additional_salary', additional_salary.name)
else:
bonus_added = frappe.db.get_value('Additional Salary', additional_salary, 'amount') + self.bonus_amount
frappe.db.set_value('Additional Salary', additional_salary, 'amount', bonus_added)
self.db_set('additional_salary', additional_salary)
def on_cancel(self):
if self.additional_salary:
bonus_removed = frappe.db.get_value('Additional Salary', self.additional_salary, 'amount') - self.bonus_amount
if bonus_removed == 0:
frappe.get_doc('Additional Salary', self.additional_salary).cancel()
else:
frappe.db.set_value('Additional Salary', self.additional_salary, 'amount', bonus_removed)
self.db_set('additional_salary', '')

View File

@ -68,12 +68,13 @@ def make_contact(supplier):
contact = frappe.get_doc({
'doctype': 'Contact',
'first_name': supplier.supplier_name,
'email_id': supplier.supplier_email,
'is_primary_contact': 1,
'links': [
{'link_doctype': 'Supplier', 'link_name': supplier.supplier_name}
]
}).insert()
})
contact.add_email(supplier.supplier_email)
contact.insert()
else:
contact = frappe.get_doc('Contact', contact_name)

View File

@ -99,7 +99,7 @@ class ProductionPlan(Document):
self.get_mr_items()
def get_so_items(self):
so_list = [d.sales_order for d in self.sales_orders if d.sales_order]
so_list = [d.sales_order for d in self.get("sales_orders", []) if d.sales_order]
if not so_list:
msgprint(_("Please enter Sales Orders in the above table"))
return []
@ -134,7 +134,7 @@ class ProductionPlan(Document):
self.calculate_total_planned_qty()
def get_mr_items(self):
mr_list = [d.material_request for d in self.material_requests if d.material_request]
mr_list = [d.material_request for d in self.get("material_requests", []) if d.material_request]
if not mr_list:
msgprint(_("Please enter Material Requests in the above table"))
return []

View File

@ -4,16 +4,17 @@
frappe.ui.form.on("Work Order", {
setup: function(frm) {
frm.custom_make_buttons = {
'Stock Entry': 'Make Stock Entry',
}
'Stock Entry': 'Start',
'Pick List': 'Create Pick List',
};
// Set query for warehouses
frm.set_query("wip_warehouse", function(doc) {
frm.set_query("wip_warehouse", function() {
return {
filters: {
'company': frm.doc.company,
}
}
};
});
frm.set_query("source_warehouse", function() {
@ -21,7 +22,7 @@ frappe.ui.form.on("Work Order", {
filters: {
'company': frm.doc.company,
}
}
};
});
frm.set_query("source_warehouse", "required_items", function() {
@ -29,7 +30,7 @@ frappe.ui.form.on("Work Order", {
filters: {
'company': frm.doc.company,
}
}
};
});
frm.set_query("sales_order", function() {
@ -37,7 +38,7 @@ frappe.ui.form.on("Work Order", {
filters: {
"status": ["not in", ["Closed", "On Hold"]]
}
}
};
});
frm.set_query("fg_warehouse", function() {
@ -46,7 +47,7 @@ frappe.ui.form.on("Work Order", {
'company': frm.doc.company,
'is_group': 0
}
}
};
});
frm.set_query("scrap_warehouse", function() {
@ -55,17 +56,19 @@ frappe.ui.form.on("Work Order", {
'company': frm.doc.company,
'is_group': 0
}
}
};
});
// Set query for BOM
frm.set_query("bom_no", function() {
if (frm.doc.production_item) {
return{
return {
query: "erpnext.controllers.queries.bom",
filters: {item: cstr(frm.doc.production_item)}
}
} else msgprint(__("Please enter Production Item first"));
};
} else {
frappe.msgprint(__("Please enter Production Item first"));
}
});
// Set query for FG Item
@ -76,7 +79,7 @@ frappe.ui.form.on("Work Order", {
['is_stock_item', '=',1],
['default_bom', '!=', '']
]
}
};
});
// Set query for FG Item
@ -85,12 +88,12 @@ frappe.ui.form.on("Work Order", {
filters:[
['Project', 'status', 'not in', 'Completed, Cancelled']
]
}
};
});
// formatter for work order operation
frm.set_indicator_formatter('operation',
function(doc) { return (frm.doc.qty==doc.completed_qty) ? "green" : "orange" });
function(doc) { return (frm.doc.qty==doc.completed_qty) ? "green" : "orange"; });
},
onload: function(frm) {
@ -133,7 +136,7 @@ frappe.ui.form.on("Work Order", {
if(not_completed && not_completed.length) {
frm.add_custom_button(__('Create Job Card'), () => {
frm.trigger("make_job_card")
frm.trigger("make_job_card");
}).addClass('btn-primary');
}
}
@ -151,7 +154,7 @@ frappe.ui.form.on("Work Order", {
condition: (d) => {
if (d.allow_alternative_item) {return true;}
}
})
});
});
}
}
@ -285,13 +288,13 @@ frappe.ui.form.on("Work Order", {
if(!frm.doc.skip_transfer){
var pending_complete = frm.doc.material_transferred_for_manufacturing - frm.doc.produced_qty;
if(pending_complete) {
var title = __('{0} items in progress', [pending_complete]);
var width = ((pending_complete / frm.doc.qty * 100) - added_min);
title = __('{0} items in progress', [pending_complete]);
bars.push({
'title': title,
'width': (width > 100 ? "99.5" : width) + '%',
'progress_class': 'progress-bar-warning'
})
});
message = message + '. ' + title;
}
}
@ -377,7 +380,7 @@ frappe.ui.form.on("Work Order", {
filters: [
["Sales Order","name", "in", r.message]
]
}
};
});
}
});
@ -401,10 +404,10 @@ frappe.ui.form.on("Work Order Item", {
frappe.model.set_value(row.doctype, row.name,
"available_qty_at_source_warehouse", r.message);
}
})
});
}
}
})
});
frappe.ui.form.on("Work Order Operation", {
workstation: function(frm, cdt, cdn) {
@ -421,7 +424,7 @@ frappe.ui.form.on("Work Order Operation", {
erpnext.work_order.calculate_cost(frm.doc);
erpnext.work_order.calculate_total_cost(frm);
}
})
});
}
},
time_in_mins: function(frm, cdt, cdn) {
@ -447,10 +450,13 @@ erpnext.work_order = {
const show_start_btn = (frm.doc.skip_transfer
|| frm.doc.transfer_material_against == 'Job Card') ? 0 : 1;
if (show_start_btn){
if (show_start_btn) {
if ((flt(doc.material_transferred_for_manufacturing) < flt(doc.qty))
&& frm.doc.status != 'Stopped') {
frm.has_start_btn = true;
frm.add_custom_button(__('Create Pick List'), function() {
erpnext.work_order.create_pick_list(frm);
});
var start_btn = frm.add_custom_button(__('Start'), function() {
erpnext.work_order.make_se(frm, 'Material Transfer for Manufacture');
});
@ -519,8 +525,8 @@ erpnext.work_order = {
calculate_total_cost: function(frm) {
var variable_cost = frm.doc.actual_operating_cost ?
flt(frm.doc.actual_operating_cost) : flt(frm.doc.planned_operating_cost)
frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost))
flt(frm.doc.actual_operating_cost) : flt(frm.doc.planned_operating_cost);
frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost));
},
set_default_warehouse: function(frm) {
@ -528,45 +534,72 @@ erpnext.work_order = {
frappe.call({
method: "erpnext.manufacturing.doctype.work_order.work_order.get_default_warehouse",
callback: function(r) {
if(!r.exe) {
if (!r.exe) {
frm.set_value("wip_warehouse", r.message.wip_warehouse);
frm.set_value("fg_warehouse", r.message.fg_warehouse)
frm.set_value("fg_warehouse", r.message.fg_warehouse);
}
}
});
}
},
make_se: function(frm, purpose) {
if(!frm.doc.skip_transfer){
var max = (purpose === "Manufacture") ?
flt(frm.doc.material_transferred_for_manufacturing) - flt(frm.doc.produced_qty) :
flt(frm.doc.qty) - flt(frm.doc.material_transferred_for_manufacturing);
get_max_transferable_qty: (frm, purpose) => {
let max = 0;
if (frm.doc.skip_transfer) return max;
if (purpose === 'Manufacture') {
max = flt(frm.doc.material_transferred_for_manufacturing) - flt(frm.doc.produced_qty);
} else {
var max = flt(frm.doc.qty) - flt(frm.doc.produced_qty);
max = flt(frm.doc.qty) - flt(frm.doc.material_transferred_for_manufacturing);
}
return flt(max, precision('qty'));
},
max = flt(max, precision("qty"));
frappe.prompt({fieldtype:"Float", label: __("Qty for {0}", [purpose]), fieldname:"qty",
description: __("Max: {0}", [max]), 'default': max }, function(data)
{
if(data.qty > max) {
frappe.msgprint(__("Quantity must not be more than {0}", [max]));
return;
}
frappe.call({
method:"erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry",
args: {
"work_order_id": frm.doc.name,
"purpose": purpose,
"qty": data.qty
},
callback: function(r) {
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
show_prompt_for_qty_input: function(frm, purpose) {
let max = this.get_max_transferable_qty(frm, purpose);
return new Promise((resolve, reject) => {
frappe.prompt({
fieldtype: 'Float',
label: __('Qty for {0}', [purpose]),
fieldname: 'qty',
description: __('Max: {0}', [max]),
default: max
}, data => {
if (data.qty > max) {
frappe.msgprint(__('Quantity must not be more than {0}', [max]));
reject();
}
data.purpose = purpose;
resolve(data);
}, __('Select Quantity'), __('Create'));
});
},
make_se: function(frm, purpose) {
this.show_prompt_for_qty_input(frm, purpose)
.then(data => {
return frappe.xcall('erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry', {
'work_order_id': frm.doc.name,
'purpose': purpose,
'qty': data.qty
});
}).then(stock_entry => {
frappe.model.sync(stock_entry);
frappe.set_route('Form', stock_entry.doctype, stock_entry.name);
});
},
create_pick_list: function(frm, purpose='Material Transfer for Manufacture') {
this.show_prompt_for_qty_input(frm, purpose)
.then(data => {
return frappe.xcall('erpnext.manufacturing.doctype.work_order.work_order.create_pick_list', {
'source_name': frm.doc.name,
'for_qty': data.qty
});
}).then(pick_list => {
frappe.model.sync(pick_list);
frappe.set_route('Form', pick_list.doctype, pick_list.name);
});
}, __("Select Quantity"), __('Create'));
},
make_consumption_se: function(frm, backflush_raw_materials_based_on) {
@ -606,6 +639,6 @@ erpnext.work_order = {
frm.reload_doc();
}
}
})
});
}
}
};

View File

@ -72,6 +72,7 @@
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
"no_copy": 1,
"options": "MFG-WO-.YYYY.-",
"print_hide": 1,
"reqd": 1,
@ -467,7 +468,7 @@
"idx": 1,
"image_field": "image",
"is_submittable": 1,
"modified": "2019-07-31 00:13:38.218277",
"modified": "2019-08-28 12:29:35.315239",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",

View File

@ -19,6 +19,7 @@ from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty
from frappe.utils.csvutils import getlink
from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty
from erpnext.utilities.transaction_base import validate_uom_is_integer
from frappe.model.mapper import get_mapped_doc
class OverProductionError(frappe.ValidationError): pass
class StockOverProductionError(frappe.ValidationError): pass
@ -707,3 +708,46 @@ def get_work_order_operation_data(work_order, operation, workstation):
for d in work_order.operations:
if d.operation == operation and d.workstation == workstation:
return d
@frappe.whitelist()
def create_pick_list(source_name, target_doc=None, for_qty=None):
for_qty = for_qty or json.loads(target_doc).get('for_qty')
max_finished_goods_qty = frappe.db.get_value('Work Order', source_name, 'qty')
def update_item_quantity(source, target, source_parent):
pending_to_issue = flt(source.required_qty) - flt(source.transferred_qty)
desire_to_transfer = flt(source.required_qty) / max_finished_goods_qty * flt(for_qty)
qty = 0
if desire_to_transfer <= pending_to_issue:
qty = desire_to_transfer
elif pending_to_issue > 0:
qty = pending_to_issue
if qty:
target.qty = qty
target.stock_qty = qty
target.uom = frappe.get_value('Item', source.item_code, 'stock_uom')
target.stock_uom = target.uom
target.conversion_factor = 1
else:
target.delete()
doc = get_mapped_doc('Work Order', source_name, {
'Work Order': {
'doctype': 'Pick List',
'validation': {
'docstatus': ['=', 1]
}
},
'Work Order Item': {
'doctype': 'Pick List Item',
'postprocess': update_item_quantity,
'condition': lambda doc: abs(doc.transferred_qty) < abs(doc.required_qty)
},
}, target_doc)
doc.for_qty = for_qty
doc.set_item_locations()
return doc

View File

@ -6,7 +6,7 @@ def get_data():
'fieldname': 'work_order',
'transactions': [
{
'items': ['Stock Entry', 'Job Card']
'items': ['Pick List', 'Stock Entry', 'Job Card']
}
]
}

View File

@ -82,7 +82,7 @@ def get_item_tax_template(item_tax_templates, rename_template_to_untitled, item_
account_name = " - ".join(parts[:-1])
company = frappe.db.get_value("Company", filters={"abbr": parts[-1]})
parent_account = frappe.db.get_value("Account",
filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0}, fieldname="parent_account")
filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account")
frappe.get_doc({
"doctype": "Account",

View File

@ -147,6 +147,15 @@ frappe.ui.form.on("Timesheet Detail", {
calculate_time_and_amount(frm);
},
task: (frm, cdt, cdn) => {
let row = frm.selected_doc;
if (row.task) {
frappe.db.get_value("Task", row.task, "project", (r) => {
frappe.model.set_value(cdt, cdn, "project", r.project);
});
}
},
from_time: function(frm, cdt, cdn) {
calculate_end_time(frm, cdt, cdn);
},
@ -200,9 +209,6 @@ frappe.ui.form.on("Timesheet Detail", {
},
activity_type: function(frm, cdt, cdn) {
frm.script_manager.copy_from_first_row('time_logs', frm.selected_doc,
'project');
frappe.call({
method: "erpnext.projects.doctype.timesheet.timesheet.get_activity_cost",
args: {

View File

@ -96,7 +96,7 @@ class Timesheet(Document):
for time in self.time_logs:
if time.from_time and time.to_time:
if flt(std_working_hours) > 0:
if flt(std_working_hours) and date_diff(time.to_time, time.from_time):
time.hours = flt(std_working_hours) * date_diff(time.to_time, time.from_time)
else:
if not time.hours:
@ -145,12 +145,17 @@ class Timesheet(Document):
def validate_time_logs(self):
for data in self.get('time_logs'):
self.validate_overlap(data)
self.validate_task_project()
def validate_overlap(self, data):
settings = frappe.get_single('Projects Settings')
self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap)
self.validate_overlap_for("employee", data, self.employee, settings.ignore_employee_time_overlap)
def validate_task_project(self):
for log in self.time_logs:
log.project = log.project or frappe.db.get_value("Task", log.task, "project")
def validate_overlap_for(self, fieldname, args, value, ignore_validation=False):
if not value or ignore_validation:
return

View File

@ -14,20 +14,33 @@
style="margin-top:-3px; margin-right: -5px;">
{%= __("Edit") %}</a>
</p>
{% if (contact_list[i].phone || contact_list[i].mobile_no ||
contact_list[i].email_id) { %}
{% if (contact_list[i].phones || contact_list[i].email_ids) { %}
<p>
{% if(contact_list[i].phone) { %}
{%= __("Phone") %}: {%= contact_list[i].phone %}<br>
{% } %}
{% if(contact_list[i].mobile_no) { %}
{%= __("Mobile No.") %}: {%= contact_list[i].mobile_no %}<br>
{% } %}
{% if(contact_list[i].email_id) { %}
{%= __("Email Address") %}: {%= contact_list[i].email_id %}
{% } %}
{% if(contact_list[i].phone) { %}
{%= __("Phone") %}: {%= contact_list[i].phone %}<span class="text-muted"> ({%= __("Primary") %})</span><br>
{% endif %}
{% if(contact_list[i].phone_nos) { %}
{% for(var j=0, k=contact_list[i].phone_nos.length; j<k; j++) { %}
{%= __("Phone") %}: {%= contact_list[i].phone_nos[j].phone %}<br>
{% } %}
{% endif %}
</p>
<p>
{% if(contact_list[i].email_id) { %}
{%= __("Email") %}: {%= contact_list[i].email_id %}<span class="text-muted"> ({%= __("Primary") %})</span><br>
{% endif %}
{% if(contact_list[i].email_ids) { %}
{% for(var j=0, k=contact_list[i].email_ids.length; j<k; j++) { %}
{%= __("Email") %}: {%= contact_list[i].email_ids[j].email_id %}<br>
{% } %}
{% endif %}
</p>
{% endif %}
<p>
{% if (contact_list[i].address) { %}
{%= __("Address") %}: {%= contact_list[i].address %}<br>
{% endif %}
</p>
</div>
{% } %}
{% if(!contact_list.length) { %}

View File

@ -337,14 +337,15 @@ def make_contact(args, is_primary_contact=1):
contact = frappe.get_doc({
'doctype': 'Contact',
'first_name': args.get('name'),
'mobile_no': args.get('mobile_no'),
'email_id': args.get('email_id'),
'is_primary_contact': is_primary_contact,
'links': [{
'link_doctype': args.get('doctype'),
'link_name': args.get('name')
}]
}).insert()
})
contact.add_email(args.get('email_id'))
contact.add_phone(args.get('mobile_no'))
contact.insert()
return contact
@ -371,7 +372,7 @@ def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, fil
return frappe.db.sql("""
select `tabContact`.name from `tabContact`, `tabDynamic Link`
where `tabContact`.name = `tabDynamic Link`.parent and `tabDynamic Link`.link_name = %(customer)s
and `tabDynamic Link`.link_doctype = 'Customer' and `tabContact`.is_primary_contact = 1
and `tabDynamic Link`.link_doctype = 'Customer'
and `tabContact`.name like %(txt)s
""", {
'customer': customer,
@ -383,7 +384,7 @@ def get_customer_primary_address(doctype, txt, searchfield, start, page_len, fil
return frappe.db.sql("""
select `tabAddress`.name from `tabAddress`, `tabDynamic Link`
where `tabAddress`.name = `tabDynamic Link`.parent and `tabDynamic Link`.link_name = %(customer)s
and `tabDynamic Link`.link_doctype = 'Customer' and `tabAddress`.is_primary_address = 1
and `tabDynamic Link`.link_doctype = 'Customer'
and `tabAddress`.name like %(txt)s
""", {
'customer': customer,

View File

@ -7,6 +7,7 @@ frappe.ui.form.on("Sales Order", {
setup: function(frm) {
frm.custom_make_buttons = {
'Delivery Note': 'Delivery',
'Pick List': 'Pick List',
'Sales Invoice': 'Invoice',
'Material Request': 'Material Request',
'Purchase Order': 'Purchase Order',
@ -109,7 +110,9 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
this._super();
let allow_delivery = false;
if(doc.docstatus==1) {
if (doc.docstatus==1) {
this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create'));
if(this.frm.has_perm("submit")) {
if(doc.status === 'On Hold') {
// un-hold
@ -233,6 +236,13 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
this.order_type(doc);
},
create_pick_list() {
frappe.model.open_mapped_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.create_pick_list",
frm: this.frm
})
},
make_work_order() {
var me = this;
this.frm.call({

View File

@ -559,7 +559,7 @@ def make_project(source_name, target_doc=None):
return doc
@frappe.whitelist()
def make_delivery_note(source_name, target_doc=None):
def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
def set_missing_values(source, target):
target.ignore_pricing_rule = 1
target.run_method("set_missing_values")
@ -584,23 +584,13 @@ def make_delivery_note(source_name, target_doc=None):
or item.get("buying_cost_center") \
or item_group.get("buying_cost_center")
target_doc = get_mapped_doc("Sales Order", source_name, {
mapper = {
"Sales Order": {
"doctype": "Delivery Note",
"validation": {
"docstatus": ["=", 1]
}
},
"Sales Order Item": {
"doctype": "Delivery Note Item",
"field_map": {
"rate": "rate",
"name": "so_detail",
"parent": "against_sales_order",
},
"postprocess": update_item,
"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
},
"Sales Taxes and Charges": {
"doctype": "Sales Taxes and Charges",
"add_if_empty": True
@ -609,7 +599,21 @@ def make_delivery_note(source_name, target_doc=None):
"doctype": "Sales Team",
"add_if_empty": True
}
}, target_doc, set_missing_values)
}
if not skip_item_mapping:
mapper["Sales Order Item"] = {
"doctype": "Delivery Note Item",
"field_map": {
"rate": "rate",
"name": "so_detail",
"parent": "against_sales_order",
},
"postprocess": update_item,
"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
}
target_doc = get_mapped_doc("Sales Order", source_name, mapper, target_doc, set_missing_values)
return target_doc
@ -987,3 +991,33 @@ def make_raw_material_request(items, company, sales_order, project=None):
def make_inter_company_purchase_order(source_name, target_doc=None):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
return make_inter_company_transaction("Sales Order", source_name, target_doc)
@frappe.whitelist()
def create_pick_list(source_name, target_doc=None):
def update_item_quantity(source, target, source_parent):
target.qty = flt(source.qty) - flt(source.delivered_qty)
target.stock_qty = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.conversion_factor)
doc = get_mapped_doc('Sales Order', source_name, {
'Sales Order': {
'doctype': 'Pick List',
'validation': {
'docstatus': ['=', 1]
}
},
'Sales Order Item': {
'doctype': 'Pick List Item',
'field_map': {
'parent': 'sales_order',
'name': 'sales_order_item'
},
'postprocess': update_item_quantity,
'condition': lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
},
}, target_doc)
doc.purpose = 'Delivery against Sales Order'
doc.set_item_locations()
return doc

View File

@ -17,7 +17,7 @@ def get_data():
'transactions': [
{
'label': _('Fulfillment'),
'items': ['Sales Invoice', 'Delivery Note']
'items': ['Sales Invoice', 'Pick List', 'Delivery Note']
},
{
'label': _('Purchasing'),

View File

@ -31,7 +31,7 @@ class SMSCenter(Document):
self.sales_partner.replace("'", "\'") or " and ifnull(dl.link_name, '') != ''"
if self.send_to in ['All Contact', 'All Customer Contact', 'All Supplier Contact', 'All Sales Partner Contact']:
rec = frappe.db.sql("""select CONCAT(ifnull(c.first_name,''), ' ', ifnull(c.last_name,'')),
c.mobile_no from `tabContact` c, `tabDynamic Link` dl where ifnull(c.mobile_no,'')!='' and
c.phone from `tabContact` c, `tabDynamic Link` dl where ifnull(c.phone,'')!='' and
c.docstatus != 2 and dl.parent = c.name%s""" % where_clause)
elif self.send_to == 'All Lead (Open)':

View File

@ -58,7 +58,7 @@ class GlobalDefaults(Document):
# Make property setters to hide rounded total fields
for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note",
"Supplier Quotation", "Purchase Order"):
"Supplier Quotation", "Purchase Order", "Purchase Invoice"):
make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check")
make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check")

View File

@ -26,6 +26,10 @@ class ItemGroup(NestedSet, WebsiteGenerator):
def validate(self):
super(ItemGroup, self).validate()
if not self.parent_item_group and not frappe.flags.in_test:
self.parent_item_group = 'All Item Groups'
self.make_route()
def on_update(self):

View File

@ -336,19 +336,20 @@ def set_price_list_and_rate(quotation, cart_settings):
def _set_price_list(quotation, cart_settings):
"""Set price list based on customer or shopping cart default"""
if quotation.selling_price_list:
return
from erpnext.accounts.party import get_default_price_list
# check if customer price list exists
selling_price_list = None
if quotation.party_name:
from erpnext.accounts.party import get_default_price_list
selling_price_list = get_default_price_list(frappe.get_doc("Customer", quotation.party_name))
selling_price_list = frappe.db.get_value('Customer', quotation.party_name, 'default_price_list')
# else check for territory based price list
if not selling_price_list:
selling_price_list = cart_settings.price_list
if not selling_price_list and quotation.party_name:
selling_price_list = get_default_price_list(frappe.get_doc("Customer", quotation.party_name))
quotation.selling_price_list = selling_price_list
def set_taxes(quotation, cart_settings):

File diff suppressed because it is too large Load Diff

View File

@ -238,7 +238,7 @@ class DeliveryTrip(Document):
try:
directions = maps_client.directions(**directions_data)
except Exception as e:
frappe.throw(_(e.message))
frappe.throw(_(e))
return directions[0] if directions else False

View File

@ -8,6 +8,7 @@ frappe.ui.form.on('Material Request', {
setup: function(frm) {
frm.custom_make_buttons = {
'Stock Entry': 'Issue Material',
'Pick List': 'Pick List',
'Purchase Order': 'Purchase Order',
'Request for Quotation': 'Request for Quotation',
'Supplier Quotation': 'Supplier Quotation',
@ -55,8 +56,13 @@ frappe.ui.form.on('Material Request', {
if (frm.doc.docstatus == 1 && frm.doc.status != 'Stopped') {
if (flt(frm.doc.per_ordered, 2) < 100) {
// make
let add_create_pick_list_button = () => {
frm.add_custom_button(__('Pick List'),
() => frm.events.create_pick_list(frm), __('Create'));
}
if (frm.doc.material_request_type === "Material Transfer") {
add_create_pick_list_button();
frm.add_custom_button(__("Transfer Material"),
() => frm.events.make_stock_entry(frm), __('Create'));
}
@ -258,6 +264,13 @@ frappe.ui.form.on('Material Request', {
});
},
create_pick_list: (frm) => {
frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.material_request.material_request.create_pick_list",
frm: frm
});
},
raise_work_orders: function(frm) {
frappe.call({
method:"erpnext.stock.doctype.material_request.material_request.raise_work_orders",

View File

@ -502,3 +502,28 @@ def raise_work_orders(material_request):
frappe.throw(_("Productions Orders cannot be raised for:") + '\n' + new_line_sep(errors))
return work_orders
@frappe.whitelist()
def create_pick_list(source_name, target_doc=None):
doc = get_mapped_doc('Material Request', source_name, {
'Material Request': {
'doctype': 'Pick List',
'field_map': {
'material_request_type': 'purpose'
},
'validation': {
'docstatus': ['=', 1]
}
},
'Material Request Item': {
'doctype': 'Pick List Item',
'field_map': {
'name': 'material_request_item',
'qty': 'stock_qty'
},
},
}, target_doc)
doc.set_item_locations()
return doc

View File

@ -8,7 +8,7 @@ def get_data():
'transactions': [
{
'label': _('Related'),
'items': ['Request for Quotation', 'Supplier Quotation', 'Purchase Order', "Stock Entry"]
'items': ['Request for Quotation', 'Supplier Quotation', 'Purchase Order', 'Stock Entry', 'Pick List']
},
{
'label': _('Manufacturing'),

View File

@ -49,7 +49,7 @@ class PackingSlip(Document):
frappe.msgprint(_("Please specify a valid 'From Case No.'"), raise_exception=1)
elif not self.to_case_no:
self.to_case_no = self.from_case_no
elif self.from_case_no > self.to_case_no:
elif cint(self.from_case_no) > cint(self.to_case_no):
frappe.msgprint(_("'To Case No.' cannot be less than 'From Case No.'"),
raise_exception=1)

View File

@ -0,0 +1,180 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Pick List', {
setup: (frm) => {
frm.custom_make_buttons = {
'Delivery Note': 'Delivery Note',
'Stock Entry': 'Stock Entry',
};
frm.set_query('parent_warehouse', () => {
return {
filters: {
'is_group': 1,
'company': frm.doc.company
}
};
});
frm.set_query('work_order', () => {
return {
query: 'erpnext.stock.doctype.pick_list.pick_list.get_pending_work_orders',
filters: {
'company': frm.doc.company
}
};
});
frm.set_query('material_request', () => {
return {
filters: {
'material_request_type': ['=', frm.doc.purpose]
}
};
});
frm.set_query('item_code', 'locations', () => {
return {
filters: {
is_stock_item: 1
}
};
});
},
get_item_locations: (frm) => {
if (!frm.doc.locations || !frm.doc.locations.length) {
frappe.msgprint(__('First add items in the Item Locations table'));
} else {
frm.call('set_item_locations');
}
},
refresh: (frm) => {
frm.trigger('add_get_items_button');
if (frm.doc.docstatus === 1) {
frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.target_document_exists', {
'pick_list_name': frm.doc.name,
'purpose': frm.doc.purpose
}).then(target_document_exists => {
if (target_document_exists) return;
if (frm.doc.purpose === 'Delivery against Sales Order') {
frm.add_custom_button(__('Delivery Note'), () => frm.trigger('create_delivery_note'), __('Create'));
} else {
frm.add_custom_button(__('Stock Entry'), () => frm.trigger('create_stock_entry'), __('Create'));
}
});
}
},
work_order: (frm) => {
frappe.db.get_value('Work Order',
frm.doc.work_order,
['qty', 'material_transferred_for_manufacturing']
).then(data => {
let qty_data = data.message;
let max = qty_data.qty - qty_data.material_transferred_for_manufacturing;
frappe.prompt({
fieldtype: 'Float',
label: __('Qty of Finished Goods Item'),
fieldname: 'qty',
description: __('Max: {0}', [max]),
default: max
}, (data) => {
frm.set_value('for_qty', data.qty);
if (data.qty > max) {
frappe.msgprint(__('Quantity must not be more than {0}', [max]));
return;
}
frm.clear_table('locations');
erpnext.utils.map_current_doc({
method: 'erpnext.manufacturing.doctype.work_order.work_order.create_pick_list',
target: frm,
source_name: frm.doc.work_order
});
}, __('Select Quantity'), __('Get Items'));
});
},
material_request: (frm) => {
erpnext.utils.map_current_doc({
method: 'erpnext.stock.doctype.material_request.material_request.create_pick_list',
target: frm,
source_name: frm.doc.material_request
});
},
purpose: (frm) => {
frm.clear_table('locations');
frm.trigger('add_get_items_button');
},
create_delivery_note: (frm) => {
frappe.model.open_mapped_doc({
method: 'erpnext.stock.doctype.pick_list.pick_list.create_delivery_note',
frm: frm
});
},
create_stock_entry: (frm) => {
frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.create_stock_entry', {
'pick_list': frm.doc,
}).then(stock_entry => {
frappe.model.sync(stock_entry);
frappe.set_route("Form", 'Stock Entry', stock_entry.name);
});
},
add_get_items_button: (frm) => {
let purpose = frm.doc.purpose;
if (purpose != 'Delivery against Sales Order' || frm.doc.docstatus !== 0) return;
let get_query_filters = {
docstatus: 1,
per_delivered: ['<', 100],
status: ['!=', ''],
customer: frm.doc.customer
};
frm.get_items_btn = frm.add_custom_button(__('Get Items'), () => {
if (!frm.doc.customer) {
frappe.msgprint(__('Please select Customer first'));
return;
}
erpnext.utils.map_current_doc({
method: 'erpnext.selling.doctype.sales_order.sales_order.create_pick_list',
source_doctype: 'Sales Order',
target: frm,
setters: {
company: frm.doc.company,
customer: frm.doc.customer
},
date_field: 'transaction_date',
get_query_filters: get_query_filters
});
});
}
});
frappe.ui.form.on('Pick List Item', {
item_code: (frm, cdt, cdn) => {
let row = frappe.get_doc(cdt, cdn);
if (row.item_code) {
get_item_details(row.item_code).then(data => {
frappe.model.set_value(cdt, cdn, 'uom', data.stock_uom);
frappe.model.set_value(cdt, cdn, 'stock_uom', data.stock_uom);
frappe.model.set_value(cdt, cdn, 'conversion_factor', 1);
});
}
},
uom: (frm, cdt, cdn) => {
let row = frappe.get_doc(cdt, cdn);
if (row.uom) {
get_item_details(row.item_code, row.uom).then(data => {
frappe.model.set_value(cdt, cdn, 'conversion_factor', data.conversion_factor);
});
}
},
qty: (frm, cdt, cdn) => {
let row = frappe.get_doc(cdt, cdn);
frappe.model.set_value(cdt, cdn, 'stock_qty', row.qty * row.conversion_factor);
},
conversion_factor: (frm, cdt, cdn) => {
let row = frappe.get_doc(cdt, cdn);
frappe.model.set_value(cdt, cdn, 'stock_qty', row.qty * row.conversion_factor);
}
});
function get_item_details(item_code, uom=null) {
return frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.get_item_details', {
item_code,
uom
});
}

View File

@ -0,0 +1,184 @@
{
"autoname": "naming_series:",
"creation": "2019-07-11 16:03:13.681045",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"naming_series",
"company",
"purpose",
"customer",
"work_order",
"material_request",
"for_qty",
"column_break_4",
"parent_warehouse",
"get_item_locations",
"section_break_6",
"locations",
"amended_from"
],
"fields": [
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
},
{
"description": "Items under this warehouse will be suggested",
"fieldname": "parent_warehouse",
"fieldtype": "Link",
"label": "Parent Warehouse",
"options": "Warehouse"
},
{
"depends_on": "eval:doc.purpose==='Delivery against Sales Order'",
"fieldname": "customer",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Customer",
"options": "Customer"
},
{
"depends_on": "eval:doc.purpose==='Material Transfer for Manufacture'",
"fieldname": "work_order",
"fieldtype": "Link",
"label": "Work Order",
"options": "Work Order"
},
{
"fieldname": "locations",
"fieldtype": "Table",
"label": "Item Locations",
"options": "Pick List Item"
},
{
"depends_on": "eval:doc.purpose==='Material Transfer for Manufacture'",
"description": "Qty of raw materials will be decided based on the qty of the Finished Goods Item",
"fieldname": "for_qty",
"fieldtype": "Float",
"label": "Qty of Finished Goods Item",
"read_only": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Pick List",
"print_hide": 1,
"read_only": 1
},
{
"default": "Material Transfer for Manufacture",
"fieldname": "purpose",
"fieldtype": "Select",
"label": "Purpose",
"options": "Material Transfer for Manufacture\nMaterial Transfer\nDelivery against Sales Order"
},
{
"depends_on": "eval:['Material Transfer', 'Material Issue'].includes(doc.purpose)",
"fieldname": "material_request",
"fieldtype": "Link",
"label": "Material Request",
"options": "Material Request"
},
{
"depends_on": "eval:doc.docstatus===0",
"fieldname": "get_item_locations",
"fieldtype": "Button",
"label": "Get Item Locations"
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
"options": "STO-PICK-.YYYY.-",
"reqd": 1,
"set_only_once": 1
}
],
"is_submittable": 1,
"modified": "2019-08-29 21:10:11.572387",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Stock Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Stock User",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Manufacturing Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Manufacturing User",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,432 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import json
from six import iteritems
from frappe.model.document import Document
from frappe import _
from collections import OrderedDict
from frappe.utils import floor, flt, today, cint
from frappe.model.mapper import get_mapped_doc, map_child_doc
from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note as create_delivery_note_from_sales_order
# TODO: Prioritize SO or WO group warehouse
class PickList(Document):
def before_save(self):
self.set_item_locations()
def before_submit(self):
for item in self.locations:
if not frappe.get_cached_value('Item', item.item_code, 'has_serial_no'):
continue
if len(item.serial_no.split('\n')) == item.picked_qty:
continue
frappe.throw(_('For item {0} at row {1}, count of serial numbers does not match with the picked quantity')
.format(frappe.bold(item.item_code), frappe.bold(item.idx)))
def set_item_locations(self):
items = self.aggregate_item_qty()
self.item_location_map = frappe._dict()
from_warehouses = None
if self.parent_warehouse:
from_warehouses = frappe.db.get_descendants('Warehouse', self.parent_warehouse)
# reset
self.delete_key('locations')
for item_doc in items:
item_code = item_doc.item_code
self.item_location_map.setdefault(item_code,
get_available_item_locations(item_code, from_warehouses, self.item_count_map.get(item_code)))
locations = get_items_with_location_and_quantity(item_doc, self.item_location_map)
item_doc.idx = None
item_doc.name = None
for row in locations:
row.update({
'picked_qty': row.stock_qty
})
location = item_doc.as_dict()
location.update(row)
self.append('locations', location)
def aggregate_item_qty(self):
locations = self.get('locations')
self.item_count_map = {}
# aggregate qty for same item
item_map = OrderedDict()
for item in locations:
item_code = item.item_code
reference = item.sales_order_item or item.material_request_item
key = (item_code, item.uom, reference)
item.idx = None
item.name = None
if item_map.get(key):
item_map[key].qty += item.qty
item_map[key].stock_qty += item.stock_qty
else:
item_map[key] = item
# maintain count of each item (useful to limit get query)
self.item_count_map.setdefault(item_code, 0)
self.item_count_map[item_code] += item.stock_qty
return item_map.values()
def get_items_with_location_and_quantity(item_doc, item_location_map):
available_locations = item_location_map.get(item_doc.item_code)
locations = []
remaining_stock_qty = item_doc.stock_qty
while remaining_stock_qty > 0 and available_locations:
item_location = available_locations.pop(0)
item_location = frappe._dict(item_location)
stock_qty = remaining_stock_qty if item_location.qty >= remaining_stock_qty else item_location.qty
qty = stock_qty / (item_doc.conversion_factor or 1)
uom_must_be_whole_number = frappe.db.get_value('UOM', item_doc.uom, 'must_be_whole_number')
if uom_must_be_whole_number:
qty = floor(qty)
stock_qty = qty * item_doc.conversion_factor
if not stock_qty: break
serial_nos = None
if item_location.serial_no:
serial_nos = '\n'.join(item_location.serial_no[0: cint(stock_qty)])
locations.append(frappe._dict({
'qty': qty,
'stock_qty': stock_qty,
'warehouse': item_location.warehouse,
'serial_no': serial_nos,
'batch_no': item_location.batch_no
}))
remaining_stock_qty -= stock_qty
qty_diff = item_location.qty - stock_qty
# if extra quantity is available push current warehouse to available locations
if qty_diff > 0:
item_location.qty = qty_diff
if item_location.serial_no:
# set remaining serial numbers
item_location.serial_no = item_location.serial_no[-qty_diff:]
available_locations = [item_location] + available_locations
# update available locations for the item
item_location_map[item_doc.item_code] = available_locations
return locations
def get_available_item_locations(item_code, from_warehouses, required_qty):
locations = []
if frappe.get_cached_value('Item', item_code, 'has_serial_no'):
locations = get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty)
elif frappe.get_cached_value('Item', item_code, 'has_batch_no'):
locations = get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty)
else:
locations = get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty)
total_qty_available = sum(location.get('qty') for location in locations)
remaining_qty = required_qty - total_qty_available
if remaining_qty > 0:
frappe.msgprint(_('{0} units of {1} is not available.')
.format(remaining_qty, frappe.get_desk_link('Item', item_code)))
return locations
def get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty):
filters = frappe._dict({
'item_code': item_code,
'warehouse': ['!=', '']
})
if from_warehouses:
filters.warehouse = ['in', from_warehouses]
serial_nos = frappe.get_all('Serial No',
fields=['name', 'warehouse'],
filters=filters,
limit=required_qty,
order_by='purchase_date',
as_list=1)
warehouse_serial_nos_map = frappe._dict()
for serial_no, warehouse in serial_nos:
warehouse_serial_nos_map.setdefault(warehouse, []).append(serial_no)
locations = []
for warehouse, serial_nos in iteritems(warehouse_serial_nos_map):
locations.append({
'qty': len(serial_nos),
'warehouse': warehouse,
'serial_no': serial_nos
})
return locations
def get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty):
warehouse_condition = 'and warehouse in %(warehouses)s' if from_warehouses else ''
batch_locations = frappe.db.sql("""
SELECT
sle.`warehouse`,
sle.`batch_no`,
SUM(sle.`actual_qty`) AS `qty`
FROM
`tabStock Ledger Entry` sle, `tabBatch` batch
WHERE
sle.batch_no = batch.name
and sle.`item_code`=%(item_code)s
and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
{warehouse_condition}
GROUP BY
`warehouse`,
`batch_no`,
`item_code`
HAVING `qty` > 0
ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`
""".format(warehouse_condition=warehouse_condition), { #nosec
'item_code': item_code,
'today': today(),
'warehouses': from_warehouses
}, as_dict=1)
return batch_locations
def get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty):
# gets all items available in different warehouses
filters = frappe._dict({
'item_code': item_code,
'actual_qty': ['>', 0]
})
if from_warehouses:
filters.warehouse = ['in', from_warehouses]
item_locations = frappe.get_all('Bin',
fields=['warehouse', 'actual_qty as qty'],
filters=filters,
limit=required_qty,
order_by='creation')
return item_locations
@frappe.whitelist()
def create_delivery_note(source_name, target_doc=None):
pick_list = frappe.get_doc('Pick List', source_name)
sales_orders = [d.sales_order for d in pick_list.locations]
sales_orders = set(sales_orders)
delivery_note = None
for sales_order in sales_orders:
delivery_note = create_delivery_note_from_sales_order(sales_order,
delivery_note, skip_item_mapping=True)
item_table_mapper = {
'doctype': 'Delivery Note Item',
'field_map': {
'rate': 'rate',
'name': 'so_detail',
'parent': 'against_sales_order',
},
'condition': lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
}
for location in pick_list.locations:
sales_order_item = frappe.get_cached_doc('Sales Order Item', location.sales_order_item)
dn_item = map_child_doc(sales_order_item, delivery_note, item_table_mapper)
if dn_item:
dn_item.warehouse = location.warehouse
dn_item.qty = location.picked_qty
dn_item.batch_no = location.batch_no
dn_item.serial_no = location.serial_no
update_delivery_note_item(sales_order_item, dn_item, delivery_note)
set_delivery_note_missing_values(delivery_note)
delivery_note.pick_list = pick_list.name
return delivery_note
@frappe.whitelist()
def create_stock_entry(pick_list):
pick_list = frappe.get_doc(json.loads(pick_list))
if stock_entry_exists(pick_list.get('name')):
return frappe.msgprint(_('Stock Entry has been already created against this Pick List'))
stock_entry = frappe.new_doc('Stock Entry')
stock_entry.pick_list = pick_list.get('name')
stock_entry.purpose = pick_list.get('purpose')
stock_entry.set_stock_entry_type()
if pick_list.get('work_order'):
stock_entry = update_stock_entry_based_on_work_order(pick_list, stock_entry)
elif pick_list.get('material_request'):
stock_entry = update_stock_entry_based_on_material_request(pick_list, stock_entry)
else:
stock_entry = update_stock_entry_items_with_no_reference(pick_list, stock_entry)
stock_entry.set_incoming_rate()
stock_entry.set_actual_qty()
stock_entry.calculate_rate_and_amount(update_finished_item_rate=False)
return stock_entry.as_dict()
@frappe.whitelist()
def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filters, as_dict):
return frappe.db.sql("""
SELECT
`name`, `company`, `planned_start_date`
FROM
`tabWork Order`
WHERE
`status` not in ('Completed', 'Stopped')
AND `qty` > `material_transferred_for_manufacturing`
AND `docstatus` = 1
AND `company` = %(company)s
AND `name` like %(txt)s
ORDER BY
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), name
LIMIT
%(start)s, %(page_length)s""",
{
'txt': "%%%s%%" % txt,
'_txt': txt.replace('%', ''),
'start': start,
'page_length': frappe.utils.cint(page_length),
'company': filters.get('company')
}, as_dict=as_dict)
@frappe.whitelist()
def target_document_exists(pick_list_name, purpose):
if purpose == 'Delivery against Sales Order':
return frappe.db.exists('Delivery Note', {
'pick_list': pick_list_name
})
return stock_entry_exists(pick_list_name)
@frappe.whitelist()
def get_item_details(item_code, uom=None):
details = frappe.db.get_value('Item', item_code, ['stock_uom', 'name'], as_dict=1)
details.uom = uom or details.stock_uom
if uom:
details.update(get_conversion_factor(item_code, uom))
return details
def update_delivery_note_item(source, target, delivery_note):
cost_center = frappe.db.get_value('Project', delivery_note.project, 'cost_center')
if not cost_center:
cost_center = get_cost_center(source.item_code, 'Item', delivery_note.company)
if not cost_center:
cost_center = get_cost_center(source.item_group, 'Item Group', delivery_note.company)
target.cost_center = cost_center
def get_cost_center(for_item, from_doctype, company):
'''Returns Cost Center for Item or Item Group'''
return frappe.db.get_value('Item Default',
fieldname=['buying_cost_center'],
filters={
'parent': for_item,
'parenttype': from_doctype,
'company': company
})
def set_delivery_note_missing_values(target):
target.run_method('set_missing_values')
target.run_method('set_po_nos')
target.run_method('calculate_taxes_and_totals')
def stock_entry_exists(pick_list_name):
return frappe.db.exists('Stock Entry', {
'pick_list': pick_list_name
})
def update_stock_entry_based_on_work_order(pick_list, stock_entry):
work_order = frappe.get_doc("Work Order", pick_list.get('work_order'))
stock_entry.work_order = work_order.name
stock_entry.company = work_order.company
stock_entry.from_bom = 1
stock_entry.bom_no = work_order.bom_no
stock_entry.use_multi_level_bom = work_order.use_multi_level_bom
stock_entry.fg_completed_qty = pick_list.for_qty
if work_order.bom_no:
stock_entry.inspection_required = frappe.db.get_value('BOM',
work_order.bom_no, 'inspection_required')
is_wip_warehouse_group = frappe.db.get_value('Warehouse', work_order.wip_warehouse, 'is_group')
if not (is_wip_warehouse_group and work_order.skip_transfer):
wip_warehouse = work_order.wip_warehouse
else:
wip_warehouse = None
stock_entry.to_warehouse = wip_warehouse
stock_entry.project = work_order.project
for location in pick_list.locations:
item = frappe._dict()
update_common_item_properties(item, location)
item.t_warehouse = wip_warehouse
stock_entry.append('items', item)
return stock_entry
def update_stock_entry_based_on_material_request(pick_list, stock_entry):
for location in pick_list.locations:
target_warehouse = None
if location.material_request_item:
target_warehouse = frappe.get_value('Material Request Item',
location.material_request_item, 'warehouse')
item = frappe._dict()
update_common_item_properties(item, location)
item.t_warehouse = target_warehouse
stock_entry.append('items', item)
return stock_entry
def update_stock_entry_items_with_no_reference(pick_list, stock_entry):
for location in pick_list.locations:
item = frappe._dict()
update_common_item_properties(item, location)
stock_entry.append('items', item)
return stock_entry
def update_common_item_properties(item, location):
item.item_code = location.item_code
item.s_warehouse = location.warehouse
item.qty = location.picked_qty * location.conversion_factor
item.transfer_qty = location.picked_qty
item.uom = location.uom
item.conversion_factor = location.conversion_factor
item.stock_uom = location.stock_uom
item.material_request = location.material_request
item.serial_no = location.serial_no
item.batch_no = location.batch_no
item.material_request_item = location.material_request_item

View File

@ -0,0 +1,12 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'pick_list',
'transactions': [
{
'items': ['Stock Entry', 'Delivery Note']
},
]
}

View File

@ -0,0 +1,220 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch']
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation \
import EmptyStockReconciliationItemsError
class TestPickList(unittest.TestCase):
def test_pick_list_picks_warehouse_for_each_item(self):
try:
frappe.get_doc({
'doctype': 'Stock Reconciliation',
'company': '_Test Company',
'purpose': 'Opening Stock',
'expense_account': 'Temporary Opening - _TC',
'items': [{
'item_code': '_Test Item Home Desktop 100',
'warehouse': '_Test Warehouse - _TC',
'valuation_rate': 100,
'qty': 5
}]
}).submit()
except EmptyStockReconciliationItemsError:
pass
pick_list = frappe.get_doc({
'doctype': 'Pick List',
'company': '_Test Company',
'customer': '_Test Customer',
'items_based_on': 'Sales Order',
'locations': [{
'item_code': '_Test Item Home Desktop 100',
'qty': 5,
'stock_qty': 5,
'conversion_factor': 1,
'sales_order': '_T-Sales Order-1',
'sales_order_item': '_T-Sales Order-1_item',
}]
})
pick_list.set_item_locations()
self.assertEqual(pick_list.locations[0].item_code, '_Test Item Home Desktop 100')
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
self.assertEqual(pick_list.locations[0].qty, 5)
def test_pick_list_splits_row_according_to_warhouse_availability(self):
try:
frappe.get_doc({
'doctype': 'Stock Reconciliation',
'company': '_Test Company',
'purpose': 'Opening Stock',
'expense_account': 'Temporary Opening - _TC',
'items': [{
'item_code': '_Test Item Warehouse Group Wise Reorder',
'warehouse': '_Test Warehouse Group-C1 - _TC',
'valuation_rate': 100,
'qty': 5
}]
}).submit()
except EmptyStockReconciliationItemsError:
pass
try:
frappe.get_doc({
'doctype': 'Stock Reconciliation',
'company': '_Test Company',
'purpose': 'Opening Stock',
'expense_account': 'Temporary Opening - _TC',
'items': [{
'item_code': '_Test Item Warehouse Group Wise Reorder',
'warehouse': '_Test Warehouse 2 - _TC',
'valuation_rate': 400,
'qty': 10
}]
}).submit()
except EmptyStockReconciliationItemsError:
pass
pick_list = frappe.get_doc({
'doctype': 'Pick List',
'company': '_Test Company',
'customer': '_Test Customer',
'items_based_on': 'Sales Order',
'locations': [{
'item_code': '_Test Item Warehouse Group Wise Reorder',
'qty': 1000,
'stock_qty': 1000,
'conversion_factor': 1,
'sales_order': '_T-Sales Order-1',
'sales_order_item': '_T-Sales Order-1_item',
}]
})
pick_list.set_item_locations()
self.assertEqual(pick_list.locations[0].item_code, '_Test Item Warehouse Group Wise Reorder')
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse Group-C1 - _TC')
self.assertEqual(pick_list.locations[0].qty, 5)
self.assertEqual(pick_list.locations[1].item_code, '_Test Item Warehouse Group Wise Reorder')
self.assertEqual(pick_list.locations[1].warehouse, '_Test Warehouse 2 - _TC')
self.assertEqual(pick_list.locations[1].qty, 10)
def test_pick_list_shows_serial_no_for_serialized_item(self):
stock_reconciliation = frappe.get_doc({
'doctype': 'Stock Reconciliation',
'company': '_Test Company',
'items': [{
'item_code': '_Test Serialized Item',
'warehouse': '_Test Warehouse - _TC',
'valuation_rate': 100,
'qty': 5,
'serial_no': '123450\n123451\n123452\n123453\n123454'
}]
})
stock_reconciliation.submit()
pick_list = frappe.get_doc({
'doctype': 'Pick List',
'company': '_Test Company',
'customer': '_Test Customer',
'items_based_on': 'Sales Order',
'locations': [{
'item_code': '_Test Serialized Item',
'qty': 1000,
'stock_qty': 1000,
'conversion_factor': 1,
'sales_order': '_T-Sales Order-1',
'sales_order_item': '_T-Sales Order-1_item',
}]
})
pick_list.set_item_locations()
self.assertEqual(pick_list.locations[0].item_code, '_Test Serialized Item')
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
self.assertEqual(pick_list.locations[0].qty, 5)
self.assertEqual(pick_list.locations[0].serial_no, '123450\n123451\n123452\n123453\n123454')
def test_pick_list_for_items_from_multiple_sales_orders(self):
try:
frappe.get_doc({
'doctype': 'Stock Reconciliation',
'company': '_Test Company',
'purpose': 'Opening Stock',
'expense_account': 'Temporary Opening - _TC',
'items': [{
'item_code': '_Test Item Home Desktop 100',
'warehouse': '_Test Warehouse - _TC',
'valuation_rate': 100,
'qty': 10
}]
}).submit()
except EmptyStockReconciliationItemsError:
pass
sales_order = frappe.get_doc({
'doctype': "Sales Order",
'customer': '_Test Customer',
'company': '_Test Company',
'items': [{
'item_code': '_Test Item Home Desktop 100',
'qty': 10,
'delivery_date': frappe.utils.today()
}],
})
sales_order.submit()
pick_list = frappe.get_doc({
'doctype': 'Pick List',
'company': '_Test Company',
'customer': '_Test Customer',
'items_based_on': 'Sales Order',
'locations': [{
'item_code': '_Test Item Home Desktop 100',
'qty': 5,
'stock_qty': 5,
'conversion_factor': 1,
'sales_order': '_T-Sales Order-1',
'sales_order_item': '_T-Sales Order-1_item',
}, {
'item_code': '_Test Item Home Desktop 100',
'qty': 5,
'stock_qty': 5,
'conversion_factor': 1,
'sales_order': sales_order.name,
'sales_order_item': sales_order.items[0].name,
}]
})
pick_list.set_item_locations()
self.assertEqual(pick_list.locations[0].item_code, '_Test Item Home Desktop 100')
self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
self.assertEqual(pick_list.locations[0].qty, 5)
self.assertEqual(pick_list.locations[0].sales_order_item, '_T-Sales Order-1_item')
self.assertEqual(pick_list.locations[1].item_code, '_Test Item Home Desktop 100')
self.assertEqual(pick_list.locations[1].warehouse, '_Test Warehouse - _TC')
self.assertEqual(pick_list.locations[1].qty, 5)
self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name)
# def test_pick_list_skips_items_in_expired_batch(self):
# pass
# def test_pick_list_from_sales_order(self):
# pass
# def test_pick_list_from_work_order(self):
# pass
# def test_pick_list_from_material_request(self):
# pass

View File

@ -0,0 +1,182 @@
{
"creation": "2019-07-11 16:01:22.832885",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"item_code",
"item_name",
"column_break_2",
"description",
"section_break_5",
"warehouse",
"quantity_section",
"qty",
"stock_qty",
"picked_qty",
"column_break_11",
"uom",
"conversion_factor",
"stock_uom",
"serial_no_and_batch_section",
"serial_no",
"column_break_20",
"batch_no",
"column_break_15",
"sales_order",
"sales_order_item",
"material_request",
"material_request_item"
],
"fields": [
{
"default": "1",
"fieldname": "qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Qty"
},
{
"fieldname": "picked_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Picked Qty"
},
{
"fieldname": "warehouse",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Warehouse",
"options": "Warehouse",
"read_only": 1
},
{
"fetch_from": "item_code.item_name",
"fieldname": "item_name",
"fieldtype": "Data",
"label": "Item Name",
"read_only": 1
},
{
"fetch_from": "item_code.description",
"fieldname": "description",
"fieldtype": "Text",
"label": "Description",
"read_only": 1
},
{
"depends_on": "serial_no",
"fieldname": "serial_no",
"fieldtype": "Small Text",
"label": "Serial No"
},
{
"depends_on": "batch_no",
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
"options": "Batch"
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break"
},
{
"fieldname": "stock_uom",
"fieldtype": "Link",
"label": "Stock UOM",
"options": "UOM",
"read_only": 1
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"fieldname": "uom",
"fieldtype": "Link",
"label": "UOM",
"options": "UOM"
},
{
"fieldname": "conversion_factor",
"fieldtype": "Float",
"label": "UOM Conversion Factor",
"read_only": 1
},
{
"fieldname": "stock_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Stock Qty",
"read_only": 1
},
{
"fieldname": "item_code",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item",
"options": "Item"
},
{
"fieldname": "quantity_section",
"fieldtype": "Section Break",
"label": "Quantity"
},
{
"fieldname": "column_break_15",
"fieldtype": "Section Break",
"label": "Reference"
},
{
"fieldname": "sales_order",
"fieldtype": "Link",
"label": "Sales Order",
"options": "Sales Order",
"read_only": 1
},
{
"fieldname": "sales_order_item",
"fieldtype": "Data",
"label": "Sales Order Item",
"read_only": 1
},
{
"fieldname": "serial_no_and_batch_section",
"fieldtype": "Section Break",
"label": "Serial No and Batch"
},
{
"fieldname": "column_break_20",
"fieldtype": "Column Break"
},
{
"fieldname": "material_request",
"fieldtype": "Link",
"label": "Material Request",
"options": "Material Request",
"read_only": 1
},
{
"fieldname": "material_request_item",
"fieldtype": "Data",
"label": "Material Request Item",
"read_only": 1
}
],
"istable": 1,
"modified": "2019-08-29 21:28:39.539007",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List Item",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

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

View File

@ -329,6 +329,11 @@ class TestPurchaseReceipt(unittest.TestCase):
location = frappe.db.get_value('Serial No', serial_nos[0].name, 'location')
self.assertEquals(location, "Test Location")
frappe.db.set_value("Asset", asset, "purchase_receipt", "")
frappe.db.set_value("Purchase Receipt Item", pr.items[0].name, "asset", "")
pr.load_from_db()
pr.cancel()
serial_nos = frappe.get_all('Serial No', {'asset': asset}, 'name') or []
self.assertEquals(len(serial_nos), 0)

View File

@ -17,6 +17,7 @@
"purchase_order",
"delivery_note_no",
"sales_invoice_no",
"pick_list",
"purchase_receipt_no",
"col2",
"posting_date",
@ -613,12 +614,19 @@
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "pick_list",
"fieldtype": "Link",
"label": "Pick List",
"options": "Pick List",
"read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 1,
"is_submittable": 1,
"modified": "2019-07-14 17:41:39.257508",
"modified": "2019-08-22 17:11:42.074154",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry",

View File

@ -152,7 +152,6 @@ class TestStockReconciliation(unittest.TestCase):
for d in to_delete_records:
stock_doc = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel()
frappe.delete_doc("Stock Reconciliation", stock_doc.name)
for d in serial_nos + serial_nos1:
if frappe.db.exists("Serial No", d):
@ -203,9 +202,6 @@ class TestStockReconciliation(unittest.TestCase):
stock_doc = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel()
frappe.delete_doc("Batch", sr.items[0].batch_no)
for d in to_delete_records:
frappe.delete_doc("Stock Reconciliation", d)
def insert_existing_sle():
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

View File

@ -22,7 +22,7 @@ sales_doctypes = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']
purchase_doctypes = ['Material Request', 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
@frappe.whitelist()
def get_item_details(args, doc=None):
def get_item_details(args, doc=None, overwrite_warehouse=True):
"""
args = {
"item_code": "",
@ -44,11 +44,12 @@ def get_item_details(args, doc=None):
"set_warehouse": ""
}
"""
args = process_args(args)
item = frappe.get_cached_doc("Item", args.item_code)
validate_item_details(args, item)
out = get_basic_details(args, item)
out = get_basic_details(args, item, overwrite_warehouse)
get_item_tax_template(args, item, out)
out["item_tax_rate"] = get_item_tax_map(args.company, args.get("item_tax_template") if out.get("item_tax_template") is None \
@ -178,7 +179,7 @@ def validate_item_details(args, item):
throw(_("Item {0} must be a Sub-contracted Item").format(item.name))
def get_basic_details(args, item):
def get_basic_details(args, item, overwrite_warehouse=True):
"""
:param args: {
"item_code": "",
@ -225,14 +226,26 @@ def get_basic_details(args, item):
item_group_defaults = get_item_group_defaults(item.name, args.company)
brand_defaults = get_brand_defaults(item.name, args.company)
warehouse = (args.get("set_warehouse") or item_defaults.get("default_warehouse") or
item_group_defaults.get("default_warehouse") or brand_defaults.get("default_warehouse") or args.warehouse)
if overwrite_warehouse or not args.warehouse:
warehouse = (
args.get("set_warehouse") or
item_defaults.get("default_warehouse") or
item_group_defaults.get("default_warehouse") or
brand_defaults.get("default_warehouse") or
args.warehouse
)
if not warehouse:
defaults = frappe.defaults.get_defaults() or {}
if defaults.get("default_warehouse") and frappe.db.exists("Warehouse",
{'name': defaults.default_warehouse, 'company': args.company}):
warehouse = defaults.default_warehouse
if not warehouse:
defaults = frappe.defaults.get_defaults() or {}
warehouse_exists = frappe.db.exists("Warehouse", {
'name': defaults.default_warehouse,
'company': args.company
})
if defaults.get("default_warehouse") and warehouse_exists:
warehouse = defaults.default_warehouse
else:
warehouse = args.warehouse
if args.get('doctype') == "Material Request" and not args.get('material_request_type'):
args['material_request_type'] = frappe.db.get_value('Material Request',

View File

View File

@ -0,0 +1,23 @@
{
"align_labels_right": 1,
"creation": "2019-08-02 07:27:42.533305",
"custom_format": 0,
"disabled": 0,
"doc_type": "Pick List",
"docstatus": 0,
"doctype": "Print Format",
"font": "Default",
"format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"<div class=\\\"print-heading\\\">\\t\\t\\t\\t<h2>Pick List<br><small>{{ doc.name }}</small>\\t\\t\\t\\t</h2></div>\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"company\", \"label\": \"Company\"}, {\"print_hide\": 0, \"fieldname\": \"customer\", \"label\": \"Customer\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"purpose\", \"label\": \"Purpose\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"item_name\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"warehouse\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"qty\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"stock_qty\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"serial_no\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"batch_no\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"locations\", \"label\": \"Item Locations\"}]",
"idx": 0,
"line_breaks": 1,
"modified": "2019-08-30 15:58:27.807219",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List",
"owner": "Administrator",
"print_format_builder": 1,
"print_format_type": "Jinja",
"raw_printing": 0,
"show_section_headings": 1,
"standard": "Yes"
}

View File

@ -181,10 +181,7 @@ class update_entries_after(object):
# rounding as per precision
self.stock_value = flt(self.stock_value, self.precision)
if self.prev_stock_value < 0 and self.stock_value >= 0 and sle.voucher_type != 'Stock Reconciliation':
stock_value_difference = sle.actual_qty * self.valuation_rate
else:
stock_value_difference = self.stock_value - self.prev_stock_value
stock_value_difference = self.stock_value - self.prev_stock_value
self.prev_stock_value = self.stock_value

View File

@ -15,7 +15,7 @@ def get_stock_value_from_bin(warehouse=None, item_code=None):
values = {}
conditions = ""
if warehouse:
conditions += """ and warehouse in (
conditions += """ and `tabBin`.warehouse in (
select w2.name from `tabWarehouse` w1
join `tabWarehouse` w2 on
w1.name = %(warehouse)s
@ -25,11 +25,12 @@ def get_stock_value_from_bin(warehouse=None, item_code=None):
values['warehouse'] = warehouse
if item_code:
conditions += " and item_code = %(item_code)s"
conditions += " and `tabBin`.item_code = %(item_code)s"
values['item_code'] = item_code
query = "select sum(stock_value) from `tabBin` where 1 = 1 %s" % conditions
query = """select sum(stock_value) from `tabBin`, `tabItem` where 1 = 1
and `tabItem`.name = `tabBin`.item_code and ifnull(`tabItem`.disabled, 0) = 0 %s""" % conditions
stock_value = frappe.db.sql(query, values)