Merge branch 'develop' of https://github.com/frappe/erpnext into quotation_blanket_order

This commit is contained in:
Deepesh Garg 2020-04-02 21:30:53 +05:30
commit 0f25a40421
24 changed files with 430 additions and 334 deletions

View File

@ -2433,8 +2433,6 @@
"Erl\u00f6se aus Verk\u00e4ufen Sachanlageverm\u00f6gen (bei Buchgewinn)": { "Erl\u00f6se aus Verk\u00e4ufen Sachanlageverm\u00f6gen (bei Buchgewinn)": {
"account_number": "4849" "account_number": "4849"
}, },
"Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn) (Gruppe)": {
"is_group": 1,
"Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn)": { "Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn)": {
"account_number": "4850" "account_number": "4850"
}, },
@ -2455,7 +2453,6 @@
}, },
"Anlagenabg\u00e4nge Finanzanlagen (inl\u00e4ndische Kap.Ges., Restbuchwert bei Buchgewinn)": { "Anlagenabg\u00e4nge Finanzanlagen (inl\u00e4ndische Kap.Ges., Restbuchwert bei Buchgewinn)": {
"account_number": "4858" "account_number": "4858"
}
}, },
"Ertr\u00e4ge aus Zuschreibungen des Sachanlageverm\u00f6gens": { "Ertr\u00e4ge aus Zuschreibungen des Sachanlageverm\u00f6gens": {
"account_number": "4910", "account_number": "4910",
@ -2578,8 +2575,6 @@
"Entnahme von Gegenst\u00e4nden ohne USt": { "Entnahme von Gegenst\u00e4nden ohne USt": {
"account_number": "4605" "account_number": "4605"
}, },
"Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt (Gruppe)": {
"is_group": 1,
"Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt": { "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt": {
"account_number": "4630" "account_number": "4630"
}, },
@ -2591,7 +2586,6 @@
}, },
"Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt (Kfz-Nutzung)": { "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt (Kfz-Nutzung)": {
"account_number": "4639" "account_number": "4639"
}
}, },
"Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 19 % USt (Gruppe)": { "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 19 % USt (Gruppe)": {
"is_group": 1, "is_group": 1,
@ -2629,14 +2623,11 @@
"Unentgeltliche Zuwendung von Gegenst\u00e4nden ohne USt": { "Unentgeltliche Zuwendung von Gegenst\u00e4nden ohne USt": {
"account_number": "4689" "account_number": "4689"
}, },
"Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze) (Gruppe)": {
"is_group": 1,
"Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze)": { "Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze)": {
"account_number": "4690" "account_number": "4690"
}, },
"Umsatzsteuerverg\u00fctungen, z.B. nach \u00a7 24 UStG": { "Umsatzsteuerverg\u00fctungen, z.B. nach \u00a7 24 UStG": {
"account_number": "4695" "account_number": "4695"
}
}, },
"Au\u00dferordentliche Ertr\u00e4ge (Gruppe)": { "Au\u00dferordentliche Ertr\u00e4ge (Gruppe)": {
"is_group": 1, "is_group": 1,
@ -2646,8 +2637,6 @@
"Au\u00dferordentliche Ertr\u00e4ge finanzwirksam": { "Au\u00dferordentliche Ertr\u00e4ge finanzwirksam": {
"account_number": "7401" "account_number": "7401"
}, },
"Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam (Gruppe)": {
"is_group": 1,
"Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam": { "Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam": {
"account_number": "7450" "account_number": "7450"
}, },
@ -2662,10 +2651,7 @@
}, },
"Gewinn aus der Ver\u00e4u\u00dferung oder der Aufgabe von Gesch\u00e4ftsaktivit\u00e4ten nach Steuern": { "Gewinn aus der Ver\u00e4u\u00dferung oder der Aufgabe von Gesch\u00e4ftsaktivit\u00e4ten nach Steuern": {
"account_number": "7454" "account_number": "7454"
}
}, },
"Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften (Gruppe)": {
"is_group": 1,
"Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften": { "Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften": {
"account_number": "7460" "account_number": "7460"
}, },
@ -2682,7 +2668,6 @@
"account_number": "7464" "account_number": "7464"
} }
} }
}
}, },
"7 - sonstige betriebliche Aufwendungen": { "7 - sonstige betriebliche Aufwendungen": {
"root_type": "Expense", "root_type": "Expense",
@ -2718,6 +2703,7 @@
}, },
"Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 16 % USt": { "Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 16 % USt": {
"account_number": "4729" "account_number": "4729"
}
}, },
"Gew\u00e4hrte Skonti (Gruppe)": { "Gew\u00e4hrte Skonti (Gruppe)": {
"is_group": 1, "is_group": 1,
@ -2752,6 +2738,8 @@
"account_number": "4748" "account_number": "4748"
} }
}, },
"Gew\u00e4hrte Boni (Gruppe)": {
"is_group": 1,
"Gew\u00e4hrte Boni 7 % USt": { "Gew\u00e4hrte Boni 7 % USt": {
"account_number": "4750" "account_number": "4750"
}, },
@ -2864,8 +2852,6 @@
"account_number": "6398" "account_number": "6398"
} }
}, },
"Versicherungen (Gruppe)": {
"is_group": 1,
"Versicherungen": { "Versicherungen": {
"account_number": "6400" "account_number": "6400"
}, },
@ -2913,54 +2899,32 @@
}, },
"Mietleasing (bewegliche Wirtschaftsg\u00fcter)": { "Mietleasing (bewegliche Wirtschaftsg\u00fcter)": {
"account_number": "6498" "account_number": "6498"
}
}, },
"Fahrzeugkosten (Gruppe)": { "Fahrzeugkosten (Gruppe)": {
"is_group": 1, "is_group": 1,
"Fahrzeugkosten": { "Fahrzeugkosten": {
"account_number": "6500" "account_number": "6500"
}, },
"Kfz-Versicherungen (Gruppe)": {
"is_group": 1,
"Kfz-Versicherungen": { "Kfz-Versicherungen": {
"account_number": "6520" "account_number": "6520"
}
}, },
"Laufende Kfz-Betriebskosten (Gruppe)": {
"is_group": 1,
"Laufende Kfz-Betriebskosten": { "Laufende Kfz-Betriebskosten": {
"account_number": "6530" "account_number": "6530"
}
}, },
"Kfz-Reparaturen (Gruppe)": {
"is_group": 1,
"Kfz-Reparaturen": { "Kfz-Reparaturen": {
"account_number": "6540" "account_number": "6540"
}
}, },
"Garagenmiete (Gruppe)": {
"is_group": 1,
"Garagenmiete": { "Garagenmiete": {
"account_number": "6550" "account_number": "6550"
}
}, },
"Mietleasing Kfz (Gruppe)": {
"is_group": 1,
"Mietleasing Kfz": { "Mietleasing Kfz": {
"account_number": "6560" "account_number": "6560"
}
}, },
"Sonstige Kfz-Kosten (Gruppe)": {
"is_group": 1,
"Sonstige Kfz-Kosten": { "Sonstige Kfz-Kosten": {
"account_number": "6570" "account_number": "6570"
}
}, },
"Mautgeb\u00fchren (Gruppe)": {
"is_group": 1,
"Mautgeb\u00fchren": { "Mautgeb\u00fchren": {
"account_number": "6580" "account_number": "6580"
}
}, },
"Kfz-Kosten f. betrieblich genutzte zum Privatverm\u00f6gen geh\u00f6rende Kraftfahrzeuge": { "Kfz-Kosten f. betrieblich genutzte zum Privatverm\u00f6gen geh\u00f6rende Kraftfahrzeuge": {
"account_number": "6590" "account_number": "6590"
@ -3022,6 +2986,8 @@
"Nicht abzugsf\u00e4hige Betriebsausgaben aus Werbe- und Repr\u00e4sentationskosten": { "Nicht abzugsf\u00e4hige Betriebsausgaben aus Werbe- und Repr\u00e4sentationskosten": {
"account_number": "6645" "account_number": "6645"
}, },
"Reisekosten Arbeitnehmer (Gruppe)": {
"is_group": 1,
"Reisekosten Arbeitnehmer": { "Reisekosten Arbeitnehmer": {
"account_number": "6650" "account_number": "6650"
}, },
@ -3036,6 +3002,7 @@
}, },
"Kilometergelderstattung Arbeitnehmer": { "Kilometergelderstattung Arbeitnehmer": {
"account_number": "6668" "account_number": "6668"
}
}, },
"Reisekosten Unternehmer (Gruppe)": { "Reisekosten Unternehmer (Gruppe)": {
"is_group": 1, "is_group": 1,

View File

@ -761,7 +761,7 @@
"depends_on": "is_fixed_asset", "depends_on": "is_fixed_asset",
"fetch_from": "item_code.asset_category", "fetch_from": "item_code.asset_category",
"fieldname": "asset_category", "fieldname": "asset_category",
"fieldtype": "Data", "fieldtype": "Link",
"label": "Asset Category", "label": "Asset Category",
"options": "Asset Category", "options": "Asset Category",
"read_only": 1 "read_only": 1
@ -777,7 +777,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-03-11 14:20:17.297284", "modified": "2020-04-01 14:20:17.297284",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice Item", "name": "Purchase Invoice Item",

View File

@ -437,13 +437,17 @@ class SalesInvoice(SellingController):
if (not for_validate) or (for_validate and not self.get(fieldname)): if (not for_validate) or (for_validate and not self.get(fieldname)):
self.set(fieldname, pos.get(fieldname)) self.set(fieldname, pos.get(fieldname))
customer_price_list = frappe.get_value("Customer", self.customer, 'default_price_list')
if pos.get("company_address"): if pos.get("company_address"):
self.company_address = pos.get("company_address") self.company_address = pos.get("company_address")
if not customer_price_list: customer_price_list, customer_group = frappe.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
self.set('selling_price_list', pos.get('selling_price_list'))
customer_group_price_list = frappe.get_value("Customer Group", customer_group, 'default_price_list')
selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list')
if selling_price_list:
self.set('selling_price_list', selling_price_list)
if not for_validate: if not for_validate:
self.update_stock = cint(pos.get("update_stock")) self.update_stock = cint(pos.get("update_stock"))

View File

@ -11,7 +11,7 @@ from frappe.utils import (add_days, getdate, formatdate, date_diff,
add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day) add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day)
from frappe.contacts.doctype.address.address import (get_address_display, from frappe.contacts.doctype.address.address import (get_address_display,
get_default_address, get_company_address) get_default_address, get_company_address)
from frappe.contacts.doctype.contact.contact import get_contact_details, get_default_contact from frappe.contacts.doctype.contact.contact import get_contact_details
from erpnext.exceptions import PartyFrozen, PartyDisabled, InvalidAccountCurrency from erpnext.exceptions import PartyFrozen, PartyDisabled, InvalidAccountCurrency
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
from erpnext import get_company_currency from erpnext import get_company_currency
@ -281,8 +281,8 @@ def validate_party_gle_currency(party_type, party, company, party_account_curren
existing_gle_currency = get_party_gle_currency(party_type, party, company) existing_gle_currency = get_party_gle_currency(party_type, party, company)
if existing_gle_currency and party_account_currency != existing_gle_currency: if existing_gle_currency and party_account_currency != existing_gle_currency:
frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}") frappe.throw(_("{0} {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.")
.format(party_type, party, existing_gle_currency), InvalidAccountCurrency) .format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)), InvalidAccountCurrency)
def validate_party_accounts(doc): def validate_party_accounts(doc):
companies = [] companies = []
@ -295,15 +295,13 @@ def validate_party_accounts(doc):
companies.append(account.company) companies.append(account.company)
party_account_currency = frappe.db.get_value("Account", account.account, "account_currency", cache=True) party_account_currency = frappe.db.get_value("Account", account.account, "account_currency", cache=True)
existing_gle_currency = get_party_gle_currency(doc.doctype, doc.name, account.company)
if frappe.db.get_default("Company"): if frappe.db.get_default("Company"):
company_default_currency = frappe.get_cached_value('Company', company_default_currency = frappe.get_cached_value('Company',
frappe.db.get_default("Company"), "default_currency") frappe.db.get_default("Company"), "default_currency")
else: else:
company_default_currency = frappe.db.get_value('Company', account.company, "default_currency") company_default_currency = frappe.db.get_value('Company', account.company, "default_currency")
if existing_gle_currency and party_account_currency != existing_gle_currency: validate_party_gle_currency(doc.doctype, doc.name, account.company, party_account_currency)
frappe.throw(_("Accounting entries have already been made in currency {0} for company {1}. Please select a receivable or payable account with currency {0}.").format(existing_gle_currency, account.company))
if doc.get("default_currency") and party_account_currency and company_default_currency: if doc.get("default_currency") and party_account_currency and company_default_currency:
if doc.default_currency != party_account_currency and doc.default_currency != company_default_currency: if doc.default_currency != party_account_currency and doc.default_currency != company_default_currency:
@ -615,3 +613,26 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None):
if data: if data:
return frappe._dict(data) return frappe._dict(data)
def get_default_contact(doctype, name):
"""
Returns default contact for the given doctype and name.
Can be ordered by `contact_type` to either is_primary_contact or is_billing_contact.
"""
out = frappe.db.sql("""
SELECT dl.parent, c.is_primary_contact, c.is_billing_contact
FROM `tabDynamic Link` dl
INNER JOIN tabContact c ON c.name = dl.parent
WHERE
dl.link_doctype=%s AND
dl.link_name=%s AND
dl.parenttype = "Contact"
ORDER BY is_primary_contact DESC, is_billing_contact DESC
""", (doctype, name))
if out:
try:
return out[0][0]
except:
return None
else:
return None

