Merge branch 'develop' into version-12

This commit is contained in:
Sahil Khan
2019-09-03 16:33:06 +05:30
97 changed files with 4103 additions and 6538 deletions

View File

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

View File

@@ -128,7 +128,8 @@ class Account(NestedSet):
"account_currency": self.account_currency, "account_currency": self.account_currency,
"parent_account": parent_acc_name_map[company] "parent_account": parent_acc_name_map[company]
}) })
doc.save() if not self.check_if_child_acc_exists(doc):
doc.save()
frappe.msgprint(_("Account {0} is added in the child company {1}") frappe.msgprint(_("Account {0} is added in the child company {1}")
.format(doc.name, company)) .format(doc.name, company))
@@ -172,6 +173,24 @@ class Account(NestedSet):
if frappe.db.get_value("GL Entry", {"account": self.name}): if frappe.db.get_value("GL Entry", {"account": self.name}):
frappe.throw(_("Currency can not be changed after making entries using some other currency")) frappe.throw(_("Currency can not be changed after making entries using some other currency"))
def check_if_child_acc_exists(self, doc):
''' Checks if a account in parent company exists in the '''
info = frappe.db.get_value("Account", {
"account_name": doc.account_name,
"account_number": doc.account_number
}, ['company', 'account_currency', 'is_group', 'root_type', 'account_type', 'balance_must_be', 'account_name'], as_dict=1)
if not info:
return
doc = vars(doc)
dict_diff = [k for k in info if k in doc and info[k] != doc[k] and k != "company"]
if dict_diff:
frappe.throw(_("Account {0} already exists in child company {1}. The following fields have different values, they should be same:<ul><li>{2}</li></ul>")
.format(info.account_name, info.company, '</li><li>'.join(dict_diff)))
else:
return True
def convert_group_to_ledger(self): def convert_group_to_ledger(self):
if self.check_if_child_exists(): if self.check_if_child_exists():
throw(_("Account with child nodes cannot be converted to ledger")) throw(_("Account with child nodes cannot be converted to ledger"))

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

View File

@@ -8,7 +8,8 @@
"customer", "customer",
"column_break_3", "column_break_3",
"posting_date", "posting_date",
"outstanding_amount" "outstanding_amount",
"debit_to"
], ],
"fields": [ "fields": [
{ {
@@ -48,10 +49,18 @@
{ {
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"fetch_from": "sales_invoice.debit_to",
"fieldname": "debit_to",
"fieldtype": "Link",
"label": "Debit to",
"options": "Account",
"read_only": 1
} }
], ],
"istable": 1, "istable": 1,
"modified": "2019-05-30 19:27:29.436153", "modified": "2019-08-07 15:13:55.808349",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Discounted Invoice", "name": "Discounted Invoice",

View File

@@ -13,41 +13,57 @@ frappe.ui.form.on('Invoice Discounting', {
}; };
}); });
frm.events.filter_accounts("bank_account", frm, {"account_type": "Bank"});
frm.events.filter_accounts("bank_charges_account", frm, {"root_type": "Expense"}); frm.events.filter_accounts("bank_account", frm, [["account_type", "=", "Bank"]]);
frm.events.filter_accounts("short_term_loan", frm, {"root_type": "Liability"}); frm.events.filter_accounts("bank_charges_account", frm, [["root_type", "=", "Expense"]]);
frm.events.filter_accounts("accounts_receivable_credit", frm, {"account_type": "Receivable"}); frm.events.filter_accounts("short_term_loan", frm, [["root_type", "=", "Liability"]]);
frm.events.filter_accounts("accounts_receivable_discounted", frm, {"account_type": "Receivable"}); frm.events.filter_accounts("accounts_receivable_discounted", frm, [["account_type", "=", "Receivable"]]);
frm.events.filter_accounts("accounts_receivable_unpaid", frm, {"account_type": "Receivable"}); frm.events.filter_accounts("accounts_receivable_credit", frm, [["account_type", "=", "Receivable"]]);
frm.events.filter_accounts("accounts_receivable_unpaid", frm, [["account_type", "=", "Receivable"]]);
}, },
filter_accounts: (fieldname, frm, addl_filters) => { filter_accounts: (fieldname, frm, addl_filters) => {
let filters = { let filters = [
"company": frm.doc.company, ["company", "=", frm.doc.company],
"is_group": 0 ["is_group", "=", 0]
}; ];
if(addl_filters) Object.assign(filters, addl_filters); if(addl_filters){
filters = $.merge(filters , addl_filters);
}
frm.set_query(fieldname, () => { return { "filters": filters }; }); frm.set_query(fieldname, () => { return { "filters": filters }; });
}, },
refresh_filters: (frm) =>{
let invoice_accounts = Object.keys(frm.doc.invoices).map(function(key) {
return frm.doc.invoices[key].debit_to;
});
let filters = [
["account_type", "=", "Receivable"],
["name", "not in", invoice_accounts]
];
frm.events.filter_accounts("accounts_receivable_credit", frm, filters);
frm.events.filter_accounts("accounts_receivable_discounted", frm, filters);
frm.events.filter_accounts("accounts_receivable_unpaid", frm, filters);
},
refresh: (frm) => { refresh: (frm) => {
frm.events.show_general_ledger(frm); frm.events.show_general_ledger(frm);
if(frm.doc.docstatus === 0) { if (frm.doc.docstatus === 0) {
frm.add_custom_button(__('Get Invoices'), function() { frm.add_custom_button(__('Get Invoices'), function() {
frm.events.get_invoices(frm); frm.events.get_invoices(frm);
}); });
} }
if(frm.doc.docstatus === 1 && frm.doc.status !== "Settled") { if (frm.doc.docstatus === 1 && frm.doc.status !== "Settled") {
if(frm.doc.status == "Sanctioned") { if (frm.doc.status == "Sanctioned") {
frm.add_custom_button(__('Disburse Loan'), function() { frm.add_custom_button(__('Disburse Loan'), function() {
frm.events.create_disbursement_entry(frm); frm.events.create_disbursement_entry(frm);
}).addClass("btn-primary"); }).addClass("btn-primary");
} }
if(frm.doc.status == "Disbursed") { if (frm.doc.status == "Disbursed") {
frm.add_custom_button(__('Close Loan'), function() { frm.add_custom_button(__('Close Loan'), function() {
frm.events.close_loan(frm); frm.events.close_loan(frm);
}).addClass("btn-primary"); }).addClass("btn-primary");
@@ -64,7 +80,7 @@ frappe.ui.form.on('Invoice Discounting', {
}, },
set_end_date: (frm) => { set_end_date: (frm) => {
if(frm.doc.loan_start_date && frm.doc.loan_period) { if (frm.doc.loan_start_date && frm.doc.loan_period) {
let end_date = frappe.datetime.add_days(frm.doc.loan_start_date, frm.doc.loan_period); let end_date = frappe.datetime.add_days(frm.doc.loan_start_date, frm.doc.loan_period);
frm.set_value("loan_end_date", end_date); frm.set_value("loan_end_date", end_date);
} }
@@ -132,6 +148,7 @@ frappe.ui.form.on('Invoice Discounting', {
frm.doc.invoices = frm.doc.invoices.filter(row => row.sales_invoice); frm.doc.invoices = frm.doc.invoices.filter(row => row.sales_invoice);
let row = frm.add_child("invoices"); let row = frm.add_child("invoices");
$.extend(row, v); $.extend(row, v);
frm.events.refresh_filters(frm);
}); });
refresh_field("invoices"); refresh_field("invoices");
} }
@@ -190,8 +207,10 @@ frappe.ui.form.on('Invoice Discounting', {
frappe.ui.form.on('Discounted Invoice', { frappe.ui.form.on('Discounted Invoice', {
sales_invoice: (frm) => { sales_invoice: (frm) => {
frm.events.calculate_total_amount(frm); frm.events.calculate_total_amount(frm);
frm.events.refresh_filters(frm);
}, },
invoices_remove: (frm) => { invoices_remove: (frm) => {
frm.events.calculate_total_amount(frm); frm.events.calculate_total_amount(frm);
frm.events.refresh_filters(frm);
} }
}); });

View File

@@ -12,6 +12,7 @@ from erpnext.accounts.general_ledger import make_gl_entries
class InvoiceDiscounting(AccountsController): class InvoiceDiscounting(AccountsController):
def validate(self): def validate(self):
self.validate_mandatory() self.validate_mandatory()
self.validate_invoices()
self.calculate_total_amount() self.calculate_total_amount()
self.set_status() self.set_status()
self.set_end_date() self.set_end_date()
@@ -24,6 +25,15 @@ class InvoiceDiscounting(AccountsController):
if self.docstatus == 1 and not (self.loan_start_date and self.loan_period): if self.docstatus == 1 and not (self.loan_start_date and self.loan_period):
frappe.throw(_("Loan Start Date and Loan Period are mandatory to save the Invoice Discounting")) frappe.throw(_("Loan Start Date and Loan Period are mandatory to save the Invoice Discounting"))
def validate_invoices(self):
discounted_invoices = [record.sales_invoice for record in
frappe.get_all("Discounted Invoice",fields = ["sales_invoice"], filters= {"docstatus":1})]
for record in self.invoices:
if record.sales_invoice in discounted_invoices:
frappe.throw("Row({0}): {1} is already discounted in {2}"
.format(record.idx, frappe.bold(record.sales_invoice), frappe.bold(record.parent)))
def calculate_total_amount(self): def calculate_total_amount(self):
self.total_amount = sum([flt(d.outstanding_amount) for d in self.invoices]) self.total_amount = sum([flt(d.outstanding_amount) for d in self.invoices])
@@ -212,7 +222,8 @@ def get_invoices(filters):
name as sales_invoice, name as sales_invoice,
customer, customer,
posting_date, posting_date,
outstanding_amount outstanding_amount,
debit_to
from `tabSales Invoice` si from `tabSales Invoice` si
where where
docstatus = 1 docstatus = 1

View File

@@ -49,7 +49,6 @@ class TestLoyaltyProgram(unittest.TestCase):
# cancel and delete # cancel and delete
for d in [si_redeem, si_original]: for d in [si_redeem, si_original]:
d.cancel() d.cancel()
frappe.delete_doc('Sales Invoice', d.name)
def test_loyalty_points_earned_multiple_tier(self): def test_loyalty_points_earned_multiple_tier(self):
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty") frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty")
@@ -91,7 +90,6 @@ class TestLoyaltyProgram(unittest.TestCase):
# cancel and delete # cancel and delete
for d in [si_redeem, si_original]: for d in [si_redeem, si_original]:
d.cancel() d.cancel()
frappe.delete_doc('Sales Invoice', d.name)
def test_cancel_sales_invoice(self): def test_cancel_sales_invoice(self):
''' cancelling the sales invoice should cancel the earned points''' ''' cancelling the sales invoice should cancel the earned points'''
@@ -143,7 +141,6 @@ class TestLoyaltyProgram(unittest.TestCase):
d.cancel() d.cancel()
except frappe.TimestampMismatchError: except frappe.TimestampMismatchError:
frappe.get_doc('Sales Invoice', d.name).cancel() frappe.get_doc('Sales Invoice', d.name).cancel()
frappe.delete_doc('Sales Invoice', d.name)
def test_loyalty_points_for_dashboard(self): def test_loyalty_points_for_dashboard(self):
doc = frappe.get_doc('Customer', 'Test Loyalty Customer') 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) paid_amount = abs(outstanding_amount)
if bank_amount: if bank_amount:
received_amount = bank_amount received_amount = bank_amount
else:
received_amount = paid_amount * doc.conversion_rate
else: else:
received_amount = abs(outstanding_amount) received_amount = abs(outstanding_amount)
if bank_amount: if bank_amount:
paid_amount = 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 = frappe.new_doc("Payment Entry")
pe.payment_type = payment_type pe.payment_type = payment_type

View File

@@ -1,29 +0,0 @@
frappe.ui.form.on('Payment Order', {
refresh: function(frm) {
if (frm.doc.docstatus==1 && frm.doc.payment_order_type==='Payment Entry') {
frm.add_custom_button(__('Generate Text File'), function() {
frm.trigger("generate_text_and_download_file");
});
}
},
generate_text_and_download_file: (frm) => {
return frappe.call({
method: "erpnext.regional.india.bank_remittance.generate_report",
args: {
name: frm.doc.name
},
freeze: true,
callback: function(r) {
{
frm.reload_doc();
const a = document.createElement('a');
let file_obj = r.message;
a.href = file_obj.file_url;
a.target = '_blank';
a.download = file_obj.file_name;
a.click();
}
}
});
}
});

View File

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

View File

@@ -227,8 +227,8 @@ def get_contacts(customers):
customers = [frappe._dict({'name': customers})] customers = [frappe._dict({'name': customers})]
for data in customers: for data in customers:
contact = frappe.db.sql(""" select email_id, phone, mobile_no from `tabContact` contact = frappe.db.sql(""" select email_id, phone from `tabContact`
where is_primary_contact =1 and name in where is_primary_contact=1 and name in
(select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s (select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s
and parenttype = 'Contact')""", data.name, as_dict=1) and parenttype = 'Contact')""", data.name, as_dict=1)
if contact: if contact:
@@ -432,7 +432,6 @@ def get_customer_id(doc, customer=None):
return cust_id return cust_id
def make_customer_and_address(customers): def make_customer_and_address(customers):
customers_list = [] customers_list = []
for customer, data in iteritems(customers): for customer, data in iteritems(customers):
@@ -449,7 +448,6 @@ def make_customer_and_address(customers):
frappe.db.commit() frappe.db.commit()
return customers_list return customers_list
def add_customer(data): def add_customer(data):
customer = data.get('full_name') or data.get('customer') customer = data.get('full_name') or data.get('customer')
if frappe.db.exists("Customer", customer.strip()): if frappe.db.exists("Customer", customer.strip()):
@@ -466,21 +464,18 @@ def add_customer(data):
frappe.db.commit() frappe.db.commit()
return customer_doc.name return customer_doc.name
def get_territory(data): def get_territory(data):
if data.get('territory'): if data.get('territory'):
return data.get('territory') return data.get('territory')
return frappe.db.get_single_value('Selling Settings','territory') or _('All Territories') return frappe.db.get_single_value('Selling Settings','territory') or _('All Territories')
def get_customer_group(data): def get_customer_group(data):
if data.get('customer_group'): if data.get('customer_group'):
return data.get('customer_group') return data.get('customer_group')
return frappe.db.get_single_value('Selling Settings', 'customer_group') or frappe.db.get_value('Customer Group', {'is_group': 0}, 'name') return frappe.db.get_single_value('Selling Settings', 'customer_group') or frappe.db.get_value('Customer Group', {'is_group': 0}, 'name')
def make_contact(args, customer): def make_contact(args, customer):
if args.get('email_id') or args.get('phone'): if args.get('email_id') or args.get('phone'):
name = frappe.db.get_value('Dynamic Link', name = frappe.db.get_value('Dynamic Link',
@@ -506,7 +501,6 @@ def make_contact(args, customer):
doc.flags.ignore_mandatory = True doc.flags.ignore_mandatory = True
doc.save(ignore_permissions=True) doc.save(ignore_permissions=True)
def make_address(args, customer): def make_address(args, customer):
if not args.get('address_line1'): if not args.get('address_line1'):
return return
@@ -521,7 +515,10 @@ def make_address(args, customer):
address = frappe.get_doc('Address', name) address = frappe.get_doc('Address', name)
else: else:
address = frappe.new_doc('Address') address = frappe.new_doc('Address')
address.country = frappe.get_cached_value('Company', args.get('company'), 'country') if args.get('company'):
address.country = frappe.get_cached_value('Company',
args.get('company'), 'country')
address.append('links', { address.append('links', {
'link_doctype': 'Customer', 'link_doctype': 'Customer',
'link_name': customer 'link_name': customer
@@ -533,7 +530,6 @@ def make_address(args, customer):
address.flags.ignore_mandatory = True address.flags.ignore_mandatory = True
address.save(ignore_permissions=True) address.save(ignore_permissions=True)
def make_email_queue(email_queue): def make_email_queue(email_queue):
name_list = [] name_list = []
for key, data in iteritems(email_queue): for key, data in iteritems(email_queue):
@@ -550,7 +546,6 @@ def make_email_queue(email_queue):
return name_list return name_list
def validate_item(doc): def validate_item(doc):
for item in doc.get('items'): for item in doc.get('items'):
if not frappe.db.exists('Item', item.get('item_code')): if not frappe.db.exists('Item', item.get('item_code')):
@@ -569,7 +564,6 @@ def validate_item(doc):
item_doc.save(ignore_permissions=True) item_doc.save(ignore_permissions=True)
frappe.db.commit() frappe.db.commit()
def submit_invoice(si_doc, name, doc, name_list): def submit_invoice(si_doc, name, doc, name_list):
try: try:
si_doc.insert() si_doc.insert()
@@ -585,7 +579,6 @@ def submit_invoice(si_doc, name, doc, name_list):
return name_list return name_list
def save_invoice(doc, name, name_list): def save_invoice(doc, name, name_list):
try: try:
if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}): if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}):

View File

@@ -206,9 +206,9 @@ class SalesInvoice(SellingController):
total_amount_in_payments = 0 total_amount_in_payments = 0
for payment in self.payments: for payment in self.payments:
total_amount_in_payments += payment.amount total_amount_in_payments += payment.amount
invoice_total = self.rounded_total or self.grand_total
if total_amount_in_payments < self.rounded_total: if total_amount_in_payments < invoice_total:
frappe.throw(_("Total payments amount can't be greater than {}".format(-self.rounded_total))) frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total)))
def validate_pos_paid_amount(self): def validate_pos_paid_amount(self):
if len(self.payments) == 0 and self.is_pos: if len(self.payments) == 0 and self.is_pos:
@@ -1510,4 +1510,4 @@ def create_invoice_discounting(source_name, target_doc=None):
"outstanding_amount": invoice.outstanding_amount "outstanding_amount": invoice.outstanding_amount
}) })
return invoice_discounting return invoice_discounting

