Merge branch 'develop' into hide_alt_field

This commit is contained in:
Marica 2021-03-25 12:15:58 +05:30 committed by GitHub
commit 7c6f72fd39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 300 additions and 153 deletions

View File

@ -4,7 +4,7 @@ on: [pull_request, workflow_dispatch, push]
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-18.04
strategy: strategy:
fail-fast: false fail-fast: false

View File

@ -24,7 +24,7 @@ class TestAccountingPeriod(unittest.TestCase):
ap1 = create_accounting_period(period_name = "Test Accounting Period 2") ap1 = create_accounting_period(period_name = "Test Accounting Period 2")
ap1.save() ap1.save()
doc = create_sales_invoice(do_not_submit=1, cost_center = "_Test Company - _TC", warehouse = "Stores - _TC") doc = create_sales_invoice(do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC")
self.assertRaises(ClosedAccountingPeriod, doc.submit) self.assertRaises(ClosedAccountingPeriod, doc.submit)
def tearDown(self): def tearDown(self):

View File

@ -6,10 +6,12 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest import unittest
test_dependencies = ["Customer", "Supplier"] from frappe.cache_manager import clear_doctype_cache
from frappe.custom.doctype.property_setter.property_setter import make_property_setter from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account
test_dependencies = ["Customer", "Supplier"]
class TestOpeningInvoiceCreationTool(unittest.TestCase): class TestOpeningInvoiceCreationTool(unittest.TestCase):
def setUp(self): def setUp(self):
if not frappe.db.exists("Company", "_Test Opening Invoice Company"): if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
@ -24,6 +26,7 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
def test_opening_sales_invoice_creation(self): def test_opening_sales_invoice_creation(self):
property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check") property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check")
try:
invoices = self.make_invoices(company="_Test Opening Invoice Company") invoices = self.make_invoices(company="_Test Opening Invoice Company")
self.assertEqual(len(invoices), 2) self.assertEqual(len(invoices), 2)
@ -39,7 +42,9 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
# Check if update stock is not enabled # Check if update stock is not enabled
self.assertEqual(si.update_stock, 0) self.assertEqual(si.update_stock, 0)
finally:
property_setter.delete() property_setter.delete()
clear_doctype_cache("Sales Invoice")
def check_expected_values(self, invoices, expected_value, invoice_type="Sales"): def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice" doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"

View File

@ -377,6 +377,12 @@ class POSInvoice(SalesInvoice):
"allow_print_before_pay": profile.get("allow_print_before_pay") "allow_print_before_pay": profile.get("allow_print_before_pay")
} }
def reset_mode_of_payments(self):
if self.pos_profile:
pos_profile = frappe.get_cached_doc('POS Profile', self.pos_profile)
update_multi_mode_option(self, pos_profile)
self.paid_amount = 0
def set_account_for_mode_of_payment(self): def set_account_for_mode_of_payment(self):
self.payments = [d for d in self.payments if d.amount or d.base_amount or d.default] self.payments = [d for d in self.payments if d.amount or d.base_amount or d.default]
for pay in self.payments: for pay in self.payments:

View File

@ -524,7 +524,7 @@ frappe.ui.form.on("Purchase Invoice", {
}, },
onload: function(frm) { onload: function(frm) {
if(frm.doc.__onload) { if(frm.doc.__onload && frm.is_new()) {
if(frm.doc.supplier) { if(frm.doc.supplier) {
frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0; frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0;
} }

View File

@ -1800,6 +1800,15 @@ class TestSalesInvoice(unittest.TestCase):
si.selling_price_list = "_Test Price List Rest of the World" si.selling_price_list = "_Test Price List Rest of the World"
si.update_stock = 1 si.update_stock = 1
si.items[0].target_warehouse = 'Work In Progress - TCP1' si.items[0].target_warehouse = 'Work In Progress - TCP1'
# Add stock to stores for succesful stock transfer
make_stock_entry(
target="Stores - TCP1",
company = "_Test Company with perpetual inventory",
qty=1,
basic_rate=100
)
add_taxes(si) add_taxes(si)
si.save() si.save()

View File

@ -46,5 +46,5 @@ def validate_disabled(doc):
frappe.throw(_("Disabled template must not be default template")) frappe.throw(_("Disabled template must not be default template"))
def validate_for_tax_category(doc): def validate_for_tax_category(doc):
if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0}): if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0, "name": ["!=", doc.name]}):
frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category))) frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category)))

View File

@ -229,7 +229,8 @@ def create_sales_invoice(**args):
'qty': args.qty or 1, 'qty': args.qty or 1,
'rate': args.rate or 10000, 'rate': args.rate or 10000,
'cost_center': 'Main - _TC', 'cost_center': 'Main - _TC',
'expense_account': 'Cost of Goods Sold - _TC' 'expense_account': 'Cost of Goods Sold - _TC',
'warehouse': args.warehouse or '_Test Warehouse - _TC'
}] }]
}) })

View File

@ -51,7 +51,11 @@ def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_
"from_date": start_date "from_date": start_date
}) })
if i==0 and filter_based_on == 'Date Range':
to_date = add_months(get_first_day(start_date), months_to_add)
else:
to_date = add_months(start_date, months_to_add) to_date = add_months(start_date, months_to_add)
start_date = to_date start_date = to_date
# Subtract one day from to_date, as it may be first day in next fiscal year or month # Subtract one day from to_date, as it may be first day in next fiscal year or month

View File

