Merge branch 'develop' into fix-sla

This commit is contained in:
Saqib 2021-12-06 14:48:09 +05:30 committed by GitHub
commit 8e5b793a0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 519 additions and 333 deletions

View File

@ -68,10 +68,12 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
party_details["tax_category"] = get_address_tax_category(party.get("tax_category"),
party_address, shipping_address if party_type != "Supplier" else party_address)
if not party_details.get("taxes_and_charges"):
party_details["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company,
customer_group=party_details.customer_group, supplier_group=party_details.supplier_group, tax_category=party_details.tax_category,
billing_address=party_address, shipping_address=shipping_address)
tax_template = set_taxes(party.name, party_type, posting_date, company,
customer_group=party_details.customer_group, supplier_group=party_details.supplier_group, tax_category=party_details.tax_category,
billing_address=party_address, shipping_address=shipping_address)
if tax_template:
party_details['taxes_and_charges'] = tax_template
if cint(fetch_payment_terms_template):
party_details["payment_terms_template"] = get_payment_terms_template(party.name, party_type, company)

View File

@ -57,8 +57,10 @@ def make_depreciation_entry(asset_name, date=None):
je.finance_book = d.finance_book
je.remark = "Depreciation Entry against {0} worth {1}".format(asset_name, d.depreciation_amount)
credit_account, debit_account = get_credit_and_debit_accounts(accumulated_depreciation_account, depreciation_expense_account)
credit_entry = {
"account": accumulated_depreciation_account,
"account": credit_account,
"credit_in_account_currency": d.depreciation_amount,
"reference_type": "Asset",
"reference_name": asset.name,
@ -66,7 +68,7 @@ def make_depreciation_entry(asset_name, date=None):
}
debit_entry = {
"account": depreciation_expense_account,
"account": debit_account,
"debit_in_account_currency": d.depreciation_amount,
"reference_type": "Asset",
"reference_name": asset.name,
@ -132,6 +134,20 @@ def get_depreciation_accounts(asset):
return fixed_asset_account, accumulated_depreciation_account, depreciation_expense_account
def get_credit_and_debit_accounts(accumulated_depreciation_account, depreciation_expense_account):
root_type = frappe.get_value("Account", depreciation_expense_account, "root_type")
if root_type == "Expense":
credit_account = accumulated_depreciation_account
debit_account = depreciation_expense_account
elif root_type == "Income":
credit_account = depreciation_expense_account
debit_account = accumulated_depreciation_account
else:
frappe.throw(_("Depreciation Expense Account should be an Income or Expense Account."))
return credit_account, debit_account
@frappe.whitelist()
def scrap_asset(asset_name):
asset = frappe.get_doc("Asset", asset_name)

View File

@ -868,6 +868,72 @@ class TestDepreciationBasics(AssetSetup):
self.assertFalse(asset.schedules[1].journal_entry)
self.assertFalse(asset.schedules[2].journal_entry)
def test_depr_entry_posting_when_depr_expense_account_is_an_expense_account(self):
"""Tests if the Depreciation Expense Account gets debited and the Accumulated Depreciation Account gets credited when the former's an Expense Account."""
asset = create_asset(
item_code = "Macbook Pro",
calculate_depreciation = 1,
available_for_use_date = "2019-12-31",
depreciation_start_date = "2020-12-31",
frequency_of_depreciation = 12,
total_number_of_depreciations = 3,
expected_value_after_useful_life = 10000,
submit = 1
)
post_depreciation_entries(date="2021-06-01")
asset.load_from_db()
je = frappe.get_doc("Journal Entry", asset.schedules[0].journal_entry)
accounting_entries = [{"account": entry.account, "debit": entry.debit, "credit": entry.credit} for entry in je.accounts]
for entry in accounting_entries:
if entry["account"] == "_Test Depreciations - _TC":
self.assertTrue(entry["debit"])
self.assertFalse(entry["credit"])
else:
self.assertTrue(entry["credit"])
self.assertFalse(entry["debit"])
def test_depr_entry_posting_when_depr_expense_account_is_an_income_account(self):
"""Tests if the Depreciation Expense Account gets credited and the Accumulated Depreciation Account gets debited when the former's an Income Account."""
depr_expense_account = frappe.get_doc("Account", "_Test Depreciations - _TC")
depr_expense_account.root_type = "Income"
depr_expense_account.parent_account = "Income - _TC"
depr_expense_account.save()
asset = create_asset(
item_code = "Macbook Pro",
calculate_depreciation = 1,
available_for_use_date = "2019-12-31",
depreciation_start_date = "2020-12-31",
frequency_of_depreciation = 12,
total_number_of_depreciations = 3,
expected_value_after_useful_life = 10000,
submit = 1
)
post_depreciation_entries(date="2021-06-01")
asset.load_from_db()
je = frappe.get_doc("Journal Entry", asset.schedules[0].journal_entry)
accounting_entries = [{"account": entry.account, "debit": entry.debit, "credit": entry.credit} for entry in je.accounts]
for entry in accounting_entries:
if entry["account"] == "_Test Depreciations - _TC":
self.assertTrue(entry["credit"])
self.assertFalse(entry["debit"])
else:
self.assertTrue(entry["debit"])
self.assertFalse(entry["credit"])
# resetting
depr_expense_account.root_type = "Expense"
depr_expense_account.parent_account = "Expenses - _TC"
depr_expense_account.save()
def test_clear_depreciation_schedule(self):
"""Tests if clear_depreciation_schedule() works as expected."""

View File

@ -33,7 +33,7 @@ frappe.ui.form.on('Asset Category', {
var d = locals[cdt][cdn];
return {
"filters": {
"root_type": "Expense",
"root_type": ["in", ["Expense", "Income"]],
"is_group": 0,
"company": d.company_name
}

View File

@ -42,10 +42,10 @@ class AssetCategory(Document):
def validate_account_types(self):
account_type_map = {
'fixed_asset_account': { 'account_type': 'Fixed Asset' },
'accumulated_depreciation_account': { 'account_type': 'Accumulated Depreciation' },
'depreciation_expense_account': { 'root_type': 'Expense' },
'capital_work_in_progress_account': { 'account_type': 'Capital Work in Progress' }
'fixed_asset_account': {'account_type': ['Fixed Asset']},
'accumulated_depreciation_account': {'account_type': ['Accumulated Depreciation']},
'depreciation_expense_account': {'root_type': ['Expense', 'Income']},
'capital_work_in_progress_account': {'account_type': ['Capital Work in Progress']}
}
for d in self.accounts:
for fieldname in account_type_map.keys():
@ -53,11 +53,11 @@ class AssetCategory(Document):
selected_account = d.get(fieldname)
key_to_match = next(iter(account_type_map.get(fieldname))) # acount_type or root_type
selected_key_type = frappe.db.get_value('Account', selected_account, key_to_match)
expected_key_type = account_type_map[fieldname][key_to_match]
expected_key_types = account_type_map[fieldname][key_to_match]
if selected_key_type != expected_key_type:
if selected_key_type not in expected_key_types:
frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.")
.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)),
.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_types)),
title=_("Invalid Account"))
def valide_cwip_account(self):

