Merge branch 'develop' into refactor-call-popup
This commit is contained in:
commit
0505135f90
@ -141,6 +141,11 @@ def get_data():
|
||||
"name": "Campaign",
|
||||
"description": _("Sales campaigns."),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Email Campaign",
|
||||
"description": _("Sends Mails to lead or contact based on a Campaign schedule"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "SMS Center",
|
||||
|
@ -40,7 +40,6 @@ status_map = {
|
||||
["To Bill", "eval:self.per_delivered == 100 and self.per_billed < 100 and self.docstatus == 1"],
|
||||
["To Deliver", "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1"],
|
||||
["Completed", "eval:self.per_delivered == 100 and self.per_billed == 100 and self.docstatus == 1"],
|
||||
["Completed", "eval:self.order_type == 'Maintenance' and self.per_billed == 100 and self.docstatus == 1"],
|
||||
["Cancelled", "eval:self.docstatus==2"],
|
||||
["Closed", "eval:self.status=='Closed'"],
|
||||
["On Hold", "eval:self.status=='On Hold'"],
|
||||
|
@ -0,0 +1,38 @@
|
||||
{
|
||||
"creation": "2019-06-30 15:56:20.306901",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"email_template",
|
||||
"send_after_days"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "send_after_days",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Send After (days)",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "email_template",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Email Template",
|
||||
"options": "Email Template",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-07-12 11:46:43.184123",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Campaign Email Schedule",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class CampaignEmailSchedule(Document):
|
||||
pass
|
0
erpnext/crm/doctype/email_campaign/__init__.py
Normal file
0
erpnext/crm/doctype/email_campaign/__init__.py
Normal file
8
erpnext/crm/doctype/email_campaign/email_campaign.js
Normal file
8
erpnext/crm/doctype/email_campaign/email_campaign.js
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Email Campaign', {
|
||||
email_campaign_for: function(frm) {
|
||||
frm.set_value('recipient', '');
|
||||
}
|
||||
});
|
95
erpnext/crm/doctype/email_campaign/email_campaign.json
Normal file
95
erpnext/crm/doctype/email_campaign/email_campaign.json
Normal file
@ -0,0 +1,95 @@
|
||||
{
|
||||
"autoname": "format:MAIL-CAMP-{YYYY}-{#####}",
|
||||
"creation": "2019-06-30 16:05:30.015615",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"campaign_name",
|
||||
"email_campaign_for",
|
||||
"recipient",
|
||||
"sender",
|
||||
"column_break_4",
|
||||
"start_date",
|
||||
"end_date",
|
||||
"status"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "campaign_name",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Campaign",
|
||||
"options": "Campaign",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Status",
|
||||
"options": "\nScheduled\nIn Progress\nCompleted\nUnsubscribed",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Start Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "end_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "End Date",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "Lead",
|
||||
"fieldname": "email_campaign_for",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Email Campaign For ",
|
||||
"options": "\nLead\nContact"
|
||||
},
|
||||
{
|
||||
"fieldname": "recipient",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Recipient",
|
||||
"options": "email_campaign_for",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "__user",
|
||||
"fieldname": "sender",
|
||||
"fieldtype": "Link",
|
||||
"label": "Sender",
|
||||
"options": "User"
|
||||
}
|
||||
],
|
||||
"modified": "2019-07-12 13:47:37.261213",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Email Campaign",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
102
erpnext/crm/doctype/email_campaign/email_campaign.py
Normal file
102
erpnext/crm/doctype/email_campaign/email_campaign.py
Normal file
@ -0,0 +1,102 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import getdate, add_days, today, nowdate, cstr
|
||||
from frappe.model.document import Document
|
||||
from frappe.core.doctype.communication.email import make
|
||||
|
||||
class EmailCampaign(Document):
|
||||
def validate(self):
|
||||
self.set_date()
|
||||
#checking if email is set for lead. Not checking for contact as email is a mandatory field for contact.
|
||||
if self.email_campaign_for == "Lead":
|
||||
self.validate_lead()
|
||||
self.validate_email_campaign_already_exists()
|
||||
self.update_status()
|
||||
|
||||
def set_date(self):
|
||||
if getdate(self.start_date) < getdate(today()):
|
||||
frappe.throw(_("Start Date cannot be before the current date"))
|
||||
#set the end date as start date + max(send after days) in campaign schedule
|
||||
send_after_days = []
|
||||
campaign = frappe.get_doc("Campaign", self.campaign_name)
|
||||
for entry in campaign.get("campaign_schedules"):
|
||||
send_after_days.append(entry.send_after_days)
|
||||
try:
|
||||
end_date = add_days(getdate(self.start_date), max(send_after_days))
|
||||
except ValueError:
|
||||
frappe.throw(_("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name))
|
||||
|
||||
def validate_lead(self):
|
||||
lead_email_id = frappe.db.get_value("Lead", self.recipient, 'email_id')
|
||||
if not lead_email_id:
|
||||
lead_name = frappe.db.get_value("Lead", self.recipient, 'lead_name')
|
||||
frappe.throw(_("Please set an email id for the Lead {0}").format(lead_name))
|
||||
|
||||
def validate_email_campaign_already_exists(self):
|
||||
email_campaign_exists = frappe.db.exists("Email Campaign", {
|
||||
"campaign_name": self.campaign_name,
|
||||
"recipient": self.recipient,
|
||||
"status": ("in", ["In Progress", "Scheduled"])
|
||||
})
|
||||
if email_campaign_exists:
|
||||
frappe.throw(_("The Campaign '{0}' already exists for the {1} '{2}'").format(self.campaign_name, self.email_campaign_for, self.recipient))
|
||||
|
||||
def update_status(self):
|
||||
start_date = getdate(self.start_date)
|
||||
end_date = getdate(self.end_date)
|
||||
today_date = getdate(today())
|
||||
if start_date > today_date:
|
||||
self.status = "Scheduled"
|
||||
elif end_date >= today_date:
|
||||
self.status = "In Progress"
|
||||
elif end_date < today_date:
|
||||
self.status = "Completed"
|
||||
|
||||
#called through hooks to send campaign mails to leads
|
||||
def send_email_to_leads_or_contacts():
|
||||
email_campaigns = frappe.get_all("Email Campaign", filters = { 'status': ('not in', ['Unsubscribed', 'Completed', 'Scheduled']) })
|
||||
for camp in email_campaigns:
|
||||
email_campaign = frappe.get_doc("Email Campaign", camp.name)
|
||||
campaign = frappe.get_cached_doc("Campaign", email_campaign.campaign_name)
|
||||
for entry in campaign.get("campaign_schedules"):
|
||||
scheduled_date = add_days(email_campaign.get('start_date'), entry.get('send_after_days'))
|
||||
if scheduled_date == getdate(today()):
|
||||
send_mail(entry, email_campaign)
|
||||
|
||||
def send_mail(entry, email_campaign):
|
||||
recipient = frappe.db.get_value(email_campaign.email_campaign_for, email_campaign.get("recipient"), 'email_id')
|
||||
|
||||
email_template = frappe.get_doc("Email Template", entry.get("email_template"))
|
||||
sender = frappe.db.get_value("User", email_campaign.get("sender"), 'email')
|
||||
|
||||
# send mail and link communication to document
|
||||
comm = make(
|
||||
doctype = "Email Campaign",
|
||||
name = email_campaign.name,
|
||||
subject = email_template.get("subject"),
|
||||
content = email_template.get("response"),
|
||||
sender = sender,
|
||||
recipients = recipient,
|
||||
communication_medium = "Email",
|
||||
sent_or_received = "Sent",
|
||||
send_email = True,
|
||||
email_template = email_template.name
|
||||
)
|
||||
return comm
|
||||
|
||||
#called from hooks on doc_event Email Unsubscribe
|
||||
def unsubscribe_recipient(unsubscribe, method):
|
||||
if unsubscribe.reference_doctype == 'Email Campaign':
|
||||
frappe.db.set_value("Email Campaign", unsubscribe.reference_name, "status", "Unsubscribed")
|
||||
|
||||
#called through hooks to update email campaign status daily
|
||||
def set_email_campaign_status():
|
||||
email_campaigns = frappe.get_all("Email Campaign", filters = { 'status': ('!=', 'Unsubscribed')})
|
||||
for entry in email_campaigns:
|
||||
email_campaign = frappe.get_doc("Email Campaign", entry.name)
|
||||
email_campaign.update_status()
|
11
erpnext/crm/doctype/email_campaign/email_campaign_list.js
Normal file
11
erpnext/crm/doctype/email_campaign/email_campaign_list.js
Normal file
@ -0,0 +1,11 @@
|
||||
frappe.listview_settings['Email Campaign'] = {
|
||||
get_indicator: function(doc) {
|
||||
var colors = {
|
||||
"Unsubscribed": "red",
|
||||
"Scheduled": "blue",
|
||||
"In Progress": "orange",
|
||||
"Completed": "green"
|
||||
};
|
||||
return [__(doc.status), colors[doc.status], "status,=," + doc.status];
|
||||
}
|
||||
};
|
10
erpnext/crm/doctype/email_campaign/test_email_campaign.py
Normal file
10
erpnext/crm/doctype/email_campaign/test_email_campaign.py
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestEmailCampaign(unittest.TestCase):
|
||||
pass
|
@ -233,6 +233,9 @@ doc_events = {
|
||||
},
|
||||
"Contact":{
|
||||
"on_trash": "erpnext.support.doctype.issue.issue.update_issue"
|
||||
},
|
||||
"Email Unsubscribe": {
|
||||
"after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient"
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,6 +275,8 @@ scheduler_events = {
|
||||
"erpnext.projects.doctype.project.project.send_project_status_email_to_users",
|
||||
"erpnext.quality_management.doctype.quality_review.quality_review.review",
|
||||
"erpnext.support.doctype.service_level_agreement.service_level_agreement.check_agreement_status",
|
||||
"erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts",
|
||||
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status"
|
||||
],
|
||||
"daily_long": [
|
||||
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms"
|
||||
|
@ -6,18 +6,13 @@
|
||||
"description": "Keep Track of Sales Campaigns. Keep track of Leads, Quotations, Sales Order etc from Campaigns to gauge Return on Investment. ",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"campaign",
|
||||
"campaign_name",
|
||||
"naming_series",
|
||||
"from_date",
|
||||
"column_break1",
|
||||
"status",
|
||||
"to_date",
|
||||
"budget_section",
|
||||
"currency",
|
||||
"column_break2",
|
||||
"budget",
|
||||
"campaign_schedules_section",
|
||||
"campaign_schedules",
|
||||
"description_section",
|
||||
"description"
|
||||
],
|
||||
@ -52,57 +47,25 @@
|
||||
"oldfieldtype": "Text",
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Status",
|
||||
"options": "\nPlanned\nIn Progress\nCompleted\nCancelled",
|
||||
"reqd": 1,
|
||||
"default": "Planned"
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "From Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "To Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break1",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "budget",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Budget"
|
||||
},
|
||||
{
|
||||
"fieldname": "description_section",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency"
|
||||
"fieldname": "campaign_schedules",
|
||||
"fieldtype": "Table",
|
||||
"label": "Campaign Schedules",
|
||||
"options": "Campaign Email Schedule"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "budget_section",
|
||||
"fieldname": "campaign_schedules_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "BUDGET"
|
||||
"label": "Campaign Schedules"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-bullhorn",
|
||||
"idx": 1,
|
||||
"modified": "2019-04-29 22:09:39.251884",
|
||||
"modified": "2019-07-22 12:03:39.832342",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Campaign",
|
||||
@ -140,5 +103,7 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1
|
||||
}
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
13
erpnext/selling/doctype/campaign/campaign_dashboard.py
Normal file
13
erpnext/selling/doctype/campaign/campaign_dashboard.py
Normal file
@ -0,0 +1,13 @@
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return {
|
||||
'fieldname': 'campaign_name',
|
||||
'transactions': [
|
||||
{
|
||||
'label': _('Email Campaigns'),
|
||||
'items': ['Email Campaign']
|
||||
}
|
||||
],
|
||||
}
|
@ -69,8 +69,7 @@ class ItemGroup(NestedSet, WebsiteGenerator):
|
||||
"items": get_product_list_for_group(product_group = self.name, start=start,
|
||||
limit=context.page_length + 1, search=frappe.form_dict.get("search")),
|
||||
"parents": get_parent_item_groups(self.parent_item_group),
|
||||
"title": self.name,
|
||||
"products_as_list": cint(frappe.db.get_single_value('Products Settings', 'products_as_list'))
|
||||
"title": self.name
|
||||
})
|
||||
|
||||
if self.slideshow:
|
||||
|
@ -122,8 +122,8 @@ class Batch(Document):
|
||||
self.expiry_date = add_days(self.manufacturing_date, shelf_life_in_days)
|
||||
|
||||
if has_expiry_date and not self.expiry_date:
|
||||
frappe.throw(_('Expiry date is mandatory for selected item'))
|
||||
frappe.msgprint(_('Set items shelf life in days, to set expiry based on manufacturing_date plus self life'))
|
||||
frappe.msgprint(_('Expiry date is mandatory for selected item.'))
|
||||
frappe.throw(_("Set item's shelf life in days, to set expiry based on manufacturing date plus shelf-life."))
|
||||
|
||||
def get_name_from_naming_series(self):
|
||||
"""
|
||||
|
@ -222,7 +222,7 @@ def validate_serial_no(sle, item_det):
|
||||
frappe.throw(_("Serial No {0} has already been received").format(serial_no),
|
||||
SerialNoDuplicateError)
|
||||
|
||||
if (sr.delivery_document_no and sle.voucher_type != 'Stock Entry'
|
||||
if (sr.delivery_document_no and sle.voucher_type not in ['Stock Entry', 'Stock Reconciliation']
|
||||
and sle.voucher_type == sr.delivery_document_type):
|
||||
return_against = frappe.db.get_value(sle.voucher_type, sle.voucher_no, 'return_against')
|
||||
if return_against and return_against != sr.delivery_document_no:
|
||||
@ -299,7 +299,7 @@ def validate_so_serial_no(sr, sales_order,):
|
||||
be delivered""").format(sales_order, sr.item_code, sr.name))
|
||||
|
||||
def has_duplicate_serial_no(sn, sle):
|
||||
if sn.warehouse:
|
||||
if sn.warehouse and sle.voucher_type != 'Stock Reconciliation':
|
||||
return True
|
||||
|
||||
if sn.company != sle.company:
|
||||
@ -415,16 +415,20 @@ def update_serial_nos_after_submit(controller, parentfield):
|
||||
if not stock_ledger_entries: return
|
||||
|
||||
for d in controller.get(parentfield):
|
||||
if d.serial_no:
|
||||
continue
|
||||
|
||||
update_rejected_serial_nos = True if (controller.doctype in ("Purchase Receipt", "Purchase Invoice")
|
||||
and d.rejected_qty) else False
|
||||
accepted_serial_nos_updated = False
|
||||
|
||||
if controller.doctype == "Stock Entry":
|
||||
warehouse = d.t_warehouse
|
||||
qty = d.transfer_qty
|
||||
else:
|
||||
warehouse = d.warehouse
|
||||
qty = d.stock_qty
|
||||
|
||||
qty = (d.qty if controller.doctype == "Stock Reconciliation"
|
||||
else d.stock_qty)
|
||||
for sle in stock_ledger_entries:
|
||||
if sle.voucher_detail_no==d.name:
|
||||
if not accepted_serial_nos_updated and qty and abs(sle.actual_qty)==qty \
|
||||
|
@ -359,7 +359,7 @@ class StockEntry(StockController):
|
||||
d.basic_rate = 0.0
|
||||
elif d.t_warehouse and not d.basic_rate:
|
||||
d.basic_rate = get_valuation_rate(d.item_code, d.t_warehouse,
|
||||
self.doctype, d.name, d.allow_zero_valuation_rate,
|
||||
self.doctype, self.name, d.allow_zero_valuation_rate,
|
||||
currency=erpnext.get_company_currency(self.company))
|
||||
|
||||
def set_actual_qty(self):
|
||||
|
@ -38,7 +38,7 @@ class StockLedgerEntry(Document):
|
||||
self.check_stock_frozen_date()
|
||||
self.actual_amt_check()
|
||||
|
||||
if not self.get("via_landed_cost_voucher") and self.voucher_type != 'Stock Reconciliation':
|
||||
if not self.get("via_landed_cost_voucher"):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import process_serial_no
|
||||
process_serial_no(self)
|
||||
|
||||
|
@ -12,8 +12,7 @@ frappe.ui.form.on("Stock Reconciliation", {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters:{
|
||||
"is_stock_item": 1,
|
||||
"has_serial_no": 0
|
||||
"is_stock_item": 1
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -77,6 +76,7 @@ frappe.ui.form.on("Stock Reconciliation", {
|
||||
|
||||
set_valuation_rate_and_qty: function(frm, cdt, cdn) {
|
||||
var d = frappe.model.get_doc(cdt, cdn);
|
||||
|
||||
if(d.item_code && d.warehouse) {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.stock_reconciliation.stock_reconciliation.get_stock_balance_for",
|
||||
@ -84,7 +84,8 @@ frappe.ui.form.on("Stock Reconciliation", {
|
||||
item_code: d.item_code,
|
||||
warehouse: d.warehouse,
|
||||
posting_date: frm.doc.posting_date,
|
||||
posting_time: frm.doc.posting_time
|
||||
posting_time: frm.doc.posting_time,
|
||||
batch_no: d.batch_no
|
||||
},
|
||||
callback: function(r) {
|
||||
frappe.model.set_value(cdt, cdn, "qty", r.message.qty);
|
||||
@ -93,7 +94,7 @@ frappe.ui.form.on("Stock Reconciliation", {
|
||||
frappe.model.set_value(cdt, cdn, "current_valuation_rate", r.message.rate);
|
||||
frappe.model.set_value(cdt, cdn, "current_amount", r.message.rate * r.message.qty);
|
||||
frappe.model.set_value(cdt, cdn, "amount", r.message.rate * r.message.qty);
|
||||
|
||||
frappe.model.set_value(cdt, cdn, "current_serial_no", r.message.serial_nos);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -152,17 +153,44 @@ frappe.ui.form.on("Stock Reconciliation Item", {
|
||||
barcode: function(frm, cdt, cdn) {
|
||||
frm.events.set_item_code(frm, cdt, cdn);
|
||||
},
|
||||
|
||||
warehouse: function(frm, cdt, cdn) {
|
||||
var child = locals[cdt][cdn];
|
||||
if (child.batch_no) {
|
||||
frappe.model.set_value(child.cdt, child.cdn, "batch_no", "");
|
||||
}
|
||||
|
||||
frm.events.set_valuation_rate_and_qty(frm, cdt, cdn);
|
||||
},
|
||||
|
||||
item_code: function(frm, cdt, cdn) {
|
||||
var child = locals[cdt][cdn];
|
||||
if (child.batch_no) {
|
||||
frappe.model.set_value(cdt, cdn, "batch_no", "");
|
||||
}
|
||||
|
||||
frm.events.set_valuation_rate_and_qty(frm, cdt, cdn);
|
||||
},
|
||||
|
||||
batch_no: function(frm, cdt, cdn) {
|
||||
frm.events.set_valuation_rate_and_qty(frm, cdt, cdn);
|
||||
},
|
||||
|
||||
qty: function(frm, cdt, cdn) {
|
||||
frm.events.set_amount_quantity(frm, cdt, cdn);
|
||||
},
|
||||
|
||||
valuation_rate: function(frm, cdt, cdn) {
|
||||
frm.events.set_amount_quantity(frm, cdt, cdn);
|
||||
},
|
||||
|
||||
serial_no: function(frm, cdt, cdn) {
|
||||
var child = locals[cdt][cdn];
|
||||
|
||||
if (child.serial_no) {
|
||||
const serial_nos = child.serial_no.trim().split('\n');
|
||||
frappe.model.set_value(cdt, cdn, "qty", serial_nos.length);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -9,7 +9,9 @@ from frappe.utils import cstr, flt, cint
|
||||
from erpnext.stock.stock_ledger import update_entries_after
|
||||
from erpnext.controllers.stock_controller import StockController
|
||||
from erpnext.accounts.utils import get_company_default
|
||||
from erpnext.stock.utils import get_stock_balance
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
from erpnext.stock.utils import get_stock_balance, get_incoming_rate, get_available_serial_nos
|
||||
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
||||
|
||||
class OpeningEntryAccountError(frappe.ValidationError): pass
|
||||
class EmptyStockReconciliationItemsError(frappe.ValidationError): pass
|
||||
@ -30,10 +32,16 @@ class StockReconciliation(StockController):
|
||||
self.validate_expense_account()
|
||||
self.set_total_qty_and_amount()
|
||||
|
||||
if self._action=="submit":
|
||||
self.make_batches('warehouse')
|
||||
|
||||
def on_submit(self):
|
||||
self.update_stock_ledger()
|
||||
self.make_gl_entries()
|
||||
|
||||
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
|
||||
update_serial_nos_after_submit(self, "items")
|
||||
|
||||
def on_cancel(self):
|
||||
self.delete_and_repost_sle()
|
||||
self.make_gl_entries_on_cancel()
|
||||
@ -42,23 +50,28 @@ class StockReconciliation(StockController):
|
||||
"""Remove items if qty or rate is not changed"""
|
||||
self.difference_amount = 0.0
|
||||
def _changed(item):
|
||||
qty, rate = get_stock_balance(item.item_code, item.warehouse,
|
||||
self.posting_date, self.posting_time, with_valuation_rate=True)
|
||||
if (item.qty==None or item.qty==qty) and (item.valuation_rate==None or item.valuation_rate==rate):
|
||||
item_dict = get_stock_balance_for(item.item_code, item.warehouse,
|
||||
self.posting_date, self.posting_time, batch_no=item.batch_no)
|
||||
if (((item.qty is None or item.qty==item_dict.get("qty")) and
|
||||
(item.valuation_rate is None or item.valuation_rate==item_dict.get("rate")) and not item.serial_no)
|
||||
or (item.serial_no and item.serial_no == item_dict.get("serial_nos"))):
|
||||
return False
|
||||
else:
|
||||
# set default as current rates
|
||||
if item.qty==None:
|
||||
item.qty = qty
|
||||
if item.qty is None:
|
||||
item.qty = item_dict.get("qty")
|
||||
|
||||
if item.valuation_rate==None:
|
||||
item.valuation_rate = rate
|
||||
if item.valuation_rate is None:
|
||||
item.valuation_rate = item_dict.get("rate")
|
||||
|
||||
item.current_qty = qty
|
||||
item.current_valuation_rate = rate
|
||||
if item_dict.get("serial_nos"):
|
||||
item.current_serial_no = item_dict.get("serial_nos")
|
||||
|
||||
item.current_qty = item_dict.get("qty")
|
||||
item.current_valuation_rate = item_dict.get("rate")
|
||||
self.difference_amount += (flt(item.qty, item.precision("qty")) * \
|
||||
flt(item.valuation_rate or rate, item.precision("valuation_rate")) \
|
||||
- flt(qty, item.precision("qty")) * flt(rate, item.precision("valuation_rate")))
|
||||
flt(item.valuation_rate or item_dict.get("rate"), item.precision("valuation_rate")) \
|
||||
- flt(item_dict.get("qty"), item.precision("qty")) * flt(item_dict.get("rate"), item.precision("valuation_rate")))
|
||||
return True
|
||||
|
||||
items = list(filter(lambda d: _changed(d), self.items))
|
||||
@ -84,12 +97,17 @@ class StockReconciliation(StockController):
|
||||
|
||||
for row_num, row in enumerate(self.items):
|
||||
# find duplicates
|
||||
if [row.item_code, row.warehouse] in item_warehouse_combinations:
|
||||
key = [row.item_code, row.warehouse]
|
||||
for field in ['serial_no', 'batch_no']:
|
||||
if row.get(field):
|
||||
key.append(row.get(field))
|
||||
|
||||
if key in item_warehouse_combinations:
|
||||
self.validation_messages.append(_get_msg(row_num, _("Duplicate entry")))
|
||||
else:
|
||||
item_warehouse_combinations.append([row.item_code, row.warehouse])
|
||||
item_warehouse_combinations.append(key)
|
||||
|
||||
self.validate_item(row.item_code, row_num+1)
|
||||
self.validate_item(row.item_code, row)
|
||||
|
||||
# validate warehouse
|
||||
if not frappe.db.get_value("Warehouse", row.warehouse):
|
||||
@ -131,7 +149,7 @@ class StockReconciliation(StockController):
|
||||
|
||||
raise frappe.ValidationError(self.validation_messages)
|
||||
|
||||
def validate_item(self, item_code, row_num):
|
||||
def validate_item(self, item_code, row):
|
||||
from erpnext.stock.doctype.item.item import validate_end_of_life, \
|
||||
validate_is_stock_item, validate_cancelled_item
|
||||
|
||||
@ -145,51 +163,130 @@ class StockReconciliation(StockController):
|
||||
validate_is_stock_item(item_code, item.is_stock_item, verbose=0)
|
||||
|
||||
# item should not be serialized
|
||||
if item.has_serial_no == 1:
|
||||
raise frappe.ValidationError(_("Serialized Item {0} cannot be updated using Stock Reconciliation, please use Stock Entry").format(item_code))
|
||||
if item.has_serial_no and not row.serial_no and not item.serial_no_series:
|
||||
raise frappe.ValidationError(_("Serial no(s) required for serialized item {0}").format(item_code))
|
||||
|
||||
# item managed batch-wise not allowed
|
||||
if item.has_batch_no == 1:
|
||||
raise frappe.ValidationError(_("Batched Item {0} cannot be updated using Stock Reconciliation, instead use Stock Entry").format(item_code))
|
||||
if item.has_batch_no and not row.batch_no and not item.create_new_batch:
|
||||
raise frappe.ValidationError(_("Batch no is required for batched item {0}").format(item_code))
|
||||
|
||||
# docstatus should be < 2
|
||||
validate_cancelled_item(item_code, item.docstatus, verbose=0)
|
||||
|
||||
except Exception as e:
|
||||
self.validation_messages.append(_("Row # ") + ("%d: " % (row_num)) + cstr(e))
|
||||
self.validation_messages.append(_("Row # ") + ("%d: " % (row.idx)) + cstr(e))
|
||||
|
||||
def update_stock_ledger(self):
|
||||
""" find difference between current and expected entries
|
||||
and create stock ledger entries based on the difference"""
|
||||
from erpnext.stock.stock_ledger import get_previous_sle
|
||||
|
||||
sl_entries = []
|
||||
for row in self.items:
|
||||
item = frappe.get_doc("Item", row.item_code)
|
||||
if item.has_serial_no or item.has_batch_no:
|
||||
self.get_sle_for_serialized_items(row, sl_entries)
|
||||
else:
|
||||
previous_sle = get_previous_sle({
|
||||
"item_code": row.item_code,
|
||||
"warehouse": row.warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time
|
||||
})
|
||||
|
||||
if previous_sle:
|
||||
if row.qty in ("", None):
|
||||
row.qty = previous_sle.get("qty_after_transaction", 0)
|
||||
|
||||
if row.valuation_rate in ("", None):
|
||||
row.valuation_rate = previous_sle.get("valuation_rate", 0)
|
||||
|
||||
if row.qty and not row.valuation_rate:
|
||||
frappe.throw(_("Valuation Rate required for Item {0} at row {1}").format(row.item_code, row.idx))
|
||||
|
||||
if ((previous_sle and row.qty == previous_sle.get("qty_after_transaction")
|
||||
and (row.valuation_rate == previous_sle.get("valuation_rate") or row.qty == 0))
|
||||
or (not previous_sle and not row.qty)):
|
||||
continue
|
||||
|
||||
sl_entries.append(self.get_sle_for_items(row))
|
||||
|
||||
if sl_entries:
|
||||
self.make_sl_entries(sl_entries)
|
||||
|
||||
def get_sle_for_serialized_items(self, row, sl_entries):
|
||||
from erpnext.stock.stock_ledger import get_previous_sle
|
||||
|
||||
serial_nos = get_serial_nos(row.serial_no)
|
||||
|
||||
|
||||
# To issue existing serial nos
|
||||
if row.current_qty and (row.current_serial_no or row.batch_no):
|
||||
args = self.get_sle_for_items(row)
|
||||
args.update({
|
||||
'actual_qty': -1 * row.current_qty,
|
||||
'serial_no': row.current_serial_no,
|
||||
'batch_no': row.batch_no,
|
||||
'valuation_rate': row.current_valuation_rate
|
||||
})
|
||||
|
||||
if row.current_serial_no:
|
||||
args.update({
|
||||
'qty_after_transaction': 0,
|
||||
})
|
||||
|
||||
sl_entries.append(args)
|
||||
|
||||
for serial_no in serial_nos:
|
||||
args = self.get_sle_for_items(row, [serial_no])
|
||||
|
||||
previous_sle = get_previous_sle({
|
||||
"item_code": row.item_code,
|
||||
"warehouse": row.warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time
|
||||
"posting_time": self.posting_time,
|
||||
"serial_no": serial_no
|
||||
})
|
||||
if previous_sle:
|
||||
if row.qty in ("", None):
|
||||
row.qty = previous_sle.get("qty_after_transaction", 0)
|
||||
|
||||
if row.valuation_rate in ("", None):
|
||||
row.valuation_rate = previous_sle.get("valuation_rate", 0)
|
||||
if previous_sle and row.warehouse != previous_sle.get("warehouse"):
|
||||
# If serial no exists in different warehouse
|
||||
|
||||
if row.qty and not row.valuation_rate:
|
||||
frappe.throw(_("Valuation Rate required for Item in row {0}").format(row.idx))
|
||||
new_args = args.copy()
|
||||
new_args.update({
|
||||
'actual_qty': -1,
|
||||
'qty_after_transaction': cint(previous_sle.get('qty_after_transaction')) - 1,
|
||||
'warehouse': previous_sle.get("warehouse", '') or row.warehouse,
|
||||
'valuation_rate': previous_sle.get("valuation_rate")
|
||||
})
|
||||
|
||||
if ((previous_sle and row.qty == previous_sle.get("qty_after_transaction")
|
||||
and (row.valuation_rate == previous_sle.get("valuation_rate") or row.qty == 0))
|
||||
or (not previous_sle and not row.qty)):
|
||||
continue
|
||||
sl_entries.append(new_args)
|
||||
|
||||
self.insert_entries(row)
|
||||
if row.qty:
|
||||
args = self.get_sle_for_items(row)
|
||||
|
||||
def insert_entries(self, row):
|
||||
args.update({
|
||||
'actual_qty': row.qty,
|
||||
'incoming_rate': row.valuation_rate,
|
||||
'valuation_rate': row.valuation_rate
|
||||
})
|
||||
|
||||
sl_entries.append(args)
|
||||
|
||||
if serial_nos == get_serial_nos(row.current_serial_no):
|
||||
# update valuation rate
|
||||
self.update_valuation_rate_for_serial_nos(row, serial_nos)
|
||||
|
||||
def update_valuation_rate_for_serial_nos(self, row, serial_nos):
|
||||
valuation_rate = row.valuation_rate if self.docstatus == 1 else row.current_valuation_rate
|
||||
for d in serial_nos:
|
||||
frappe.db.set_value("Serial No", d, 'purchase_rate', valuation_rate)
|
||||
|
||||
def get_sle_for_items(self, row, serial_nos=None):
|
||||
"""Insert Stock Ledger Entries"""
|
||||
args = frappe._dict({
|
||||
|
||||
if not serial_nos and row.serial_no:
|
||||
serial_nos = get_serial_nos(row.serial_no)
|
||||
|
||||
data = frappe._dict({
|
||||
"doctype": "Stock Ledger Entry",
|
||||
"item_code": row.item_code,
|
||||
"warehouse": row.warehouse,
|
||||
@ -197,13 +294,19 @@ class StockReconciliation(StockController):
|
||||
"posting_time": self.posting_time,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"voucher_detail_no": row.name,
|
||||
"company": self.company,
|
||||
"stock_uom": frappe.db.get_value("Item", row.item_code, "stock_uom"),
|
||||
"is_cancelled": "No",
|
||||
"qty_after_transaction": flt(row.qty, row.precision("qty")),
|
||||
"is_cancelled": "No" if self.docstatus != 2 else "Yes",
|
||||
"serial_no": '\n'.join(serial_nos) if serial_nos else '',
|
||||
"batch_no": row.batch_no,
|
||||
"valuation_rate": flt(row.valuation_rate, row.precision("valuation_rate"))
|
||||
})
|
||||
self.make_sl_entries([args])
|
||||
|
||||
if not row.batch_no:
|
||||
data.qty_after_transaction = flt(row.qty, row.precision("qty"))
|
||||
|
||||
return data
|
||||
|
||||
def delete_and_repost_sle(self):
|
||||
""" Delete Stock Ledger Entries related to this voucher
|
||||
@ -217,6 +320,15 @@ class StockReconciliation(StockController):
|
||||
frappe.db.sql("""delete from `tabStock Ledger Entry`
|
||||
where voucher_type=%s and voucher_no=%s""", (self.doctype, self.name))
|
||||
|
||||
sl_entries = []
|
||||
for row in self.items:
|
||||
if row.serial_no or row.batch_no or row.current_serial_no:
|
||||
self.get_sle_for_serialized_items(row, sl_entries)
|
||||
|
||||
if sl_entries:
|
||||
sl_entries.reverse()
|
||||
self.make_sl_entries(sl_entries)
|
||||
|
||||
# repost future entries for selected item_code, warehouse
|
||||
for entries in existing_entries:
|
||||
update_entries_after({
|
||||
@ -310,17 +422,52 @@ def get_items(warehouse, posting_date, posting_time, company):
|
||||
return res
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_stock_balance_for(item_code, warehouse, posting_date, posting_time):
|
||||
def get_stock_balance_for(item_code, warehouse,
|
||||
posting_date, posting_time, batch_no=None, with_valuation_rate= True):
|
||||
frappe.has_permission("Stock Reconciliation", "write", throw = True)
|
||||
|
||||
qty, rate = get_stock_balance(item_code, warehouse,
|
||||
posting_date, posting_time, with_valuation_rate=True)
|
||||
item_dict = frappe.db.get_value("Item", item_code,
|
||||
["has_serial_no", "has_batch_no"], as_dict=1)
|
||||
|
||||
serial_nos = ""
|
||||
if item_dict.get("has_serial_no"):
|
||||
qty, rate, serial_nos = get_qty_rate_for_serial_nos(item_code,
|
||||
warehouse, posting_date, posting_time, item_dict)
|
||||
else:
|
||||
qty, rate = get_stock_balance(item_code, warehouse,
|
||||
posting_date, posting_time, with_valuation_rate=with_valuation_rate)
|
||||
|
||||
if item_dict.get("has_batch_no"):
|
||||
qty = get_batch_qty(batch_no, warehouse) or 0
|
||||
|
||||
return {
|
||||
'qty': qty,
|
||||
'rate': rate
|
||||
'rate': rate,
|
||||
'serial_nos': serial_nos
|
||||
}
|
||||
|
||||
def get_qty_rate_for_serial_nos(item_code, warehouse, posting_date, posting_time, item_dict):
|
||||
args = {
|
||||
"item_code": item_code,
|
||||
"warehouse": warehouse,
|
||||
"posting_date": posting_date,
|
||||
"posting_time": posting_time,
|
||||
}
|
||||
|
||||
serial_nos_list = [serial_no.get("name")
|
||||
for serial_no in get_available_serial_nos(item_code, warehouse)]
|
||||
|
||||
qty = len(serial_nos_list)
|
||||
serial_nos = '\n'.join(serial_nos_list)
|
||||
args.update({
|
||||
'qty': qty,
|
||||
"serial_nos": serial_nos
|
||||
})
|
||||
|
||||
rate = get_incoming_rate(args, raise_error_if_no_rate=False) or 0
|
||||
|
||||
return qty, rate, serial_nos
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_difference_account(purpose, company):
|
||||
if purpose == 'Stock Reconciliation':
|
||||
|
@ -13,9 +13,12 @@ from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after
|
||||
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.utils import get_stock_balance, get_incoming_rate, get_available_serial_nos, get_stock_value_on
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
class TestStockReconciliation(unittest.TestCase):
|
||||
def setUp(self):
|
||||
create_batch_or_serial_no_items()
|
||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
|
||||
self.insert_existing_sle()
|
||||
|
||||
@ -106,6 +109,135 @@ class TestStockReconciliation(unittest.TestCase):
|
||||
make_stock_entry(posting_date="2013-01-05", posting_time="07:00", item_code="_Test Item",
|
||||
target="_Test Warehouse - _TC", qty=15, basic_rate=1200)
|
||||
|
||||
def test_stock_reco_for_serialized_item(self):
|
||||
set_perpetual_inventory()
|
||||
|
||||
to_delete_records = []
|
||||
to_delete_serial_nos = []
|
||||
|
||||
# Add new serial nos
|
||||
serial_item_code = "Stock-Reco-Serial-Item-1"
|
||||
serial_warehouse = "_Test Warehouse for Stock Reco1 - _TC"
|
||||
|
||||
sr = create_stock_reconciliation(item_code=serial_item_code,
|
||||
warehouse = serial_warehouse, qty=5, rate=200)
|
||||
|
||||
# print(sr.name)
|
||||
serial_nos = get_serial_nos(sr.items[0].serial_no)
|
||||
self.assertEqual(len(serial_nos), 5)
|
||||
|
||||
args = {
|
||||
"item_code": serial_item_code,
|
||||
"warehouse": serial_warehouse,
|
||||
"posting_date": nowdate(),
|
||||
"posting_time": nowtime(),
|
||||
"serial_no": sr.items[0].serial_no
|
||||
}
|
||||
|
||||
valuation_rate = get_incoming_rate(args)
|
||||
self.assertEqual(valuation_rate, 200)
|
||||
|
||||
to_delete_records.append(sr.name)
|
||||
|
||||
sr = create_stock_reconciliation(item_code=serial_item_code,
|
||||
warehouse = serial_warehouse, qty=5, rate=300, serial_no = '\n'.join(serial_nos))
|
||||
|
||||
# print(sr.name)
|
||||
serial_nos1 = get_serial_nos(sr.items[0].serial_no)
|
||||
self.assertEqual(len(serial_nos1), 5)
|
||||
|
||||
args = {
|
||||
"item_code": serial_item_code,
|
||||
"warehouse": serial_warehouse,
|
||||
"posting_date": nowdate(),
|
||||
"posting_time": nowtime(),
|
||||
"serial_no": sr.items[0].serial_no
|
||||
}
|
||||
|
||||
valuation_rate = get_incoming_rate(args)
|
||||
self.assertEqual(valuation_rate, 300)
|
||||
|
||||
to_delete_records.append(sr.name)
|
||||
to_delete_records.reverse()
|
||||
|
||||
for d in to_delete_records:
|
||||
stock_doc = frappe.get_doc("Stock Reconciliation", d)
|
||||
stock_doc.cancel()
|
||||
frappe.delete_doc("Stock Reconciliation", stock_doc.name)
|
||||
|
||||
for d in serial_nos + serial_nos1:
|
||||
if frappe.db.exists("Serial No", d):
|
||||
frappe.delete_doc("Serial No", d)
|
||||
|
||||
def test_stock_reco_for_batch_item(self):
|
||||
set_perpetual_inventory()
|
||||
|
||||
to_delete_records = []
|
||||
to_delete_serial_nos = []
|
||||
|
||||
# Add new serial nos
|
||||
item_code = "Stock-Reco-batch-Item-1"
|
||||
warehouse = "_Test Warehouse for Stock Reco2 - _TC"
|
||||
|
||||
sr = create_stock_reconciliation(item_code=item_code,
|
||||
warehouse = warehouse, qty=5, rate=200, do_not_submit=1)
|
||||
sr.save(ignore_permissions=True)
|
||||
sr.submit()
|
||||
|
||||
self.assertTrue(sr.items[0].batch_no)
|
||||
to_delete_records.append(sr.name)
|
||||
|
||||
sr1 = create_stock_reconciliation(item_code=item_code,
|
||||
warehouse = warehouse, qty=6, rate=300, batch_no=sr.items[0].batch_no)
|
||||
|
||||
args = {
|
||||
"item_code": item_code,
|
||||
"warehouse": warehouse,
|
||||
"posting_date": nowdate(),
|
||||
"posting_time": nowtime(),
|
||||
}
|
||||
|
||||
valuation_rate = get_incoming_rate(args)
|
||||
self.assertEqual(valuation_rate, 300)
|
||||
to_delete_records.append(sr1.name)
|
||||
|
||||
|
||||
sr2 = create_stock_reconciliation(item_code=item_code,
|
||||
warehouse = warehouse, qty=0, rate=0, batch_no=sr.items[0].batch_no)
|
||||
|
||||
stock_value = get_stock_value_on(warehouse, nowdate(), item_code)
|
||||
self.assertEqual(stock_value, 0)
|
||||
to_delete_records.append(sr2.name)
|
||||
|
||||
to_delete_records.reverse()
|
||||
for d in to_delete_records:
|
||||
stock_doc = frappe.get_doc("Stock Reconciliation", d)
|
||||
stock_doc.cancel()
|
||||
|
||||
frappe.delete_doc("Batch", sr.items[0].batch_no)
|
||||
for d in to_delete_records:
|
||||
frappe.delete_doc("Stock Reconciliation", d)
|
||||
|
||||
def create_batch_or_serial_no_items():
|
||||
create_warehouse("_Test Warehouse for Stock Reco1",
|
||||
{"is_group": 0, "parent_warehouse": "_Test Warehouse Group - _TC"})
|
||||
|
||||
create_warehouse("_Test Warehouse for Stock Reco2",
|
||||
{"is_group": 0, "parent_warehouse": "_Test Warehouse Group - _TC"})
|
||||
|
||||
serial_item_doc = create_item("Stock-Reco-Serial-Item-1", is_stock_item=1)
|
||||
if not serial_item_doc.has_serial_no:
|
||||
serial_item_doc.has_serial_no = 1
|
||||
serial_item_doc.serial_no_series = "SRSI.####"
|
||||
serial_item_doc.save(ignore_permissions=True)
|
||||
|
||||
batch_item_doc = create_item("Stock-Reco-batch-Item-1", is_stock_item=1)
|
||||
if not batch_item_doc.has_batch_no:
|
||||
batch_item_doc.has_batch_no = 1
|
||||
batch_item_doc.create_new_batch = 1
|
||||
serial_item_doc.batch_number_series = "BASR.#####"
|
||||
batch_item_doc.save(ignore_permissions=True)
|
||||
|
||||
def create_stock_reconciliation(**args):
|
||||
args = frappe._dict(args)
|
||||
sr = frappe.new_doc("Stock Reconciliation")
|
||||
@ -120,11 +252,14 @@ def create_stock_reconciliation(**args):
|
||||
"item_code": args.item_code or "_Test Item",
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"qty": args.qty,
|
||||
"valuation_rate": args.rate
|
||||
"valuation_rate": args.rate,
|
||||
"serial_no": args.serial_no,
|
||||
"batch_no": args.batch_no
|
||||
})
|
||||
|
||||
try:
|
||||
sr.submit()
|
||||
if not args.do_not_submit:
|
||||
sr.submit()
|
||||
except EmptyStockReconciliationItemsError:
|
||||
pass
|
||||
return sr
|
||||
@ -140,3 +275,4 @@ def set_valuation_method(item_code, valuation_method):
|
||||
}, allow_negative_stock=1)
|
||||
|
||||
test_dependencies = ["Item", "Warehouse"]
|
||||
|
||||
|
@ -1,560 +1,182 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2015-02-17 01:06:05.072764",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Other",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"creation": "2015-02-17 01:06:05.072764",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Other",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"barcode",
|
||||
"item_code",
|
||||
"item_name",
|
||||
"warehouse",
|
||||
"column_break_6",
|
||||
"qty",
|
||||
"valuation_rate",
|
||||
"amount",
|
||||
"serial_no_and_batch_section",
|
||||
"serial_no",
|
||||
"column_break_11",
|
||||
"batch_no",
|
||||
"section_break_3",
|
||||
"current_qty",
|
||||
"current_serial_no",
|
||||
"column_break_9",
|
||||
"current_valuation_rate",
|
||||
"current_amount",
|
||||
"section_break_14",
|
||||
"quantity_difference",
|
||||
"column_break_16",
|
||||
"amount_difference"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "barcode",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Barcode",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "barcode",
|
||||
"fieldtype": "Data",
|
||||
"label": "Barcode",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Item Code",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Item",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 3,
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Item Name",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"in_global_search": 1,
|
||||
"label": "Item Name",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 3,
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Warehouse",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Warehouse",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 3,
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Warehouse",
|
||||
"options": "Warehouse",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"description": "",
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Quantity",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Quantity"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"description": "",
|
||||
"fieldname": "valuation_rate",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Valuation Rate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "valuation_rate",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Valuation Rate",
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_3",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Before reconciliation",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "serial_no_and_batch_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Serial No and Batch"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "",
|
||||
"fieldname": "current_qty",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Current Qty",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Serial No"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "",
|
||||
"fieldname": "current_valuation_rate",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Current Valuation Rate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break_3",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Before reconciliation"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "",
|
||||
"fieldname": "current_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Current Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "current_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Current Qty",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_14",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "current_serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Current Serial No",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "quantity_difference",
|
||||
"fieldtype": "Read Only",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Quantity Difference",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_16",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "current_valuation_rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Current Valuation Rate",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "amount_difference",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Amount Difference",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldname": "current_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Current Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_14",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "quantity_difference",
|
||||
"fieldtype": "Read Only",
|
||||
"label": "Quantity Difference"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_16",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "amount_difference",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount Difference",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
"options": "Batch"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2017-08-03 00:03:40.412071",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Reconciliation Item",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-06-14 17:10:53.188305",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Reconciliation Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -55,10 +55,12 @@ def get_conditions(filters):
|
||||
#get all details
|
||||
def get_stock_ledger_entries(filters):
|
||||
conditions = get_conditions(filters)
|
||||
return frappe.db.sql("""select item_code, batch_no, warehouse,
|
||||
posting_date, actual_qty
|
||||
return frappe.db.sql("""
|
||||
select item_code, batch_no, warehouse, posting_date, sum(actual_qty) as actual_qty
|
||||
from `tabStock Ledger Entry`
|
||||
where docstatus < 2 and ifnull(batch_no, '') != '' %s order by item_code, warehouse""" %
|
||||
where docstatus < 2 and ifnull(batch_no, '') != '' %s
|
||||
group by voucher_no, batch_no, item_code, warehouse
|
||||
order by item_code, warehouse""" %
|
||||
conditions, as_dict=1)
|
||||
|
||||
def get_item_warehouse_batch_map(filters, float_precision):
|
||||
|
@ -157,9 +157,12 @@ class update_entries_after(object):
|
||||
if sle.serial_no:
|
||||
self.get_serialized_values(sle)
|
||||
self.qty_after_transaction += flt(sle.actual_qty)
|
||||
if sle.voucher_type == "Stock Reconciliation":
|
||||
self.qty_after_transaction = sle.qty_after_transaction
|
||||
|
||||
self.stock_value = flt(self.qty_after_transaction) * flt(self.valuation_rate)
|
||||
else:
|
||||
if sle.voucher_type=="Stock Reconciliation":
|
||||
if sle.voucher_type=="Stock Reconciliation" and not sle.batch_no:
|
||||
# assert
|
||||
self.valuation_rate = sle.valuation_rate
|
||||
self.qty_after_transaction = sle.qty_after_transaction
|
||||
@ -371,7 +374,7 @@ class update_entries_after(object):
|
||||
"""get Stock Ledger Entries after a particular datetime, for reposting"""
|
||||
return get_stock_ledger_entries(self.previous_sle or frappe._dict({
|
||||
"item_code": self.args.get("item_code"), "warehouse": self.args.get("warehouse") }),
|
||||
">", "asc", for_update=True)
|
||||
">", "asc", for_update=True, check_serial_no=False)
|
||||
|
||||
def raise_exceptions(self):
|
||||
deficiency = min(e["diff"] for e in self.exceptions)
|
||||
@ -412,7 +415,8 @@ def get_previous_sle(args, for_update=False):
|
||||
sle = get_stock_ledger_entries(args, "<=", "desc", "limit 1", for_update=for_update)
|
||||
return sle and sle[0] or {}
|
||||
|
||||
def get_stock_ledger_entries(previous_sle, operator=None, order="desc", limit=None, for_update=False, debug=False):
|
||||
def get_stock_ledger_entries(previous_sle, operator=None,
|
||||
order="desc", limit=None, for_update=False, debug=False, check_serial_no=True):
|
||||
"""get stock ledger entries filtered by specific posting datetime conditions"""
|
||||
conditions = " and timestamp(posting_date, posting_time) {0} timestamp(%(posting_date)s, %(posting_time)s)".format(operator)
|
||||
if previous_sle.get("warehouse"):
|
||||
@ -420,6 +424,9 @@ def get_stock_ledger_entries(previous_sle, operator=None, order="desc", limit=No
|
||||
elif previous_sle.get("warehouse_condition"):
|
||||
conditions += " and " + previous_sle.get("warehouse_condition")
|
||||
|
||||
if check_serial_no and previous_sle.get("serial_no"):
|
||||
conditions += " and serial_no like {}".format(frappe.db.escape('%{0}%'.format(previous_sle.get("serial_no"))))
|
||||
|
||||
if not previous_sle.get("posting_date"):
|
||||
previous_sle["posting_date"] = "1900-01-01"
|
||||
if not previous_sle.get("posting_time"):
|
||||
@ -479,6 +486,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
|
||||
if not allow_zero_rate and not valuation_rate and raise_error_if_no_rate \
|
||||
and cint(erpnext.is_perpetual_inventory_enabled(company)):
|
||||
frappe.local.message_log = []
|
||||
frappe.throw(_("Valuation rate not found for the Item {0}, which is required to do accounting entries for {1} {2}. If the item is transacting as a zero valuation rate item in the {1}, please mention that in the {1} Item table. Otherwise, please create an incoming stock transaction for the item or mention valuation rate in the Item record, and then try submiting/cancelling this entry").format(item_code, voucher_type, voucher_no))
|
||||
frappe.throw(_("Valuation rate not found for the Item {0}, which is required to do accounting entries for {1} {2}. If the item is transacting as a zero valuation rate item in the {1}, please mention that in the {1} Item table. Otherwise, please create an incoming stock transaction for the item or mention valuation rate in the Item record, and then try submiting / cancelling this entry.")
|
||||
.format(item_code, voucher_type, voucher_no))
|
||||
|
||||
return valuation_rate
|
||||
|
@ -173,7 +173,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
|
||||
in_rate = get_valuation_rate(args.get('item_code'), args.get('warehouse'),
|
||||
args.get('voucher_type'), voucher_no, args.get('allow_zero_valuation'),
|
||||
currency=erpnext.get_company_currency(args.get('company')), company=args.get('company'),
|
||||
raise_error_if_no_rate=True)
|
||||
raise_error_if_no_rate=raise_error_if_no_rate)
|
||||
|
||||
return in_rate
|
||||
|
||||
@ -277,3 +277,7 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto
|
||||
new_row.append(None)
|
||||
|
||||
result[row_idx] = new_row
|
||||
|
||||
def get_available_serial_nos(item_code, warehouse):
|
||||
return frappe.get_all("Serial No", filters = {'item_code': item_code,
|
||||
'warehouse': warehouse, 'delivery_document_no': ''}) or []
|
Loading…
Reference in New Issue
Block a user