@ -829,10 +829,10 @@ class AccountsController(TransactionBase):
party_account_currency = get_party_account_currency(party_type, party, self.company) party_account_currency = get_party_account_currency(party_type, party, self.company)
if (party_account_currency if (party_account_currency
and party_account_currency != self.company_currency and (self.currency != party_account_currency
and self.currency != party_account_currency): and self.currency != self.company_currency)):
frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}") frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")
.format(party_type, party, party_account_currency), InvalidCurrency) .format(party_type, party, frappe.bold(party_account_currency), InvalidCurrency))
# Note: not validating with gle account because we don't have the account # Note: not validating with gle account because we don't have the account
# at quotation / sales order level and we shouldn't stop someone # at quotation / sales order level and we shouldn't stop someone
@ -898,7 +898,7 @@ class AccountsController(TransactionBase):
date = self.get("due_date") date = self.get("due_date")
due_date = date or posting_date due_date = date or posting_date
if party_account_currency == self.company_currency: if self.company_currency == self.currency:
grand_total = self.get("base_rounded_total") or self.base_grand_total grand_total = self.get("base_rounded_total") or self.base_grand_total
else: else:
grand_total = self.get("rounded_total") or self.grand_total grand_total = self.get("rounded_total") or self.grand_total
@ -959,7 +959,7 @@ class AccountsController(TransactionBase):
for d in self.get("payment_schedule"): for d in self.get("payment_schedule"):
total += flt(d.payment_amount) total += flt(d.payment_amount)
if party_account_currency == self.company_currency: if self.company_currency == self.currency:
total = flt(total, self.precision("base_grand_total")) total = flt(total, self.precision("base_grand_total"))
grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total')) grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total'))
else: else:

View File

@ -795,7 +795,7 @@ class init_landed_taxes_and_totals(object):
for d in self.doc.get(self.tax_field): for d in self.doc.get(self.tax_field):
if d.account_currency == company_currency: if d.account_currency == company_currency:
d.exchange_rate = 1 d.exchange_rate = 1
elif not d.exchange_rate or d.exchange_rate == 1 or self.doc.posting_date: elif not d.exchange_rate:
d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account, d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
account_currency=d.account_currency, company=self.doc.company) account_currency=d.account_currency, company=self.doc.company)

View File

@ -248,7 +248,6 @@ def make_quotation(source_name, target_doc=None):
"doctype": "Quotation", "doctype": "Quotation",
"field_map": { "field_map": {
"opportunity_from": "quotation_to", "opportunity_from": "quotation_to",
"opportunity_type": "order_type",
"name": "enq_no", "name": "enq_no",
} }
}, },

View File

@ -10,6 +10,7 @@
"naming_series", "naming_series",
"student", "student",
"student_name", "student_name",
"student_mobile_number",
"course_schedule", "course_schedule",
"student_group", "student_group",
"column_break_3", "column_break_3",
@ -93,11 +94,19 @@
"options": "Student Attendance", "options": "Student Attendance",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
},
{
"fetch_from": "student.student_mobile_number",
"fieldname": "student_mobile_number",
"fieldtype": "Read Only",
"label": "Student Mobile Number",
"options": "Phone"
} }
], ],
"index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-07-08 13:55:42.580181", "modified": "2021-03-24 00:02:11.005895",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Student Attendance", "name": "Student Attendance",

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest, os, json import unittest, os, json
from frappe.utils import cstr from frappe.utils import cstr, cint
from erpnext.erpnext_integrations.connectors.shopify_connection import create_order from erpnext.erpnext_integrations.connectors.shopify_connection import create_order
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import make_item from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import make_item
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer
@ -13,9 +13,14 @@ from frappe.core.doctype.data_import.data_import import import_doc
class ShopifySettings(unittest.TestCase): class ShopifySettings(unittest.TestCase):
def setUp(self): @classmethod
def setUpClass(cls):
frappe.set_user("Administrator") frappe.set_user("Administrator")
cls.allow_negative_stock = cint(frappe.db.get_value('Stock Settings', None, 'allow_negative_stock'))
if not cls.allow_negative_stock:
frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 1)
# use the fixture data # use the fixture data
import_doc(frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json")) import_doc(frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json"))
@ -24,9 +29,15 @@ class ShopifySettings(unittest.TestCase):
frappe.reload_doctype("Delivery Note") frappe.reload_doctype("Delivery Note")
frappe.reload_doctype("Sales Invoice") frappe.reload_doctype("Sales Invoice")
self.setup_shopify() cls.setup_shopify()
def setup_shopify(self): @classmethod
def tearDownClass(cls):
if not cls.allow_negative_stock:
frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0)
@classmethod
def setup_shopify(cls):
shopify_settings = frappe.get_doc("Shopify Settings") shopify_settings = frappe.get_doc("Shopify Settings")
shopify_settings.taxes = [] shopify_settings.taxes = []
@ -56,21 +67,20 @@ class ShopifySettings(unittest.TestCase):
"delivery_note_series": "DN-" "delivery_note_series": "DN-"
}).save(ignore_permissions=True) }).save(ignore_permissions=True)
self.shopify_settings = shopify_settings cls.shopify_settings = shopify_settings
def test_order(self): def test_order(self):
### Create Customer ### # Create Customer
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer: with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer:
shopify_customer = json.load(shopify_customer) shopify_customer = json.load(shopify_customer)
create_customer(shopify_customer.get("customer"), self.shopify_settings) create_customer(shopify_customer.get("customer"), self.shopify_settings)
### Create Item ### # Create Item
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_item.json")) as shopify_item: with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_item.json")) as shopify_item:
shopify_item = json.load(shopify_item) shopify_item = json.load(shopify_item)
make_item("_Test Warehouse - _TC", shopify_item.get("product")) make_item("_Test Warehouse - _TC", shopify_item.get("product"))
# Create Order
### Create Order ###
with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_order.json")) as shopify_order: with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_order.json")) as shopify_order:
shopify_order = json.load(shopify_order) shopify_order = json.load(shopify_order)
@ -80,17 +90,17 @@ class ShopifySettings(unittest.TestCase):
self.assertEqual(cstr(shopify_order.get("order").get("id")), sales_order.shopify_order_id) self.assertEqual(cstr(shopify_order.get("order").get("id")), sales_order.shopify_order_id)
#check for customer # Check for customer
shopify_order_customer_id = cstr(shopify_order.get("order").get("customer").get("id")) shopify_order_customer_id = cstr(shopify_order.get("order").get("customer").get("id"))
sales_order_customer_id = frappe.get_value("Customer", sales_order.customer, "shopify_customer_id") sales_order_customer_id = frappe.get_value("Customer", sales_order.customer, "shopify_customer_id")
self.assertEqual(shopify_order_customer_id, sales_order_customer_id) self.assertEqual(shopify_order_customer_id, sales_order_customer_id)
#check sales invoice # Check sales invoice
sales_invoice = frappe.get_doc("Sales Invoice", {"shopify_order_id": sales_order.shopify_order_id}) sales_invoice = frappe.get_doc("Sales Invoice", {"shopify_order_id": sales_order.shopify_order_id})
self.assertEqual(sales_invoice.rounded_total, sales_order.rounded_total) self.assertEqual(sales_invoice.rounded_total, sales_order.rounded_total)
#check delivery note # Check delivery note
delivery_note_count = frappe.db.sql("""select count(*) from `tabDelivery Note` delivery_note_count = frappe.db.sql("""select count(*) from `tabDelivery Note`
where shopify_order_id = %s""", sales_order.shopify_order_id)[0][0] where shopify_order_id = %s""", sales_order.shopify_order_id)[0][0]