View File

@ -4,15 +4,17 @@
// attach required files // attach required files
{% include 'erpnext/public/js/controllers/buying.js' %}; {% include 'erpnext/public/js/controllers/buying.js' %};
frappe.ui.form.on('Suppier Quotation', {
setup: function(frm) {
frm.custom_make_buttons = {
'Purchase Order': 'Purchase Order'
}
}
});
erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.extend({ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.extend({
setup: function() {
this.frm.custom_make_buttons = {
'Purchase Order': 'Purchase Order',
'Quotation': 'Quotation',
'Subscription': 'Subscription'
}
this._super();
},
refresh: function() { refresh: function() {
var me = this; var me = this;
this._super(); this._super();

View File

@ -135,10 +135,17 @@ class Lead(SellingController):
# do not create an address if no fields are available, # do not create an address if no fields are available,
# skipping country since the system auto-sets it from system defaults # skipping country since the system auto-sets it from system defaults
if not any([self.get(field) for field in address_fields if field != "country"]): address = frappe.new_doc("Address")
mandatory_fields = [ df.fieldname for df in address.meta.fields if df.reqd ]
if not all([self.get(field) for field in mandatory_fields]):
frappe.msgprint(_('Missing mandatory fields in address. \
{0} to create address' ).format("<a href='desk#Form/Address/New Address 1' \
> Click here </a>"),
alert=True, indicator='yellow')
return return
address = frappe.new_doc("Address")
address.update({addr_field: self.get(addr_field) for addr_field in address_fields}) address.update({addr_field: self.get(addr_field) for addr_field in address_fields})
address.update({info_field: self.get(info_field) for info_field in info_fields}) address.update({info_field: self.get(info_field) for info_field in info_fields})
address.insert() address.insert()

View File

@ -0,0 +1,60 @@
{
"custom_fields": [
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2019-12-02 11:00:03.432994",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Contact",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "is_billing_contact",
"fieldtype": "Check",
"hidden": 0,
"idx": 27,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"insert_after": "is_primary_contact",
"label": "Is Billing Contact",
"length": 0,
"modified": "2019-12-02 11:00:03.432994",
"modified_by": "Administrator",
"name": "Contact-is_billing_contact",
"no_copy": 0,
"options": null,
"owner": "Administrator",
"parent": null,
"parentfield": null,
"parenttype": null,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"translatable": 0,
"unique": 0,
"width": null
}
],
"custom_perms": [],
"doctype": "Contact",
"property_setters": [],
"sync_on_migrate": 1
}

View File

@ -43,8 +43,7 @@ frappe.ui.form.on("BOM", {
frm.set_query("item_code", "items", function() { frm.set_query("item_code", "items", function() {
return { return {
query: "erpnext.controllers.queries.item_query", query: "erpnext.controllers.queries.item_query"
filters: [["Item", "name", "!=", cur_frm.doc.item]]
}; };
}); });
@ -135,6 +134,7 @@ frappe.ui.form.on("BOM", {
frappe.call({ frappe.call({
method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order", method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order",
args: { args: {
bom_no: frm.doc.name,
item: frm.doc.item, item: frm.doc.item,
qty: data.qty || 0.0, qty: data.qty || 0.0,
project: frm.doc.project project: frm.doc.project

View File

@ -114,10 +114,6 @@ class BOM(WebsiteGenerator):
child = self.append('operations', d) child = self.append('operations', d)
child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2) child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2)
def validate_rm_item(self, item):
if (item[0]['name'] in [it.item_code for it in self.items]) and item[0]['name'] == self.item:
frappe.throw(_("BOM #{0}: Raw material cannot be same as main Item").format(self.name))
def set_bom_material_details(self): def set_bom_material_details(self):
for item in self.get("items"): for item in self.get("items"):
self.validate_bom_currecny(item) self.validate_bom_currecny(item)
@ -147,7 +143,6 @@ class BOM(WebsiteGenerator):
args = json.loads(args) args = json.loads(args)
item = self.get_item_det(args['item_code']) item = self.get_item_det(args['item_code'])
self.validate_rm_item(item)
args['bom_no'] = args['bom_no'] or item and cstr(item[0]['default_bom']) or '' args['bom_no'] = args['bom_no'] or item and cstr(item[0]['default_bom']) or ''
args['transfer_for_manufacture'] = (cstr(args.get('include_item_in_manufacturing', '')) or args['transfer_for_manufacture'] = (cstr(args.get('include_item_in_manufacturing', '')) or

View File

@ -20,7 +20,7 @@ frappe.ui.form.on('Job Card', {
} }
} }
if (frm.doc.docstatus == 0 && frm.doc.for_quantity > frm.doc.total_completed_qty if (frm.doc.docstatus == 0 && (frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity)
&& (!frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) { && (!frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) {
frm.trigger("prepare_timer_buttons"); frm.trigger("prepare_timer_buttons");
} }
@ -59,10 +59,14 @@ frappe.ui.form.on('Job Card', {
let completed_time = frappe.datetime.now_datetime(); let completed_time = frappe.datetime.now_datetime();
frm.trigger("hide_timer"); frm.trigger("hide_timer");
if (frm.doc.for_quantity) {
frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'), frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'),
fieldname: 'qty', reqd: 1, default: frm.doc.for_quantity}, data => { fieldname: 'qty', reqd: 1, default: frm.doc.for_quantity}, data => {
frm.events.complete_job(frm, completed_time, data.qty); frm.events.complete_job(frm, completed_time, data.qty);
}, __("Enter Value"), __("Complete")); }, __("Enter Value"), __("Complete"));
} else {
frm.events.complete_job(frm, completed_time, 0);
}
}).addClass("btn-primary"); }).addClass("btn-primary");
} }
}, },

View File

@ -99,8 +99,7 @@
"fieldname": "for_quantity", "fieldname": "for_quantity",
"fieldtype": "Float", "fieldtype": "Float",
"in_list_view": 1, "in_list_view": 1,
"label": "Qty To Manufacture", "label": "Qty To Manufacture"
"reqd": 1
}, },
{ {
"fieldname": "wip_warehouse", "fieldname": "wip_warehouse",
@ -122,6 +121,7 @@
"options": "Employee" "options": "Employee"
}, },
{ {
"allow_bulk_edit": 1,
"fieldname": "time_logs", "fieldname": "time_logs",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Time Logs", "label": "Time Logs",
@ -290,7 +290,7 @@
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-12-03 13:08:57.926201", "modified": "2020-03-27 13:36:35.417502",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Job Card", "name": "Job Card",

View File

@ -191,12 +191,9 @@ class JobCard(Document):
if not self.time_logs: if not self.time_logs:
frappe.throw(_("Time logs are required for job card {0}").format(self.name)) frappe.throw(_("Time logs are required for job card {0}").format(self.name))
if self.total_completed_qty <= 0.0: if self.for_quantity and self.total_completed_qty != self.for_quantity:
frappe.throw(_("Total completed qty must be greater than zero")) frappe.throw(_("The total completed qty({0}) must be equal to qty to manufacture({1})"
.format(frappe.bold(self.total_completed_qty),frappe.bold(self.for_quantity))))
if self.total_completed_qty != self.for_quantity:
frappe.throw(_("The total completed qty({0}) must be equal to qty to manufacture({1})")
.format(frappe.bold(self.total_completed_qty),frappe.bold(self.for_quantity)))
def update_work_order(self): def update_work_order(self):
if not self.work_order: if not self.work_order:
@ -205,27 +202,34 @@ class JobCard(Document):
for_quantity, time_in_mins = 0, 0 for_quantity, time_in_mins = 0, 0
from_time_list, to_time_list = [], [] from_time_list, to_time_list = [], []
for d in frappe.get_all('Job Card',
filters = {'docstatus': 1, 'operation_id': self.operation_id}):
doc = frappe.get_doc('Job Card', d.name)
for_quantity += doc.total_completed_qty data = frappe.get_all('Job Card',
time_in_mins += doc.total_time_in_mins fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
for time_log in doc.time_logs: filters = {"docstatus": 1, "work_order": self.work_order,
if time_log.from_time: "workstation": self.workstation, "operation": self.operation})
from_time_list.append(time_log.from_time)
if time_log.to_time: if data and len(data) > 0:
to_time_list.append(time_log.to_time) for_quantity = data[0].completed_qty
time_in_mins = data[0].time_in_mins
if for_quantity: if for_quantity:
time_data = frappe.db.sql("""
SELECT
min(from_time) as start_time, max(to_time) as end_time
FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
WHERE
jctl.parent = jc.name and jc.work_order = %s
and jc.workstation = %s and jc.operation = %s and jc.docstatus = 1
""", (self.work_order, self.workstation, self.operation), as_dict=1)
wo = frappe.get_doc('Work Order', self.work_order) wo = frappe.get_doc('Work Order', self.work_order)
for data in wo.operations: for data in wo.operations:
if data.name == self.operation_id: if data.workstation == self.workstation and data.operation == self.operation:
data.completed_qty = for_quantity data.completed_qty = for_quantity
data.actual_operation_time = time_in_mins data.actual_operation_time = time_in_mins
data.actual_start_time = min(from_time_list) if from_time_list else None data.actual_start_time = time_data[0].start_time if time_data else None
data.actual_end_time = max(to_time_list) if to_time_list else None data.actual_end_time = time_data[0].end_time if time_data else None
wo.flags.ignore_validate_update_after_submit = True wo.flags.ignore_validate_update_after_submit = True
wo.update_operation_status() wo.update_operation_status()

View File

@ -144,7 +144,7 @@ class ProductionPlan(Document):
item_condition = " and mr_item.item_code ={0}".format(frappe.db.escape(self.item_code)) item_condition = " and mr_item.item_code ={0}".format(frappe.db.escape(self.item_code))
items = frappe.db.sql("""select distinct parent, name, item_code, warehouse, description, items = frappe.db.sql("""select distinct parent, name, item_code, warehouse, description,
(qty - ordered_qty) as pending_qty (qty - ordered_qty) * conversion_factor as pending_qty
from `tabMaterial Request Item` mr_item from `tabMaterial Request Item` mr_item
where parent in (%s) and docstatus = 1 and qty > ordered_qty where parent in (%s) and docstatus = 1 and qty > ordered_qty
and exists (select name from `tabBOM` bom where bom.item=mr_item.item_code and exists (select name from `tabBOM` bom where bom.item=mr_item.item_code

View File

@ -552,24 +552,33 @@ class WorkOrder(Document):
d.db_set('transferred_qty', flt(transferred_qty), update_modified = False) d.db_set('transferred_qty', flt(transferred_qty), update_modified = False)
def update_consumed_qty_for_required_items(self): def update_consumed_qty_for_required_items(self):
'''update consumed qty from submitted stock entries for that item against '''
the work order''' Update consumed qty from submitted stock entries
against a work order for each stock item
'''
for d in self.required_items: for item in self.required_items:
consumed_qty = frappe.db.sql('''select sum(qty) consumed_qty = frappe.db.sql('''
from `tabStock Entry` entry, `tabStock Entry Detail` detail SELECT
where SUM(qty)
FROM
`tabStock Entry` entry,
`tabStock Entry Detail` detail
WHERE
entry.work_order = %(name)s entry.work_order = %(name)s
and (entry.purpose = "Material Consumption for Manufacture" AND (entry.purpose = "Material Consumption for Manufacture"
or entry.purpose = "Manufacture") OR entry.purpose = "Manufacture")
and entry.docstatus = 1 AND entry.docstatus = 1
and detail.parent = entry.name AND detail.parent = entry.name
and (detail.item_code = %(item)s or detail.original_item = %(item)s)''', { AND detail.s_warehouse IS NOT null
AND (detail.item_code = %(item)s
OR detail.original_item = %(item)s)
''', {
'name': self.name, 'name': self.name,
'item': d.item_code 'item': item.item_code
})[0][0] })[0][0]
d.db_set('consumed_qty', flt(consumed_qty), update_modified = False) item.db_set('consumed_qty', flt(consumed_qty), update_modified=False)
def make_bom(self): def make_bom(self):
data = frappe.db.sql(""" select sed.item_code, sed.qty, sed.s_warehouse data = frappe.db.sql(""" select sed.item_code, sed.qty, sed.s_warehouse
@ -648,7 +657,7 @@ def get_item_details(item, project = None):
return res return res
@frappe.whitelist() @frappe.whitelist()
def make_work_order(item, qty=0, project=None): def make_work_order(bom_no, item, qty=0, project=None):
if not frappe.has_permission("Work Order", "write"): if not frappe.has_permission("Work Order", "write"):
frappe.throw(_("Not permitted"), frappe.PermissionError) frappe.throw(_("Not permitted"), frappe.PermissionError)
@ -657,6 +666,7 @@ def make_work_order(item, qty=0, project=None):
wo_doc = frappe.new_doc("Work Order") wo_doc = frappe.new_doc("Work Order")
wo_doc.production_item = item wo_doc.production_item = item
wo_doc.update(item_details) wo_doc.update(item_details)
wo_doc.bom_no = bom_no
if flt(qty) > 0: if flt(qty) > 0:
wo_doc.qty = flt(qty) wo_doc.qty = flt(qty)

View File

@ -362,12 +362,17 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
['serial_no', 'batch_no', 'barcode'].forEach(field => { ['serial_no', 'batch_no', 'barcode'].forEach(field => {
if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) { if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) {
let value = (row_to_modify[field] && field === "serial_no")
? row_to_modify[field] + '\n' + data[field] : data[field];
frappe.model.set_value(row_to_modify.doctype, frappe.model.set_value(row_to_modify.doctype,
row_to_modify.name, field, data[field]); row_to_modify.name, field, value);
} }
}); });
scan_barcode_field.set_value(''); scan_barcode_field.set_value('');
refresh_field("items");
}); });
} }
return false; return false;

