mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-19 19:37:56 +00:00
Merge branch 'develop' into fix-stock-entry-feed
This commit is contained in:
@@ -101,6 +101,30 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
|
||||
refresh: function(doc, dt, dn) {
|
||||
var me = this;
|
||||
this._super();
|
||||
|
||||
if ((!doc.is_return) && (doc.status!="Closed" || doc.is_new())) {
|
||||
if (this.frm.doc.docstatus===0) {
|
||||
this.frm.add_custom_button(__('Sales Order'),
|
||||
function() {
|
||||
erpnext.utils.map_current_doc({
|
||||
method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
|
||||
source_doctype: "Sales Order",
|
||||
target: me.frm,
|
||||
setters: {
|
||||
customer: me.frm.doc.customer || undefined,
|
||||
},
|
||||
get_query_filters: {
|
||||
docstatus: 1,
|
||||
status: ["not in", ["Closed", "On Hold"]],
|
||||
per_delivered: ["<", 99.99],
|
||||
company: me.frm.doc.company,
|
||||
project: me.frm.doc.project || undefined,
|
||||
}
|
||||
})
|
||||
}, __("Get items from"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!doc.is_return && doc.status!="Closed") {
|
||||
if(flt(doc.per_installed, 2) < 100 && doc.docstatus==1)
|
||||
this.frm.add_custom_button(__('Installation Note'), function() {
|
||||
@@ -127,27 +151,6 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
|
||||
if (!doc.__islocal && doc.docstatus==1) {
|
||||
this.frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||
}
|
||||
|
||||
if (this.frm.doc.docstatus===0) {
|
||||
this.frm.add_custom_button(__('Sales Order'),
|
||||
function() {
|
||||
erpnext.utils.map_current_doc({
|
||||
method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
|
||||
source_doctype: "Sales Order",
|
||||
target: me.frm,
|
||||
setters: {
|
||||
customer: me.frm.doc.customer || undefined,
|
||||
},
|
||||
get_query_filters: {
|
||||
docstatus: 1,
|
||||
status: ["!=", "Closed"],
|
||||
per_delivered: ["<", 99.99],
|
||||
company: me.frm.doc.company,
|
||||
project: me.frm.doc.project || undefined,
|
||||
}
|
||||
})
|
||||
}, __("Get items from"));
|
||||
}
|
||||
}
|
||||
|
||||
if (doc.docstatus==1) {
|
||||
|
||||
@@ -103,7 +103,7 @@ class DeliveryNote(SellingController):
|
||||
self.set_status()
|
||||
self.so_required()
|
||||
self.validate_proj_cust()
|
||||
self.check_close_sales_order("against_sales_order")
|
||||
self.check_sales_order_on_hold_or_close("against_sales_order")
|
||||
self.validate_for_items()
|
||||
self.validate_warehouse()
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
@@ -225,7 +225,7 @@ class DeliveryNote(SellingController):
|
||||
def on_cancel(self):
|
||||
super(DeliveryNote, self).on_cancel()
|
||||
|
||||
self.check_close_sales_order("against_sales_order")
|
||||
self.check_sales_order_on_hold_or_close("against_sales_order")
|
||||
self.check_next_docstatus()
|
||||
|
||||
self.update_prevdoc_status()
|
||||
@@ -396,19 +396,7 @@ def get_invoiced_qty_map(delivery_note):
|
||||
|
||||
return invoiced_qty_map
|
||||
|
||||
def get_returned_qty_map_against_so(sales_orders):
|
||||
"""returns a map: {so_detail: returned_qty}"""
|
||||
returned_qty_map = {}
|
||||
|
||||
for name, returned_qty in frappe.get_all('Sales Order Item', fields = ["name", "returned_qty"],
|
||||
filters = {'parent': ('in', sales_orders), 'docstatus': 1}, as_list=1):
|
||||
if not returned_qty_map.get(name):
|
||||
returned_qty_map[name] = 0
|
||||
returned_qty_map[name] += returned_qty
|
||||
|
||||
return returned_qty_map
|
||||
|
||||
def get_returned_qty_map_against_dn(delivery_note):
|
||||
def get_returned_qty_map(delivery_note):
|
||||
"""returns a map: {so_detail: returned_qty}"""
|
||||
returned_qty_map = frappe._dict(frappe.db.sql("""select dn_item.item_code, sum(abs(dn_item.qty)) as qty
|
||||
from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn
|
||||
@@ -425,8 +413,7 @@ def get_returned_qty_map_against_dn(delivery_note):
|
||||
def make_sales_invoice(source_name, target_doc=None):
|
||||
doc = frappe.get_doc('Delivery Note', source_name)
|
||||
sales_orders = [d.against_sales_order for d in doc.items]
|
||||
returned_qty_map_against_so = get_returned_qty_map_against_so(sales_orders)
|
||||
returned_qty_map_against_dn = get_returned_qty_map_against_dn(source_name)
|
||||
returned_qty_map = get_returned_qty_map(source_name)
|
||||
invoiced_qty_map = get_invoiced_qty_map(source_name)
|
||||
|
||||
def set_missing_values(source, target):
|
||||
@@ -447,17 +434,16 @@ def make_sales_invoice(source_name, target_doc=None):
|
||||
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.qty, returned_qty = get_pending_qty(source_doc)
|
||||
if not source_doc.so_detail:
|
||||
returned_qty_map_against_dn[source_doc.item_code] = returned_qty
|
||||
returned_qty_map[source_doc.item_code] = returned_qty
|
||||
|
||||
if source_doc.serial_no and source_parent.per_billed > 0:
|
||||
target_doc.serial_no = get_delivery_note_serial_no(source_doc.item_code,
|
||||
target_doc.qty, source_parent.name)
|
||||
|
||||
def get_pending_qty(item_row):
|
||||
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) - returned_qty_map_against_so.get(item_row.so_detail, 0)
|
||||
returned_qty = flt(returned_qty_map_against_dn.get(item_row.item_code, 0))
|
||||
if not item_row.so_detail:
|
||||
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
|
||||
returned_qty = flt(returned_qty_map.get(item_row.item_code, 0))
|
||||
if returned_qty:
|
||||
if returned_qty >= pending_qty:
|
||||
pending_qty = 0
|
||||
returned_qty -= pending_qty
|
||||
|
||||
@@ -655,7 +655,7 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
si = make_sales_invoice(dn.name)
|
||||
self.assertEquals(si.items[0].qty, 1)
|
||||
|
||||
def test_make_sales_invoice_from_dn_with_returned_qty_against_dn(self):
|
||||
def test_make_sales_invoice_from_dn_with_returned_qty_duplicate_items(self):
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
||||
|
||||
dn = create_delivery_note(qty=8, do_not_submit=True)
|
||||
|
||||
@@ -1781,7 +1781,7 @@
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 1,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
@@ -4272,7 +4272,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 1,
|
||||
"modified": "2019-03-08 11:47:59.269724",
|
||||
"modified": "2019-04-08 11:47:59.269724",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
|
||||
@@ -49,9 +49,6 @@ class Item(WebsiteGenerator):
|
||||
|
||||
self.set_onload('stock_exists', self.stock_ledger_created())
|
||||
self.set_asset_naming_series()
|
||||
if self.is_fixed_asset:
|
||||
asset = self.asset_exists()
|
||||
self.set_onload("asset_exists", True if asset else False)
|
||||
|
||||
def set_asset_naming_series(self):
|
||||
if not hasattr(self, '_asset_naming_series'):
|
||||
@@ -93,8 +90,6 @@ class Item(WebsiteGenerator):
|
||||
self.set_opening_stock()
|
||||
|
||||
def validate(self):
|
||||
self.get_doc_before_save()
|
||||
|
||||
super(Item, self).validate()
|
||||
|
||||
if not self.item_name:
|
||||
@@ -120,6 +115,7 @@ class Item(WebsiteGenerator):
|
||||
self.validate_stock_exists_for_template_item()
|
||||
self.validate_attributes()
|
||||
self.validate_variant_attributes()
|
||||
self.validate_variant_based_on_change()
|
||||
self.validate_website_image()
|
||||
self.make_thumbnail()
|
||||
self.validate_fixed_asset()
|
||||
@@ -127,6 +123,7 @@ class Item(WebsiteGenerator):
|
||||
self.validate_uom_conversion_factor()
|
||||
self.validate_item_defaults()
|
||||
self.validate_customer_provided_part()
|
||||
self.update_defaults_from_item_group()
|
||||
self.validate_stock_for_has_batch_and_has_serial()
|
||||
|
||||
if not self.get("__islocal"):
|
||||
@@ -759,11 +756,10 @@ class Item(WebsiteGenerator):
|
||||
frappe.throw(
|
||||
_('Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item'))
|
||||
|
||||
def asset_exists(self):
|
||||
if not hasattr(self, '_asset_created'):
|
||||
self._asset_created = frappe.db.get_all("Asset",
|
||||
filters={"item_code": self.name, "docstatus": 1}, limit=1)
|
||||
return self._asset_created
|
||||
def validate_variant_based_on_change(self):
|
||||
if not self.is_new() and (self.variant_of or (self.has_variants and frappe.get_all("Item", {"variant_of": self.name}))):
|
||||
if self.variant_based_on != frappe.db.get_value("Item", self.name, "variant_based_on"):
|
||||
frappe.throw(_("Variant Based On cannot be changed"))
|
||||
|
||||
def validate_uom(self):
|
||||
if not self.get("__islocal"):
|
||||
@@ -785,10 +781,13 @@ class Item(WebsiteGenerator):
|
||||
d.conversion_factor = value
|
||||
|
||||
def validate_attributes(self):
|
||||
if not (self.has_variants or self.variant_of):
|
||||
return
|
||||
|
||||
if not self.variant_based_on:
|
||||
self.variant_based_on = 'Item Attribute'
|
||||
|
||||
if (self.has_variants or self.variant_of) and self.variant_based_on == 'Item Attribute':
|
||||
if self.variant_based_on == 'Item Attribute':
|
||||
attributes = []
|
||||
if not self.attributes:
|
||||
frappe.throw(_("Attribute table is mandatory"))
|
||||
@@ -800,7 +799,7 @@ class Item(WebsiteGenerator):
|
||||
attributes.append(d.attribute)
|
||||
|
||||
def validate_variant_attributes(self):
|
||||
if self.variant_of and self.variant_based_on == 'Item Attribute':
|
||||
if self.is_new() and self.variant_of and self.variant_based_on == 'Item Attribute':
|
||||
args = {}
|
||||
for d in self.attributes:
|
||||
if cstr(d.attribute_value).strip() == '':
|
||||
@@ -1060,4 +1059,8 @@ def update_variants(variants, template, publish_progress=True):
|
||||
variant.save()
|
||||
count+=1
|
||||
if publish_progress:
|
||||
frappe.publish_progress(count*100/len(variants), title = _("Updating Variants..."))
|
||||
frappe.publish_progress(count*100/len(variants), title = _("Updating Variants..."))
|
||||
|
||||
def on_doctype_update():
|
||||
# since route is a Text column, it needs a length for indexing
|
||||
frappe.db.add_index("Item", ["route(500)"])
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"item_attribute_values": [
|
||||
{"attribute_value": "Small", "abbr": "S"},
|
||||
{"attribute_value": "Medium", "abbr": "M"},
|
||||
{"attribute_value": "Large", "abbr": "L"}
|
||||
{"attribute_value": "Large", "abbr": "L"},
|
||||
{"attribute_value": "Extra Small", "abbr": "XSL"}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -128,7 +128,7 @@ frappe.ui.form.on('Material Request', {
|
||||
},
|
||||
get_query_filters: {
|
||||
docstatus: 1,
|
||||
status: ["!=", "Closed"],
|
||||
status: ["not in", ["Closed", "On Hold"]],
|
||||
per_delivered: ["<", 99.99],
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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.work_order.work_order import get_item_details
|
||||
from erpnext.buying.utils import check_for_closed_status, validate_for_items
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items
|
||||
from erpnext.stock.doctype.item.item import get_item_defaults
|
||||
|
||||
from six import string_types
|
||||
@@ -62,6 +62,7 @@ class MaterialRequest(BuyingController):
|
||||
super(MaterialRequest, self).validate()
|
||||
|
||||
self.validate_schedule_date()
|
||||
self.check_for_on_hold_or_closed_status('Sales Order', 'sales_order')
|
||||
self.validate_uom_is_integer("uom", "qty")
|
||||
|
||||
if not self.status:
|
||||
@@ -100,7 +101,8 @@ class MaterialRequest(BuyingController):
|
||||
|
||||
def before_cancel(self):
|
||||
# if MRQ is already closed, no point saving the document
|
||||
check_for_closed_status(self.doctype, self.name)
|
||||
check_on_hold_or_closed_status(self.doctype, self.name)
|
||||
|
||||
self.set_status(update=True, status='Cancelled')
|
||||
|
||||
def check_modified_date(self):
|
||||
|
||||
@@ -88,7 +88,7 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
|
||||
},
|
||||
get_query_filters: {
|
||||
docstatus: 1,
|
||||
status: ["!=", "Closed"],
|
||||
status: ["not in", ["Closed", "On Hold"]],
|
||||
per_received: ["<", 99.99],
|
||||
company: me.frm.doc.company
|
||||
}
|
||||
|
||||
@@ -12,7 +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
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_disabled
|
||||
from six import iteritems
|
||||
|
||||
@@ -62,7 +62,7 @@ class PurchaseReceipt(BuyingController):
|
||||
self.validate_uom_is_integer("uom", ["qty", "received_qty"])
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
|
||||
self.check_for_closed_status()
|
||||
self.check_on_hold_or_closed_status()
|
||||
|
||||
if getdate(self.posting_date) > getdate(nowdate()):
|
||||
throw(_("Posting Date cannot be future date"))
|
||||
@@ -103,13 +103,13 @@ class PurchaseReceipt(BuyingController):
|
||||
return po_qty, po_warehouse
|
||||
|
||||
# Check for Closed status
|
||||
def check_for_closed_status(self):
|
||||
def check_on_hold_or_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):
|
||||
check_list.append(d.purchase_order)
|
||||
check_for_closed_status('Purchase Order', d.purchase_order)
|
||||
check_on_hold_or_closed_status('Purchase Order', d.purchase_order)
|
||||
|
||||
# on submit
|
||||
def on_submit(self):
|
||||
@@ -147,7 +147,7 @@ class PurchaseReceipt(BuyingController):
|
||||
def on_cancel(self):
|
||||
super(PurchaseReceipt, self).on_cancel()
|
||||
|
||||
self.check_for_closed_status()
|
||||
self.check_on_hold_or_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
|
||||
@@ -407,10 +407,7 @@ def update_billed_amount_based_on_po(po_detail, update_modified=True):
|
||||
def make_purchase_invoice(source_name, target_doc=None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
doc = frappe.get_doc('Purchase Receipt', source_name)
|
||||
purchase_orders = [d.purchase_order for d in doc.items]
|
||||
returned_qty_map_against_po = get_returned_qty_map_against_po(purchase_orders)
|
||||
returned_qty_map_against_pr = get_returned_qty_map_against_pr(source_name)
|
||||
|
||||
returned_qty_map = get_returned_qty_map(source_name)
|
||||
invoiced_qty_map = get_invoiced_qty_map(source_name)
|
||||
|
||||
def set_missing_values(source, target):
|
||||
@@ -424,14 +421,12 @@ def make_purchase_invoice(source_name, target_doc=None):
|
||||
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.qty, returned_qty = get_pending_qty(source_doc)
|
||||
if not source_doc.purchase_order_item:
|
||||
returned_qty_map_against_pr[source_doc.item_code] = returned_qty
|
||||
returned_qty_map[source_doc.item_code] = returned_qty
|
||||
|
||||
def get_pending_qty(item_row):
|
||||
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0) \
|
||||
- returned_qty_map_against_po.get(item_row.purchase_order_item, 0)
|
||||
returned_qty = flt(returned_qty_map_against_pr.get(item_row.item_code, 0))
|
||||
if not item_row.purchase_order_item:
|
||||
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
|
||||
returned_qty = flt(returned_qty_map.get(item_row.item_code, 0))
|
||||
if returned_qty:
|
||||
if returned_qty >= pending_qty:
|
||||
pending_qty = 0
|
||||
returned_qty -= pending_qty
|
||||
@@ -484,19 +479,7 @@ def get_invoiced_qty_map(purchase_receipt):
|
||||
|
||||
return invoiced_qty_map
|
||||
|
||||
def get_returned_qty_map_against_po(purchase_orders):
|
||||
"""returns a map: {so_detail: returned_qty}"""
|
||||
returned_qty_map = {}
|
||||
|
||||
for name, returned_qty in frappe.get_all('Purchase Order Item', fields = ["name", "returned_qty"],
|
||||
filters = {'parent': ('in', purchase_orders), 'docstatus': 1}, as_list=1):
|
||||
if not returned_qty_map.get(name):
|
||||
returned_qty_map[name] = 0
|
||||
returned_qty_map[name] += returned_qty
|
||||
|
||||
return returned_qty_map
|
||||
|
||||
def get_returned_qty_map_against_pr(purchase_receipt):
|
||||
def get_returned_qty_map(purchase_receipt):
|
||||
"""returns a map: {so_detail: returned_qty}"""
|
||||
returned_qty_map = frappe._dict(frappe.db.sql("""select pr_item.item_code, sum(abs(pr_item.qty)) as qty
|
||||
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
|
||||
|
||||
@@ -418,7 +418,7 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
pi = make_purchase_invoice(pr.name)
|
||||
self.assertEquals(pi.items[0].qty, 3)
|
||||
|
||||
def test_make_purchase_invoice_from_dn_with_returned_qty_against_dn(self):
|
||||
def test_make_purchase_invoice_from_pr_with_returned_qty_duplicate_items(self):
|
||||
pr1 = make_purchase_receipt(qty=8, do_not_submit=True)
|
||||
pr1.append("items", {
|
||||
"item_code": "_Test Item",
|
||||
|
||||
@@ -459,3 +459,13 @@ def get_delivery_note_serial_no(item_code, qty, delivery_note):
|
||||
serial_nos = '\n'.join(dn_serial_nos)
|
||||
|
||||
return serial_nos
|
||||
|
||||
@frappe.whitelist()
|
||||
def auto_fetch_serial_number(qty, item_code, warehouse):
|
||||
serial_numbers = frappe.get_list("Serial No", filters={
|
||||
"item_code": item_code,
|
||||
"warehouse": warehouse,
|
||||
"delivery_document_no": "",
|
||||
"sales_invoice": ""
|
||||
}, limit=qty, order_by="creation")
|
||||
return [item['name'] for item in serial_numbers]
|
||||
|
||||
@@ -154,10 +154,10 @@ def get_opening_balance(filters, columns):
|
||||
"posting_date": filters.from_date,
|
||||
"posting_time": "00:00:00"
|
||||
})
|
||||
row = [""]*len(columns)
|
||||
row[1] = _("'Opening'")
|
||||
for i, v in ((9, 'qty_after_transaction'), (11, 'valuation_rate'), (12, 'stock_value')):
|
||||
row[i] = last_entry.get(v, 0)
|
||||
row = {}
|
||||
row["item_code"] = _("'Opening'")
|
||||
for dummy, v in ((9, 'qty_after_transaction'), (11, 'valuation_rate'), (12, 'stock_value')):
|
||||
row[v] = last_entry.get(v, 0)
|
||||
|
||||
return row
|
||||
|
||||
|
||||
Reference in New Issue
Block a user