View File

@ -53,7 +53,7 @@
"discharge_ordered_date", "discharge_ordered_date",
"discharge_practitioner", "discharge_practitioner",
"discharge_encounter", "discharge_encounter",
"discharge_date", "discharge_datetime",
"cb_discharge", "cb_discharge",
"discharge_instructions", "discharge_instructions",
"followup_date", "followup_date",
@ -404,14 +404,15 @@
"permlevel": 1 "permlevel": 1
}, },
{ {
"fieldname": "discharge_date", "fieldname": "discharge_datetime",
"fieldtype": "Date", "fieldtype": "Datetime",
"label": "Discharge Date", "label": "Discharge Date",
"read_only": 1 "read_only": 1
} }
], ],
"index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2020-05-21 02:26:22.144575", "modified": "2021-03-18 14:44:11.689956",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Inpatient Record", "name": "Inpatient Record",

View File

@ -151,7 +151,7 @@ def check_out_inpatient(inpatient_record):
def discharge_patient(inpatient_record): def discharge_patient(inpatient_record):
validate_inpatient_invoicing(inpatient_record) validate_inpatient_invoicing(inpatient_record)
inpatient_record.discharge_date = today() inpatient_record.discharge_datetime = now_datetime()
inpatient_record.status = "Discharged" inpatient_record.status = "Discharged"
inpatient_record.save(ignore_permissions = True) inpatient_record.save(ignore_permissions = True)

View File

