From cc8b2b2fdbd987aa960605079cfc2cd1b0bae64d Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 31 Mar 2017 12:44:29 +0530 Subject: [PATCH] [fix] [refactor] demo for v8 and remove purchase common --- erpnext/__init__.py | 8 + erpnext/accounts/doctype/gl_entry/gl_entry.py | 13 +- .../doctype/journal_entry/journal_entry.py | 21 +- .../purchase_invoice/purchase_invoice.py | 9 +- .../doctype/shipping_rule/shipping_rule.py | 5 +- erpnext/accounts/party.py | 7 - .../report/gross_profit/gross_profit.py | 15 +- .../buying/doctype/purchase_common/README.md | 1 - .../doctype/purchase_common/__init__.py | 1 - .../purchase_common/purchase_common.js | 372 ------------------ .../purchase_common/purchase_common.json | 26 -- .../purchase_common/purchase_common.py | 105 ----- .../doctype/purchase_order/purchase_order.py | 24 +- .../request_for_quotation.py | 36 +- .../supplier_quotation/supplier_quotation.py | 8 +- erpnext/buying/utils.py | 80 ++++ erpnext/controllers/accounts_controller.py | 6 +- erpnext/controllers/buying_controller.py | 15 +- erpnext/controllers/selling_controller.py | 8 +- erpnext/controllers/stock_controller.py | 30 +- erpnext/controllers/taxes_and_totals.py | 7 +- .../crm/doctype/opportunity/opportunity.py | 7 +- erpnext/demo/setup/setup_data.py | 4 + erpnext/demo/user/hr.py | 10 +- erpnext/hr/doctype/salary_slip/salary_slip.py | 11 +- erpnext/patches.txt | 3 +- erpnext/setup/doctype/company/company.py | 6 +- erpnext/setup/setup_wizard/setup_wizard.py | 2 + erpnext/setup/utils.py | 11 +- .../material_request/material_request.py | 11 +- .../purchase_receipt/purchase_receipt.py | 24 +- erpnext/stock/stock_ledger.py | 30 +- 32 files changed, 233 insertions(+), 683 deletions(-) delete mode 100644 erpnext/buying/doctype/purchase_common/README.md delete mode 100644 erpnext/buying/doctype/purchase_common/__init__.py delete mode 100644 erpnext/buying/doctype/purchase_common/purchase_common.js delete mode 100644 erpnext/buying/doctype/purchase_common/purchase_common.json delete mode 100644 erpnext/buying/doctype/purchase_common/purchase_common.py create mode 100644 erpnext/buying/utils.py diff --git a/erpnext/__init__.py b/erpnext/__init__.py index ce9a142fd17..95c8a309a94 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -25,6 +25,14 @@ def get_default_currency(): if company: return frappe.db.get_value('Company', company, 'default_currency') +def get_company_currency(company): + '''Returns the default company currency''' + if not frappe.flags.company_currency: + frappe.flags.company_currency = {} + if not company in frappe.flags.company_currency: + frappe.flags.company_currency[company] = frappe.db.get_value('Company', company, 'default_currency') + return frappe.flags.company_currency[company] + def set_perpetual_inventory(enable=1): accounts_settings = frappe.get_doc("Accounts Settings") accounts_settings.auto_accounting_for_stock = enable diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index ce60298c17d..304af373081 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -2,13 +2,12 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext from frappe import _ from frappe.utils import flt, fmt_money, getdate, formatdate from frappe.model.document import Document from erpnext.accounts.party import validate_party_gle_currency, validate_party_frozen_disabled from erpnext.accounts.utils import get_account_currency -from erpnext.setup.doctype.company.company import get_company_currency from erpnext.accounts.utils import get_fiscal_year from erpnext.exceptions import InvalidAccountCurrency @@ -19,7 +18,7 @@ class GLEntry(Document): self.flags.ignore_submit_comment = True self.check_mandatory() self.validate_and_set_fiscal_year() - + if not self.flags.from_repost: self.pl_must_have_cost_center() self.check_pl_account() @@ -32,7 +31,7 @@ class GLEntry(Document): if not from_repost: self.validate_account_details(adv_adj) check_freezing_date(self.posting_date, adv_adj) - + validate_frozen_account(self.account, adv_adj) validate_balance_type(self.account, adv_adj) @@ -56,7 +55,7 @@ class GLEntry(Document): elif account_type == "Payable": frappe.throw(_("{0} {1}: Supplier is required against Payable account {2}") .format(self.voucher_type, self.voucher_no, self.account)) - + # Zero value transaction is not allowed if not (flt(self.debit) or flt(self.credit)): frappe.throw(_("{0} {1}: Either debit or credit amount is required for {2}") @@ -116,7 +115,7 @@ class GLEntry(Document): validate_party_frozen_disabled(self.party_type, self.party) def validate_currency(self): - company_currency = get_company_currency(self.company) + company_currency = erpnext.get_company_currency(self.company) account_currency = get_account_currency(self.account) if not self.account_currency: @@ -124,7 +123,7 @@ class GLEntry(Document): if account_currency != self.account_currency: frappe.throw(_("{0} {1}: Accounting Entry for {2} can only be made in currency: {3}") - .format(self.voucher_type, self.voucher_no, self.account, + .format(self.voucher_type, self.voucher_no, self.account, (account_currency or company_currency)), InvalidAccountCurrency) if self.party_type and self.party: diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 06724b1cfc4..a471c48c575 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -2,12 +2,11 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe, json +import frappe, erpnext, json from frappe.utils import cstr, flt, fmt_money, formatdate from frappe import msgprint, _, scrub from erpnext.controllers.accounts_controller import AccountsController from erpnext.accounts.utils import get_balance_on, get_account_currency -from erpnext.setup.utils import get_company_currency from erpnext.accounts.party import get_party_account from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount from erpnext.hr.doctype.employee_loan.employee_loan import update_disbursement_status @@ -325,11 +324,11 @@ class JournalEntry(AccountsController): if d.account_currency == self.company_currency: d.exchange_rate = 1 elif not d.exchange_rate or d.exchange_rate == 1 or \ - (d.reference_type in ("Sales Invoice", "Purchase Invoice") + (d.reference_type in ("Sales Invoice", "Purchase Invoice") and d.reference_name and self.posting_date): - + # Modified to include the posting date for which to retreive the exchange rate - d.exchange_rate = get_exchange_rate(self.posting_date, d.account, d.account_currency, + d.exchange_rate = get_exchange_rate(self.posting_date, d.account, d.account_currency, self.company, d.reference_type, d.reference_name, d.debit, d.credit, d.exchange_rate) if not d.exchange_rate: @@ -656,7 +655,7 @@ def get_payment_entry(ref_doc, args): if args.get("party_account"): # Modified to include the posting date for which the exchange rate is required. # Assumed to be the posting date in the reference document - exchange_rate = get_exchange_rate(ref_doc.get("posting_date") or ref_doc.get("transaction_date"), + exchange_rate = get_exchange_rate(ref_doc.get("posting_date") or ref_doc.get("transaction_date"), args.get("party_account"), args.get("party_account_currency"), ref_doc.company, ref_doc.doctype, ref_doc.name) @@ -692,8 +691,8 @@ def get_payment_entry(ref_doc, args): bank_row.update(bank_account) # Modified to include the posting date for which the exchange rate is required. # Assumed to be the posting date of the reference date - bank_row.exchange_rate = get_exchange_rate(ref_doc.get("posting_date") - or ref_doc.get("transaction_date"), bank_account["account"], + bank_row.exchange_rate = get_exchange_rate(ref_doc.get("posting_date") + or ref_doc.get("transaction_date"), bank_account["account"], bank_account["account_currency"], ref_doc.company) bank_row.cost_center = cost_center @@ -746,7 +745,7 @@ def get_outstanding(args): if isinstance(args, basestring): args = json.loads(args) - company_currency = get_company_currency(args.get("company")) + company_currency = erpnext.get_company_currency(args.get("company")) if args.get("doctype") == "Journal Entry": condition = " and party=%(party)s" if args.get("party") else "" @@ -805,7 +804,7 @@ def get_account_balance_and_party_type(account, date, company, debit=None, credi if not frappe.has_permission("Account"): frappe.msgprint(_("No Permission"), raise_exception=1) - company_currency = get_company_currency(company) + company_currency = erpnext.get_company_currency(company) account_details = frappe.db.get_value("Account", account, ["account_type", "account_currency"], as_dict=1) if not account_details: @@ -853,7 +852,7 @@ def get_exchange_rate(posting_date, account=None, account_currency=None, company if not account_currency: account_currency = account_details.account_currency - company_currency = get_company_currency(company) + company_currency = erpnext.get_company_currency(company) if account_currency != company_currency: if reference_type in ("Sales Invoice", "Purchase Invoice") and reference_name: diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index dd3b4ba3c9e..1972cc9da91 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -2,10 +2,9 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext from frappe.utils import cint, formatdate, flt, getdate from frappe import _, throw -from erpnext.setup.utils import get_company_currency import frappe.defaults from erpnext.controllers.buying_controller import BuyingController @@ -15,6 +14,7 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_bille from erpnext.controllers.stock_controller import get_warehouse_account from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entries, delete_gl_entries from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt +from erpnext.buying.utils import check_for_closed_status form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -93,7 +93,7 @@ class PurchaseInvoice(BuyingController): super(PurchaseInvoice, self).set_missing_values(for_validate) def check_conversion_rate(self): - default_currency = get_company_currency(self.company) + default_currency = erpnext.get_company_currency(self.company) if not default_currency: throw(_('Please enter default currency in Company Master')) if (self.currency == default_currency and flt(self.conversion_rate) != 1.00) or not self.conversion_rate or (self.currency != default_currency and flt(self.conversion_rate) == 1.00): @@ -113,12 +113,11 @@ class PurchaseInvoice(BuyingController): def check_for_closed_status(self): check_list = [] - pc_obj = frappe.get_doc('Purchase Common') for d in self.get('items'): if d.purchase_order and not d.purchase_order in check_list and not d.purchase_receipt: check_list.append(d.purchase_order) - pc_obj.check_for_closed_status('Purchase Order', d.purchase_order) + check_for_closed_status('Purchase Order', d.purchase_order) def validate_with_previous_doc(self): super(PurchaseInvoice, self).validate_with_previous_doc({ diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py index 80e4fb7415d..7faaf11cef8 100644 --- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py +++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py @@ -4,11 +4,10 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext from frappe import _, msgprint, throw from frappe.utils import flt, fmt_money from frappe.model.document import Document -from erpnext.setup.utils import get_company_currency class OverlappingConditionError(frappe.ValidationError): pass class FromGreaterThanToError(frappe.ValidationError): pass @@ -77,7 +76,7 @@ class ShippingRule(Document): overlaps.append([d1, d2]) if overlaps: - company_currency = get_company_currency(self.company) + company_currency = erpnext.get_company_currency(self.company) msgprint(_("Overlapping conditions found between:")) messages = [] for d1, d2 in overlaps: diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 8aedf78fefc..f4c5c4d25c0 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -151,13 +151,6 @@ def set_account_and_due_date(party, account, party_type, company, posting_date, } return out -def get_company_currency(): - company_currency = frappe._dict() - for d in frappe.get_all("Company", fields=["name", "default_currency"]): - company_currency.setdefault(d.name, d.default_currency) - - return company_currency - @frappe.whitelist() def get_party_account(party_type, party, company): """Returns the account for the given `party`. diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 48c6d9a8ab9..9c5d56ee3c9 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -12,7 +12,7 @@ from frappe.utils import flt def execute(filters=None): if not filters: filters = frappe._dict() - company_currency = frappe.db.get_value("Company", filters.company, "default_currency") + filters.currency = frappe.db.get_value("Company", filters.company, "default_currency") gross_profit_data = GrossProfitGenerator(filters) @@ -50,7 +50,7 @@ def execute(filters=None): for col in group_wise_columns.get(scrub(filters.group_by)): row.append(src.get(col)) - row.append(company_currency) + row.append(filters.currency) data.append(row) return columns, data @@ -224,7 +224,8 @@ class GrossProfitGenerator(object): else: average_buying_rate = get_incoming_rate(row) if not average_buying_rate: - average_buying_rate = get_valuation_rate(item_code, row.warehouse, allow_zero_rate=True) + average_buying_rate = get_valuation_rate(item_code, row.warehouse, + allow_zero_rate=True, currency=self.filters.currency) self.average_buying_rate[item_code] = average_buying_rate return self.average_buying_rate[item_code] @@ -235,7 +236,7 @@ class GrossProfitGenerator(object): select (a.base_rate / a.conversion_factor) from `tabPurchase Invoice Item` a where a.item_code = %s and a.docstatus=1 - and modified <= %s + and modified <= %s order by a.modified desc limit 1""", (item_code,self.filters.to_date)) else: last_purchase_rate = frappe.db.sql(""" @@ -253,7 +254,7 @@ class GrossProfitGenerator(object): conditions += " and posting_date >= %(from_date)s" if self.filters.to_date: conditions += " and posting_date <= %(to_date)s" - + if self.filters.group_by=="Sales Person": sales_person_cols = ", sales.sales_person, sales.allocated_amount, sales.incentives" sales_team_table = "left join `tabSales Team` sales on sales.parent = `tabSales Invoice`.name" @@ -269,7 +270,7 @@ class GrossProfitGenerator(object): `tabSales Invoice Item`.dn_detail, `tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty, `tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount, `tabSales Invoice Item`.name as "item_row" {sales_person_cols} - from + from `tabSales Invoice` inner join `tabSales Invoice Item` on `tabSales Invoice Item`.parent = `tabSales Invoice`.name {sales_team_table} @@ -277,7 +278,7 @@ class GrossProfitGenerator(object): `tabSales Invoice`.docstatus = 1 and `tabSales Invoice`.is_return != 1 {conditions} {match_cond} order by `tabSales Invoice`.posting_date desc, `tabSales Invoice`.posting_time desc""" - .format(conditions=conditions, sales_person_cols=sales_person_cols, + .format(conditions=conditions, sales_person_cols=sales_person_cols, sales_team_table=sales_team_table, match_cond = get_match_cond('Sales Invoice')), self.filters, as_dict=1) def load_stock_ledger_entries(self): diff --git a/erpnext/buying/doctype/purchase_common/README.md b/erpnext/buying/doctype/purchase_common/README.md deleted file mode 100644 index bedec2a0e45..00000000000 --- a/erpnext/buying/doctype/purchase_common/README.md +++ /dev/null @@ -1 +0,0 @@ -Common scripts for purchase transactions. \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_common/__init__.py b/erpnext/buying/doctype/purchase_common/__init__.py deleted file mode 100644 index baffc488252..00000000000 --- a/erpnext/buying/doctype/purchase_common/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/buying/doctype/purchase_common/purchase_common.js b/erpnext/buying/doctype/purchase_common/purchase_common.js deleted file mode 100644 index 6867dd0d5f6..00000000000 --- a/erpnext/buying/doctype/purchase_common/purchase_common.js +++ /dev/null @@ -1,372 +0,0 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt - -frappe.provide("erpnext.buying"); - -cur_frm.cscript.tax_table = "Purchase Taxes and Charges"; - -{% include 'erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js' %} - -cur_frm.email_field = "contact_email"; - -erpnext.buying.BuyingController = erpnext.TransactionController.extend({ - setup: function() { - this._super(); - }, - - onload: function() { - this.setup_queries(); - this._super(); - - if(this.frm.get_field('shipping_address')) { - this.frm.set_query("shipping_address", function(){ - if(me.frm.doc.customer){ - return { - query: 'frappe.geo.doctype.address.address.address_query', - filters: { link_doctype: 'Customer', link_name: me.frm.doc.customer } - }; - } else - return erpnext.queries.company_address_query(me.frm.doc) - }); - } - }, - - setup_queries: function() { - var me = this; - - if(this.frm.fields_dict.buying_price_list) { - this.frm.set_query("buying_price_list", function() { - return{ - filters: { 'buying': 1 } - } - }); - } - - me.frm.set_query('supplier', erpnext.queries.supplier); - me.frm.set_query('contact_person', erpnext.queries.contact_query); - me.frm.set_query('supplier_address', erpnext.queries.address_query); - - if(this.frm.fields_dict.supplier) { - this.frm.set_query("supplier", function() { - return{ query: "erpnext.controllers.queries.supplier_query" }}); - } - - this.frm.set_query("item_code", "items", function() { - if(me.frm.doc.is_subcontracted == "Yes") { - return{ - query: "erpnext.controllers.queries.item_query", - filters:{ 'is_sub_contracted_item': 1 } - } - } else { - return{ - query: "erpnext.controllers.queries.item_query", - filters: {'is_purchase_item': 1} - } - } - }); - }, - - refresh: function(doc) { - frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'supplier', doctype: 'Supplier'}; - - this.frm.toggle_display("supplier_name", - (this.frm.doc.supplier_name && this.frm.doc.supplier_name!==this.frm.doc.supplier)); - - if(this.frm.doc.docstatus==0 && - (this.frm.doctype==="Purchase Order" || this.frm.doctype==="Material Request")) { - this.set_from_product_bundle(); - } - - this._super(); - }, - - supplier: function() { - var me = this; - erpnext.utils.get_party_details(this.frm, null, null, function(){me.apply_pricing_rule()}); - }, - - supplier_address: function() { - erpnext.utils.get_address_display(this.frm); - }, - - buying_price_list: function() { - this.apply_price_list(); - }, - - price_list_rate: function(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - frappe.model.round_floats_in(item, ["price_list_rate", "discount_percentage"]); - - item.rate = flt(item.price_list_rate * (1 - item.discount_percentage / 100.0), - precision("rate", item)); - - this.calculate_taxes_and_totals(); - }, - - discount_percentage: function(doc, cdt, cdn) { - this.price_list_rate(doc, cdt, cdn); - }, - - qty: function(doc, cdt, cdn) { - var item = frappe.get_doc(cdt, cdn); - if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && (doc.update_stock || doc.is_return))) { - frappe.model.round_floats_in(item, ["qty", "received_qty"]); - - if(!doc.is_return && this.validate_negative_quantity(cdt, cdn, item, ["qty", "received_qty"])){ return } - - if(!item.rejected_qty && item.qty) { - item.received_qty = item.qty; - } - - frappe.model.round_floats_in(item, ["qty", "received_qty"]); - item.rejected_qty = flt(item.received_qty - item.qty, precision("rejected_qty", item)); - } - - this._super(doc, cdt, cdn); - }, - - received_qty: function(doc, cdt, cdn) { - this.calculate_accepted_qty(doc, cdt, cdn) - }, - - rejected_qty: function(doc, cdt, cdn) { - this.calculate_accepted_qty(doc, cdt, cdn) - }, - - calculate_accepted_qty: function(doc, cdt, cdn){ - var item = frappe.get_doc(cdt, cdn); - frappe.model.round_floats_in(item, ["received_qty", "rejected_qty"]); - - if(!doc.is_return && this.validate_negative_quantity(cdt, cdn, item, ["received_qty", "rejected_qty"])){ return } - - item.qty = flt(item.received_qty - item.rejected_qty, precision("qty", item)); - this.qty(doc, cdt, cdn); - }, - - validate_negative_quantity: function(cdt, cdn, item, fieldnames){ - if(!item || !fieldnames) { return } - - var is_negative_qty = false; - for(var i = 0; i 0 && qty > 0 && cur_frm.doc.items[i].item_code == d.item_code && !cur_frm.doc.items[i].material_request_item) - { - cur_frm.doc.items[i].material_request = d.mr_name; - cur_frm.doc.items[i].material_request_item = d.mr_item; - my_qty = Math.min(qty, d.qty); - qty = qty - my_qty; - d.qty = d.qty - my_qty; - cur_frm.doc.items[i].stock_qty = my_qty*cur_frm.doc.items[i].conversion_factor; - cur_frm.doc.items[i].qty = my_qty; - - frappe.msgprint("Assigning " + d.mr_name + " to " + d.item_code + " (row " + cur_frm.doc.items[i].idx + ")"); - if (qty > 0) - { - frappe.msgprint("Splitting " + qty + " units of " + d.item_code); - var newrow = frappe.model.add_child(cur_frm.doc, cur_frm.doc.items[i].doctype, "items"); - item_length++; - - for (key in cur_frm.doc.items[i]) - { - newrow[key] = cur_frm.doc.items[i][key]; - } - - newrow.idx = item_length; - newrow["stock_qty"] = newrow.conversion_factor*qty; - newrow["qty"] = qty; - - newrow["material_request"] = ""; - newrow["material_request_item"] = ""; - - } - - - - } - - }); - i++; - } - refresh_field("items"); - //cur_frm.save(); - } - }); - } -}); - -cur_frm.add_fetch('project', 'cost_center', 'cost_center'); - -erpnext.buying.get_default_bom = function(frm) { - $.each(frm.doc["items"] || [], function(i, d) { - if (d.item_code && d.bom === "") { - return frappe.call({ - type: "GET", - method: "erpnext.stock.get_item_details.get_default_bom", - args: { - "item_code": d.item_code, - }, - callback: function(r) { - if(r) { - frappe.model.set_value(d.doctype, d.name, "bom", r.message); - } - } - }) - } - }); -} - -erpnext.buying.get_items_from_product_bundle = function(frm) { - var dialog = new frappe.ui.Dialog({ - title: __("Get Items from Product Bundle"), - fields: [ - { - "fieldtype": "Link", - "label": __("Product Bundle"), - "fieldname": "product_bundle", - "options":"Product Bundle", - "reqd": 1 - }, - { - "fieldtype": "Currency", - "label": __("Quantity"), - "fieldname": "quantity", - "reqd": 1, - "default": 1 - }, - { - "fieldtype": "Button", - "label": __("Get Items"), - "fieldname": "get_items", - "cssClass": "btn-primary" - } - ] - }); - - dialog.fields_dict.get_items.$input.click(function() { - args = dialog.get_values(); - if(!args) return; - dialog.hide(); - return frappe.call({ - type: "GET", - method: "erpnext.stock.doctype.packed_item.packed_item.get_items_from_product_bundle", - args: { - args: { - item_code: args.product_bundle, - quantity: args.quantity, - parenttype: frm.doc.doctype, - parent: frm.doc.name, - supplier: frm.doc.supplier, - currency: frm.doc.currency, - conversion_rate: frm.doc.conversion_rate, - price_list: frm.doc.buying_price_list, - price_list_currency: frm.doc.price_list_currency, - plc_conversion_rate: frm.doc.plc_conversion_rate, - company: frm.doc.company, - is_subcontracted: frm.doc.is_subcontracted, - transaction_date: frm.doc.transaction_date || frm.doc.posting_date, - ignore_pricing_rule: frm.doc.ignore_pricing_rule - } - }, - freeze: true, - callback: function(r) { - if(!r.exc && r.message) { - for ( var i=0; i< r.message.length; i++ ) { - var d = frm.add_child("items"); - var item = r.message[i]; - for ( var key in item) { - if ( !is_null(item[key]) ) { - d[key] = item[key]; - } - } - if(frappe.meta.get_docfield(d.doctype, "price_list_rate", d.name)) { - frm.script_manager.trigger("price_list_rate", d.doctype, d.name); - } - } - frm.refresh_field("items"); - } - } - }) - }); - dialog.show(); -} diff --git a/erpnext/buying/doctype/purchase_common/purchase_common.json b/erpnext/buying/doctype/purchase_common/purchase_common.json deleted file mode 100644 index fd08d089823..00000000000 --- a/erpnext/buying/doctype/purchase_common/purchase_common.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "creation": "2012-03-27 14:35:51", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "fields": [], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "in_create": 0, - "in_dialog": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "modified": "2013-12-20 19:23:27", - "modified_by": "Administrator", - "module": "Buying", - "name": "Purchase Common", - "owner": "Administrator", - "permissions": [], - "read_only": 0, - "read_only_onload": 0 -} \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_common/purchase_common.py b/erpnext/buying/doctype/purchase_common/purchase_common.py deleted file mode 100644 index 844a6551ba4..00000000000 --- a/erpnext/buying/doctype/purchase_common/purchase_common.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe, json -from frappe.utils import flt, cstr, cint -from frappe import _ - -from erpnext.stock.doctype.item.item import get_last_purchase_details -from erpnext.controllers.buying_controller import BuyingController - -class PurchaseCommon(BuyingController): - def update_last_purchase_rate(self, obj, is_submit): - """updates last_purchase_rate in item table for each item""" - - import frappe.utils - this_purchase_date = frappe.utils.getdate(obj.get('posting_date') or obj.get('transaction_date')) - - for d in obj.get("items"): - # get last purchase details - last_purchase_details = get_last_purchase_details(d.item_code, obj.name) - - # compare last purchase date and this transaction's date - last_purchase_rate = None - if last_purchase_details and \ - (last_purchase_details.purchase_date > this_purchase_date): - last_purchase_rate = last_purchase_details['base_rate'] - elif is_submit == 1: - # even if this transaction is the latest one, it should be submitted - # for it to be considered for latest purchase rate - if flt(d.conversion_factor): - last_purchase_rate = flt(d.base_rate) / flt(d.conversion_factor) - else: - frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx)) - - # update last purchsae rate - if last_purchase_rate: - frappe.db.sql("""update `tabItem` set last_purchase_rate = %s where name = %s""", - (flt(last_purchase_rate), d.item_code)) - - def validate_for_items(self, obj): - items = [] - for d in obj.get("items"): - if not d.qty: - if obj.doctype == "Purchase Receipt" and d.rejected_qty: - continue - frappe.throw(_("Please enter quantity for Item {0}").format(d.item_code)) - - # udpate with latest quantities - bin = frappe.db.sql("""select projected_qty from `tabBin` where - item_code = %s and warehouse = %s""", (d.item_code, d.warehouse), as_dict=1) - - f_lst ={'projected_qty': bin and flt(bin[0]['projected_qty']) or 0, 'ordered_qty': 0, 'received_qty' : 0} - if d.doctype in ('Purchase Receipt Item', 'Purchase Invoice Item'): - f_lst.pop('received_qty') - for x in f_lst : - if d.meta.get_field(x): - d.set(x, f_lst[x]) - - item = frappe.db.sql("""select is_stock_item, - is_sub_contracted_item, end_of_life, disabled from `tabItem` where name=%s""", - d.item_code, as_dict=1)[0] - - from erpnext.stock.doctype.item.item import validate_end_of_life - validate_end_of_life(d.item_code, item.end_of_life, item.disabled) - - # validate stock item - if item.is_stock_item==1 and d.qty and not d.warehouse and not d.delivered_by_supplier: - frappe.throw(_("Warehouse is mandatory for stock Item {0} in row {1}").format(d.item_code, d.idx)) - - items.append(cstr(d.item_code)) - - if items and len(items) != len(set(items)) and \ - not cint(frappe.db.get_single_value("Buying Settings", "allow_multiple_items") or 0): - frappe.throw(_("Same item cannot be entered multiple times.")) - - def check_for_closed_status(self, doctype, docname): - status = frappe.db.get_value(doctype, docname, "status") - - if status == "Closed": - frappe.throw(_("{0} {1} status is {2}").format(doctype, docname, status), frappe.InvalidStatusError) - -@frappe.whitelist() -def get_linked_material_requests(items): - items = json.loads(items) - mr_list = [] - for item in items: - material_request = frappe.db.sql("""SELECT distinct mr.name AS mr_name, - (mr_item.qty - mr_item.ordered_qty) AS qty, - mr_item.item_code AS item_code, - mr_item.name AS mr_item - FROM `tabMaterial Request` mr, `tabMaterial Request Item` mr_item - WHERE mr.name = mr_item.parent - AND mr_item.item_code = %(item)s - AND mr.material_request_type = 'Purchase' - AND mr.per_ordered < 99.99 - AND mr.docstatus = 1 - AND mr.status != 'Stopped' - ORDER BY mr_item.item_code ASC""",{"item": item}, as_dict=1) - if material_request: - mr_list.append(material_request) - - return mr_list - - \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 796e0f2790f..96351e355f4 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -11,6 +11,8 @@ from erpnext.controllers.buying_controller import BuyingController from erpnext.stock.doctype.item.item import get_last_purchase_details from erpnext.stock.stock_balance import update_bin_qty, get_ordered_qty from frappe.desk.notifications import clear_doctype_notifications +from erpnext.buying.utils import (validate_for_items, check_for_closed_status, + update_last_purchase_rate) form_grid_templates = { @@ -37,9 +39,8 @@ class PurchaseOrder(BuyingController): super(PurchaseOrder, self).validate() self.set_status() - pc_obj = frappe.get_doc('Purchase Common') - pc_obj.validate_for_items(self) - self.check_for_closed_status(pc_obj) + validate_for_items(self) + self.check_for_closed_status() self.validate_uom_is_integer("uom", "qty") self.validate_uom_is_integer("stock_uom", ["qty", "required_qty"]) @@ -111,12 +112,12 @@ class PurchaseOrder(BuyingController): = d.rate = item_last_purchase_rate # Check for Closed status - def check_for_closed_status(self, pc_obj): + def check_for_closed_status(self): check_list =[] for d in self.get('items'): if d.meta.get_field('material_request') and d.material_request and d.material_request not in check_list: check_list.append(d.material_request) - pc_obj.check_for_closed_status('Material Request', d.material_request) + check_for_closed_status('Material Request', d.material_request) def update_requested_qty(self): material_request_map = {} @@ -155,7 +156,7 @@ class PurchaseOrder(BuyingController): if date_diff and date_diff[0][0]: msgprint(_("{0} {1} has been modified. Please refresh.").format(self.doctype, self.name), raise_exception=True) - + def update_status(self, status): self.check_modified_date() self.set_status(update=True, status=status) @@ -168,8 +169,6 @@ class PurchaseOrder(BuyingController): if self.is_against_so(): self.update_status_updater() - purchase_controller = frappe.get_doc("Purchase Common") - self.update_prevdoc_status() self.update_requested_qty() self.update_ordered_qty() @@ -177,7 +176,7 @@ class PurchaseOrder(BuyingController): frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.base_grand_total) - purchase_controller.update_last_purchase_rate(self, is_submit = 1) + update_last_purchase_rate(self, is_submit = 1) def on_cancel(self): if self.is_against_so(): @@ -186,8 +185,7 @@ class PurchaseOrder(BuyingController): if self.has_drop_ship_item(): self.update_delivered_qty_in_sales_order() - pc_obj = frappe.get_doc('Purchase Common') - self.check_for_closed_status(pc_obj) + self.check_for_closed_status() frappe.db.set(self,'status','Cancelled') @@ -197,7 +195,7 @@ class PurchaseOrder(BuyingController): self.update_requested_qty() self.update_ordered_qty() - pc_obj.update_last_purchase_rate(self, is_submit = 0) + update_last_purchase_rate(self, is_submit = 0) def on_update(self): pass @@ -303,7 +301,7 @@ def make_purchase_invoice(source_name, target_doc=None): target.amount = flt(obj.amount) - flt(obj.billed_amt) target.base_amount = target.amount * flt(source_parent.conversion_rate) target.qty = target.amount / flt(obj.rate) if (flt(obj.rate) and flt(obj.billed_amt)) else flt(obj.qty) - + item = frappe.db.get_value("Item", target.item_code, ["item_group", "buying_cost_center"], as_dict=1) target.cost_center = frappe.db.get_value("Project", obj.project, "cost_center") \ or item.buying_cost_center \ diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index ab9efaed315..34a904f576c 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -14,12 +14,14 @@ from frappe.core.doctype.communication.email import make from erpnext.accounts.party import get_party_account_currency, get_party_details from erpnext.stock.doctype.material_request.material_request import set_missing_values from erpnext.controllers.buying_controller import BuyingController +from erpnext.buying.utils import validate_for_items STANDARD_USERS = ("Guest", "Administrator") class RequestforQuotation(BuyingController): def validate(self): self.validate_duplicate_supplier() + validate_for_items(self) self.validate_common() self.update_email_id() @@ -28,10 +30,6 @@ class RequestforQuotation(BuyingController): if len(supplier_list) != len(set(supplier_list)): frappe.throw(_("Same supplier has been entered multiple times")) - def validate_common(self): - pc = frappe.get_doc('Purchase Common') - pc.validate_for_items(self) - def update_email_id(self): for rfq_supplier in self.suppliers: if not rfq_supplier.email_id: @@ -130,7 +128,7 @@ class RequestforQuotation(BuyingController): self.send_email(data, sender, subject, message, attachments) def send_email(self, data, sender, subject, message, attachments): - make(subject = subject, content=message,recipients=data.email_id, + make(subject = subject, content=message,recipients=data.email_id, sender=sender,attachments = attachments, send_email=True, doctype=self.doctype, name=self.name)["name"] @@ -250,26 +248,26 @@ def get_rfq_doc(doctype, name, supplier_idx): args = doc.get('suppliers')[cint(supplier_idx) - 1] doc.update_supplier_part_no(args) return doc - + @frappe.whitelist() def get_item_from_material_requests_based_on_supplier(source_name, target_doc = None): mr_items_list = frappe.db.sql(""" SELECT mr.name, mr_item.item_code FROM - `tabItem` as item, - `tabItem Supplier` as item_supp, - `tabMaterial Request Item` as mr_item, - `tabMaterial Request` as mr - WHERE item_supp.supplier = %(supplier)s - AND item.name = item_supp.parent - AND mr_item.parent = mr.name - AND mr_item.item_code = item.name - AND mr.status != "Stopped" - AND mr.material_request_type = "Purchase" - AND mr.docstatus = 1 + `tabItem` as item, + `tabItem Supplier` as item_supp, + `tabMaterial Request Item` as mr_item, + `tabMaterial Request` as mr + WHERE item_supp.supplier = %(supplier)s + AND item.name = item_supp.parent + AND mr_item.parent = mr.name + AND mr_item.item_code = item.name + AND mr.status != "Stopped" + AND mr.material_request_type = "Purchase" + AND mr.docstatus = 1 AND mr.per_ordered < 99.99""", {"supplier": source_name}, as_dict=1) - + material_requests = {} for d in mr_items_list: material_requests.setdefault(d.name, []).append(d.item_code) @@ -293,5 +291,5 @@ def get_item_from_material_requests_based_on_supplier(source_name, target_doc = ] } }, target_doc) - + return target_doc diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py index 30899c81ccc..1cb5a18662c 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py @@ -8,6 +8,7 @@ from frappe.utils import flt from frappe.model.mapper import get_mapped_doc from erpnext.controllers.buying_controller import BuyingController +from erpnext.buying.utils import validate_for_items form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -24,7 +25,7 @@ class SupplierQuotation(BuyingController): validate_status(self.status, ["Draft", "Submitted", "Stopped", "Cancelled"]) - self.validate_common() + validate_for_items(self) self.validate_with_previous_doc() self.validate_uom_is_integer("uom", "qty") @@ -50,11 +51,6 @@ class SupplierQuotation(BuyingController): } }) - - def validate_common(self): - pc = frappe.get_doc('Purchase Common') - pc.validate_for_items(self) - def get_list_context(context=None): from erpnext.controllers.website_list_for_contact import get_list_context list_context = get_list_context(context) diff --git a/erpnext/buying/utils.py b/erpnext/buying/utils.py new file mode 100644 index 00000000000..28c757948a8 --- /dev/null +++ b/erpnext/buying/utils.py @@ -0,0 +1,80 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils import flt, cstr, cint +from frappe import _ + +from erpnext.stock.doctype.item.item import get_last_purchase_details +from erpnext.stock.doctype.item.item import validate_end_of_life + +def update_last_purchase_rate(doc, is_submit): + """updates last_purchase_rate in item table for each item""" + + import frappe.utils + this_purchase_date = frappe.utils.getdate(doc.get('posting_date') or doc.get('transaction_date')) + + for d in doc.get("items"): + # get last purchase details + last_purchase_details = get_last_purchase_details(d.item_code, doc.name) + + # compare last purchase date and this transaction's date + last_purchase_rate = None + if last_purchase_details and \ + (last_purchase_details.purchase_date > this_purchase_date): + last_purchase_rate = last_purchase_details['base_rate'] + elif is_submit == 1: + # even if this transaction is the latest one, it should be submitted + # for it to be considered for latest purchase rate + if flt(d.conversion_factor): + last_purchase_rate = flt(d.base_rate) / flt(d.conversion_factor) + else: + frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx)) + + # update last purchsae rate + if last_purchase_rate: + frappe.db.sql("""update `tabItem` set last_purchase_rate = %s where name = %s""", + (flt(last_purchase_rate), d.item_code)) + +def validate_for_items(doc): + items = [] + for d in doc.get("items"): + if not d.qty: + if doc.doctype == "Purchase Receipt" and d.rejected_qty: + continue + frappe.throw(_("Please enter quantity for Item {0}").format(d.item_code)) + + # update with latest quantities + bin = frappe.db.sql("""select projected_qty from `tabBin` where + item_code = %s and warehouse = %s""", (d.item_code, d.warehouse), as_dict=1) + + f_lst ={'projected_qty': bin and flt(bin[0]['projected_qty']) or 0, 'ordered_qty': 0, 'received_qty' : 0} + if d.doctype in ('Purchase Receipt Item', 'Purchase Invoice Item'): + f_lst.pop('received_qty') + for x in f_lst : + if d.meta.get_field(x): + d.set(x, f_lst[x]) + + item = frappe.db.sql("""select is_stock_item, + is_sub_contracted_item, end_of_life, disabled from `tabItem` where name=%s""", + d.item_code, as_dict=1)[0] + + validate_end_of_life(d.item_code, item.end_of_life, item.disabled) + + # validate stock item + if item.is_stock_item==1 and d.qty and not d.warehouse and not d.delivered_by_supplier: + frappe.throw(_("Warehouse is mandatory for stock Item {0} in row {1}").format(d.item_code, d.idx)) + + items.append(cstr(d.item_code)) + + if items and len(items) != len(set(items)) and \ + not cint(frappe.db.get_single_value("Buying Settings", "allow_multiple_items") or 0): + frappe.throw(_("Same item cannot be entered multiple times.")) + +def check_for_closed_status(doctype, docname): + status = frappe.db.get_value(doctype, docname, "status") + + if status == "Closed": + frappe.throw(_("{0} {1} status is {2}").format(doctype, docname, status), frappe.InvalidStatusError) + diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 518f68632f8..910c19c9ca0 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -2,10 +2,10 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext from frappe import _, throw from frappe.utils import today, flt, cint, fmt_money, formatdate, getdate -from erpnext.setup.utils import get_company_currency, get_exchange_rate +from erpnext.setup.utils import get_exchange_rate from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency from erpnext.utilities.transaction_base import TransactionBase from erpnext.controllers.recurring_document import convert_to_recurring, validate_recurring_document @@ -22,7 +22,7 @@ class AccountsController(TransactionBase): @property def company_currency(self): if not hasattr(self, "__company_currency"): - self.__company_currency = get_company_currency(self.company) + self.__company_currency = erpnext.get_company_currency(self.company) return self.__company_currency diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index a1c71852d81..5bc8bb38f86 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -6,9 +6,10 @@ import frappe from frappe import _, msgprint from frappe.utils import flt,cint, cstr -from erpnext.setup.utils import get_company_currency from erpnext.accounts.party import get_party_details from erpnext.stock.get_item_details import get_conversion_factor +from erpnext.buying.utils import validate_for_items +from erpnext.stock.stock_ledger import get_valuation_rate from erpnext.controllers.stock_controller import StockController @@ -40,9 +41,7 @@ class BuyingController(StockController): # self.validate_purchase_return() self.validate_rejected_warehouse() self.validate_accepted_rejected_qty() - - pc_obj = frappe.get_doc('Purchase Common') - pc_obj.validate_for_items(self) + validate_for_items(self) #sub-contracting self.validate_for_subcontracting() @@ -88,9 +87,8 @@ class BuyingController(StockController): def set_total_in_words(self): from frappe.utils import money_in_words - company_currency = get_company_currency(self.company) if self.meta.get_field("base_in_words"): - self.base_in_words = money_in_words(self.base_grand_total, company_currency) + self.base_in_words = money_in_words(self.base_grand_total, self.company_currency) if self.meta.get_field("in_words"): self.in_words = money_in_words(self.grand_total, self.currency) @@ -225,9 +223,8 @@ class BuyingController(StockController): "serial_no": rm.serial_no }) if not rm.rate: - from erpnext.stock.stock_ledger import get_valuation_rate - rm.rate = get_valuation_rate(bom_item.item_code, self.supplier_warehouse, - self.doctype, self.name) + rm.rate = get_valuation_rate(bom_item.item_code, self.supplier_warehouse, + self.doctype, self.name, currency=self.company_currency) else: rm.rate = bom_item.rate diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 5f50ae32a24..c235d3435a3 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -4,11 +4,9 @@ from __future__ import unicode_literals import frappe from frappe.utils import cint, flt, cstr, comma_or -from erpnext.setup.utils import get_company_currency from frappe import _, throw from erpnext.stock.get_item_details import get_bin_details from erpnext.stock.utils import get_incoming_rate -from erpnext.stock.stock_ledger import get_valuation_rate from erpnext.stock.get_item_details import get_conversion_factor from erpnext.controllers.stock_controller import StockController @@ -113,13 +111,11 @@ class SellingController(StockController): def set_total_in_words(self): from frappe.utils import money_in_words - company_currency = get_company_currency(self.company) - disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total")) if self.meta.get_field("base_in_words"): self.base_in_words = money_in_words(disable_rounded_total and - abs(self.base_grand_total) or abs(self.base_rounded_total), company_currency) + abs(self.base_grand_total) or abs(self.base_rounded_total), self.company_currency) if self.meta.get_field("in_words"): self.in_words = money_in_words(disable_rounded_total and abs(self.grand_total) or abs(self.rounded_total), self.currency) @@ -170,7 +166,7 @@ class SellingController(StockController): if d.meta.get_field("stock_qty"): if not d.conversion_factor: frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx)) - d.stock_qty = flt(d.qty) * flt(d.conversion_factor) + d.stock_qty = flt(d.qty) * flt(d.conversion_factor) def validate_selling_price(self): def throw_message(item_name, rate, ref_rate_field): diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index add882cbbfc..9f0534529f7 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -54,9 +54,9 @@ class StockController(AccountsController): self.check_expense_account(item_row) - # If item is not a sample item + # If item is not a sample item # and ( valuation rate not mentioned in an incoming entry - # or incoming entry not found while delivering the item), + # or incoming entry not found while delivering the item), # try to pick valuation rate from previous sle or Item master and update in SLE # Otherwise, throw an exception @@ -96,25 +96,25 @@ class StockController(AccountsController): return process_gl_map(gl_list) def update_stock_ledger_entries(self, sle): - sle.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, - self.doctype, self.name) + sle.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, + self.doctype, self.name, currency=self.company_currency) sle.stock_value = flt(sle.qty_after_transaction) * flt(sle.valuation_rate) sle.stock_value_difference = flt(sle.actual_qty) * flt(sle.valuation_rate) - + if sle.name: frappe.db.sql(""" - update - `tabStock Ledger Entry` - set + update + `tabStock Ledger Entry` + set stock_value = %(stock_value)s, - valuation_rate = %(valuation_rate)s, - stock_value_difference = %(stock_value_difference)s - where + valuation_rate = %(valuation_rate)s, + stock_value_difference = %(stock_value_difference)s + where name = %(name)s""", (sle)) - + return sle - + def get_voucher_details(self, default_expense_account, default_cost_center, sle_map): if self.doctype == "Stock Reconciliation": return [frappe._dict({ "name": voucher_detail_no, "expense_account": default_expense_account, @@ -163,9 +163,9 @@ class StockController(AccountsController): def get_stock_ledger_details(self): stock_ledger = {} stock_ledger_entries = frappe.db.sql(""" - select + select name, warehouse, stock_value_difference, valuation_rate, - voucher_detail_no, item_code, posting_date, posting_time, + voucher_detail_no, item_code, posting_date, posting_time, actual_qty, qty_after_transaction from `tabStock Ledger Entry` diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 0e02df8d968..bec57f4efbd 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -3,10 +3,9 @@ from __future__ import unicode_literals import json -import frappe +import frappe, erpnext from frappe import _, scrub from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction -from erpnext.setup.utils import get_company_currency from erpnext.controllers.accounts_controller import validate_conversion_rate, \ validate_taxes_and_charges, validate_inclusive_tax @@ -38,7 +37,7 @@ class calculate_taxes_and_totals(object): def validate_conversion_rate(self): # validate conversion rate - company_currency = get_company_currency(self.doc.company) + company_currency = erpnext.get_company_currency(self.doc.company) if not self.doc.currency or self.doc.currency == company_currency: self.doc.currency = company_currency self.doc.conversion_rate = 1.0 @@ -327,7 +326,7 @@ class calculate_taxes_and_totals(object): self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total")) if self.doc.meta.get_field("base_rounded_total"): - company_currency = get_company_currency(self.doc.company) + company_currency = erpnext.get_company_currency(self.doc.company) self.doc.base_rounded_total = \ round_based_on_smallest_currency_fraction(self.doc.base_grand_total, diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 913d2e4ae62..3c553a5da81 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -194,11 +194,12 @@ def make_quotation(source_name, target_doc=None): quotation.transaction_date) quotation.conversion_rate = exchange_rate - + # get default taxes taxes = get_default_taxes_and_charges("Sales Taxes and Charges Template") - quotation.extend("taxes", taxes) - + if taxes: + quotation.extend("taxes", taxes) + quotation.run_method("set_missing_values") quotation.run_method("calculate_taxes_and_totals") diff --git a/erpnext/demo/setup/setup_data.py b/erpnext/demo/setup/setup_data.py index 400b0767460..c1a8bed64bd 100644 --- a/erpnext/demo/setup/setup_data.py +++ b/erpnext/demo/setup/setup_data.py @@ -63,6 +63,10 @@ def complete_setup(domain='Manufacturing'): "language": "english" }) + company = erpnext.get_default_company() + company.db_set('default_payroll_payable_account', + frappe.db.get_value('Account', dict(account_name='Payroll Payable'))) + def setup_demo_page(): # home page should always be "start" website_settings = frappe.get_doc("Website Settings", "Website Settings") diff --git a/erpnext/demo/user/hr.py b/erpnext/demo/user/hr.py index 25366024c1c..0b644c42d36 100644 --- a/erpnext/demo/user/hr.py +++ b/erpnext/demo/user/hr.py @@ -34,14 +34,16 @@ def work(): process_payroll.salary_slip_based_on_timesheet = 0 process_payroll.create_salary_slips() process_payroll.submit_salary_slips() - process_payroll.make_journal_entry(reference_date=frappe.flags.current_date, - reference_number=random_string(10)) + process_payroll.make_accural_jv_entry() + # process_payroll.make_journal_entry(reference_date=frappe.flags.current_date, + # reference_number=random_string(10)) process_payroll.salary_slip_based_on_timesheet = 1 process_payroll.create_salary_slips() process_payroll.submit_salary_slips() - process_payroll.make_journal_entry(reference_date=frappe.flags.current_date, - reference_number=random_string(10)) + process_payroll.make_accural_jv_entry() + # process_payroll.make_journal_entry(reference_date=frappe.flags.current_date, + # reference_number=random_string(10)) if frappe.db.get_global('demo_hr_user'): make_timesheet_records() diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 34b729f1028..a4d6460a356 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -2,13 +2,12 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words from frappe.model.naming import make_autoname from frappe import msgprint, _ -from erpnext.setup.utils import get_company_currency from erpnext.hr.doctype.process_payroll.process_payroll import get_start_end_dates from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.utilities.transaction_base import TransactionBase @@ -33,7 +32,7 @@ class SalarySlip(TransactionBase): # if self.salary_slip_based_on_timesheet or not self.net_pay: self.calculate_net_pay() - company_currency = get_company_currency(self.company) + company_currency = erpnext.get_company_currency(self.company) self.total_in_words = money_in_words(self.rounded_total, company_currency) if frappe.db.get_single_value("HR Settings", "max_working_hours_against_timesheet"): @@ -348,7 +347,7 @@ class SalarySlip(TransactionBase): self.sum_components('earnings', 'gross_pay') self.sum_components('deductions', 'total_deduction') - + self.set_loan_repayment() self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment)) @@ -356,11 +355,11 @@ class SalarySlip(TransactionBase): self.precision("net_pay") if disable_rounded_total else 0) def set_loan_repayment(self): - employee_loan = frappe.db.sql("""select sum(principal_amount) as principal_amount, sum(interest_amount) as interest_amount, + employee_loan = frappe.db.sql("""select sum(principal_amount) as principal_amount, sum(interest_amount) as interest_amount, sum(total_payment) as total_loan_repayment from `tabRepayment Schedule` where payment_date between %s and %s and parent in (select name from `tabEmployee Loan` where employee = %s and repay_from_salary = 1 and docstatus = 1)""", - (self.start_date, self.end_date, self.employee), as_dict=True) + (self.start_date, self.end_date, self.employee), as_dict=True) if employee_loan: self.principal_amount = employee_loan[0].principal_amount self.interest_amount = employee_loan[0].interest_amount diff --git a/erpnext/patches.txt b/erpnext/patches.txt index cb8ba03d6b7..3711bc82b2d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -381,4 +381,5 @@ erpnext.patches.v7_2.move_dates_from_salary_structure_to_employee erpnext.patches.v7_2.make_all_assessment_group erpnext.patches.v8_0.manufacturer_childtable_migrate erpnext.patches.v8_0.repost_reserved_qty_for_multiple_sales_uom -erpnext.patches.v8_0.addresses_linked_to_lead \ No newline at end of file +erpnext.patches.v8_0.addresses_linked_to_lead +execute:frappe.delete_doc('DocType', 'Purchase Common') \ No newline at end of file diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index dd7bca9f73a..26298bcc577 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -123,7 +123,7 @@ class Company(Document): {"company": self.name, "account_type": "Receivable", "is_group": 0})) frappe.db.set(self, "default_payable_account", frappe.db.get_value("Account", {"company": self.name, "account_type": "Payable", "is_group": 0})) - + def validate_coa_input(self): if self.create_chart_of_accounts_based_on == "Existing Company": self.chart_of_accounts = None @@ -294,7 +294,3 @@ def get_name_with_abbr(name, company): parts.append(company_abbr) return " - ".join(parts) - -def get_company_currency(company): - return frappe.local_cache("company_currency", company, - lambda: frappe.db.get_value("Company", company, "default_currency")) diff --git a/erpnext/setup/setup_wizard/setup_wizard.py b/erpnext/setup/setup_wizard/setup_wizard.py index 26509ed901d..922479c8ee5 100644 --- a/erpnext/setup/setup_wizard/setup_wizard.py +++ b/erpnext/setup/setup_wizard/setup_wizard.py @@ -177,6 +177,7 @@ def set_defaults(args): selling_settings.cust_master_name = "Customer Name" selling_settings.so_required = "No" selling_settings.dn_required = "No" + selling_settings.allow_multiple_items = 1 selling_settings.save() buying_settings = frappe.get_doc("Buying Settings") @@ -184,6 +185,7 @@ def set_defaults(args): buying_settings.po_required = "No" buying_settings.pr_required = "No" buying_settings.maintain_same_rate = 1 + buying_settings.allow_multiple_items = 1 buying_settings.save() notification_control = frappe.get_doc("Notification Control") diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index cb638373b17..55a0fd3c011 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -3,19 +3,10 @@ from __future__ import unicode_literals import frappe -from frappe import _, throw +from frappe import _ from frappe.utils import flt from frappe.utils import get_datetime_str, nowdate -def get_company_currency(company): - currency = frappe.db.get_value("Company", company, "default_currency", cache=True) - if not currency: - currency = frappe.db.get_default("currency") - if not currency: - throw(_('Please specify Default Currency in Company Master and Global Defaults')) - - return currency - def get_root_of(doctype): """Get root element of a DocType with a tree structure""" result = frappe.db.sql_list("""select name from `tab%s` diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index ca254142415..82c4c19fdf2 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -13,7 +13,7 @@ from frappe.model.mapper import get_mapped_doc from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty from erpnext.controllers.buying_controller import BuyingController from erpnext.manufacturing.doctype.production_order.production_order import get_item_details - +from erpnext.buying.utils import check_for_closed_status, validate_for_items form_grid_templates = { "items": "templates/form_grid/material_request_grid.html" @@ -72,12 +72,9 @@ class MaterialRequest(BuyingController): from erpnext.controllers.status_updater import validate_status validate_status(self.status, ["Draft", "Submitted", "Stopped", "Cancelled"]) - pc_obj = frappe.get_doc('Purchase Common') - pc_obj.validate_for_items(self) + validate_for_items(self) # self.set_title() - - # self.validate_qty_against_so() # NOTE: Since Item BOM and FG quantities are combined, using current data, it cannot be validated # Though the creation of Material Request from a Production Plan can be rethought to fix this @@ -112,9 +109,7 @@ class MaterialRequest(BuyingController): self.update_requested_qty() def on_cancel(self): - pc_obj = frappe.get_doc('Purchase Common') - - pc_obj.check_for_closed_status(self.doctype, self.name) + check_for_closed_status(self.doctype, self.name) self.update_requested_qty() diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index b89987c57e6..1f8fd8d9cf8 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -12,6 +12,7 @@ from frappe.utils import getdate from erpnext.controllers.buying_controller import BuyingController from erpnext.accounts.utils import get_account_currency from frappe.desk.notifications import clear_doctype_notifications +from erpnext.buying.utils import check_for_closed_status, update_last_purchase_rate form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -56,8 +57,7 @@ class PurchaseReceipt(BuyingController): self.validate_uom_is_integer("uom", ["qty", "received_qty"]) self.validate_uom_is_integer("stock_uom", "stock_qty") - pc_obj = frappe.get_doc('Purchase Common') - self.check_for_closed_status(pc_obj) + self.check_for_closed_status() if getdate(self.posting_date) > getdate(nowdate()): throw(_("Posting Date cannot be future date")) @@ -98,17 +98,16 @@ class PurchaseReceipt(BuyingController): return po_qty, po_warehouse # Check for Closed status - def check_for_closed_status(self, pc_obj): + def check_for_closed_status(self): check_list =[] for d in self.get('items'): - if d.meta.get_field('purchase_order') and d.purchase_order and d.purchase_order not in check_list: + if (d.meta.get_field('purchase_order') and d.purchase_order + and d.purchase_order not in check_list): check_list.append(d.purchase_order) - pc_obj.check_for_closed_status('Purchase Order', d.purchase_order) + check_for_closed_status('Purchase Order', d.purchase_order) # on submit def on_submit(self): - purchase_controller = frappe.get_doc("Purchase Common") - # Check for Approving Authority frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.base_grand_total) @@ -120,7 +119,7 @@ class PurchaseReceipt(BuyingController): self.update_billing_status() if not self.is_return: - purchase_controller.update_last_purchase_rate(self, 1) + update_last_purchase_rate(self, 1) # Updating stock ledger should always be called after updating prevdoc status, # because updating ordered qty in bin depends upon updated ordered qty in PO @@ -140,9 +139,7 @@ class PurchaseReceipt(BuyingController): frappe.throw(_("Purchase Invoice {0} is already submitted").format(self.submit_rv[0][0])) def on_cancel(self): - pc_obj = frappe.get_doc('Purchase Common') - - self.check_for_closed_status(pc_obj) + self.check_for_closed_status() # Check if Purchase Invoice has been submitted against current Purchase Order submitted = frappe.db.sql("""select t1.name from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2 @@ -157,7 +154,7 @@ class PurchaseReceipt(BuyingController): self.update_billing_status() if not self.is_return: - pc_obj.update_last_purchase_rate(self, 0) + update_last_purchase_rate(self, 0) # Updating stock ledger should always be called after updating prevdoc status, # because updating ordered qty in bin depends upon updated ordered qty in PO @@ -170,9 +167,6 @@ class PurchaseReceipt(BuyingController): bin = frappe.db.sql("select actual_qty from `tabBin` where item_code = %s and warehouse = %s", (d.rm_item_code, self.supplier_warehouse), as_dict = 1) d.current_stock = bin and flt(bin[0]['actual_qty']) or 0 - def get_rate(self,arg): - return frappe.get_doc('Purchase Common').get_rate(arg,self) - def get_gl_entries(self, warehouse_account=None): from erpnext.accounts.general_ledger import process_gl_map diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 948a6262815..82f9bf1f8b5 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -2,7 +2,7 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe +import frappe, erpnext from frappe import _ from frappe.utils import cint, flt, cstr, now from erpnext.stock.utils import get_valuation_method @@ -258,14 +258,15 @@ class update_entries_after(object): if not self.valuation_rate and actual_qty > 0: self.valuation_rate = sle.incoming_rate - + # Get valuation rate from previous SLE or Item master, if item is not a sample item if not self.valuation_rate and sle.voucher_detail_no: is_sample_item = self.check_if_sample_item(sle.voucher_type, sle.voucher_detail_no) if not is_sample_item: - self.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, - sle.voucher_type, sle.voucher_no, self.allow_zero_rate) - + self.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, + sle.voucher_type, sle.voucher_no, self.allow_zero_rate, + currency=erpnext.get_company_currency(sle.company)) + def get_fifo_values(self, sle): incoming_rate = flt(sle.incoming_rate) actual_qty = flt(sle.actual_qty) @@ -291,11 +292,12 @@ class update_entries_after(object): # Get valuation rate from last sle if exists or from valuation rate field in item master is_sample_item = self.check_if_sample_item(sle.voucher_type, sle.voucher_detail_no) if not is_sample_item: - _rate = get_valuation_rate(sle.item_code, sle.warehouse, - sle.voucher_type, sle.voucher_no, self.allow_zero_rate) + _rate = get_valuation_rate(sle.item_code, sle.warehouse, + sle.voucher_type, sle.voucher_no, self.allow_zero_rate, + currency=erpnext.get_company_currency(sle.company)) else: _rate = 0 - + self.stock_queue.append([0, _rate]) index = None @@ -341,11 +343,11 @@ class update_entries_after(object): if not self.stock_queue: self.stock_queue.append([0, sle.incoming_rate or sle.outgoing_rate or self.valuation_rate]) - + def check_if_sample_item(self, voucher_type, voucher_detail_no): ref_item_dt = voucher_type + (" Detail" if voucher_type == "Stock Entry" else " Item") return frappe.db.get_value(ref_item_dt, voucher_detail_no, "is_sample_item") - + def get_sle_before_datetime(self): """get previous stock ledger entry before current time-bucket""" return get_stock_ledger_entries(self.args, "<", "desc", "limit 1", for_update=False) @@ -419,7 +421,8 @@ def get_stock_ledger_entries(previous_sle, operator=None, order="desc", limit=No "order": order }, previous_sle, as_dict=1, debug=debug) -def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, allow_zero_rate=False): +def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, + allow_zero_rate=False, currency=None): # Get valuation rate from last sle for the same item and warehouse last_valuation_rate = frappe.db.sql("""select valuation_rate from `tabStock Ledger Entry` @@ -441,6 +444,11 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, allow_zer # syste does not found any SLE, then take valuation rate from Item valuation_rate = frappe.db.get_value("Item", item_code, "valuation_rate") + if not valuation_rate: + # try in price list + valuation_rate = frappe.db.get_value('Item Price', + dict(item_code=item_code, buying=1, currency=currency), 'price_list_rate') + if not allow_zero_rate and not valuation_rate \ and cint(frappe.db.get_value("Accounts Settings", None, "auto_accounting_for_stock")): frappe.local.message_log = []