View File

@ -8,12 +8,19 @@ frappe.ui.form.ItemQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({
render_dialog: function() { render_dialog: function() {
this.mandatory = this.get_variant_fields().concat(this.mandatory); this.mandatory = this.get_variant_fields().concat(this.mandatory);
this.mandatory = this.mandatory.concat(this.get_attributes_fields()); this.mandatory = this.mandatory.concat(this.get_attributes_fields());
this.check_naming_series_based_on();
this._super(); this._super();
this.init_post_render_dialog_operations(); this.init_post_render_dialog_operations();
this.preset_fields_for_template(); this.preset_fields_for_template();
this.dialog.$wrapper.find('.edit-full').text(__('Edit in full page for more options like assets, serial nos, batches etc.')) this.dialog.$wrapper.find('.edit-full').text(__('Edit in full page for more options like assets, serial nos, batches etc.'))
}, },
check_naming_series_based_on: function() {
if (frappe.defaults.get_default("item_naming_by") === "Naming Series") {
this.mandatory = this.mandatory.filter(d => d.fieldname !== "item_code");
}
},
init_post_render_dialog_operations: function() { init_post_render_dialog_operations: function() {
this.dialog.fields_dict.attribute_html.$wrapper.append(frappe.render_template("item_quick_entry")); this.dialog.fields_dict.attribute_html.$wrapper.append(frappe.render_template("item_quick_entry"));
this.init_for_create_variant_trigger(); this.init_for_create_variant_trigger();

View File

@ -496,7 +496,7 @@ def close_or_unclose_sales_orders(names, status):
def get_requested_item_qty(sales_order): def get_requested_item_qty(sales_order):
return frappe._dict(frappe.db.sql(""" return frappe._dict(frappe.db.sql("""
select sales_order_item, sum(stock_qty) select sales_order_item, sum(qty)
from `tabMaterial Request Item` from `tabMaterial Request Item`
where docstatus = 1 where docstatus = 1
and sales_order = %s and sales_order = %s
@ -507,16 +507,12 @@ def get_requested_item_qty(sales_order):
def make_material_request(source_name, target_doc=None): def make_material_request(source_name, target_doc=None):
requested_item_qty = get_requested_item_qty(source_name) requested_item_qty = get_requested_item_qty(source_name)
def postprocess(source, doc):
doc.material_request_type = "Purchase"
def update_item(source, target, source_parent): def update_item(source, target, source_parent):
# qty is for packed items, because packed items don't have stock_qty field # qty is for packed items, because packed items don't have stock_qty field
qty = source.get("stock_qty") or source.get("qty") qty = source.get("qty")
target.project = source_parent.project target.project = source_parent.project
target.qty = qty - requested_item_qty.get(source.name, 0) target.qty = qty - requested_item_qty.get(source.name, 0)
target.conversion_factor = 1 target.stock_qty = flt(target.qty) * flt(target.conversion_factor)
target.stock_qty = qty - requested_item_qty.get(source.name, 0)
doc = get_mapped_doc("Sales Order", source_name, { doc = get_mapped_doc("Sales Order", source_name, {
"Sales Order": { "Sales Order": {
@ -537,14 +533,12 @@ def make_material_request(source_name, target_doc=None):
"doctype": "Material Request Item", "doctype": "Material Request Item",
"field_map": { "field_map": {
"name": "sales_order_item", "name": "sales_order_item",
"parent": "sales_order", "parent": "sales_order"
"stock_uom": "uom",
"stock_qty": "qty"
}, },
"condition": lambda doc: not frappe.db.exists('Product Bundle', doc.item_code) and doc.stock_qty > requested_item_qty.get(doc.name, 0), "condition": lambda doc: not frappe.db.exists('Product Bundle', doc.item_code) and doc.stock_qty > requested_item_qty.get(doc.name, 0),
"postprocess": update_item "postprocess": update_item
} }
}, target_doc, postprocess) }, target_doc)
return doc return doc

View File

@ -64,29 +64,39 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
for d in item_prices_data: for d in item_prices_data:
item_prices[d.item_code] = d item_prices[d.item_code] = d
# prepare filter for bin query
if display_items_in_stock: bin_filters = {'item_code': ['in', items]}
filters = {'actual_qty': [">", 0], 'item_code': ['in', items]}
if warehouse: if warehouse:
filters['warehouse'] = warehouse bin_filters['warehouse'] = warehouse
if display_items_in_stock:
bin_filters['actual_qty'] = [">", 0]
bin_data = frappe._dict( # query item bin
frappe.get_all("Bin", fields = ["item_code", "sum(actual_qty) as actual_qty"], bin_data = frappe.get_all(
filters = filters, group_by = "item_code") 'Bin', fields=['item_code', 'sum(actual_qty) as actual_qty'],
filters=bin_filters, group_by='item_code'
) )
for item in items_data: # convert list of dict into dict as {item_code: actual_qty}
row = {} bin_dict = {}
for b in bin_data:
bin_dict[b.get('item_code')] = b.get('actual_qty')
for item in items_data:
item_code = item.item_code
item_price = item_prices.get(item_code) or {}
item_stock_qty = bin_dict.get(item_code)
if display_items_in_stock and not item_stock_qty:
pass
else:
row = {}
row.update(item) row.update(item)
item_price = item_prices.get(item.item_code) or {}
row.update({ row.update({
'price_list_rate': item_price.get('price_list_rate'), 'price_list_rate': item_price.get('price_list_rate'),
'currency': item_price.get('currency'), 'currency': item_price.get('currency'),
'actual_qty': bin_data.get('actual_qty') 'actual_qty': item_stock_qty,
}) })
result.append(row) result.append(row)
res = { res = {

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import getdate, cint from frappe.utils import getdate, cint, cstr
import calendar import calendar
def execute(filters=None): def execute(filters=None):
@ -48,7 +48,7 @@ def execute(filters=None):
new = new_customers_in.get(key, [0,0.0]) new = new_customers_in.get(key, [0,0.0])
repeat = repeat_customers_in.get(key, [0,0.0]) repeat = repeat_customers_in.get(key, [0,0.0])
out.append([year, calendar.month_name[month], out.append([cstr(year), calendar.month_name[month],
new[0], repeat[0], new[0] + repeat[0], new[0], repeat[0], new[0] + repeat[0],
new[1], repeat[1], new[1] + repeat[1]]) new[1], repeat[1], new[1] + repeat[1]])

View File

@ -13,12 +13,16 @@ install_docs = [
] ]
def get_warehouse_account_map(company=None): def get_warehouse_account_map(company=None):
if not frappe.flags.warehouse_account_map or frappe.flags.in_test: company_warehouse_account_map = company and frappe.flags.setdefault('warehouse_account_map', {}).get(company)
warehouse_account_map = frappe.flags.warehouse_account_map
if not warehouse_account_map or not company_warehouse_account_map or frappe.flags.in_test:
warehouse_account = frappe._dict() warehouse_account = frappe._dict()
filters = {} filters = {}
if company: if company:
filters['company'] = company filters['company'] = company
frappe.flags.setdefault('warehouse_account_map', {}).setdefault(company, {})
for d in frappe.get_all('Warehouse', for d in frappe.get_all('Warehouse',
fields = ["name", "account", "parent_warehouse", "company", "is_group"], fields = ["name", "account", "parent_warehouse", "company", "is_group"],
@ -30,10 +34,12 @@ def get_warehouse_account_map(company=None):
if d.account: if d.account:
d.account_currency = frappe.db.get_value('Account', d.account, 'account_currency', cache=True) d.account_currency = frappe.db.get_value('Account', d.account, 'account_currency', cache=True)
warehouse_account.setdefault(d.name, d) warehouse_account.setdefault(d.name, d)
if company:
frappe.flags.warehouse_account_map[company] = warehouse_account
else:
frappe.flags.warehouse_account_map = warehouse_account frappe.flags.warehouse_account_map = warehouse_account
return frappe.flags.warehouse_account_map return frappe.flags.warehouse_account_map.get(company) or frappe.flags.warehouse_account_map
def get_warehouse_account(warehouse, warehouse_account=None): def get_warehouse_account(warehouse, warehouse_account=None):
account = warehouse.account account = warehouse.account

View File

@ -501,7 +501,7 @@ def raise_work_orders(material_request):
wo_order = frappe.new_doc("Work Order") wo_order = frappe.new_doc("Work Order")
wo_order.update({ wo_order.update({
"production_item": d.item_code, "production_item": d.item_code,
"qty": d.qty - d.ordered_qty, "qty": d.stock_qty - d.ordered_qty,
"fg_warehouse": d.warehouse, "fg_warehouse": d.warehouse,
"wip_warehouse": default_wip_warehouse, "wip_warehouse": default_wip_warehouse,
"description": d.description, "description": d.description,

View File

@ -310,14 +310,14 @@ frappe.ui.form.on('Stock Entry', {
method: "erpnext.stock.get_item_details.get_serial_no", method: "erpnext.stock.get_item_details.get_serial_no",
args: {"args": args}, args: {"args": args},
callback: function(r) { callback: function(r) {
if (!r.exe){ if (!r.exe && r.message){
frappe.model.set_value(cdt, cdn, "serial_no", r.message); frappe.model.set_value(cdt, cdn, "serial_no", r.message);
}
if (callback) { if (callback) {
callback(); callback();
} }
} }
}
}); });
}, },
@ -623,12 +623,17 @@ frappe.ui.form.on('Stock Entry Detail', {
if(r.message) { if(r.message) {
var d = locals[cdt][cdn]; var d = locals[cdt][cdn];
$.each(r.message, function(k, v) { $.each(r.message, function(k, v) {
if (v) {
frappe.model.set_value(cdt, cdn, k, v); // qty and it's subsequent fields weren't triggered frappe.model.set_value(cdt, cdn, k, v); // qty and it's subsequent fields weren't triggered
}
}); });
refresh_field("items"); refresh_field("items");
if (!d.serial_no) {
erpnext.stock.select_batch_and_serial_no(frm, d); erpnext.stock.select_batch_and_serial_no(frm, d);
} }
} }
}
}); });
} }
}, },

View File

@ -238,7 +238,7 @@ class StockEntry(StockController):
if self.purpose == "Manufacture" and self.work_order: if self.purpose == "Manufacture" and self.work_order:
production_item = frappe.get_value('Work Order', self.work_order, 'production_item') production_item = frappe.get_value('Work Order', self.work_order, 'production_item')
for item in self.items: for item in self.items:
if item.item_code == production_item and item.qty != self.fg_completed_qty: if item.item_code == production_item and item.t_warehouse and item.qty != self.fg_completed_qty:
frappe.throw(_("Finished product quantity <b>{0}</b> and For Quantity <b>{1}</b> cannot be different") frappe.throw(_("Finished product quantity <b>{0}</b> and For Quantity <b>{1}</b> cannot be different")
.format(item.qty, self.fg_completed_qty)) .format(item.qty, self.fg_completed_qty))
@ -298,13 +298,8 @@ class StockEntry(StockController):
if validate_for_manufacture: if validate_for_manufacture:
if d.bom_no: if d.bom_no:
d.s_warehouse = None d.s_warehouse = None
if not d.t_warehouse: if not d.t_warehouse:
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
elif self.pro_doc and (cstr(d.t_warehouse) != self.pro_doc.fg_warehouse and cstr(d.t_warehouse) != self.pro_doc.scrap_warehouse):
frappe.throw(_("Target warehouse in row {0} must be same as Work Order").format(d.idx))
else: else:
d.t_warehouse = None d.t_warehouse = None
if not d.s_warehouse: if not d.s_warehouse:

View File

@ -428,7 +428,7 @@ class update_entries_after(object):
frappe.get_desk_link(self.exceptions[0]["voucher_type"], self.exceptions[0]["voucher_no"])) frappe.get_desk_link(self.exceptions[0]["voucher_type"], self.exceptions[0]["voucher_no"]))
if self.verbose: if self.verbose:
frappe.throw(msg, NegativeStockError, title='Insufficent Stock') frappe.throw(msg, NegativeStockError, title='Insufficient Stock')
else: else:
raise NegativeStockError(msg) raise NegativeStockError(msg)