View File

@@ -818,7 +818,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_gl_entries[i][2], gle.credit) self.assertEqual(expected_gl_entries[i][2], gle.credit)
si.cancel() si.cancel()
frappe.delete_doc('Sales Invoice', si.name)
gle = frappe.db.sql("""select * from `tabGL Entry` gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) 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 #delete invoices to avoid clashing
for d in invoices: for d in invoices:
d.cancel() d.cancel()
frappe.delete_doc("Purchase Invoice", d.name)
def test_single_threshold_tds(self): def test_single_threshold_tds(self):
invoices = [] invoices = []
@@ -83,7 +82,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
# delete invoices to avoid clashing # delete invoices to avoid clashing
for d in invoices: for d in invoices:
d.cancel() d.cancel()
frappe.delete_doc("Purchase Invoice", d.name)
def test_single_threshold_tds_with_previous_vouchers(self): def test_single_threshold_tds_with_previous_vouchers(self):
invoices = [] invoices = []
@@ -102,7 +100,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
# delete invoices to avoid clashing # delete invoices to avoid clashing
for d in invoices: for d in invoices:
d.cancel() d.cancel()
frappe.delete_doc("Purchase Invoice", d.name)
def create_purchase_invoice(**args): def create_purchase_invoice(**args):
# return sales invoice doc object # return sales invoice doc object

View File

@@ -816,7 +816,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
contact = me.contacts[data.name]; contact = me.contacts[data.name];
if(reg.test(data.name.toLowerCase()) if(reg.test(data.name.toLowerCase())
|| reg.test(data.customer_name.toLowerCase()) || reg.test(data.customer_name.toLowerCase())
|| (contact && reg.test(contact["mobile_no"]))
|| (contact && reg.test(contact["phone"])) || (contact && reg.test(contact["phone"]))
|| (data.customer_group && reg.test(data.customer_group.toLowerCase()))){ || (data.customer_group && reg.test(data.customer_group.toLowerCase()))){
return data; return data;
@@ -834,7 +833,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
if(contact && !c['phone']) { if(contact && !c['phone']) {
c["phone"] = contact["phone"]; c["phone"] = contact["phone"];
c["email_id"] = contact["email_id"]; c["email_id"] = contact["email_id"];
c["mobile_no"] = contact["mobile_no"];
} }
me.customers_mapper.push({ me.customers_mapper.push({
@@ -844,10 +842,9 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
customer_group: c.customer_group, customer_group: c.customer_group,
territory: c.territory, territory: c.territory,
phone: contact ? contact["phone"] : '', phone: contact ? contact["phone"] : '',
mobile_no: contact ? contact["mobile_no"] : '',
email_id: contact ? contact["email_id"] : '', email_id: contact ? contact["email_id"] : '',
searchtext: ['customer_name', 'customer_group', 'name', 'value', searchtext: ['customer_name', 'customer_group', 'name', 'value',
'label', 'email_id', 'phone', 'mobile_no'] 'label', 'email_id', 'phone']
.map(key => c[key]).join(' ') .map(key => c[key]).join(' ')
.toLowerCase() .toLowerCase()
}); });
@@ -1762,18 +1759,11 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
this.si_docs = this.get_submitted_invoice() || []; this.si_docs = this.get_submitted_invoice() || [];
this.email_queue_list = this.get_email_queue() || {}; this.email_queue_list = this.get_email_queue() || {};
this.customers_list = this.get_customers_details() || {}; this.customers_list = this.get_customers_details() || {};
if(this.customer_doc) {
this.freeze = this.customer_doc.display
}
freeze_screen = this.freeze_screen || false;
if ((this.si_docs.length || this.email_queue_list || this.customers_list) && !this.freeze) {
this.freeze = true;
if (this.si_docs.length || this.email_queue_list || this.customers_list) {
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.sales_invoice.pos.make_invoice", method: "erpnext.accounts.doctype.sales_invoice.pos.make_invoice",
freeze: freeze_screen, freeze: true,
args: { args: {
doc_list: me.si_docs, doc_list: me.si_docs,
email_queue_list: me.email_queue_list, email_queue_list: me.email_queue_list,

View File

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

View File

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

View File

@@ -14,33 +14,44 @@ class TestAccountsReceivable(unittest.TestCase):
filters = { filters = {
'company': '_Test Company 2', '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() name = make_sales_invoice()
report = execute(filters) 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]) for i in range(3):
self.assertEqual(expected_data[1], report[1][1][7:9]) row = report[1][i-1]
self.assertEqual(expected_data[2], report[1][2][7:9]) 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) make_payment(name)
report = execute(filters) 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]) for i in range(2):
self.assertEqual(expected_data_after_payment[1], report[1][1][7:9]) 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) make_credit_note(name)
report = execute(filters) report = execute(filters)
expected_data_after_credit_note = [[100,100,30,100,-30]] expected_data_after_credit_note = [100, 0, 0, 40, -40]
self.assertEqual(expected_data_after_credit_note[0], report[1][0][7:12])
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(): def make_sales_invoice():
frappe.set_user("Administrator") frappe.set_user("Administrator")
@@ -64,7 +75,7 @@ def make_sales_invoice():
return si.name return si.name
def make_payment(docname): 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.paid_from = "Debtors - _TC2"
pe.insert() pe.insert()
pe.submit() pe.submit()

View File

@@ -3,236 +3,11 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _, scrub from frappe import _
from frappe.utils import flt from frappe.utils import flt, cint
from erpnext.accounts.party import get_partywise_advanced_payment_amount from erpnext.accounts.party import get_partywise_advanced_payment_amount
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
from six import iteritems 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): def execute(filters=None):
args = { args = {
@@ -241,3 +16,119 @@ def execute(filters=None):
} }
return AccountsReceivableSummary(filters).run(args) 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

@@ -135,11 +135,11 @@ def get_chart_data(filters, columns, asset, liability, equity):
datasets = [] datasets = []
if asset_data: if asset_data:
datasets.append({'name':'Assets', 'values': asset_data}) datasets.append({'name': _('Assets'), 'values': asset_data})
if liability_data: if liability_data:
datasets.append({'name':'Liabilities', 'values': liability_data}) datasets.append({'name': _('Liabilities'), 'values': liability_data})
if equity_data: if equity_data:
datasets.append({'name':'Equity', 'values': equity_data}) datasets.append({'name': _('Equity'), 'values': equity_data})
chart = { chart = {
"data": { "data": {

View File

@@ -27,8 +27,8 @@ frappe.query_reports["Payment Period Based On Invoice Date"] = {
fieldname:"payment_type", fieldname:"payment_type",
label: __("Payment Type"), label: __("Payment Type"),
fieldtype: "Select", fieldtype: "Select",
options: "Incoming\nOutgoing", options: __("Incoming") + "\n" + __("Outgoing"),
default: "Incoming" default: __("Incoming")
}, },
{ {
"fieldname":"party_type", "fieldname":"party_type",

View File

@@ -39,8 +39,8 @@ def execute(filters=None):
return columns, data return columns, data
def validate_filters(filters): def validate_filters(filters):
if (filters.get("payment_type") == "Incoming" and filters.get("party_type") == "Supplier") or \ if (filters.get("payment_type") == _("Incoming") and filters.get("party_type") == "Supplier") or \
(filters.get("payment_type") == "Outgoing" and filters.get("party_type") == "Customer"): (filters.get("payment_type") == _("Outgoing") and filters.get("party_type") == "Customer"):
frappe.throw(_("{0} payment entries can not be filtered by {1}")\ frappe.throw(_("{0} payment entries can not be filtered by {1}")\
.format(filters.payment_type, filters.party_type)) .format(filters.payment_type, filters.party_type))
@@ -51,7 +51,7 @@ def get_columns(filters):
_("Party Type") + "::100", _("Party Type") + "::100",
_("Party") + ":Dynamic Link/Party Type:140", _("Party") + ":Dynamic Link/Party Type:140",
_("Posting Date") + ":Date:100", _("Posting Date") + ":Date:100",
_("Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == "Outgoing" else ":Link/Sales Invoice:130"), _("Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == _("Outgoing") else ":Link/Sales Invoice:130"),
_("Invoice Posting Date") + ":Date:130", _("Invoice Posting Date") + ":Date:130",
_("Payment Due Date") + ":Date:130", _("Payment Due Date") + ":Date:130",
_("Debit") + ":Currency:120", _("Debit") + ":Currency:120",
@@ -69,7 +69,7 @@ def get_conditions(filters):
conditions = [] conditions = []
if not filters.party_type: if not filters.party_type:
if filters.payment_type == "Outgoing": if filters.payment_type == _("Outgoing"):
filters.party_type = "Supplier" filters.party_type = "Supplier"
else: else:
filters.party_type = "Customer" filters.party_type = "Customer"
@@ -101,7 +101,7 @@ def get_entries(filters):
def get_invoice_posting_date_map(filters): def get_invoice_posting_date_map(filters):
invoice_details = {} invoice_details = {}
dt = "Sales Invoice" if filters.get("payment_type") == "Incoming" else "Purchase Invoice" dt = "Sales Invoice" if filters.get("payment_type") == _("Incoming") else "Purchase Invoice"
for t in frappe.db.sql("select name, posting_date, due_date from `tab{0}`".format(dt), as_dict=1): for t in frappe.db.sql("select name, posting_date, due_date from `tab{0}`".format(dt), as_dict=1):
invoice_details[t.name] = t invoice_details[t.name] = t

View File

@@ -75,11 +75,11 @@ def get_chart_data(filters, columns, income, expense, net_profit_loss):
datasets = [] datasets = []
if income_data: if income_data:
datasets.append({'name': 'Income', 'values': income_data}) datasets.append({'name': _('Income'), 'values': income_data})
if expense_data: if expense_data:
datasets.append({'name': 'Expense', 'values': expense_data}) datasets.append({'name': _('Expense'), 'values': expense_data})
if net_profit: if net_profit:
datasets.append({'name': 'Net Profit/Loss', 'values': net_profit}) datasets.append({'name': _('Net Profit/Loss'), 'values': net_profit})
chart = { chart = {
"data": { "data": {

View File

@@ -255,9 +255,15 @@ class Asset(AccountsController):
precision = self.precision("gross_purchase_amount") precision = self.precision("gross_purchase_amount")
if row.depreciation_method in ("Straight Line", "Manual"): if row.depreciation_method in ("Straight Line", "Manual"):
depreciation_left = (cint(row.total_number_of_depreciations) - cint(self.number_of_depreciations_booked))
if not depreciation_left:
frappe.msgprint(_("All the depreciations has been booked"))
depreciation_amount = flt(row.expected_value_after_useful_life)
return depreciation_amount
depreciation_amount = (flt(row.value_after_depreciation) - depreciation_amount = (flt(row.value_after_depreciation) -
flt(row.expected_value_after_useful_life)) / (cint(row.total_number_of_depreciations) - flt(row.expected_value_after_useful_life)) / depreciation_left
cint(self.number_of_depreciations_booked))
else: else:
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision) depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision)
@@ -275,7 +281,7 @@ class Asset(AccountsController):
flt(accumulated_depreciation_after_full_schedule), flt(accumulated_depreciation_after_full_schedule),
self.precision('gross_purchase_amount')) self.precision('gross_purchase_amount'))
if (row.expected_value_after_useful_life and if (row.expected_value_after_useful_life and
row.expected_value_after_useful_life < asset_value_after_full_schedule): row.expected_value_after_useful_life < asset_value_after_full_schedule):
frappe.throw(_("Depreciation Row {0}: Expected value after useful life must be greater than or equal to {1}") frappe.throw(_("Depreciation Row {0}: Expected value after useful life must be greater than or equal to {1}")
.format(row.idx, asset_value_after_full_schedule)) .format(row.idx, asset_value_after_full_schedule))

View File

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

View File

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

View File

@@ -1,379 +1,111 @@
{ {
"allow_copy": 0, "creation": "2013-06-25 11:04:03",
"allow_guest_to_view": 0, "description": "Settings for Buying Module",
"allow_import": 0, "doctype": "DocType",
"allow_rename": 0, "document_type": "Other",
"beta": 0, "field_order": [
"creation": "2013-06-25 11:04:03", "supp_master_name",
"custom": 0, "supplier_group",
"description": "Settings for Buying Module", "buying_price_list",
"docstatus": 0, "column_break_3",
"doctype": "DocType", "po_required",
"document_type": "Other", "pr_required",
"editable_grid": 0, "maintain_same_rate",
"allow_multiple_items",
"subcontract",
"backflush_raw_materials_of_subcontract_based_on",
"column_break_11",
"over_transfer_allowance"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "default": "Supplier Name",
"allow_in_quick_entry": 0, "fieldname": "supp_master_name",
"allow_on_submit": 0, "fieldtype": "Select",
"bold": 0, "label": "Supplier Naming By",
"collapsible": 0, "options": "Supplier Name\nNaming Series"
"columns": 0,
"default": "Supplier Name",
"fieldname": "supp_master_name",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Supplier Naming By",
"length": 0,
"no_copy": 0,
"options": "Supplier Name\nNaming Series",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "supplier_group",
"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": "Default Supplier Group",
"length": 0,
"no_copy": 0,
"options": "Supplier Group",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "buying_price_list",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Buying Price List",
"length": 0,
"no_copy": 0,
"options": "Price List",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "po_required",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Purchase Order Required",
"length": 0,
"no_copy": 0,
"options": "No\nYes",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "pr_required",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Purchase Receipt Required",
"length": 0,
"no_copy": 0,
"options": "No\nYes",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "maintain_same_rate",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Maintain same rate throughout purchase cycle",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allow_multiple_items",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Allow Item to be added multiple times in a transaction",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0, "fieldname": "supplier_group",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Default Supplier Group",
"bold": 0, "options": "Supplier Group"
"collapsible": 0, },
"columns": 0,
"fieldname": "subcontract",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Subcontract",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "buying_price_list",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Default Buying Price List",
"bold": 0, "options": "Price List"
"collapsible": 0, },
"columns": 0, {
"default": "Material Transferred for Subcontract", "fieldname": "column_break_3",
"fieldname": "backflush_raw_materials_of_subcontract_based_on", "fieldtype": "Column Break"
"fieldtype": "Select", },
"hidden": 0, {
"ignore_user_permissions": 0, "fieldname": "po_required",
"ignore_xss_filter": 0, "fieldtype": "Select",
"in_filter": 0, "label": "Purchase Order Required",
"in_global_search": 0, "options": "No\nYes"
"in_list_view": 0, },
"in_standard_filter": 0, {
"label": "Backflush Raw Materials of Subcontract Based On", "fieldname": "pr_required",
"length": 0, "fieldtype": "Select",
"no_copy": 0, "label": "Purchase Receipt Required",
"options": "BOM\nMaterial Transferred for Subcontract", "options": "No\nYes"
"permlevel": 0, },
"precision": "", {
"print_hide": 0, "default": "0",
"print_hide_if_no_value": 0, "fieldname": "maintain_same_rate",
"read_only": 0, "fieldtype": "Check",
"remember_last_selected_value": 0, "label": "Maintain same rate throughout purchase cycle"
"report_hide": 0, },
"reqd": 0, {
"search_index": 0, "default": "0",
"set_only_once": 0, "fieldname": "allow_multiple_items",
"translatable": 0, "fieldtype": "Check",
"unique": 0 "label": "Allow Item to be added multiple times in a transaction"
},
{
"fieldname": "subcontract",
"fieldtype": "Section Break",
"label": "Subcontract"
},
{
"default": "Material Transferred for Subcontract",
"fieldname": "backflush_raw_materials_of_subcontract_based_on",
"fieldtype": "Select",
"label": "Backflush Raw Materials of Subcontract Based On",
"options": "BOM\nMaterial Transferred for Subcontract"
},
{
"depends_on": "eval:doc.backflush_raw_materials_of_subcontract_based_on == \"BOM\"",
"description": "Percentage you are allowed to transfer more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to transfer 110 units.",
"fieldname": "over_transfer_allowance",
"fieldtype": "Float",
"label": "Over Transfer Allowance (%)"
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
} }
], ],
"has_web_view": 0, "icon": "fa fa-cog",
"hide_heading": 0, "idx": 1,
"hide_toolbar": 0, "issingle": 1,
"icon": "fa fa-cog", "modified": "2019-08-20 13:13:09.055189",
"idx": 1, "modified_by": "Administrator",
"image_view": 0, "module": "Buying",
"in_create": 0, "name": "Buying Settings",
"is_submittable": 0, "owner": "Administrator",
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2018-07-31 07:52:38.062488",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "create": 1,
"cancel": 0, "email": 1,
"create": 1, "print": 1,
"delete": 0, "read": 1,
"email": 1, "role": "System Manager",
"export": 0, "share": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ]
"quick_entry": 0, }
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"track_changes": 0,
"track_seen": 0
}

View File

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

View File

@@ -477,6 +477,7 @@ def make_rm_stock_entry(purchase_order, rm_items):
rm_item_code = rm_item_data["rm_item_code"] rm_item_code = rm_item_data["rm_item_code"]
items_dict = { items_dict = {
rm_item_code: { rm_item_code: {
"po_detail": rm_item_data.get("name"),
"item_name": rm_item_data["item_name"], "item_name": rm_item_data["item_name"],
"description": item_wh.get(rm_item_code, {}).get('description', ""), "description": item_wh.get(rm_item_code, {}).get('description', ""),
'qty': rm_item_data["qty"], 'qty': rm_item_data["qty"],

View File

@@ -1,404 +1,134 @@
{ {
"allow_copy": 0, "creation": "2013-02-22 01:27:42",
"allow_events_in_timeline": 0, "doctype": "DocType",
"allow_guest_to_view": 0, "editable_grid": 1,
"allow_import": 0, "field_order": [
"allow_rename": 0, "main_item_code",
"beta": 0, "rm_item_code",
"creation": "2013-02-22 01:27:42", "required_qty",
"custom": 0, "supplied_qty",
"docstatus": 0, "rate",
"doctype": "DocType", "amount",
"editable_grid": 1, "column_break_6",
"bom_detail_no",
"reference_name",
"conversion_factor",
"stock_uom",
"reserve_warehouse"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "main_item_code",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Item Code",
"columns": 2, "oldfieldname": "main_item_code",
"fieldname": "main_item_code", "oldfieldtype": "Data",
"fieldtype": "Link", "options": "Item",
"hidden": 0, "read_only": 1
"ignore_user_permissions": 0, },
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Item Code",
"length": 0,
"no_copy": 0,
"oldfieldname": "main_item_code",
"oldfieldtype": "Data",
"options": "Item",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "rm_item_code",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Raw Material Item Code",
"columns": 2, "oldfieldname": "rm_item_code",
"fieldname": "rm_item_code", "oldfieldtype": "Data",
"fieldtype": "Link", "options": "Item",
"hidden": 0, "read_only": 1
"ignore_user_permissions": 0, },
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Raw Material Item Code",
"length": 0,
"no_copy": 0,
"oldfieldname": "rm_item_code",
"oldfieldtype": "Data",
"options": "Item",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "required_qty",
"allow_on_submit": 0, "fieldtype": "Float",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Required Qty",
"columns": 2, "oldfieldname": "required_qty",
"fieldname": "required_qty", "oldfieldtype": "Currency",
"fieldtype": "Float", "read_only": 1
"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": "Supplied Qty",
"length": 0,
"no_copy": 0,
"oldfieldname": "required_qty",
"oldfieldtype": "Currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "rate",
"allow_on_submit": 0, "fieldtype": "Currency",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Rate",
"columns": 2, "oldfieldname": "rate",
"fieldname": "rate", "oldfieldtype": "Currency",
"fieldtype": "Currency", "options": "Company:company:default_currency"
"hidden": 0, },
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Rate",
"length": 0,
"no_copy": 0,
"oldfieldname": "rate",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "amount",
"allow_in_quick_entry": 0, "fieldtype": "Currency",
"allow_on_submit": 0, "label": "Amount",
"bold": 0, "oldfieldname": "amount",
"collapsible": 0, "oldfieldtype": "Currency",
"columns": 0, "options": "Company:company:default_currency",
"fieldname": "amount", "read_only": 1
"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": "Amount",
"length": 0,
"no_copy": 0,
"oldfieldname": "amount",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_6",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"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
},
{ {
"allow_bulk_edit": 0, "fieldname": "bom_detail_no",
"allow_in_quick_entry": 0, "fieldtype": "Data",
"allow_on_submit": 0, "label": "BOM Detail No",
"bold": 0, "oldfieldname": "bom_detail_no",
"collapsible": 0, "oldfieldtype": "Data",
"columns": 0, "read_only": 1
"fieldname": "bom_detail_no", },
"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": "BOM Detail No",
"length": 0,
"no_copy": 0,
"oldfieldname": "bom_detail_no",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "reference_name",
"allow_in_quick_entry": 0, "fieldtype": "Data",
"allow_on_submit": 0, "label": "Reference Name",
"bold": 0, "oldfieldname": "reference_name",
"collapsible": 0, "oldfieldtype": "Data",
"columns": 0, "read_only": 1
"fieldname": "reference_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": "Reference Name",
"length": 0,
"no_copy": 0,
"oldfieldname": "reference_name",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "conversion_factor",
"allow_in_quick_entry": 0, "fieldtype": "Float",
"allow_on_submit": 0, "hidden": 1,
"bold": 0, "label": "Conversion Factor",
"collapsible": 0, "oldfieldname": "conversion_factor",
"columns": 0, "oldfieldtype": "Currency",
"fieldname": "conversion_factor", "read_only": 1
"fieldtype": "Float", },
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Conversion Factor",
"length": 0,
"no_copy": 0,
"oldfieldname": "conversion_factor",
"oldfieldtype": "Currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "stock_uom",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Stock Uom",
"bold": 0, "oldfieldname": "stock_uom",
"collapsible": 0, "oldfieldtype": "Data",
"columns": 0, "options": "UOM",
"fieldname": "stock_uom", "read_only": 1
"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": "Stock Uom",
"length": 0,
"no_copy": 0,
"oldfieldname": "stock_uom",
"oldfieldtype": "Data",
"options": "UOM",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "reserve_warehouse",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Reserve Warehouse",
"columns": 2, "options": "Warehouse"
"fieldname": "reserve_warehouse", },
"fieldtype": "Link", {
"hidden": 0, "fieldname": "supplied_qty",
"ignore_user_permissions": 0, "fieldtype": "Float",
"ignore_xss_filter": 0, "in_list_view": 1,
"in_filter": 0, "label": "Supplied Qty",
"in_global_search": 0, "read_only": 1
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Reserve Warehouse",
"length": 0,
"no_copy": 0,
"options": "Warehouse",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "hide_toolbar": 1,
"hide_heading": 0, "idx": 1,
"hide_toolbar": 1, "istable": 1,
"idx": 1, "modified": "2019-08-20 13:37:32.702068",
"image_view": 0, "modified_by": "Administrator",
"in_create": 0, "module": "Buying",
"is_submittable": 0, "name": "Purchase Order Item Supplied",
"issingle": 0, "owner": "dhanalekshmi@webnotestech.com",
"istable": 1, "permissions": []
"max_attachments": 0,
"modified": "2019-01-07 16:51:58.016007",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item Supplied",
"owner": "dhanalekshmi@webnotestech.com",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"track_changes": 0,
"track_seen": 0,
"track_views": 0
} }

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

@@ -73,6 +73,10 @@ def set_caller_information(doc, state):
# contact_name or lead_name # contact_name or lead_name
display_name_field = '{}_name'.format(fieldname) 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: for number in numbers:
number = strip_number(number) number = strip_number(number)
if not number: continue if not number: continue

View File

@@ -14,6 +14,12 @@ def get_data():
"dependencies": ["Item", "Supplier"], "dependencies": ["Item", "Supplier"],
"description": _("Purchase Orders given to Suppliers."), "description": _("Purchase Orders given to Suppliers."),
}, },
{
"type": "doctype",
"name": "Purchase Invoice",
"onboard": 1,
"dependencies": ["Item", "Supplier"]
},
{ {
"type": "doctype", "type": "doctype",
"name": "Material Request", "name": "Material Request",

View File

@@ -41,6 +41,11 @@ def get_data():
"name": "Lead Source", "name": "Lead Source",
"description": _("Track Leads by Lead Source.") "description": _("Track Leads by Lead Source.")
}, },
{
"type": "doctype",
"name": "Contract",
"description": _("Helps you keep tracks of Contracts based on Supplier, Customer and Employee"),
},
] ]
}, },
{ {

View File

@@ -304,12 +304,6 @@ def get_data():
"name": "Customers Without Any Sales Transactions", "name": "Customers Without Any Sales Transactions",
"doctype": "Customer" "doctype": "Customer"
}, },
{
"type": "report",
"is_query_report": True,
"name": "Sales Partners Commission",
"doctype": "Customer"
},
{ {
"type": "report", "type": "report",
"is_query_report": True, "is_query_report": True,

View File

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

View File

@@ -263,7 +263,7 @@ class AccountsController(TransactionBase):
if self.get("is_subcontracted"): if self.get("is_subcontracted"):
args["is_subcontracted"] = self.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(): for fieldname, value in ret.items():
if item.meta.get_field(fieldname) and value is not None: if item.meta.get_field(fieldname) and value is not None:

View File

@@ -45,6 +45,7 @@ class SellingController(StockController):
self.set_gross_profit() self.set_gross_profit()
set_default_income_account_for_item(self) set_default_income_account_for_item(self)
self.set_customer_address() self.set_customer_address()
self.validate_for_duplicate_items()
def set_missing_values(self, for_validate=False): def set_missing_values(self, for_validate=False):
@@ -381,6 +382,34 @@ class SellingController(StockController):
if self.get(address_field): if self.get(address_field):
self.set(address_display_field, get_address_display(self.get(address_field))) self.set(address_display_field, get_address_display(self.get(address_field)))
def validate_for_duplicate_items(self):
check_list, chk_dupl_itm = [], []
if cint(frappe.db.get_single_value("Selling Settings", "allow_multiple_items")):
return
for d in self.get('items'):
if self.doctype == "Sales Invoice":
e = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or '']
f = [d.item_code, d.description, d.sales_order or d.delivery_note]
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 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:
if e in check_list:
frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
else:
check_list.append(e)
else:
if f in chk_dupl_itm:
frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
else:
chk_dupl_itm.append(f)
def validate_items(self): def validate_items(self):
# validate items to see if they have is_sales_item enabled # validate items to see if they have is_sales_item enabled
from erpnext.controllers.buying_controller import validate_item_type from erpnext.controllers.buying_controller import validate_item_type

View File

@@ -88,7 +88,7 @@ def get_status(start_date, end_date):
end_date = getdate(end_date) end_date = getdate(end_date)
now_date = getdate(nowdate()) 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(): 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' # create new customer and create new contact against 'new.opportunity@example.com'
customer = make_customer(opp_doc.party_name).insert(ignore_permissions=True) customer = make_customer(opp_doc.party_name).insert(ignore_permissions=True)
frappe.get_doc({ contact = frappe.get_doc({
"doctype": "Contact", "doctype": "Contact",
"email_id": new_lead_email_id,
"first_name": "_Test Opportunity Customer", "first_name": "_Test Opportunity Customer",
"links": [{ "links": [{
"link_doctype": "Customer", "link_doctype": "Customer",
"link_name": customer.name "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) opp_doc = frappe.get_doc(args).insert(ignore_permissions=True)
self.assertTrue(opp_doc.party_name) self.assertTrue(opp_doc.party_name)

View File

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

View File

@@ -58,7 +58,7 @@ class ShopifySettings(Document):
d.raise_for_status() d.raise_for_status()
self.update_webhook_table(method, d.json()) self.update_webhook_table(method, d.json())
except Exception as e: except Exception as e:
make_shopify_log(status="Warning", message=e.message, exception=False) make_shopify_log(status="Warning", message=e, exception=False)
def unregister_webhooks(self): def unregister_webhooks(self):
session = get_request_session() session = get_request_session()
@@ -71,7 +71,7 @@ class ShopifySettings(Document):
res.raise_for_status() res.raise_for_status()
deleted_webhooks.append(d) deleted_webhooks.append(d)
except Exception as e: except Exception as e:
frappe.log_error(message=frappe.get_traceback(), title=e.message[:140]) frappe.log_error(message=frappe.get_traceback(), title=e)
for d in deleted_webhooks: for d in deleted_webhooks:
self.remove(d) self.remove(d)

View File

@@ -45,7 +45,6 @@ class TestExpenseClaim(unittest.TestCase):
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 700) self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 700)
expense_claim2.cancel() 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("Task", task_name, "total_expense_claim"), 200)
self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200) self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200)

View File

@@ -5,6 +5,8 @@ frappe.listview_settings['Leave Application'] = {
return [__("Approved"), "green", "status,=,Approved"]; return [__("Approved"), "green", "status,=,Approved"];
} else if (doc.status === "Rejected") { } else if (doc.status === "Rejected") {
return [__("Rejected"), "red", "status,=,Rejected"]; return [__("Rejected"), "red", "status,=,Rejected"];
} else {
return [__("Open"), "red", "status,=,Open"];
} }
} }
}; };

View File

@@ -95,26 +95,25 @@ def process_expired_allocation():
'expire_carry_forwarded_leaves_after_days': (">", 0) 'expire_carry_forwarded_leaves_after_days': (">", 0)
}, fieldname=['name']) }, 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 expired_allocation = frappe.db.sql_list("""SELECT name
FROM `tabLeave Ledger Entry` FROM `tabLeave Ledger Entry`
WHERE WHERE
`transaction_type`='Leave Allocation' `transaction_type`='Leave Allocation'
AND `is_expired`=1""") AND `is_expired`=1""")
expire_allocation = frappe.get_all("Leave Ledger Entry", expire_allocation = frappe.get_all("Leave Ledger Entry",
fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'], fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'],
filters={ filters={
'to_date': ("<", today()), 'to_date': ("<", today()),
'transaction_type': 'Leave Allocation', 'transaction_type': 'Leave Allocation',
'transaction_name': ('not in', expired_allocation) 'transaction_name': ('not in', expired_allocation)
}, },
or_filters={ or_filters={
'is_carry_forward': 0, 'is_carry_forward': 0,
'leave_type': ('in', leave_type) 'leave_type': ('in', leave_type)
}) })
if expire_allocation: if expire_allocation:
create_expiry_ledger_entry(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) monthly_interest_rate = flt(self.rate_of_interest) / (12 *100)
if monthly_interest_rate: if monthly_interest_rate:
min_repayment_amount = self.loan_amount*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 " \ frappe.throw(_("Repayment Amount must be greater than " \
+ str(flt(min_repayment_amount, 2)))) + str(flt(min_repayment_amount, 2))))
self.repayment_periods = math.ceil((math.log(self.repayment_amount) - 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, { doclist = get_mapped_doc("Loan Application", source_name, {
"Loan Application": { "Loan Application": {
"doctype": "Loan", "doctype": "Loan",
"field_map": {
"repayment_amount": "monthly_repayment_amount"
},
"validation": { "validation": {
"docstatus": ["=", 1] "docstatus": ["=", 1]
} }
} }
}, target_doc) }, target_doc)
return doclist return doclist

View File

@@ -23,7 +23,8 @@ def get_columns():
_("Model") + ":data:50", _("Location") + ":data:100", _("Model") + ":data:50", _("Location") + ":data:100",
_("Log") + ":Link/Vehicle Log:100", _("Odometer") + ":Int:80", _("Log") + ":Link/Vehicle Log:100", _("Odometer") + ":Int:80",
_("Date") + ":Date:100", _("Fuel Qty") + ":Float:80", _("Date") + ":Date:100", _("Fuel Qty") + ":Float:80",
_("Fuel Price") + ":Float:100",_("Service Expense") + ":Float:100" _("Fuel Price") + ":Float:100",_("Fuel Expense") + ":Float:100",
_("Service Expense") + ":Float:100"
] ]
return columns return columns
@@ -32,7 +33,8 @@ def get_log_data(filters):
data = frappe.db.sql("""select data = frappe.db.sql("""select
vhcl.license_plate as "License", vhcl.make as "Make", vhcl.model as "Model", vhcl.license_plate as "License", vhcl.make as "Make", vhcl.model as "Model",
vhcl.location as "Location", log.name as "Log", log.odometer as "Odometer", vhcl.location as "Location", log.name as "Log", log.odometer as "Odometer",
log.date as "Date", log.fuel_qty as "Fuel Qty", log.price as "Fuel Price" log.date as "Date", log.fuel_qty as "Fuel Qty", log.price as "Fuel Price",
log.fuel_qty * log.price as "Fuel Expense"
from from
`tabVehicle` vhcl,`tabVehicle Log` log `tabVehicle` vhcl,`tabVehicle Log` log
where where
@@ -58,7 +60,7 @@ def get_chart_data(data,period_list):
total_ser_exp=0 total_ser_exp=0
for row in data: for row in data:
if row["Date"] <= period.to_date and row["Date"] >= period.from_date: if row["Date"] <= period.to_date and row["Date"] >= period.from_date:
total_fuel_exp+=flt(row["Fuel Price"]) total_fuel_exp+=flt(row["Fuel Expense"])
total_ser_exp+=flt(row["Service Expense"]) total_ser_exp+=flt(row["Service Expense"])
fueldata.append([period.key,total_fuel_exp]) fueldata.append([period.key,total_fuel_exp])
servicedata.append([period.key,total_ser_exp]) servicedata.append([period.key,total_ser_exp])
@@ -84,4 +86,4 @@ def get_chart_data(data,period_list):
} }
} }
chart["type"] = "line" chart["type"] = "line"
return chart return chart