@ -324,6 +324,7 @@ scheduler_events = {
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts", "erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
"erpnext.support.doctype.issue.issue.set_service_level_agreement_variance", "erpnext.support.doctype.issue.issue.set_service_level_agreement_variance",
"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders", "erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries"
], ],
"daily": [ "daily": [
"erpnext.stock.reorder_item.reorder_item", "erpnext.stock.reorder_item.reorder_item",

View File

@ -13,11 +13,21 @@ class TestJobApplicant(unittest.TestCase):
def create_job_applicant(**args): def create_job_applicant(**args):
args = frappe._dict(args) args = frappe._dict(args)
job_applicant = frappe.get_doc({
"doctype": "Job Applicant", filters = {
"applicant_name": args.applicant_name or "_Test Applicant", "applicant_name": args.applicant_name or "_Test Applicant",
"email_id": args.email_id or "test_applicant@example.com", "email_id": args.email_id or "test_applicant@example.com",
}
if frappe.db.exists("Job Applicant", filters):
return frappe.get_doc("Job Applicant", filters)
job_applicant = frappe.get_doc({
"doctype": "Job Applicant",
"status": args.status or "Open" "status": args.status or "Open"
}) })
job_applicant.update(filters)
job_applicant.save() job_applicant.save()
return job_applicant return job_applicant

View File

@ -13,14 +13,15 @@ from erpnext.hr.doctype.staffing_plan.test_staffing_plan import make_company
class TestJobOffer(unittest.TestCase): class TestJobOffer(unittest.TestCase):
def test_job_offer_creation_against_vacancies(self): def test_job_offer_creation_against_vacancies(self):
create_staffing_plan(staffing_details=[{ frappe.db.set_value("HR Settings", None, "check_vacancies", 1)
"designation": "Designer", job_applicant = create_job_applicant(email_id="test_job_offer@example.com")
job_offer = create_job_offer(job_applicant=job_applicant.name, designation="UX Designer")
create_staffing_plan(name='Test No Vacancies', staffing_details=[{
"designation": "UX Designer",
"vacancies": 0, "vacancies": 0,
"estimated_cost_per_position": 5000 "estimated_cost_per_position": 5000
}]) }])
frappe.db.set_value("HR Settings", None, "check_vacancies", 1)
job_applicant = create_job_applicant(email_id="test_job_offer@example.com")
job_offer = create_job_offer(job_applicant=job_applicant.name, designation="Researcher")
self.assertRaises(frappe.ValidationError, job_offer.submit) self.assertRaises(frappe.ValidationError, job_offer.submit)
# test creation of job offer when vacancies are not present # test creation of job offer when vacancies are not present

View File

@ -39,6 +39,7 @@ class StaffingPlan(Document):
detail.current_count = designation_counts['employee_count'] detail.current_count = designation_counts['employee_count']
detail.current_openings = designation_counts['job_openings'] detail.current_openings = designation_counts['job_openings']
detail.total_estimated_cost = 0
if detail.number_of_positions > 0: if detail.number_of_positions > 0:
if detail.vacancies > 0 and detail.estimated_cost_per_position: if detail.vacancies > 0 and detail.estimated_cost_per_position:
detail.total_estimated_cost = cint(detail.vacancies) * flt(detail.estimated_cost_per_position) detail.total_estimated_cost = cint(detail.vacancies) * flt(detail.estimated_cost_per_position)

View File

@ -275,6 +275,11 @@ class TestLoan(unittest.TestCase):
frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250 frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250
where loan_security='Test Security 2'""") where loan_security='Test Security 2'""")
create_process_loan_security_shortfall()
loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name})
self.assertEquals(loan_security_shortfall.status, "Completed")
self.assertEquals(loan_security_shortfall.shortfall_amount, 0)
def test_loan_security_unpledge(self): def test_loan_security_unpledge(self):
pledge = [{ pledge = [{
"loan_security": "Test Security 1", "loan_security": "Test Security 1",

View File

@ -55,6 +55,9 @@ def check_for_ltv_shortfall(process_loan_security_shortfall):
'total_interest_payable', 'disbursed_amount', 'status'], 'total_interest_payable', 'disbursed_amount', 'status'],
filters={'status': ('in',['Disbursed','Partially Disbursed']), 'is_secured_loan': 1}) filters={'status': ('in',['Disbursed','Partially Disbursed']), 'is_secured_loan': 1})
loan_shortfall_map = frappe._dict(frappe.get_all("Loan Security Shortfall",
fields=["loan", "name"], filters={"status": "Pending"}, as_list=1))
loan_security_map = {} loan_security_map = {}
for loan in loans: for loan in loans:
@ -71,14 +74,19 @@ def check_for_ltv_shortfall(process_loan_security_shortfall):
for security, qty in pledged_securities.items(): for security, qty in pledged_securities.items():
if not ltv_ratio: if not ltv_ratio:
ltv_ratio = get_ltv_ratio(security) ltv_ratio = get_ltv_ratio(security)
security_value += loan_security_price_map.get(security) * qty security_value += flt(loan_security_price_map.get(security)) * flt(qty)
current_ratio = (outstanding_amount/security_value) * 100 current_ratio = (outstanding_amount/security_value) * 100 if security_value else 0
if current_ratio > ltv_ratio: if current_ratio > ltv_ratio:
shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100) shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100)
create_loan_security_shortfall(loan.name, outstanding_amount, security_value, shortfall_amount, create_loan_security_shortfall(loan.name, outstanding_amount, security_value, shortfall_amount,
process_loan_security_shortfall) process_loan_security_shortfall)
elif loan_shortfall_map.get(loan.name):
shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100)
if shortfall_amount <= 0:
shortfall = loan_shortfall_map.get(loan.name)
update_pending_shortfall(shortfall)
def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, process_loan_security_shortfall): def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, process_loan_security_shortfall):
existing_shortfall = frappe.db.get_value("Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name") existing_shortfall = frappe.db.get_value("Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name")
@ -101,3 +109,11 @@ def get_ltv_ratio(loan_security):
ltv_ratio = frappe.db.get_value('Loan Security Type', loan_security_type, 'loan_to_value_ratio') ltv_ratio = frappe.db.get_value('Loan Security Type', loan_security_type, 'loan_to_value_ratio')
return ltv_ratio return ltv_ratio
def update_pending_shortfall(shortfall):
# Get all pending loan security shortfall
frappe.db.set_value("Loan Security Shortfall", shortfall,
{
"status": "Completed",
"shortfall_amount": 0
})

View File

@ -255,6 +255,9 @@ class JobCard(Document):
data.actual_operation_time = time_in_mins data.actual_operation_time = time_in_mins
data.actual_start_time = time_data[0].start_time if time_data else None data.actual_start_time = time_data[0].start_time if time_data else None
data.actual_end_time = time_data[0].end_time if time_data else None data.actual_end_time = time_data[0].end_time if time_data else None
if data.get("workstation") != self.workstation:
# workstations can change in a job card
data.workstation = self.workstation
wo.flags.ignore_validate_update_after_submit = True wo.flags.ignore_validate_update_after_submit = True
wo.update_operation_status() wo.update_operation_status()

View File