View File

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

View File

@ -0,0 +1,114 @@
{
"actions": [],
"creation": "2021-09-09 17:03:22.754446",
"description": "Settings for Selling Module",
"doctype": "DocType",
"document_type": "Other",
"engine": "InnoDB",
"field_order": [
"section_break_5",
"campaign_naming_by",
"allow_lead_duplication_based_on_emails",
"column_break_4",
"create_event_on_next_contact_date",
"auto_creation_of_contact",
"opportunity_section",
"close_opportunity_after_days",
"column_break_9",
"create_event_on_next_contact_date_opportunity",
"quotation_section",
"default_valid_till"
],
"fields": [
{
"fieldname": "campaign_naming_by",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Campaign Naming By",
"options": "Campaign Name\nNaming Series"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"fieldname": "default_valid_till",
"fieldtype": "Data",
"label": "Default Quotation Validity Days"
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"label": "Lead"
},
{
"default": "0",
"fieldname": "allow_lead_duplication_based_on_emails",
"fieldtype": "Check",
"label": "Allow Lead Duplication based on Emails"
},
{
"default": "1",
"fieldname": "auto_creation_of_contact",
"fieldtype": "Check",
"label": "Auto Creation of Contact"
},
{
"default": "1",
"fieldname": "create_event_on_next_contact_date",
"fieldtype": "Check",
"label": "Create Event on Next Contact Date"
},
{
"fieldname": "opportunity_section",
"fieldtype": "Section Break",
"label": "Opportunity"
},
{
"default": "15",
"description": "Auto close Opportunity Replied after the no. of days mentioned above",
"fieldname": "close_opportunity_after_days",
"fieldtype": "Int",
"label": "Close Replied Opportunity After Days"
},
{
"default": "1",
"fieldname": "create_event_on_next_contact_date_opportunity",
"fieldtype": "Check",
"label": "Create Event on Next Contact Date"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "quotation_section",
"fieldtype": "Section Break",
"label": "Quotation"
}
],
"icon": "fa fa-cog",
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"migration_hash": "3ae78b12dd1c64d551736c6e82092f90",
"modified": "2021-11-03 09:00:36.883496",
"modified_by": "Administrator",
"module": "CRM",
"name": "CRM Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,9 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class CRMSettings(Document):
pass

View File

@ -0,0 +1,9 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
import unittest
class TestCRMSettings(unittest.TestCase):
pass

View File

@ -11,6 +11,7 @@ from frappe.utils import (
cint,
comma_and,
cstr,
get_link_to_form,
getdate,
has_gravatar,
nowdate,
@ -91,13 +92,14 @@ class Lead(SellingController):
self.contact_doc.save()
def add_calendar_event(self, opts=None, force=False):
super(Lead, self).add_calendar_event({
"owner": self.lead_owner,
"starts_on": self.contact_date,
"ends_on": self.ends_on or "",
"subject": ('Contact ' + cstr(self.lead_name)),
"description": ('Contact ' + cstr(self.lead_name)) + (self.contact_by and ('. By : ' + cstr(self.contact_by)) or '')
}, force)
if frappe.db.get_single_value('CRM Settings', 'create_event_on_next_contact_date'):
super(Lead, self).add_calendar_event({
"owner": self.lead_owner,
"starts_on": self.contact_date,
"ends_on": self.ends_on or "",
"subject": ('Contact ' + cstr(self.lead_name)),
"description": ('Contact ' + cstr(self.lead_name)) + (self.contact_by and ('. By : ' + cstr(self.contact_by)) or '')
}, force)
def update_prospects(self):
prospects = frappe.get_all('Prospect Lead', filters={'lead': self.name}, fields=['parent'])
@ -108,12 +110,13 @@ class Lead(SellingController):
def check_email_id_is_unique(self):
if self.email_id:
# validate email is unique
duplicate_leads = frappe.get_all("Lead", filters={"email_id": self.email_id, "name": ["!=", self.name]})
duplicate_leads = [lead.name for lead in duplicate_leads]
if not frappe.db.get_single_value('CRM Settings', 'allow_lead_duplication_based_on_emails'):
duplicate_leads = frappe.get_all("Lead", filters={"email_id": self.email_id, "name": ["!=", self.name]})
duplicate_leads = [frappe.bold(get_link_to_form('Lead', lead.name)) for lead in duplicate_leads]
if duplicate_leads:
frappe.throw(_("Email Address must be unique, already exists for {0}")
.format(comma_and(duplicate_leads)), frappe.DuplicateEntryError)
if duplicate_leads:
frappe.throw(_("Email Address must be unique, already exists for {0}")
.format(comma_and(duplicate_leads)), frappe.DuplicateEntryError)
def on_trash(self):
frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""", self.name)
@ -172,41 +175,42 @@ class Lead(SellingController):
self.title = self.company_name or self.lead_name
def create_contact(self):
if not self.lead_name:
self.set_full_name()
self.set_lead_name()
if frappe.db.get_single_value('CRM Settings', 'auto_creation_of_contact'):
if not self.lead_name:
self.set_full_name()
self.set_lead_name()
contact = frappe.new_doc("Contact")
contact.update({
"first_name": self.first_name or self.lead_name,
"last_name": self.last_name,
"salutation": self.salutation,
"gender": self.gender,
"designation": self.designation,
"company_name": self.company_name,
})
if self.email_id:
contact.append("email_ids", {
"email_id": self.email_id,
"is_primary": 1
contact = frappe.new_doc("Contact")
contact.update({
"first_name": self.first_name or self.lead_name,
"last_name": self.last_name,
"salutation": self.salutation,
"gender": self.gender,
"designation": self.designation,
"company_name": self.company_name,
})
if self.phone:
contact.append("phone_nos", {
"phone": self.phone,
"is_primary_phone": 1
})
if self.email_id:
contact.append("email_ids", {
"email_id": self.email_id,
"is_primary": 1
})
if self.mobile_no:
contact.append("phone_nos", {
"phone": self.mobile_no,
"is_primary_mobile_no":1
})
if self.phone:
contact.append("phone_nos", {
"phone": self.phone,
"is_primary_phone": 1
})
contact.insert(ignore_permissions=True)
if self.mobile_no:
contact.append("phone_nos", {
"phone": self.mobile_no,
"is_primary_mobile_no":1
})
return contact
contact.insert(ignore_permissions=True)
return contact
@frappe.whitelist()
def make_customer(source_name, target_doc=None):

View File

@ -8,6 +8,7 @@ import frappe
from frappe import _
from frappe.email.inbox import link_communication_to_document
from frappe.model.mapper import get_mapped_doc
from frappe.query_builder import DocType
from frappe.utils import cint, cstr, flt, get_fullname
from erpnext.setup.utils import get_exchange_rate
@ -28,7 +29,6 @@ class Opportunity(TransactionBase):
})
self.make_new_lead_if_required()
self.validate_item_details()
self.validate_uom_is_integer("uom", "qty")
self.validate_cust_name()
@ -70,21 +70,21 @@ class Opportunity(TransactionBase):
"""Set lead against new opportunity"""
if (not self.get("party_name")) and self.contact_email:
# check if customer is already created agains the self.contact_email
customer = frappe.db.sql("""select
distinct `tabDynamic Link`.link_name as customer
from
`tabContact`,
`tabDynamic Link`
where `tabContact`.email_id='{0}'
and
`tabContact`.name=`tabDynamic Link`.parent
and
ifnull(`tabDynamic Link`.link_name, '')<>''
and
`tabDynamic Link`.link_doctype='Customer'
""".format(self.contact_email), as_dict=True)
if customer and customer[0].customer:
self.party_name = customer[0].customer
dynamic_link, contact = DocType("Dynamic Link"), DocType("Contact")
customer = frappe.qb.from_(
dynamic_link
).join(
contact
).on(
(contact.name == dynamic_link.parent)
& (dynamic_link.link_doctype == "Customer")
& (contact.email_id == self.contact_email)
).select(
dynamic_link.link_name
).distinct().run(as_dict=True)
if customer and customer[0].link_name:
self.party_name = customer[0].link_name
self.opportunity_from = "Customer"
return
@ -191,30 +191,31 @@ class Opportunity(TransactionBase):
self.add_calendar_event()
def add_calendar_event(self, opts=None, force=False):
if not opts:
opts = frappe._dict()
if frappe.db.get_single_value('CRM Settings', 'create_event_on_next_contact_date_opportunity'):
if not opts:
opts = frappe._dict()
opts.description = ""
opts.contact_date = self.contact_date
opts.description = ""
opts.contact_date = self.contact_date
if self.party_name and self.opportunity_from == 'Customer':
if self.contact_person:
opts.description = 'Contact '+cstr(self.contact_person)
else:
opts.description = 'Contact customer '+cstr(self.party_name)
elif self.party_name and self.opportunity_from == 'Lead':
if self.contact_display:
opts.description = 'Contact '+cstr(self.contact_display)
else:
opts.description = 'Contact lead '+cstr(self.party_name)
if self.party_name and self.opportunity_from == 'Customer':
if self.contact_person:
opts.description = 'Contact '+cstr(self.contact_person)
else:
opts.description = 'Contact customer '+cstr(self.party_name)
elif self.party_name and self.opportunity_from == 'Lead':
if self.contact_display:
opts.description = 'Contact '+cstr(self.contact_display)
else:
opts.description = 'Contact lead '+cstr(self.party_name)
opts.subject = opts.description
opts.description += '. By : ' + cstr(self.contact_by)
opts.subject = opts.description
opts.description += '. By : ' + cstr(self.contact_by)
if self.to_discuss:
opts.description += ' To Discuss : ' + cstr(self.to_discuss)
if self.to_discuss:
opts.description += ' To Discuss : ' + cstr(self.to_discuss)
super(Opportunity, self).add_calendar_event(opts, force)
super(Opportunity, self).add_calendar_event(opts, force)
def validate_item_details(self):
if not self.get('items'):
@ -363,7 +364,7 @@ def set_multiple_status(names, status):
def auto_close_opportunity():
""" auto close the `Replied` Opportunities after 7 days """
auto_close_after_days = frappe.db.get_single_value("Selling Settings", "close_opportunity_after_days") or 15
auto_close_after_days = frappe.db.get_single_value("CRM Settings", "close_opportunity_after_days") or 15
opportunities = frappe.db.sql(""" select name from tabOpportunity where status='Replied' and
modified<DATE_SUB(CURDATE(), INTERVAL %s DAY) """, (auto_close_after_days), as_dict=True)

View File

@ -20,7 +20,6 @@
"configuration_cb",
"shipping_account_head",
"section_break_12",
"nexus_address",
"nexus"
],
"fields": [
@ -87,15 +86,11 @@
"fieldtype": "Column Break"
},
{
"depends_on": "nexus",
"fieldname": "section_break_12",
"fieldtype": "Section Break",
"label": "Nexus List"
},
{
"fieldname": "nexus_address",
"fieldtype": "HTML",
"label": "Nexus Address"
},
{
"fieldname": "nexus",
"fieldtype": "Table",
@ -107,20 +102,21 @@
"fieldname": "configuration_cb",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_10",
"fieldtype": "Column Break"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company"
},
{
"fieldname": "column_break_10",
"fieldtype": "Column Break"
}
],
"issingle": 1,
"links": [],
"modified": "2021-11-08 18:02:29.232090",
"migration_hash": "8ca1ea3309ed28547b19da8e6e27e96f",
"modified": "2021-11-30 11:17:24.647979",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "TaxJar Settings",

View File

@ -16,9 +16,9 @@ from erpnext.erpnext_integrations.taxjar_integration import get_client
class TaxJarSettings(Document):
def on_update(self):
TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions")
TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax")
TAXJAR_SANDBOX_MODE = frappe.db.get_single_value("TaxJar Settings", "is_sandbox")
TAXJAR_CREATE_TRANSACTIONS = self.taxjar_create_transactions
TAXJAR_CALCULATE_TAX = self.taxjar_calculate_tax
TAXJAR_SANDBOX_MODE = self.is_sandbox
fields_already_exist = frappe.db.exists('Custom Field', {'dt': ('in', ['Item','Sales Invoice Item']), 'fieldname':'product_tax_category'})
fields_hidden = frappe.get_value('Custom Field', {'dt': ('in', ['Sales Invoice Item'])}, 'hidden')

View File

@ -323,10 +323,14 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No
target.maintenance_schedule = source.name
target.maintenance_schedule_detail = s_id
def update_sales(source, target, parent):
def update_sales_and_serial(source, target, parent):
sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person')
target.service_person = sales_person
target.serial_no = ''
serial_nos = get_serial_nos(target.serial_no)
if len(serial_nos) == 1:
target.serial_no = serial_nos[0]
else:
target.serial_no = ''
doclist = get_mapped_doc("Maintenance Schedule", source_name, {
"Maintenance Schedule": {
@ -342,7 +346,7 @@ def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=No
"Maintenance Schedule Item": {
"doctype": "Maintenance Visit Purpose",
"condition": lambda doc: doc.item_name == item_name,
"postprocess": update_sales
"postprocess": update_sales_and_serial
}
}, target_doc)

View File

@ -278,6 +278,7 @@ erpnext.patches.v13_0.update_tds_check_field #3
erpnext.patches.v13_0.add_custom_field_for_south_africa #2
erpnext.patches.v13_0.update_recipient_email_digest
erpnext.patches.v13_0.shopify_deprecation_warning
erpnext.patches.v13_0.remove_bad_selling_defaults
erpnext.patches.v13_0.migrate_stripe_api
erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries
erpnext.patches.v13_0.einvoicing_deprecation_warning
@ -312,4 +313,5 @@ erpnext.patches.v13_0.update_category_in_ltds_certificate
erpnext.patches.v13_0.create_pan_field_for_india #2
erpnext.patches.v14_0.delete_hub_doctypes
erpnext.patches.v13_0.create_ksa_vat_custom_fields
erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
erpnext.patches.v14_0.migrate_crm_settings

View File

@ -3,9 +3,9 @@
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
import erpnext
from erpnext.regional.india.setup import setup
def execute():
@ -30,7 +30,14 @@ def execute():
frappe.reload_doc('Regional', 'Report', report)
if erpnext.get_region() == "India":
setup(patch=True)
create_custom_field('Salary Component',
dict(fieldname='component_type',
label='Component Type',
fieldtype='Select',
insert_after='description',
options='\nProvident Fund\nAdditional Provident Fund\nProvident Fund Loan\nProfessional Tax',
depends_on='eval:doc.type == "Deduction"')
)
if frappe.db.exists("Salary Component", "Income Tax"):
frappe.db.set_value("Salary Component", "Income Tax", "is_income_tax_component", 1)

View File

@ -0,0 +1,15 @@
import frappe
from frappe import _
def execute():
selling_settings = frappe.get_single("Selling Settings")
if selling_settings.customer_group in (_("All Customer Groups"), "All Customer Groups"):
selling_settings.customer_group = None
if selling_settings.territory in (_("All Territories"), "All Territories"):
selling_settings.territory = None
selling_settings.flags.ignore_mandatory=True
selling_settings.save(ignore_permissions=True)

View File

@ -0,0 +1,16 @@
import frappe
def execute():
settings = frappe.db.get_value('Selling Settings', 'Selling Settings', [
'campaign_naming_by',
'close_opportunity_after_days',
'default_valid_till'
], as_dict=True)
frappe.reload_doc('crm', 'doctype', 'crm_settings')
frappe.db.set_value('CRM Settings', 'CRM Settings', {
'campaign_naming_by': settings.campaign_naming_by,
'close_opportunity_after_days': settings.close_opportunity_after_days,
'default_valid_till': settings.default_valid_till
})

View File

@ -206,28 +206,18 @@ def get_regional_address_details(party_details, doctype, company):
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
master_doctype = "Sales Taxes and Charges Template"
get_tax_template_based_on_category(master_doctype, company, party_details)
if party_details.get('taxes_and_charges'):
return party_details
if not party_details.company_gstin:
return party_details
tax_template_by_category = get_tax_template_based_on_category(master_doctype, company, party_details)
elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
master_doctype = "Purchase Taxes and Charges Template"
get_tax_template_based_on_category(master_doctype, company, party_details)
tax_template_by_category = get_tax_template_based_on_category(master_doctype, company, party_details)
if party_details.get('taxes_and_charges'):
return party_details
if not party_details.supplier_gstin:
return party_details
if tax_template_by_category:
party_details.get['taxes_and_charges'] = tax_template_by_category
return
if not party_details.place_of_supply: return party_details
if not party_details.company_gstin: return party_details
if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin
and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice",
"Purchase Order", "Purchase Receipt") and party_details.supplier_gstin and party_details.supplier_gstin[:2] != party_details.place_of_supply[:2])):
@ -237,6 +227,7 @@ def get_regional_address_details(party_details, doctype, company):
if not default_tax:
return party_details
party_details["taxes_and_charges"] = default_tax
party_details.taxes = get_taxes_and_charges(master_doctype, default_tax)
@ -268,9 +259,7 @@ def get_tax_template_based_on_category(master_doctype, company, party_details):
default_tax = frappe.db.get_value(master_doctype, {'company': company, 'tax_category': party_details.get('tax_category')},
'name')
if default_tax:
party_details["taxes_and_charges"] = default_tax
party_details.taxes = get_taxes_and_charges(master_doctype, default_tax)
return default_tax
def get_tax_template(master_doctype, company, is_inter_state, state_code):
tax_categories = frappe.get_all('Tax Category', fields = ['name', 'is_inter_state', 'gst_state'],

View File

@ -1,7 +1,6 @@
import io
import os
from base64 import b64encode
from urllib.parse import quote
import frappe
from frappe import _
@ -78,7 +77,7 @@ def create_qr_code(doc, method):
tlv_array.append(''.join([tag, length, value]))
# Invoice Amount
invoice_amount = str(doc.total)
invoice_amount = str(doc.grand_total)
tag = bytes([4]).hex()
length = bytes([len(invoice_amount)]).hex()
value = invoice_amount.encode('utf-8').hex()
@ -102,9 +101,10 @@ def create_qr_code(doc, method):
url = qr_create(base64_string, error='L')
url.png(qr_image, scale=2, quiet_zone=1)
urlencoded_name = quote(doc.name)
name = frappe.generate_hash(doc.name, 5)
# making file
filename = f"QR-CODE-{urlencoded_name}.png".replace(os.path.sep, "__")
filename = f"QRCode-{name}.png".replace(os.path.sep, "__")
_file = frappe.get_doc({
"doctype": "File",
"file_name": filename,

View File

@ -11,11 +11,6 @@
"customer_group",
"column_break_4",
"territory",
"crm_settings_section",
"campaign_naming_by",
"default_valid_till",
"column_break_9",
"close_opportunity_after_days",
"item_price_settings_section",
"selling_price_list",
"maintain_same_rate_action",
@ -43,13 +38,6 @@
"label": "Customer Naming By",
"options": "Customer Name\nNaming Series\nAuto Name"
},
{
"fieldname": "campaign_naming_by",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Campaign Naming By",
"options": "Campaign Name\nNaming Series\nAuto Name"
},
{
"fieldname": "customer_group",
"fieldtype": "Link",
@ -71,18 +59,6 @@
"label": "Default Price List",
"options": "Price List"
},
{
"default": "15",
"description": "Auto close Opportunity after the no. of days mentioned above",
"fieldname": "close_opportunity_after_days",
"fieldtype": "Int",
"label": "Close Opportunity After Days"
},
{
"fieldname": "default_valid_till",
"fieldtype": "Data",
"label": "Default Quotation Validity Days"
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
@ -169,15 +145,6 @@
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "crm_settings_section",
"fieldtype": "Section Break",
"label": "CRM Settings"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"fieldname": "item_price_settings_section",
"fieldtype": "Section Break",
@ -204,7 +171,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-09-08 19:38:10.175989",
"modified": "2021-09-13 12:32:17.004404",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling Settings",

View File

@ -8,7 +8,6 @@ import frappe
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.model.document import Document
from frappe.utils import cint
from frappe.utils.nestedset import get_root_of
class SellingSettings(Document):
@ -37,9 +36,3 @@ class SellingSettings(Document):
editable_bundle_item_rates = cint(self.editable_bundle_item_rates)
make_property_setter("Packed Item", "rate", "read_only", not(editable_bundle_item_rates), "Check", validate_fields_for_doctype=False)
def set_default_customer_group_and_territory(self):
if not self.customer_group:
self.customer_group = get_root_of('Customer Group')
if not self.territory:
self.territory = get_root_of('Territory')

View File

@ -303,7 +303,6 @@ def set_more_defaults():
def update_selling_defaults():
selling_settings = frappe.get_doc("Selling Settings")
selling_settings.set_default_customer_group_and_territory()
selling_settings.cust_master_name = "Customer Name"
selling_settings.so_required = "No"
selling_settings.dn_required = "No"

View File

@ -53,6 +53,7 @@ def before_tests():
frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 0)
enable_all_roles_and_domains()
set_defaults_for_tests()
frappe.db.commit()
@ -127,6 +128,14 @@ def enable_all_roles_and_domains():
[d.name for d in domains])
add_all_roles_to('Administrator')
def set_defaults_for_tests():
from frappe.utils.nestedset import get_root_of
selling_settings = frappe.get_single("Selling Settings")
selling_settings.customer_group = get_root_of("Customer Group")
selling_settings.territory = get_root_of("Territory")
selling_settings.save()
def insert_record(records):
for r in records:

View File

@ -10,7 +10,7 @@
"idx": 0,
"label": "ERPNext Settings",
"links": [],
"modified": "2021-10-26 21:32:55.323591",
"modified": "2021-11-05 21:32:55.323591",
"modified_by": "Administrator",
"module": "Setup",
"name": "ERPNext Settings",
@ -123,6 +123,13 @@
"label": "Products Settings",
"link_to": "Products Settings",
"type": "DocType"
},
{
"doc_view": "",
"icon": "crm",
"label": "CRM Settings",
"link_to": "CRM Settings",
"type": "DocType"
}
],
"title": "ERPNext Settings"

View File

@ -22,7 +22,7 @@ def boot_session(bootinfo):
'customer_group')
bootinfo.sysdefaults.allow_stale = cint(frappe.db.get_single_value('Accounts Settings',
'allow_stale'))
bootinfo.sysdefaults.quotation_valid_till = cint(frappe.db.get_single_value('Selling Settings',
bootinfo.sysdefaults.quotation_valid_till = cint(frappe.db.get_single_value('CRM Settings',
'default_valid_till'))
# if no company, show a dialog box to create a new company

View File

@ -6,7 +6,7 @@ import frappe
from frappe.model.document import Document
from frappe.query_builder import Case
from frappe.query_builder.functions import Coalesce, Sum
from frappe.utils import flt, nowdate
from frappe.utils import flt
class Bin(Document):
@ -127,33 +127,11 @@ def on_doctype_update():
def update_stock(bin_name, args, allow_negative_stock=False, via_landed_cost_voucher=False):
'''Called from erpnext.stock.utils.update_bin'''
"""WARNING: This function is deprecated. Inline this function instead of using it."""
from erpnext.stock.stock_ledger import repost_current_voucher
update_qty(bin_name, args)
if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation":
from erpnext.stock.stock_ledger import update_entries_after, update_qty_in_future_sle
if not args.get("posting_date"):
args["posting_date"] = nowdate()
if args.get("is_cancelled") and via_landed_cost_voucher:
return
# Reposts only current voucher SL Entries
# Updates valuation rate, stock value, stock queue for current transaction
update_entries_after({
"item_code": args.get('item_code'),
"warehouse": args.get('warehouse'),
"posting_date": args.get("posting_date"),
"posting_time": args.get("posting_time"),
"voucher_type": args.get("voucher_type"),
"voucher_no": args.get("voucher_no"),
"sle_id": args.get('name'),
"creation": args.get('creation')
}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
# update qty in future sle and Validate negative qty
update_qty_in_future_sle(args, allow_negative_stock)
repost_current_voucher(args, allow_negative_stock, via_landed_cost_voucher)
def get_bin_details(bin_name):
return frappe.db.get_value('Bin', bin_name, ['actual_qty', 'ordered_qty',

View File

@ -1035,7 +1035,7 @@
"image_field": "image",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-11-30 02:33:06.572442",
"modified": "2021-12-03 08:32:03.869294",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
@ -1103,7 +1103,7 @@
"search_fields": "item_name,description,item_group,customer_code",
"show_name_in_global_search": 1,
"show_preview_popup": 1,
"sort_field": "idx desc,modified desc",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "item_name",
"track_changes": 1

View File

@ -724,7 +724,6 @@ class Item(WebsiteGenerator):
def recalculate_bin_qty(self, new_name):
from erpnext.stock.stock_balance import repost_stock
frappe.db.auto_commit_on_many_writes = 1
existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
@ -738,7 +737,6 @@ class Item(WebsiteGenerator):
repost_stock(new_name, warehouse)
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock)
frappe.db.auto_commit_on_many_writes = 0
@frappe.whitelist()
def copy_specification_from_item_group(self):

View File

@ -545,7 +545,7 @@ class StockEntry(StockController):
scrap_items_cost = sum([flt(d.basic_amount) for d in self.get("items") if d.is_scrap_item])
# Get raw materials cost from BOM if multiple material consumption entries
if frappe.db.get_single_value("Manufacturing Settings", "material_consumption", cache=True):
if not outgoing_items_cost and frappe.db.get_single_value("Manufacturing Settings", "material_consumption", cache=True):
bom_items = self.get_bom_raw_materials(finished_item_qty)
outgoing_items_cost = sum([flt(row.qty)*flt(row.rate) for row in bom_items.values()])

View File

@ -41,6 +41,7 @@ def get_sle(**args):
class TestStockEntry(unittest.TestCase):
def tearDown(self):
frappe.set_user("Administrator")
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", "0")
def test_fifo(self):
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
@ -582,6 +583,65 @@ class TestStockEntry(unittest.TestCase):
self.assertEqual(fg_cost,
flt(rm_cost + bom_operation_cost + work_order.additional_operating_cost, 2))
def test_work_order_manufacture_with_material_consumption(self):
from erpnext.manufacturing.doctype.work_order.work_order import (
make_stock_entry as _make_stock_entry,
)
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", "1")
bom_no = frappe.db.get_value("BOM", {"item": "_Test FG Item",
"is_default": 1, "docstatus": 1})
work_order = frappe.new_doc("Work Order")
work_order.update({
"company": "_Test Company",
"fg_warehouse": "_Test Warehouse 1 - _TC",
"production_item": "_Test FG Item",
"bom_no": bom_no,
"qty": 1.0,
"stock_uom": "_Test UOM",
"wip_warehouse": "_Test Warehouse - _TC"
})
work_order.insert()
work_order.submit()
make_stock_entry(item_code="_Test Item",
target="Stores - _TC", qty=10, basic_rate=5000.0)
make_stock_entry(item_code="_Test Item Home Desktop 100",
target="Stores - _TC", qty=10, basic_rate=1000.0)
s = frappe.get_doc(_make_stock_entry(work_order.name, "Material Transfer for Manufacture", 1))
for d in s.get("items"):
d.s_warehouse = "Stores - _TC"
s.insert()
s.submit()
# When Stock Entry has RM and FG
s = frappe.get_doc(_make_stock_entry(work_order.name, "Manufacture", 1))
s.save()
rm_cost = 0
for d in s.get('items'):
if d.s_warehouse:
rm_cost += d.amount
fg_cost = list(filter(lambda x: x.item_code=="_Test FG Item", s.get("items")))[0].amount
scrap_cost = list(filter(lambda x: x.is_scrap_item, s.get("items")))[0].amount
self.assertEqual(fg_cost,
flt(rm_cost - scrap_cost, 2))
# When Stock Entry has only FG + Scrap
s.items.pop(0)
s.items.pop(0)
s.submit()
rm_cost = 0
for d in s.get('items'):
if d.s_warehouse:
rm_cost += d.amount
self.assertEqual(rm_cost, 0)
expected_fg_cost = s.get_basic_rate_for_manufactured_item(1)
fg_cost = list(filter(lambda x: x.item_code=="_Test FG Item", s.get("items")))[0].amount
self.assertEqual(flt(fg_cost, 2), flt(expected_fg_cost, 2))
def test_variant_work_order(self):
bom_no = frappe.db.get_value("BOM", {"item": "_Test Variant Item",

View File

@ -33,65 +33,6 @@ class TestWarehouse(ERPNextTestCase):
self.assertEqual(p_warehouse.name, child_warehouse.parent_warehouse)
self.assertEqual(child_warehouse.is_group, 0)
def test_warehouse_renaming(self):
create_warehouse("Test Warehouse for Renaming 1", company="_Test Company with perpetual inventory")
account = get_inventory_account("_Test Company with perpetual inventory", "Test Warehouse for Renaming 1 - TCP1")
self.assertTrue(frappe.db.get_value("Warehouse", filters={"account": account}))
# Rename with abbr
if frappe.db.exists("Warehouse", "Test Warehouse for Renaming 2 - TCP1"):
frappe.delete_doc("Warehouse", "Test Warehouse for Renaming 2 - TCP1")
frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 1 - TCP1", "Test Warehouse for Renaming 2 - TCP1")
self.assertTrue(frappe.db.get_value("Warehouse",
filters={"account": "Test Warehouse for Renaming 1 - TCP1"}))
# Rename without abbr
if frappe.db.exists("Warehouse", "Test Warehouse for Renaming 3 - TCP1"):
frappe.delete_doc("Warehouse", "Test Warehouse for Renaming 3 - TCP1")
frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 2 - TCP1", "Test Warehouse for Renaming 3")
self.assertTrue(frappe.db.get_value("Warehouse",
filters={"account": "Test Warehouse for Renaming 1 - TCP1"}))
# Another rename with multiple dashes
if frappe.db.exists("Warehouse", "Test - Warehouse - Company - TCP1"):
frappe.delete_doc("Warehouse", "Test - Warehouse - Company - TCP1")
frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 3 - TCP1", "Test - Warehouse - Company")
def test_warehouse_merging(self):
company = "_Test Company with perpetual inventory"
create_warehouse("Test Warehouse for Merging 1", company=company,
properties={"parent_warehouse": "All Warehouses - TCP1"})
create_warehouse("Test Warehouse for Merging 2", company=company,
properties={"parent_warehouse": "All Warehouses - TCP1"})
make_stock_entry(item_code="_Test Item", target="Test Warehouse for Merging 1 - TCP1",
qty=1, rate=100, company=company)
make_stock_entry(item_code="_Test Item", target="Test Warehouse for Merging 2 - TCP1",
qty=1, rate=100, company=company)
existing_bin_qty = (
cint(frappe.db.get_value("Bin",
{"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 1 - TCP1"}, "actual_qty"))
+ cint(frappe.db.get_value("Bin",
{"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 2 - TCP1"}, "actual_qty"))
)
frappe.rename_doc("Warehouse", "Test Warehouse for Merging 1 - TCP1",
"Test Warehouse for Merging 2 - TCP1", merge=True)
self.assertFalse(frappe.db.exists("Warehouse", "Test Warehouse for Merging 1 - TCP1"))
bin_qty = frappe.db.get_value("Bin",
{"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 2 - TCP1"}, "actual_qty")
self.assertEqual(bin_qty, existing_bin_qty)
self.assertTrue(frappe.db.get_value("Warehouse",
filters={"account": "Test Warehouse for Merging 2 - TCP1"}))
def test_unlinking_warehouse_from_item_defaults(self):
company = "_Test Company"

View File

@ -1,7 +1,6 @@
{
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"creation": "2013-03-07 18:50:32",
"description": "A logical Warehouse against which stock entries are made.",
"doctype": "DocType",
@ -245,7 +244,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2021-04-09 19:54:56.263965",
"modified": "2021-12-03 04:40:06.414630",
"modified_by": "Administrator",
"module": "Stock",
"name": "Warehouse",

View File

@ -10,7 +10,6 @@ from frappe.contacts.address_and_contact import load_address_and_contact
from frappe.utils import cint, flt
from frappe.utils.nestedset import NestedSet
import erpnext
from erpnext.stock import get_warehouse_account
@ -68,57 +67,6 @@ class Warehouse(NestedSet):
return frappe.db.sql("""select name from `tabWarehouse`
where parent_warehouse = %s limit 1""", self.name)
def before_rename(self, old_name, new_name, merge=False):
super(Warehouse, self).before_rename(old_name, new_name, merge)
# Add company abbr if not provided
new_warehouse = erpnext.encode_company_abbr(new_name, self.company)
if merge:
if not frappe.db.exists("Warehouse", new_warehouse):
frappe.throw(_("Warehouse {0} does not exist").format(new_warehouse))
if self.company != frappe.db.get_value("Warehouse", new_warehouse, "company"):
frappe.throw(_("Both Warehouse must belong to same Company"))
return new_warehouse
def after_rename(self, old_name, new_name, merge=False):
super(Warehouse, self).after_rename(old_name, new_name, merge)
new_warehouse_name = self.get_new_warehouse_name_without_abbr(new_name)
self.db_set("warehouse_name", new_warehouse_name)
if merge:
self.recalculate_bin_qty(new_name)
def get_new_warehouse_name_without_abbr(self, name):
company_abbr = frappe.get_cached_value('Company', self.company, "abbr")
parts = name.rsplit(" - ", 1)
if parts[-1].lower() == company_abbr.lower():
name = parts[0]
return name
def recalculate_bin_qty(self, new_name):
from erpnext.stock.stock_balance import repost_stock
frappe.db.auto_commit_on_many_writes = 1
existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
repost_stock_for_items = frappe.db.sql_list("""select distinct item_code
from tabBin where warehouse=%s""", new_name)
# Delete all existing bins to avoid duplicate bins for the same item and warehouse
frappe.db.sql("delete from `tabBin` where warehouse=%s", new_name)
for item_code in repost_stock_for_items:
repost_stock(item_code, new_name)
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock)
frappe.db.auto_commit_on_many_writes = 0
def convert_to_group_or_ledger(self):
if self.is_group:
self.convert_to_ledger()

View File

@ -7,9 +7,10 @@ import json
import frappe
from frappe import _
from frappe.model.meta import get_field_precision
from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, now
from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, now, nowdate
import erpnext
from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty
from erpnext.stock.utils import (
get_incoming_outgoing_rate_for_cancel,
get_or_make_bin,
@ -17,19 +18,15 @@ from erpnext.stock.utils import (
)
# future reposting
class NegativeStockError(frappe.ValidationError): pass
class SerialNoExistsInFutureTransaction(frappe.ValidationError):
pass
_exceptions = frappe.local('stockledger_exceptions')
# _exceptions = []
def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
from erpnext.controllers.stock_controller import future_sle_exists
if sl_entries:
from erpnext.stock.utils import update_bin
cancel = sl_entries[0].get("is_cancelled")
if cancel:
validate_cancellation(sl_entries)
@ -64,7 +61,38 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc
# preserve previous_qty_after_transaction for qty reposting
args.previous_qty_after_transaction = sle.get("previous_qty_after_transaction")
update_bin(args, allow_negative_stock, via_landed_cost_voucher)
is_stock_item = frappe.get_cached_value('Item', args.get("item_code"), 'is_stock_item')
if is_stock_item:
bin_name = get_or_make_bin(args.get("item_code"), args.get("warehouse"))
update_bin_qty(bin_name, args)
repost_current_voucher(args, allow_negative_stock, via_landed_cost_voucher)
else:
frappe.msgprint(_("Item {0} ignored since it is not a stock item").format(args.get("item_code")))
def repost_current_voucher(args, allow_negative_stock=False, via_landed_cost_voucher=False):
if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation":
if not args.get("posting_date"):
args["posting_date"] = nowdate()
if args.get("is_cancelled") and via_landed_cost_voucher:
return
# Reposts only current voucher SL Entries
# Updates valuation rate, stock value, stock queue for current transaction
update_entries_after({
"item_code": args.get('item_code'),
"warehouse": args.get('warehouse'),
"posting_date": args.get("posting_date"),
"posting_time": args.get("posting_time"),
"voucher_type": args.get("voucher_type"),
"voucher_no": args.get("voucher_no"),
"sle_id": args.get('name'),
"creation": args.get('creation')
}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
# update qty in future sle and Validate negative qty
update_qty_in_future_sle(args, allow_negative_stock)
def get_args_for_future_sle(row):
return frappe._dict({
@ -803,9 +831,9 @@ class update_entries_after(object):
def update_bin(self):
# update bin for each warehouse
for warehouse, data in self.data.items():
bin_record = get_or_make_bin(self.item_code, warehouse)
bin_name = get_or_make_bin(self.item_code, warehouse)
frappe.db.set_value('Bin', bin_record, {
frappe.db.set_value('Bin', bin_name, {
"valuation_rate": data.valuation_rate,
"actual_qty": data.qty_after_transaction,
"stock_value": data.stock_value

View File

@ -187,7 +187,7 @@ def get_bin(item_code, warehouse):
bin_obj.flags.ignore_permissions = True
return bin_obj
def get_or_make_bin(item_code, warehouse) -> str:
def get_or_make_bin(item_code: str , warehouse: str) -> str:
bin_record = frappe.db.get_value('Bin', {'item_code': item_code, 'warehouse': warehouse})
if not bin_record:
@ -203,11 +203,12 @@ def get_or_make_bin(item_code, warehouse) -> str:
return bin_record
def update_bin(args, allow_negative_stock=False, via_landed_cost_voucher=False):
"""WARNING: This function is deprecated. Inline this function instead of using it."""
from erpnext.stock.doctype.bin.bin import update_stock
is_stock_item = frappe.get_cached_value('Item', args.get("item_code"), 'is_stock_item')
if is_stock_item:
bin_record = get_or_make_bin(args.get("item_code"), args.get("warehouse"))
update_stock(bin_record, args, allow_negative_stock, via_landed_cost_voucher)
bin_name = get_or_make_bin(args.get("item_code"), args.get("warehouse"))
update_stock(bin_name, args, allow_negative_stock, via_landed_cost_voucher)
else:
frappe.msgprint(_("Item {0} ignored since it is not a stock item").format(args.get("item_code")))