View File

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

View File

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

View File

@@ -72,6 +72,7 @@
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Series", "label": "Series",
"no_copy": 1,
"options": "MFG-WO-.YYYY.-", "options": "MFG-WO-.YYYY.-",
"print_hide": 1, "print_hide": 1,
"reqd": 1, "reqd": 1,
@@ -467,7 +468,7 @@
"idx": 1, "idx": 1,
"image_field": "image", "image_field": "image",
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-07-31 00:13:38.218277", "modified": "2019-08-28 12:29:35.315239",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Work Order", "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 frappe.utils.csvutils import getlink
from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty 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 erpnext.utilities.transaction_base import validate_uom_is_integer
from frappe.model.mapper import get_mapped_doc
class OverProductionError(frappe.ValidationError): pass class OverProductionError(frappe.ValidationError): pass
class StockOverProductionError(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: for d in work_order.operations:
if d.operation == operation and d.workstation == workstation: if d.operation == operation and d.workstation == workstation:
return d 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', 'fieldname': 'work_order',
'transactions': [ 'transactions': [
{ {
'items': ['Stock Entry', 'Job Card'] 'items': ['Pick List', 'Stock Entry', 'Job Card']
} }
] ]
} }

View File

@@ -605,7 +605,6 @@ erpnext.patches.v11_1.rename_depends_on_lwp
execute:frappe.delete_doc("Report", "Inactive Items") execute:frappe.delete_doc("Report", "Inactive Items")
erpnext.patches.v11_1.delete_scheduling_tool erpnext.patches.v11_1.delete_scheduling_tool
erpnext.patches.v12_0.rename_tolerance_fields erpnext.patches.v12_0.rename_tolerance_fields
erpnext.patches.v12_0.make_custom_fields_for_bank_remittance #14-06-2019
execute:frappe.delete_doc_if_exists("Page", "support-analytics") execute:frappe.delete_doc_if_exists("Page", "support-analytics")
erpnext.patches.v12_0.remove_patient_medical_record_page erpnext.patches.v12_0.remove_patient_medical_record_page
erpnext.patches.v11_1.move_customer_lead_to_dynamic_column erpnext.patches.v11_1.move_customer_lead_to_dynamic_column
@@ -626,4 +625,10 @@ erpnext.patches.v12_0.add_default_buying_selling_terms_in_company
erpnext.patches.v12_0.update_ewaybill_field_position erpnext.patches.v12_0.update_ewaybill_field_position
erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes
erpnext.patches.v11_1.set_status_for_material_request_type_manufacture erpnext.patches.v11_1.set_status_for_material_request_type_manufacture
erpnext.patches.v12_0.generate_leave_ledger_entries execute:frappe.reload_doc('desk', 'doctype','dashboard_chart_link')
execute:frappe.reload_doc('desk', 'doctype','dashboard')
execute:frappe.reload_doc('desk', 'doctype','dashboard_chart_source')
execute:frappe.reload_doc('desk', 'doctype','dashboard_chart')
erpnext.patches.v12_0.add_default_dashboards
erpnext.patches.v12_0.remove_bank_remittance_custom_fields
erpnext.patches.v12_0.generate_leave_ledger_entries