@ -333,8 +333,7 @@
"fieldname": "operations", "fieldname": "operations",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Operations", "label": "Operations",
"options": "Work Order Operation", "options": "Work Order Operation"
"read_only": 1
}, },
{ {
"depends_on": "operations", "depends_on": "operations",
@ -496,7 +495,7 @@
"image_field": "image", "image_field": "image",
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-05 19:32:43.323054", "modified": "2021-03-16 13:27:51.116484",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Work Order", "name": "Work Order",

View File

@ -61,7 +61,7 @@ class ForecastingReport(ExponentialSmoothingForecast):
from_date = add_years(self.filters.from_date, cint(self.filters.no_of_years) * -1) from_date = add_years(self.filters.from_date, cint(self.filters.no_of_years) * -1)
self.period_list = get_period_list(from_date, self.filters.to_date, self.period_list = get_period_list(from_date, self.filters.to_date,
from_date, self.filters.to_date, None, self.filters.periodicity, ignore_fiscal_year=True) from_date, self.filters.to_date, "Date Range", self.filters.periodicity, ignore_fiscal_year=True)
order_data = self.get_data_for_forecast() or [] order_data = self.get_data_for_forecast() or []

View File

@ -48,7 +48,7 @@ class Membership(Document):
last_membership = erpnext.get_last_membership(self.member) last_membership = erpnext.get_last_membership(self.member)
# if person applied for offline membership # if person applied for offline membership
if last_membership and last_membership != self.name and not frappe.session.user == "Administrator": if last_membership and last_membership.name != self.name and not frappe.session.user == "Administrator":
# if last membership does not expire in 30 days, then do not allow to renew # if last membership does not expire in 30 days, then do not allow to renew
if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) : if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) :
frappe.throw(_("You can only renew if your membership expires within 30 days")) frappe.throw(_("You can only renew if your membership expires within 30 days"))
@ -90,6 +90,7 @@ class Membership(Document):
self.validate_membership_type_and_settings(plan, settings) self.validate_membership_type_and_settings(plan, settings)
invoice = make_invoice(self, member, plan, settings) invoice = make_invoice(self, member, plan, settings)
self.reload()
self.invoice = invoice.name self.invoice = invoice.name
if with_payment_entry: if with_payment_entry:
@ -284,6 +285,7 @@ def trigger_razorpay_subscription(*args, **kwargs):
settings = frappe.get_doc("Non Profit Settings") settings = frappe.get_doc("Non Profit Settings")
if settings.allow_invoicing and settings.automate_membership_invoicing: if settings.allow_invoicing and settings.automate_membership_invoicing:
membership.reload()
membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True) membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True)
except Exception as e: except Exception as e:

View File

@ -760,3 +760,4 @@ erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae
execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext')
erpnext.patches.v13_0.rename_discharge_date_in_ip_record

View File

@ -0,0 +1,8 @@
from __future__ import unicode_literals
import frappe
from frappe.model.utils.rename_field import rename_field
def execute():
frappe.reload_doc("Healthcare", "doctype", "Inpatient Record")
if frappe.db.has_column("Inpatient Record", "discharge_date"):
rename_field("Inpatient Record", "discharge_date", "discharge_datetime")

View File

@ -12,7 +12,7 @@ from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.payroll.doctype.salary_slip.test_salary_slip import get_salary_component_account, \ from erpnext.payroll.doctype.salary_slip.test_salary_slip import get_salary_component_account, \
make_earning_salary_component, make_deduction_salary_component, create_account, make_employee_salary_slip make_earning_salary_component, make_deduction_salary_component, create_account, make_employee_salary_slip
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure, create_salary_structure_assignment from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure, create_salary_structure_assignment
from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry, create_loan_type, create_loan_accounts
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
class TestPayrollEntry(unittest.TestCase): class TestPayrollEntry(unittest.TestCase):
@ -168,15 +168,23 @@ class TestPayrollEntry(unittest.TestCase):
salary_structure = "Test Salary Structure for Loan" salary_structure = "Test Salary Structure for Loan"
make_salary_structure(salary_structure, "Monthly", employee=employee_doc.name, company="_Test Company", currency=company_doc.default_currency) make_salary_structure(salary_structure, "Monthly", employee=employee_doc.name, company="_Test Company", currency=company_doc.default_currency)
if not frappe.db.exists("Loan Type", "Car Loan"):
create_loan_accounts()
create_loan_type("Car Loan", 500000, 8.4,
is_term_loan=1,
mode_of_payment='Cash',
payment_account='Payment Account - _TC',
loan_account='Loan Account - _TC',
interest_income_account='Interest Income Account - _TC',
penalty_income_account='Penalty Income Account - _TC')
loan = create_loan(applicant, "Car Loan", 280000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1)) loan = create_loan(applicant, "Car Loan", 280000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1))
loan.repay_from_salary = 1 loan.repay_from_salary = 1
loan.submit() loan.submit()
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=add_months(nowdate(), -1)) make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=add_months(nowdate(), -1))
process_loan_interest_accrual_for_term_loans(posting_date=nowdate()) process_loan_interest_accrual_for_term_loans(posting_date=nowdate())
dates = get_start_end_dates('Monthly', nowdate()) dates = get_start_end_dates('Monthly', nowdate())
make_payroll_entry(company="_Test Company", start_date=dates.start_date, payable_account=company_doc.default_payroll_payable_account, make_payroll_entry(company="_Test Company", start_date=dates.start_date, payable_account=company_doc.default_payroll_payable_account,
currency=company_doc.default_currency, end_date=dates.end_date, branch=branch, cost_center="Main - _TC", payment_account="Cash - _TC") currency=company_doc.default_currency, end_date=dates.end_date, branch=branch, cost_center="Main - _TC", payment_account="Cash - _TC")

View File