View File

@@ -0,0 +1,8 @@
# Copyright (c) 2019, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
from erpnext.setup.setup_wizard.operations.install_fixtures import add_dashboards
def execute():
add_dashboards()

View File

@@ -1,12 +0,0 @@
from __future__ import unicode_literals
import frappe
from erpnext.regional.india.setup import make_custom_fields
def execute():
frappe.reload_doc("accounts", "doctype", "tax_category")
frappe.reload_doc("stock", "doctype", "item_manufacturer")
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
make_custom_fields()

View File

@@ -0,0 +1,14 @@
from __future__ import unicode_literals
import frappe
from erpnext.regional.india.setup import make_custom_fields
def execute():
frappe.reload_doc("accounts", "doctype", "tax_category")
frappe.reload_doc("stock", "doctype", "item_manufacturer")
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
if frappe.db.exists("Custom Field", "Company-bank_remittance_section"):
deprecated_fields = ['bank_remittance_section', 'client_code', 'remittance_column_break', 'product_code']
for i in range(len(deprecated_fields)):
frappe.delete_doc("Custom Field", 'Company-'+deprecated_fields[i])

View File

@@ -96,7 +96,7 @@ class Timesheet(Document):
for time in self.time_logs: for time in self.time_logs:
if time.from_time and time.to_time: 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) time.hours = flt(std_working_hours) * date_diff(time.to_time, time.from_time)
else: else:
if not time.hours: if not time.hours:

View File

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

View File

@@ -1,190 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Frappe and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.utils import cint,cstr, today
from frappe import _
import re
import datetime
from collections import OrderedDict
def create_bank_remittance_txt(name):
payment_order = frappe.get_cached_doc("Payment Order", name)
no_of_records = len(payment_order.get("references"))
total_amount = sum(entry.get("amount") for entry in payment_order.get("references"))
product_code, client_code, company_email = frappe.db.get_value("Company",
filters={'name' : payment_order.company},
fieldname=['product_code', 'client_code', 'email'])
header, file_name = get_header_row(payment_order, client_code)
batch = get_batch_row(payment_order, no_of_records, total_amount, product_code)
detail = []
for ref_doc in payment_order.get("references"):
detail += get_detail_row(ref_doc, payment_order, company_email)
trailer = get_trailer_row(no_of_records, total_amount)
detail_records = "\n".join(detail)
return "\n".join([header, batch, detail_records, trailer]), file_name
@frappe.whitelist()
def generate_report(name):
data, file_name = create_bank_remittance_txt(name)
f = frappe.get_doc({
'doctype': 'File',
'file_name': file_name,
'content': data,
"attached_to_doctype": 'Payment Order',
"attached_to_name": name,
'is_private': True
})
f.save()
return {
'file_url': f.file_url,
'file_name': file_name
}
def generate_file_name(name, company_account, date):
''' generate file name with format (account_code)_mmdd_(payment_order_no) '''
bank, acc_no = frappe.db.get_value("Bank Account", {"name": company_account}, ['bank', 'bank_account_no'])
return bank[:1]+str(acc_no)[-4:]+'_'+date.strftime("%m%d")+sanitize_data(name, '')[4:]+'.txt'
def get_header_row(doc, client_code):
''' Returns header row and generated file name '''
file_name = generate_file_name(doc.name, doc.company_bank_account, doc.posting_date)
header = ["H"]
header.append(validate_field_size(client_code, "Client Code", 20))
header += [''] * 3
header.append(validate_field_size(file_name, "File Name", 20))
return "~".join(header), file_name
def get_batch_row(doc, no_of_records, total_amount, product_code):
batch = ["B"]
batch.append(validate_field_size(no_of_records, "No Of Records", 5))
batch.append(validate_amount(format(total_amount, '0.2f'), 17))
batch.append(sanitize_data(doc.name, '_')[:20])
batch.append(format_date(doc.posting_date))
batch.append(validate_field_size(product_code,"Product Code", 20))
return "~".join(batch)
def get_detail_row(ref_doc, payment_entry, company_email):
payment_date = format_date(payment_entry.posting_date)
payment_entry = frappe.get_cached_doc('Payment Entry', ref_doc.payment_entry)
supplier_bank_details = frappe.get_cached_doc('Bank Account', ref_doc.bank_account)
company_bank_acc_no = frappe.db.get_value("Bank Account", {'name': payment_entry.bank_account}, ['bank_account_no'])
addr_link = frappe.db.get_value('Dynamic Link',
{
'link_doctype': 'Supplier',
'link_name': 'Sample Supplier',
'parenttype':'Address',
'parent': ('like', '%-Billing')
}, 'parent')
supplier_billing_address = frappe.get_cached_doc('Address', addr_link)
email = ','.join(filter(None, [supplier_billing_address.email_id, company_email]))
detail = OrderedDict(
record_identifier='D',
payment_ref_no=sanitize_data(ref_doc.payment_entry),
payment_type=cstr(payment_entry.mode_of_payment)[:10],
amount=str(validate_amount(format(ref_doc.amount, '.2f'),13)),
payment_date=payment_date,
instrument_date=payment_date,
instrument_number='',
dr_account_no_client=str(validate_field_size(company_bank_acc_no, "Company Bank Account", 20)),
dr_description='',
dr_ref_no='',
cr_ref_no='',
bank_code_indicator='M',
beneficiary_code='',
beneficiary_name=sanitize_data(validate_information(payment_entry, "party", 160), ' '),
beneficiary_bank=sanitize_data(validate_information(supplier_bank_details, "bank", 10)),
beneficiary_branch_code=cstr(validate_information(supplier_bank_details, "branch_code", 11)),
beneficiary_acc_no=validate_information(supplier_bank_details, "bank_account_no", 20),
location='',
print_location='',
beneficiary_address_1=validate_field_size(sanitize_data(cstr(supplier_billing_address.address_line1), ' '), " Beneficiary Address 1", 50),
beneficiary_address_2=validate_field_size(sanitize_data(cstr(supplier_billing_address.address_line2), ' '), " Beneficiary Address 2", 50),
beneficiary_address_3='',
beneficiary_address_4='',
beneficiary_address_5='',
beneficiary_city=validate_field_size(cstr(supplier_billing_address.city), "Beneficiary City", 20),
beneficiary_zipcode=validate_field_size(cstr(supplier_billing_address.pincode), "Pin Code", 6),
beneficiary_state=validate_field_size(cstr(supplier_billing_address.state), "Beneficiary State", 20),
beneficiary_email=cstr(email)[:255],
beneficiary_mobile=validate_field_size(cstr(supplier_billing_address.phone), "Beneficiary Mobile", 10),
payment_details_1='',
payment_details_2='',
payment_details_3='',
payment_details_4='',
delivery_mode=''
)
detail_record = ["~".join(list(detail.values()))]
detail_record += get_advice_rows(payment_entry)
return detail_record
def get_advice_rows(payment_entry):
''' Returns multiple advice rows for a single detail entry '''
payment_entry_date = payment_entry.posting_date.strftime("%b%y%d%m").upper()
mode_of_payment = payment_entry.mode_of_payment
advice_rows = []
for record in payment_entry.references:
advice = ['E']
advice.append(cstr(mode_of_payment))
advice.append(cstr(record.total_amount))
advice.append('')
advice.append(cstr(record.outstanding_amount))
advice.append(record.reference_name)
advice.append(format_date(record.due_date))
advice.append(payment_entry_date)
advice_rows.append("~".join(advice))
return advice_rows
def get_trailer_row(no_of_records, total_amount):
''' Returns trailer row '''
trailer = ["T"]
trailer.append(validate_field_size(no_of_records, "No of Records", 5))
trailer.append(validate_amount(format(total_amount, "0.2f"), 17))
return "~".join(trailer)
def sanitize_data(val, replace_str=''):
''' Remove all the non-alphanumeric characters from string '''
pattern = re.compile('[\W_]+')
return pattern.sub(replace_str, val)
def format_date(val):
''' Convert a datetime object to DD/MM/YYYY format '''
return val.strftime("%d/%m/%Y")
def validate_amount(val, max_int_size):
''' Validate amount to be within the allowed limits '''
int_size = len(str(val).split('.')[0])
if int_size > max_int_size:
frappe.throw(_("Amount for a single transaction exceeds maximum allowed amount, create a separate payment order by splitting the transactions"))
return val
def validate_information(obj, attr, max_size):
''' Checks if the information is not set in the system and is within the size '''
if hasattr(obj, attr):
return validate_field_size(getattr(obj, attr), frappe.unscrub(attr), max_size)
else:
frappe.throw(_("{0} is mandatory for generating remittance payments, set the field and try again".format(frappe.unscrub(attr))))
def validate_field_size(val, label, max_size):
''' check the size of the val '''
if len(cstr(val)) > max_size:
frappe.throw(_("{0} field is limited to size {1}".format(label, max_size)))
return cstr(val)