@ -738,21 +738,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
} }
else { else {
var valid_serial_nos = []; var valid_serial_nos = [];
var serialnos = [];
// Replacing all occurences of comma with carriage return // Replacing all occurences of comma with carriage return
var serial_nos = item.serial_no.trim().replace(/,/g, '\n'); item.serial_no = item.serial_no.replace(/,/g, '\n');
serialnos = item.serial_no.split("\n");
serial_nos = serial_nos.trim().split('\n'); for (var i = 0; i < serialnos.length; i++) {
if (serialnos[i] != "") {
// Trim each string and push unique string to new list valid_serial_nos.push(serialnos[i]);
for (var x=0; x<=serial_nos.length - 1; x++) {
if (serial_nos[x].trim() != "" && valid_serial_nos.indexOf(serial_nos[x].trim()) == -1) {
valid_serial_nos.push(serial_nos[x].trim());
} }
} }
// Add the new list to the serial no. field in grid with each in new line
item.serial_no = valid_serial_nos.join('\n');
item.conversion_factor = item.conversion_factor || 1; item.conversion_factor = item.conversion_factor || 1;
refresh_field("serial_no", item.name, item.parentfield); refresh_field("serial_no", item.name, item.parentfield);

View File

@ -349,13 +349,12 @@ class GSTR3BReport(Document):
return inter_state_supply_details return inter_state_supply_details
def get_inward_nil_exempt(self, state): def get_inward_nil_exempt(self, state):
inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount, inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount,
i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i
where p.docstatus = 1 and p.name = i.parent where p.docstatus = 1 and p.name = i.parent
and i.is_nil_exempt = 1 or i.is_non_gst = 1 and and (i.is_nil_exempt = 1 or i.is_non_gst = 1) and
month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s
group by p.place_of_supply """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1) group by p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
inward_nil_exempt_details = { inward_nil_exempt_details = {
"gst": { "gst": {

View File

@ -1,11 +1,12 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe
import json import json
from frappe.utils import flt, add_days, nowdate
import frappe.permissions
import unittest import unittest
import frappe
import frappe.permissions
from frappe.utils import flt, add_days, nowdate
from frappe.core.doctype.user_permission.test_user_permission import create_user
from erpnext.selling.doctype.sales_order.sales_order \ from erpnext.selling.doctype.sales_order.sales_order \
import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
@ -444,10 +445,8 @@ class TestSalesOrder(unittest.TestCase):
def test_update_child_perm(self): def test_update_child_perm(self):
so = make_sales_order(item_code= "_Test Item", qty=4) so = make_sales_order(item_code= "_Test Item", qty=4)
user = 'test@example.com' test_user = create_user("test_so_child_perms@example.com", "Accounts User")
test_user = frappe.get_doc('User', user) frappe.set_user(test_user.name)
test_user.add_roles("Accounts User")
frappe.set_user(user)
# update qty # update qty
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': so.items[0].name}]) trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': so.items[0].name}])
@ -456,18 +455,14 @@ class TestSalesOrder(unittest.TestCase):
# add new item # add new item
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}]) trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}])
self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name) self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
test_user.remove_roles("Accounts User")
frappe.set_user("Administrator")
def test_update_child_qty_rate_with_workflow(self): def test_update_child_qty_rate_with_workflow(self):
from frappe.model.workflow import apply_workflow from frappe.model.workflow import apply_workflow
frappe.set_user("Administrator")
workflow = make_sales_order_workflow() workflow = make_sales_order_workflow()
so = make_sales_order(item_code= "_Test Item", qty=1, rate=150, do_not_submit=1) so = make_sales_order(item_code= "_Test Item", qty=1, rate=150, do_not_submit=1)
apply_workflow(so, 'Approve') apply_workflow(so, 'Approve')
frappe.set_user("Administrator")
user = 'test@example.com' user = 'test@example.com'
test_user = frappe.get_doc('User', user) test_user = frappe.get_doc('User', user)
test_user.add_roles("Sales User", "Test Junior Approver") test_user.add_roles("Sales User", "Test Junior Approver")
@ -618,33 +613,31 @@ class TestSalesOrder(unittest.TestCase):
frappe.db.set_value("Stock Settings", None, "default_warehouse", old_stock_settings_value) frappe.db.set_value("Stock Settings", None, "default_warehouse", old_stock_settings_value)
def test_warehouse_user(self): def test_warehouse_user(self):
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com") test_user = create_user("test_so_warehouse_user@example.com", "Sales User", "Stock User")
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com")
frappe.permissions.add_user_permission("Company", "_Test Company 1", "test2@example.com")
test_user = frappe.get_doc("User", "test@example.com")
test_user.add_roles("Sales User", "Stock User")
test_user.remove_roles("Sales Manager")
test_user_2 = frappe.get_doc("User", "test2@example.com") test_user_2 = frappe.get_doc("User", "test2@example.com")
test_user_2.add_roles("Sales User", "Stock User") test_user_2.add_roles("Sales User", "Stock User")
test_user_2.remove_roles("Sales Manager") test_user_2.remove_roles("Sales Manager")
frappe.set_user("test@example.com") frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", test_user.name)
frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", test_user_2.name)
frappe.permissions.add_user_permission("Company", "_Test Company 1", test_user_2.name)
so = make_sales_order(company="_Test Company 1", frappe.set_user(test_user.name)
so = make_sales_order(company="_Test Company 1", customer="_Test Customer 1",
warehouse="_Test Warehouse 2 - _TC1", do_not_save=True) warehouse="_Test Warehouse 2 - _TC1", do_not_save=True)
so.conversion_rate = 0.02 so.conversion_rate = 0.02
so.plc_conversion_rate = 0.02 so.plc_conversion_rate = 0.02
self.assertRaises(frappe.PermissionError, so.insert) self.assertRaises(frappe.PermissionError, so.insert)
frappe.set_user("test2@example.com") frappe.set_user(test_user_2.name)
so.insert() so.insert()
frappe.set_user("Administrator") frappe.set_user("Administrator")
frappe.permissions.remove_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com") frappe.permissions.remove_user_permission("Warehouse", "_Test Warehouse 1 - _TC", test_user.name)
frappe.permissions.remove_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com") frappe.permissions.remove_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", test_user_2.name)
frappe.permissions.remove_user_permission("Company", "_Test Company 1", "test2@example.com") frappe.permissions.remove_user_permission("Company", "_Test Company 1", test_user_2.name)
def test_block_delivery_note_against_cancelled_sales_order(self): def test_block_delivery_note_against_cancelled_sales_order(self):
so = make_sales_order() so = make_sales_order()

View File

@ -397,6 +397,7 @@ erpnext.PointOfSale.Controller = class {
this.recent_order_list.toggle_component(false); this.recent_order_list.toggle_component(false);
frappe.run_serially([ frappe.run_serially([
() => this.frm.refresh(name), () => this.frm.refresh(name),
() => this.frm.call('reset_mode_of_payments'),
() => this.cart.load_invoice(), () => this.cart.load_invoice(),
() => this.item_selector.toggle_component(true) () => this.item_selector.toggle_component(true)
]); ]);

View File

@ -64,10 +64,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
{fieldname: 'print', fieldtype: 'Data', label: 'Print Preview'} {fieldname: 'print', fieldtype: 'Data', label: 'Print Preview'}
], ],
primary_action: () => { primary_action: () => {
const frm = this.events.get_frm(); this.print_receipt();
frm.doc = this.doc;
frm.print_preview.lang_code = frm.doc.language;
frm.print_preview.printit(true);
}, },
primary_action_label: __('Print'), primary_action_label: __('Print'),
}); });
@ -192,13 +189,21 @@ erpnext.PointOfSale.PastOrderSummary = class {
}); });
this.$summary_container.on('click', '.print-btn', () => { this.$summary_container.on('click', '.print-btn', () => {
const frm = this.events.get_frm(); this.print_receipt();
frm.doc = this.doc;
frm.print_preview.lang_code = frm.doc.language;
frm.print_preview.printit(true);
}); });
} }
print_receipt() {
const frm = this.events.get_frm();
frappe.utils.print(
frm.doctype,
frm.docname,
frm.pos_print_format,
frm.doc.letter_head,
frm.doc.language || frappe.boot.lang
);
}
attach_shortcuts() { attach_shortcuts() {
const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl'; const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl';
this.$summary_container.find('.print-btn').attr("title", `${ctrl_label}+P`); this.$summary_container.find('.print-btn').attr("title", `${ctrl_label}+P`);

View File

@ -10,6 +10,7 @@ from frappe import msgprint, throw, _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.model.naming import parse_naming_series from frappe.model.naming import parse_naming_series
from frappe.permissions import get_doctypes_with_read from frappe.permissions import get_doctypes_with_read
from frappe.core.doctype.doctype.doctype import validate_series
class NamingSeriesNotSetError(frappe.ValidationError): pass class NamingSeriesNotSetError(frappe.ValidationError): pass
@ -126,7 +127,7 @@ class NamingSeries(Document):
dt = frappe.get_doc("DocType", self.select_doc_for_series) dt = frappe.get_doc("DocType", self.select_doc_for_series)
options = self.scrub_options_list(self.set_options.split("\n")) options = self.scrub_options_list(self.set_options.split("\n"))
for series in options: for series in options:
dt.validate_series(series) validate_series(dt, series)
for i in sr: for i in sr:
if i[0]: if i[0]:
existing_series = [d.split('.')[0] for d in i[0].split("\n")] existing_series = [d.split('.')[0] for d in i[0].split("\n")]

View File

@ -25,8 +25,8 @@ class PickList(Document):
if not frappe.get_cached_value('Item', item.item_code, 'has_serial_no'): if not frappe.get_cached_value('Item', item.item_code, 'has_serial_no'):
continue continue
if not item.serial_no: if not item.serial_no:
frappe.throw(_("Row #{0}: {1} does not have any available serial numbers in {2}".format( frappe.throw(_("Row #{0}: {1} does not have any available serial numbers in {2}").format(
frappe.bold(item.idx), frappe.bold(item.item_code), frappe.bold(item.warehouse))), frappe.bold(item.idx), frappe.bold(item.item_code), frappe.bold(item.warehouse)),
title=_("Serial Nos Required")) title=_("Serial Nos Required"))
if len(item.serial_no.split('\n')) == item.picked_qty: if len(item.serial_no.split('\n')) == item.picked_qty:
continue continue
@ -380,7 +380,7 @@ def create_stock_entry(pick_list):
stock_entry.set_incoming_rate() stock_entry.set_incoming_rate()
stock_entry.set_actual_qty() stock_entry.set_actual_qty()
stock_entry.calculate_rate_and_amount(update_finished_item_rate=False) stock_entry.calculate_rate_and_amount()
return stock_entry.as_dict() return stock_entry.as_dict()

View File

@ -5,7 +5,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cint, get_link_to_form from frappe.utils import cint, get_link_to_form, add_to_date, today
from erpnext.stock.stock_ledger import repost_future_sle from erpnext.stock.stock_ledger import repost_future_sle
from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced
from frappe.utils.user import get_users_with_role from frappe.utils.user import get_users_with_role
@ -54,7 +54,6 @@ def repost(doc):
repost_sl_entries(doc) repost_sl_entries(doc)
repost_gl_entries(doc) repost_gl_entries(doc)
check_if_stock_and_account_balance_synced(doc.posting_date, doc.company)
doc.set_status('Completed') doc.set_status('Completed')
except Exception: except Exception:
@ -112,4 +111,24 @@ def notify_error_to_stock_managers(doc, traceback):
) )
frappe.sendmail(recipients=recipients, subject=subject, message=message) frappe.sendmail(recipients=recipients, subject=subject, message=message)
def repost_entries():
riv_entries = get_repost_item_valuation_entries()
for row in riv_entries:
doc = frappe.get_cached_doc('Repost Item Valuation', row.name)
repost(doc)
riv_entries = get_repost_item_valuation_entries()
if riv_entries:
return
for d in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}):
check_if_stock_and_account_balance_synced(today(), d.company)
def get_repost_item_valuation_entries():
date = add_to_date(today(), hours=-12)
return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation`
WHERE status != 'Completed' and creation <= %s and docstatus = 1
ORDER BY timestamp(posting_date, posting_time) asc, creation asc
""", date, as_dict=1)

View File

@ -29,6 +29,8 @@ class StockReconciliation(StockController):
self.remove_items_with_no_change() self.remove_items_with_no_change()
self.validate_data() self.validate_data()
self.validate_expense_account() self.validate_expense_account()
self.validate_customer_provided_item()
self.set_zero_value_for_customer_provided_items()
self.set_total_qty_and_amount() self.set_total_qty_and_amount()
self.validate_putaway_capacity() self.validate_putaway_capacity()
@ -217,7 +219,7 @@ class StockReconciliation(StockController):
if row.valuation_rate in ("", None): if row.valuation_rate in ("", None):
row.valuation_rate = previous_sle.get("valuation_rate", 0) row.valuation_rate = previous_sle.get("valuation_rate", 0)
if row.qty and not row.valuation_rate: if row.qty and not row.valuation_rate and not row.allow_zero_valuation_rate:
frappe.throw(_("Valuation Rate required for Item {0} at row {1}").format(row.item_code, row.idx)) 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") if ((previous_sle and row.qty == previous_sle.get("qty_after_transaction")
@ -436,6 +438,20 @@ class StockReconciliation(StockController):
if frappe.db.get_value("Account", self.expense_account, "report_type") == "Profit and Loss": if frappe.db.get_value("Account", self.expense_account, "report_type") == "Profit and Loss":
frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Reconciliation is an Opening Entry"), OpeningEntryAccountError) frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Reconciliation is an Opening Entry"), OpeningEntryAccountError)
def set_zero_value_for_customer_provided_items(self):
changed_any_values = False
for d in self.get('items'):
is_customer_item = frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item')
if is_customer_item and d.valuation_rate:
d.valuation_rate = 0.0
changed_any_values = True
if changed_any_values:
msgprint(_("Valuation rate for customer provided items has been set to zero."),
title=_("Note"), indicator="blue")
def set_total_qty_and_amount(self): def set_total_qty_and_amount(self):
for d in self.get("items"): for d in self.get("items"):
d.amount = flt(d.qty, d.precision("qty")) * flt(d.valuation_rate, d.precision("valuation_rate")) d.amount = flt(d.qty, d.precision("qty")) * flt(d.valuation_rate, d.precision("valuation_rate"))

View File

@ -193,6 +193,16 @@ class TestStockReconciliation(unittest.TestCase):
stock_doc = frappe.get_doc("Stock Reconciliation", d) stock_doc = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel() stock_doc.cancel()
def test_customer_provided_items(self):
item_code = 'Stock-Reco-customer-Item-100'
create_item(item_code, is_customer_provided_item = 1,
customer = '_Test Customer', is_purchase_item = 0)
sr = create_stock_reconciliation(item_code = item_code, qty = 10, rate = 420)
self.assertEqual(sr.get("items")[0].allow_zero_valuation_rate, 1)
self.assertEqual(sr.get("items")[0].valuation_rate, 0)
self.assertEqual(sr.get("items")[0].amount, 0)
def insert_existing_sle(warehouse): def insert_existing_sle(warehouse):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

View File

@ -13,6 +13,7 @@
"qty", "qty",
"valuation_rate", "valuation_rate",
"amount", "amount",
"allow_zero_valuation_rate",
"serial_no_and_batch_section", "serial_no_and_batch_section",
"serial_no", "serial_no",
"column_break_11", "column_break_11",
@ -166,10 +167,19 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Batch No", "label": "Batch No",
"options": "Batch" "options": "Batch"
},
{
"default": "0",
"fieldname": "allow_zero_valuation_rate",
"fieldtype": "Check",
"label": "Allow Zero Valuation Rate",
"print_hide": 1,
"read_only": 1
} }
], ],
"istable": 1, "istable": 1,
"modified": "2019-06-14 17:10:53.188305", "links": [],
"modified": "2021-03-23 11:09:44.407157",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Reconciliation Item", "name": "Stock Reconciliation Item",

View File

@ -198,7 +198,7 @@ def get_item_warehouse_map(filters, sle):
else: else:
qty_diff = flt(d.actual_qty) qty_diff = flt(d.actual_qty)
value_diff = flt(d.stock_value) - flt(qty_dict.bal_val) value_diff = flt(d.stock_value_difference)
if d.posting_date < from_date: if d.posting_date < from_date:
qty_dict.opening_qty += qty_diff qty_dict.opening_qty += qty_diff