View File

@@ -407,14 +407,6 @@ def make_custom_fields(update=True):
fieldtype='Link', options='Salary Component', insert_after='basic_component'), fieldtype='Link', options='Salary Component', insert_after='basic_component'),
dict(fieldname='arrear_component', label='Arrear Component', dict(fieldname='arrear_component', label='Arrear Component',
fieldtype='Link', options='Salary Component', insert_after='hra_component'), fieldtype='Link', options='Salary Component', insert_after='hra_component'),
dict(fieldname='bank_remittance_section', label='Bank Remittance Settings',
fieldtype='Section Break', collapsible=1, insert_after='arrear_component'),
dict(fieldname='client_code', label='Client Code', fieldtype='Data',
insert_after='bank_remittance_section'),
dict(fieldname='remittance_column_break', fieldtype='Column Break',
insert_after='client_code'),
dict(fieldname='product_code', label='Product Code', fieldtype='Data',
insert_after='remittance_column_break'),
], ],
'Employee Tax Exemption Declaration':[ 'Employee Tax Exemption Declaration':[
dict(fieldname='hra_section', label='HRA Exemption', dict(fieldname='hra_section', label='HRA Exemption',

View File

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

View File

@@ -7,6 +7,7 @@ frappe.ui.form.on("Sales Order", {
setup: function(frm) { setup: function(frm) {
frm.custom_make_buttons = { frm.custom_make_buttons = {
'Delivery Note': 'Delivery', 'Delivery Note': 'Delivery',
'Pick List': 'Pick List',
'Sales Invoice': 'Invoice', 'Sales Invoice': 'Invoice',
'Material Request': 'Material Request', 'Material Request': 'Material Request',
'Purchase Order': 'Purchase Order', 'Purchase Order': 'Purchase Order',
@@ -109,7 +110,9 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
this._super(); this._super();
let allow_delivery = false; 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(this.frm.has_perm("submit")) {
if(doc.status === 'On Hold') { if(doc.status === 'On Hold') {
// un-hold // un-hold
@@ -233,6 +236,13 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
this.order_type(doc); 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() { make_work_order() {
var me = this; var me = this;
this.frm.call({ this.frm.call({

View File

@@ -72,9 +72,7 @@ class SalesOrder(SellingController):
frappe.msgprint(_("Warning: Sales Order {0} already exists against Customer's Purchase Order {1}").format(so[0][0], self.po_no)) frappe.msgprint(_("Warning: Sales Order {0} already exists against Customer's Purchase Order {1}").format(so[0][0], self.po_no))
def validate_for_items(self): def validate_for_items(self):
check_list = []
for d in self.get('items'): for d in self.get('items'):
check_list.append(cstr(d.item_code))
# used for production plan # used for production plan
d.transaction_date = self.transaction_date d.transaction_date = self.transaction_date
@@ -83,13 +81,6 @@ class SalesOrder(SellingController):
where item_code = %s and warehouse = %s", (d.item_code, d.warehouse)) where item_code = %s and warehouse = %s", (d.item_code, d.warehouse))
d.projected_qty = tot_avail_qty and flt(tot_avail_qty[0][0]) or 0 d.projected_qty = tot_avail_qty and flt(tot_avail_qty[0][0]) or 0
# check for same entry multiple times
unique_chk_list = set(check_list)
if len(unique_chk_list) != len(check_list) and \
not cint(frappe.db.get_single_value("Selling Settings", "allow_multiple_items")):
frappe.msgprint(_("Same item has been entered multiple times"),
title=_("Warning"), indicator='orange')
def product_bundle_has_stock_item(self, product_bundle): def product_bundle_has_stock_item(self, product_bundle):
"""Returns true if product bundle has stock item""" """Returns true if product bundle has stock item"""
ret = len(frappe.db.sql("""select i.name from tabItem i, `tabProduct Bundle Item` pbi ret = len(frappe.db.sql("""select i.name from tabItem i, `tabProduct Bundle Item` pbi
@@ -568,7 +559,7 @@ def make_project(source_name, target_doc=None):
return doc return doc
@frappe.whitelist() @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): def set_missing_values(source, target):
target.ignore_pricing_rule = 1 target.ignore_pricing_rule = 1
target.run_method("set_missing_values") target.run_method("set_missing_values")
@@ -593,23 +584,13 @@ def make_delivery_note(source_name, target_doc=None):
or item.get("buying_cost_center") \ or item.get("buying_cost_center") \
or item_group.get("buying_cost_center") or item_group.get("buying_cost_center")
target_doc = get_mapped_doc("Sales Order", source_name, { mapper = {
"Sales Order": { "Sales Order": {
"doctype": "Delivery Note", "doctype": "Delivery Note",
"validation": { "validation": {
"docstatus": ["=", 1] "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": { "Sales Taxes and Charges": {
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"add_if_empty": True "add_if_empty": True
@@ -618,7 +599,21 @@ def make_delivery_note(source_name, target_doc=None):
"doctype": "Sales Team", "doctype": "Sales Team",
"add_if_empty": True "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 return target_doc
@@ -996,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): def make_inter_company_purchase_order(source_name, target_doc=None):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
return make_inter_company_transaction("Sales Order", source_name, target_doc) 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': [ 'transactions': [
{ {
'label': _('Fulfillment'), 'label': _('Fulfillment'),
'items': ['Sales Invoice', 'Delivery Note'] 'items': ['Sales Invoice', 'Pick List', 'Delivery Note']
}, },
{ {
'label': _('Purchasing'), 'label': _('Purchasing'),

View File

@@ -31,7 +31,7 @@ class SMSCenter(Document):
self.sales_partner.replace("'", "\'") or " and ifnull(dl.link_name, '') != ''" 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']: 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,'')), 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) c.docstatus != 2 and dl.parent = c.name%s""" % where_clause)
elif self.send_to == 'All Lead (Open)': elif self.send_to == 'All Lead (Open)':

View File

@@ -4,7 +4,7 @@ frappe.provide('erpnext.pos');
frappe.pages['point-of-sale'].on_page_load = function(wrapper) { frappe.pages['point-of-sale'].on_page_load = function(wrapper) {
frappe.ui.make_app_page({ frappe.ui.make_app_page({
parent: wrapper, parent: wrapper,
title: 'Point of Sale', title: __('Point of Sale'),
single_column: true single_column: true
}); });

View File

@@ -58,7 +58,7 @@ class GlobalDefaults(Document):
# Make property setters to hide rounded total fields # Make property setters to hide rounded total fields
for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note", 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", "hidden", self.disable_rounded_total, "Check")
make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "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): def validate(self):
super(ItemGroup, self).validate() 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() self.make_route()
def on_update(self): def on_update(self):

View File

@@ -0,0 +1,107 @@
from __future__ import unicode_literals
from frappe import _
import frappe
import json
def get_default_dashboards():
company = frappe.get_doc("Company", frappe.defaults.get_defaults().company)
income_account = company.default_income_account or get_account("Income Account", company.name)
expense_account = company.default_expense_account or get_account("Expense Account", company.name)
bank_account = company.default_bank_account or get_account("Bank", company.name)
return {
"Dashboards": [
{
"doctype": "Dashboard",
"dashboard_name": "Accounts",
"charts": [
{ "chart": "Outgoing Bills (Sales Invoice)" },
{ "chart": "Incoming Bills (Purchase Invoice)" },
{ "chart": "Bank Balance" },
{ "chart": "Income" },
{ "chart": "Expenses" }
]
}
],
"Charts": [
{
"doctype": "Dashboard Chart",
"time_interval": "Quarterly",
"chart_name": "Income",
"timespan": "Last Year",
"color": None,
"filters_json": json.dumps({"company": company.name, "account": income_account}),
"source": "Account Balance Timeline",
"chart_type": "Custom",
"timeseries": 1,
"owner": "Administrator",
"type": "Line",
"width": "Half"
},
{
"doctype": "Dashboard Chart",
"time_interval": "Quarterly",
"chart_name": "Expenses",
"timespan": "Last Year",
"color": None,
"filters_json": json.dumps({"company": company.name, "account": expense_account}),
"source": "Account Balance Timeline",
"chart_type": "Custom",
"timeseries": 1,
"owner": "Administrator",
"type": "Line",
"width": "Half"
},
{
"doctype": "Dashboard Chart",
"time_interval": "Quarterly",
"chart_name": "Bank Balance",
"timespan": "Last Year",
"color": "#ffb868",
"filters_json": json.dumps({"company": company.name, "account": bank_account}),
"source": "Account Balance Timeline",
"chart_type": "Custom",
"timeseries": 1,
"owner": "Administrator",
"type": "Line",
"width": "Half"
},
{
"doctype": "Dashboard Chart",
"time_interval": "Monthly",
"chart_name": "Incoming Bills (Purchase Invoice)",
"timespan": "Last Year",
"color": "#a83333",
"value_based_on": "base_grand_total",
"filters_json": json.dumps({}),
"chart_type": "Sum",
"timeseries": 1,
"based_on": "posting_date",
"owner": "Administrator",
"document_type": "Purchase Invoice",
"type": "Bar",
"width": "Half"
},
{
"doctype": "Dashboard Chart",
"time_interval": "Monthly",
"chart_name": "Outgoing Bills (Sales Invoice)",
"timespan": "Last Year",
"color": "#7b933d",
"value_based_on": "base_grand_total",
"filters_json": json.dumps({}),
"chart_type": "Sum",
"timeseries": 1,
"based_on": "posting_date",
"owner": "Administrator",
"document_type": "Sales Invoice",
"type": "Bar",
"width": "Half"
}
]
}
def get_account(account_type, company):
accounts = frappe.get_list("Account", filters={"account_type": account_type, "company": company})
if accounts:
return accounts[0].name

View File

@@ -475,13 +475,14 @@ def install_defaults(args=None):
frappe.db.set_value("Company", args.company_name, "default_bank_account", bank_account.name, update_modified=False) frappe.db.set_value("Company", args.company_name, "default_bank_account", bank_account.name, update_modified=False)
return doc
except RootNotEditable: except RootNotEditable:
frappe.throw(_("Bank account cannot be named as {0}").format(args.bank_account)) frappe.throw(_("Bank account cannot be named as {0}").format(args.bank_account))
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
# bank account same as a CoA entry # bank account same as a CoA entry
pass pass
add_dashboards()
# Now, with fixtures out of the way, onto concrete stuff # Now, with fixtures out of the way, onto concrete stuff
records = [ records = [
@@ -499,6 +500,13 @@ def install_defaults(args=None):
make_records(records) make_records(records)
def add_dashboards():
from erpnext.setup.setup_wizard.data.dashboard_charts import get_default_dashboards
dashboard_data = get_default_dashboards()
make_records(dashboard_data["Charts"])
make_records(dashboard_data["Dashboards"])
def get_fy_details(fy_start_date, fy_end_date): def get_fy_details(fy_start_date, fy_end_date):
start_year = getdate(fy_start_date).year start_year = getdate(fy_start_date).year

File diff suppressed because it is too large Load Diff

View File

@@ -166,24 +166,7 @@ class DeliveryNote(SellingController):
frappe.throw(_("Customer {0} does not belong to project {1}").format(self.customer, self.project)) frappe.throw(_("Customer {0} does not belong to project {1}").format(self.customer, self.project))
def validate_for_items(self): def validate_for_items(self):
check_list, chk_dupl_itm = [], []
if cint(frappe.db.get_single_value("Selling Settings", "allow_multiple_items")):
return
for d in self.get('items'): for d in self.get('items'):
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]
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
if e in check_list:
frappe.msgprint(_("Note: Item {0} entered multiple times").format(d.item_code))
else:
check_list.append(e)
else:
if f in chk_dupl_itm:
frappe.msgprint(_("Note: Item {0} entered multiple times").format(d.item_code))
else:
chk_dupl_itm.append(f)
#Customer Provided parts will have zero valuation rate #Customer Provided parts will have zero valuation rate
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'): if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
d.allow_zero_valuation_rate = 1 d.allow_zero_valuation_rate = 1

View File

@@ -124,6 +124,7 @@ class Item(WebsiteGenerator):
self.update_defaults_from_item_group() self.update_defaults_from_item_group()
self.validate_auto_reorder_enabled_in_stock_settings() self.validate_auto_reorder_enabled_in_stock_settings()
self.cant_change() self.cant_change()
self.update_show_in_website()
if not self.get("__islocal"): if not self.get("__islocal"):
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
@@ -476,6 +477,10 @@ class Item(WebsiteGenerator):
[self.remove(d) for d in to_remove] [self.remove(d) for d in to_remove]
def update_show_in_website(self):
if self.disabled:
self.show_in_website = False
def update_template_tables(self): def update_template_tables(self):
template = frappe.get_doc("Item", self.variant_of) template = frappe.get_doc("Item", self.variant_of)

View File

@@ -8,6 +8,7 @@ frappe.ui.form.on('Material Request', {
setup: function(frm) { setup: function(frm) {
frm.custom_make_buttons = { frm.custom_make_buttons = {
'Stock Entry': 'Issue Material', 'Stock Entry': 'Issue Material',
'Pick List': 'Pick List',
'Purchase Order': 'Purchase Order', 'Purchase Order': 'Purchase Order',
'Request for Quotation': 'Request for Quotation', 'Request for Quotation': 'Request for Quotation',
'Supplier Quotation': 'Supplier 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 (frm.doc.docstatus == 1 && frm.doc.status != 'Stopped') {
if (flt(frm.doc.per_ordered, 2) < 100) { 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") { if (frm.doc.material_request_type === "Material Transfer") {
add_create_pick_list_button();
frm.add_custom_button(__("Transfer Material"), frm.add_custom_button(__("Transfer Material"),
() => frm.events.make_stock_entry(frm), __('Create')); () => 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) { raise_work_orders: function(frm) {
frappe.call({ frappe.call({
method:"erpnext.stock.doctype.material_request.material_request.raise_work_orders", 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)) frappe.throw(_("Productions Orders cannot be raised for:") + '\n' + new_line_sep(errors))
return work_orders 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': [ 'transactions': [
{ {
'label': _('Related'), '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'), 'label': _('Manufacturing'),

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') location = frappe.db.get_value('Serial No', serial_nos[0].name, 'location')
self.assertEquals(location, "Test 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() pr.cancel()
serial_nos = frappe.get_all('Serial No', {'asset': asset}, 'name') or [] serial_nos = frappe.get_all('Serial No', {'asset': asset}, 'name') or []
self.assertEquals(len(serial_nos), 0) self.assertEquals(len(serial_nos), 0)

View File

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

View File

@@ -525,15 +525,21 @@ class StockEntry(StockController):
backflush_raw_materials_based_on = frappe.db.get_single_value("Buying Settings", backflush_raw_materials_based_on = frappe.db.get_single_value("Buying Settings",
"backflush_raw_materials_of_subcontract_based_on") "backflush_raw_materials_of_subcontract_based_on")
qty_allowance = flt(frappe.db.get_single_value("Buying Settings",
"over_transfer_allowance"))
if (self.purpose == "Send to Subcontractor" and self.purchase_order and if (self.purpose == "Send to Subcontractor" and self.purchase_order and
backflush_raw_materials_based_on == 'BOM'): backflush_raw_materials_based_on == 'BOM'):
purchase_order = frappe.get_doc("Purchase Order", self.purchase_order) purchase_order = frappe.get_doc("Purchase Order", self.purchase_order)
for se_item in self.items: for se_item in self.items:
item_code = se_item.original_item or se_item.item_code item_code = se_item.original_item or se_item.item_code
precision = cint(frappe.db.get_default("float_precision")) or 3 precision = cint(frappe.db.get_default("float_precision")) or 3
total_allowed = sum([flt(d.required_qty) for d in purchase_order.supplied_items \ required_qty = sum([flt(d.required_qty) for d in purchase_order.supplied_items \
if d.rm_item_code == item_code]) if d.rm_item_code == item_code])
if not total_allowed:
total_allowed = required_qty + (required_qty * (qty_allowance/100))
if not required_qty:
frappe.throw(_("Item {0} not found in 'Raw Materials Supplied' table in Purchase Order {1}") frappe.throw(_("Item {0} not found in 'Raw Materials Supplied' table in Purchase Order {1}")
.format(se_item.item_code, self.purchase_order)) .format(se_item.item_code, self.purchase_order))
total_supplied = frappe.db.sql("""select sum(transfer_qty) total_supplied = frappe.db.sql("""select sum(transfer_qty)
@@ -1110,6 +1116,7 @@ class StockEntry(StockController):
se_child.allow_alternative_item = item_dict[d].get("allow_alternative_item", 0) se_child.allow_alternative_item = item_dict[d].get("allow_alternative_item", 0)
se_child.subcontracted_item = item_dict[d].get("main_item_code") se_child.subcontracted_item = item_dict[d].get("main_item_code")
se_child.original_item = item_dict[d].get("original_item") se_child.original_item = item_dict[d].get("original_item")
se_child.po_detail = item_dict[d].get("po_detail")
if item_dict[d].get("idx"): if item_dict[d].get("idx"):
se_child.idx = item_dict[d].get("idx") se_child.idx = item_dict[d].get("idx")
@@ -1161,7 +1168,14 @@ class StockEntry(StockController):
where po.name = poitemsup.parent where po.name = poitemsup.parent
and po.name = %s""", self.purchase_order)) and po.name = %s""", self.purchase_order))
#Update reserved sub contracted quantity in bin based on Supplied Item Details #Update Supplied Qty in PO Supplied Items
frappe.db.sql("""UPDATE `tabPurchase Order Item Supplied` pos
SET pos.supplied_qty = (SELECT ifnull(sum(transfer_qty), 0) FROM `tabStock Entry Detail` sed
WHERE pos.name = sed.po_detail and sed.docstatus = 1)
WHERE pos.docstatus = 1""")
#Update reserved sub contracted quantity in bin based on Supplied Item Details and
for d in self.get("items"): for d in self.get("items"):
item_code = d.get('original_item') or d.get('item_code') item_code = d.get('original_item') or d.get('item_code')
reserve_warehouse = item_wh.get(item_code) reserve_warehouse = item_wh.get(item_code)

View File

@@ -59,6 +59,7 @@
"reference_section", "reference_section",
"against_stock_entry", "against_stock_entry",
"ste_detail", "ste_detail",
"po_detail",
"column_break_51", "column_break_51",
"transferred_qty", "transferred_qty",
"reference_purchase_receipt", "reference_purchase_receipt",
@@ -480,11 +481,20 @@
"label": "Project", "label": "Project",
"options": "Project", "options": "Project",
"read_only": 1 "read_only": 1
},
{
"fieldname": "po_detail",
"fieldtype": "Data",
"hidden": 1,
"label": "PO Supplied Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2019-07-12 11:34:53.190749", "modified": "2019-08-20 14:01:02.319754",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry Detail", "name": "Stock Entry Detail",

View File

@@ -152,7 +152,6 @@ class TestStockReconciliation(unittest.TestCase):
for d in to_delete_records: for d in to_delete_records:
stock_doc = frappe.get_doc("Stock Reconciliation", d) stock_doc = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel() stock_doc.cancel()
frappe.delete_doc("Stock Reconciliation", stock_doc.name)
for d in serial_nos + serial_nos1: for d in serial_nos + serial_nos1:
if frappe.db.exists("Serial No", d): 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 = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel() 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(): def insert_existing_sle():
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry 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'] purchase_doctypes = ['Material Request', 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
@frappe.whitelist() @frappe.whitelist()
def get_item_details(args, doc=None): def get_item_details(args, doc=None, overwrite_warehouse=True):
""" """
args = { args = {
"item_code": "", "item_code": "",
@@ -44,11 +44,12 @@ def get_item_details(args, doc=None):
"set_warehouse": "" "set_warehouse": ""
} }
""" """
args = process_args(args) args = process_args(args)
item = frappe.get_cached_doc("Item", args.item_code) item = frappe.get_cached_doc("Item", args.item_code)
validate_item_details(args, item) 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) 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 \ 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)) 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: { :param args: {
"item_code": "", "item_code": "",
@@ -225,14 +226,26 @@ def get_basic_details(args, item):
item_group_defaults = get_item_group_defaults(item.name, args.company) item_group_defaults = get_item_group_defaults(item.name, args.company)
brand_defaults = get_brand_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 if overwrite_warehouse or not args.warehouse:
item_group_defaults.get("default_warehouse") or brand_defaults.get("default_warehouse") or 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: if not warehouse:
defaults = frappe.defaults.get_defaults() or {} defaults = frappe.defaults.get_defaults() or {}
if defaults.get("default_warehouse") and frappe.db.exists("Warehouse", warehouse_exists = frappe.db.exists("Warehouse", {
{'name': defaults.default_warehouse, 'company': args.company}): 'name': defaults.default_warehouse,
warehouse = 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'): if args.get('doctype') == "Material Request" and not args.get('material_request_type'):
args['material_request_type'] = frappe.db.get_value('Material Request', 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

@@ -227,9 +227,9 @@ class update_entries_after(object):
elif actual_qty < 0: elif actual_qty < 0:
# In case of delivery/stock issue, get average purchase rate # In case of delivery/stock issue, get average purchase rate
# of serial nos of current entry # of serial nos of current entry
stock_value_change = -1 * flt(frappe.db.sql("""select sum(purchase_rate) stock_value_change = -1 * flt(frappe.get_all("Serial No",
from `tabSerial No` where name in (%s)""" % (", ".join(["%s"]*len(serial_no))), fields=["sum(purchase_rate)"],
tuple(serial_no))[0][0]) filters = {'name': ('in', serial_no)}, as_list=1)[0][0])
new_stock_qty = self.qty_after_transaction + actual_qty new_stock_qty = self.qty_after_transaction + actual_qty

View File

@@ -1 +1,3 @@
<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">Powered by ERPNext</a> {% set domains = frappe.get_doc("Domain Settings").active_domains %}
<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">Powered by ERPNext - {{ domains[0].domain if domains else 'Open Source' }} ERP Software</a>