Merge pull request #27039 from nextchamp-saqib/common-party-acc
feat: common party accounting
diff --git a/erpnext/accounts/custom/address.py b/erpnext/accounts/custom/address.py
index c417a49..834227b 100644
--- a/erpnext/accounts/custom/address.py
+++ b/erpnext/accounts/custom/address.py
@@ -1,7 +1,7 @@
import frappe
from frappe import _
from frappe.contacts.doctype.address.address import Address
-from frappe.contacts.doctype.address.address import get_address_templates
+from frappe.contacts.doctype.address.address import get_address_templates, get_address_display
class ERPNextAddress(Address):
def validate(self):
@@ -22,6 +22,16 @@
frappe.throw(_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
title=_("Company Not Linked"))
+ def on_update(self):
+ """
+ After Address is updated, update the related 'Primary Address' on Customer.
+ """
+ address_display = get_address_display(self.as_dict())
+ filters = { "customer_primary_address": self.name }
+ customers = frappe.db.get_all("Customer", filters=filters, as_list=True)
+ for customer_name in customers:
+ frappe.db.set_value("Customer", customer_name[0], "primary_address", address_display)
+
@frappe.whitelist()
def get_shipping_address(company, address = None):
filters = [
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index 0544a46..7ea71fc 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -55,6 +55,11 @@
self.clear_sales_invoice(payment_entry, for_cancel=for_cancel)
def clear_simple_entry(self, payment_entry, for_cancel=False):
+ if payment_entry.payment_document == "Payment Entry":
+ if frappe.db.get_value("Payment Entry", payment_entry.payment_entry, "payment_type") == "Internal Transfer":
+ if len(get_reconciled_bank_transactions(payment_entry)) < 2:
+ return
+
clearance_date = self.date if not for_cancel else None
frappe.db.set_value(
payment_entry.payment_document, payment_entry.payment_entry,
@@ -70,6 +75,17 @@
),
"clearance_date", clearance_date)
+def get_reconciled_bank_transactions(payment_entry):
+ reconciled_bank_transactions = frappe.get_all(
+ 'Bank Transaction Payments',
+ filters = {
+ 'payment_entry': payment_entry.payment_entry
+ },
+ fields = ['parent']
+ )
+
+ return reconciled_bank_transactions
+
def get_total_allocated_amount(payment_entry):
return frappe.db.sql("""
SELECT
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 83f0222..5a19426 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1140,6 +1140,18 @@
self.assertEqual(loss_for_si['credit'], loss_for_return_si['debit'])
self.assertEqual(loss_for_si['debit'], loss_for_return_si['credit'])
+ def test_incoming_rate_for_stand_alone_credit_note(self):
+ return_si = create_sales_invoice(is_return=1, update_stock=1, qty=-1, rate=90000, incoming_rate=10,
+ company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', debit_to='Debtors - TCP1',
+ income_account='Sales - TCP1', expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1')
+
+ incoming_rate = frappe.db.get_value('Stock Ledger Entry', {'voucher_no': return_si.name}, 'incoming_rate')
+ debit_amount = frappe.db.get_value('GL Entry',
+ {'voucher_no': return_si.name, 'account': 'Stock In Hand - TCP1'}, 'debit')
+
+ self.assertEqual(debit_amount, 10.0)
+ self.assertEqual(incoming_rate, 10.0)
+
def test_discount_on_net_total(self):
si = frappe.copy_doc(test_records[2])
si.apply_discount_on = "Net Total"
@@ -2419,7 +2431,8 @@
"asset": args.asset or None,
"cost_center": args.cost_center or "_Test Cost Center - _TC",
"serial_no": args.serial_no,
- "conversion_factor": 1
+ "conversion_factor": 1,
+ "incoming_rate": args.incoming_rate or 0
})
if not args.do_not_save:
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index c77076c..b90f3f0 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -53,7 +53,6 @@
"column_break_24",
"base_net_rate",
"base_net_amount",
- "incoming_rate",
"drop_ship",
"delivered_by_supplier",
"accounting",
@@ -81,6 +80,7 @@
"target_warehouse",
"quality_inspection",
"batch_no",
+ "incoming_rate",
"col_break5",
"allow_zero_valuation_rate",
"serial_no",
@@ -807,12 +807,12 @@
"read_only": 1
},
{
+ "depends_on": "eval:parent.is_return && parent.update_stock && !parent.return_against",
"fieldname": "incoming_rate",
"fieldtype": "Currency",
- "label": "Incoming Rate",
+ "label": "Incoming Rate (Costing)",
"no_copy": 1,
- "print_hide": 1,
- "read_only": 1
+ "print_hide": 1
},
{
"depends_on": "eval: doc.uom != doc.stock_uom",
@@ -833,7 +833,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2021-08-12 20:15:47.668399",
+ "modified": "2021-08-19 13:41:53.435827",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 7c4ff73..8bf7b78f 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -367,21 +367,25 @@
)
# Discounts
- if self.additional_discount_percentage:
- invoice.additional_discount_percentage = self.additional_discount_percentage
+ if self.is_trialling():
+ invoice.additional_discount_percentage = 100
+ else:
+ if self.additional_discount_percentage:
+ invoice.additional_discount_percentage = self.additional_discount_percentage
- if self.additional_discount_amount:
- invoice.discount_amount = self.additional_discount_amount
+ if self.additional_discount_amount:
+ invoice.discount_amount = self.additional_discount_amount
- if self.additional_discount_percentage or self.additional_discount_amount:
- discount_on = self.apply_additional_discount
- invoice.apply_discount_on = discount_on if discount_on else 'Grand Total'
+ if self.additional_discount_percentage or self.additional_discount_amount:
+ discount_on = self.apply_additional_discount
+ invoice.apply_discount_on = discount_on if discount_on else 'Grand Total'
# Subscription period
invoice.from_date = self.current_invoice_start
invoice.to_date = self.current_invoice_end
invoice.flags.ignore_mandatory = True
+
invoice.save()
if self.submit_invoice:
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 8fdbbf9..9a61b79 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -59,7 +59,7 @@
"credit_in_account_currency": d.depreciation_amount,
"reference_type": "Asset",
"reference_name": asset.name,
- "cost_center": ""
+ "cost_center": depreciation_cost_center
}
debit_entry = {
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 5ee1f2f..01486fc 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -394,19 +394,6 @@
if not return_against:
return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against")
- if not return_against and voucher_type == 'Sales Invoice' and sle:
- return get_incoming_rate({
- "item_code": sle.item_code,
- "warehouse": sle.warehouse,
- "posting_date": sle.get('posting_date'),
- "posting_time": sle.get('posting_time'),
- "qty": sle.actual_qty,
- "serial_no": sle.get('serial_no'),
- "company": sle.company,
- "voucher_type": sle.voucher_type,
- "voucher_no": sle.voucher_no
- }, raise_error_if_no_rate=False)
-
return_against_item_field = get_return_against_item_fields(voucher_type)
filters = get_filters(voucher_type, voucher_no, voucher_detail_no,
@@ -417,7 +404,24 @@
else:
select_field = "abs(stock_value_difference / actual_qty)"
- return flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
+ rate = flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
+ if not (rate and return_against) and voucher_type in ['Sales Invoice', 'Delivery Note']:
+ rate = frappe.db.get_value(f'{voucher_type} Item', voucher_detail_no, 'incoming_rate')
+
+ if not rate and sle:
+ rate = get_incoming_rate({
+ "item_code": sle.item_code,
+ "warehouse": sle.warehouse,
+ "posting_date": sle.get('posting_date'),
+ "posting_time": sle.get('posting_time'),
+ "qty": sle.actual_qty,
+ "serial_no": sle.get('serial_no'),
+ "company": sle.company,
+ "voucher_type": sle.voucher_type,
+ "voucher_no": sle.voucher_no
+ }, raise_error_if_no_rate=False)
+
+ return rate
def get_return_against_item_fields(voucher_type):
return_against_item_fields = {
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index fc2cc97..4ea0e11 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -362,7 +362,7 @@
sales_order.update_reserved_qty(so_item_rows)
def set_incoming_rate(self):
- if self.doctype not in ("Delivery Note", "Sales Invoice", "Sales Order"):
+ if self.doctype not in ("Delivery Note", "Sales Invoice"):
return
items = self.get("items") + (self.get("packed_items") or [])
@@ -371,18 +371,19 @@
# Get incoming rate based on original item cost based on valuation method
qty = flt(d.get('stock_qty') or d.get('actual_qty'))
- d.incoming_rate = get_incoming_rate({
- "item_code": d.item_code,
- "warehouse": d.warehouse,
- "posting_date": self.get('posting_date') or self.get('transaction_date'),
- "posting_time": self.get('posting_time') or nowtime(),
- "qty": qty if cint(self.get("is_return")) else (-1 * qty),
- "serial_no": d.get('serial_no'),
- "company": self.company,
- "voucher_type": self.doctype,
- "voucher_no": self.name,
- "allow_zero_valuation": d.get("allow_zero_valuation")
- }, raise_error_if_no_rate=False)
+ if not d.incoming_rate:
+ d.incoming_rate = get_incoming_rate({
+ "item_code": d.item_code,
+ "warehouse": d.warehouse,
+ "posting_date": self.get('posting_date') or self.get('transaction_date'),
+ "posting_time": self.get('posting_time') or nowtime(),
+ "qty": qty if cint(self.get("is_return")) else (-1 * qty),
+ "serial_no": d.get('serial_no'),
+ "company": self.company,
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ "allow_zero_valuation": d.get("allow_zero_valuation")
+ }, raise_error_if_no_rate=False)
# For internal transfers use incoming rate as the valuation rate
if self.is_internal_transfer():
diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.json b/erpnext/healthcare/doctype/fee_validity/fee_validity.json
index b001bf0..d76b42e 100644
--- a/erpnext/healthcare/doctype/fee_validity/fee_validity.json
+++ b/erpnext/healthcare/doctype/fee_validity/fee_validity.json
@@ -46,13 +46,13 @@
{
"fieldname": "visited",
"fieldtype": "Int",
- "label": "Visited yet",
+ "label": "Visits Completed",
"read_only": 1
},
{
"fieldname": "valid_till",
"fieldtype": "Date",
- "label": "Valid till",
+ "label": "Valid Till",
"read_only": 1
},
{
@@ -106,7 +106,7 @@
],
"in_create": 1,
"links": [],
- "modified": "2020-03-17 20:25:06.487418",
+ "modified": "2021-08-26 10:51:05.609349",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Fee Validity",
diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.py b/erpnext/healthcare/doctype/fee_validity/fee_validity.py
index 5b9c179..59586e0 100644
--- a/erpnext/healthcare/doctype/fee_validity/fee_validity.py
+++ b/erpnext/healthcare/doctype/fee_validity/fee_validity.py
@@ -11,7 +11,6 @@
class FeeValidity(Document):
def validate(self):
self.update_status()
- self.set_start_date()
def update_status(self):
if self.visited >= self.max_visits:
@@ -19,13 +18,6 @@
else:
self.status = 'Pending'
- def set_start_date(self):
- self.start_date = getdate()
- for appointment in self.ref_appointments:
- appointment_date = frappe.db.get_value('Patient Appointment', appointment.appointment, 'appointment_date')
- if getdate(appointment_date) < self.start_date:
- self.start_date = getdate(appointment_date)
-
def create_fee_validity(appointment):
if not check_is_new_patient(appointment):
@@ -36,11 +28,9 @@
fee_validity.patient = appointment.patient
fee_validity.max_visits = frappe.db.get_single_value('Healthcare Settings', 'max_visits') or 1
valid_days = frappe.db.get_single_value('Healthcare Settings', 'valid_days') or 1
- fee_validity.visited = 1
+ fee_validity.visited = 0
+ fee_validity.start_date = getdate(appointment.appointment_date)
fee_validity.valid_till = getdate(appointment.appointment_date) + datetime.timedelta(days=int(valid_days))
- fee_validity.append('ref_appointments', {
- 'appointment': appointment.name
- })
fee_validity.save(ignore_permissions=True)
return fee_validity
diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
index 82e7136..4a17872 100644
--- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
+++ b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
@@ -22,14 +22,14 @@
item = create_healthcare_service_items()
healthcare_settings = frappe.get_single("Healthcare Settings")
healthcare_settings.enable_free_follow_ups = 1
- healthcare_settings.max_visits = 2
+ healthcare_settings.max_visits = 1
healthcare_settings.valid_days = 7
healthcare_settings.automate_appointment_invoicing = 1
healthcare_settings.op_consulting_charge_item = item
healthcare_settings.save(ignore_permissions=True)
patient, medical_department, practitioner = create_healthcare_docs()
- # For first appointment, invoice is generated
+ # For first appointment, invoice is generated. First appointment not considered in fee validity
appointment = create_appointment(patient, practitioner, nowdate())
invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced")
self.assertEqual(invoiced, 1)
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json
index 8162f03..cb455eb 100644
--- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json
+++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json
@@ -282,7 +282,7 @@
],
"image_field": "image",
"links": [],
- "modified": "2021-01-22 10:14:43.187675",
+ "modified": "2021-08-24 10:42:08.513054",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare Practitioner",
@@ -295,6 +295,7 @@
"read": 1,
"report": 1,
"role": "Laboratory User",
+ "select": 1,
"share": 1,
"write": 1
},
@@ -307,6 +308,7 @@
"read": 1,
"report": 1,
"role": "Physician",
+ "select": 1,
"share": 1,
"write": 1
},
@@ -319,6 +321,7 @@
"read": 1,
"report": 1,
"role": "Nursing User",
+ "select": 1,
"share": 1,
"write": 1
}
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
index 6e996bd..73ec3bc 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
@@ -142,7 +142,6 @@
{
"fieldname": "practitioner",
"fieldtype": "Link",
- "ignore_user_permissions": 1,
"in_standard_filter": 1,
"label": "Healthcare Practitioner",
"options": "Healthcare Practitioner",
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index 05e2cd3..7db4fa6 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -109,9 +109,13 @@
frappe.db.set_value('Patient Appointment', self.name, 'notes', comments)
def update_fee_validity(self):
+ if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'):
+ return
+
fee_validity = manage_fee_validity(self)
if fee_validity:
- frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till))
+ frappe.msgprint(_('{0}: {1} has fee validity till {2}').format(self.patient,
+ frappe.bold(self.patient_name), fee_validity.valid_till))
@frappe.whitelist()
def get_therapy_types(self):
diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
index 2df6921..157b3e1 100644
--- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
@@ -107,14 +107,17 @@
patient, medical_department, practitioner = create_healthcare_docs()
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 1)
appointment = create_appointment(patient, practitioner, nowdate())
- fee_validity = frappe.db.get_value('Fee Validity Reference', {'appointment': appointment.name}, 'parent')
+ fee_validity = frappe.db.get_value('Fee Validity', {'patient': patient, 'practitioner': practitioner})
# fee validity created
self.assertTrue(fee_validity)
- visited = frappe.db.get_value('Fee Validity', fee_validity, 'visited')
+ # first follow up appointment
+ appointment = create_appointment(patient, practitioner, nowdate())
+ self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), 1)
+
update_status(appointment.name, 'Cancelled')
# check fee validity updated
- self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), visited - 1)
+ self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), 0)
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
@@ -199,10 +202,33 @@
assert new_invoice_count == invoice_count + 1
+ def test_patient_appointment_should_consider_permissions_while_fetching_appointments(self):
+ patient, medical_department, practitioner = create_healthcare_docs()
+ create_appointment(patient, practitioner, nowdate())
-def create_healthcare_docs():
+ patient, medical_department, new_practitioner = create_healthcare_docs(practitioner_name='Dr. John')
+ create_appointment(patient, new_practitioner, nowdate())
+
+ roles = [{"doctype": "Has Role", "role": "Physician"}]
+ user = create_user(roles=roles)
+ new_practitioner = frappe.get_doc('Healthcare Practitioner', new_practitioner)
+ new_practitioner.user_id = user.email
+ new_practitioner.save()
+
+ frappe.set_user(user.name)
+ appointments = frappe.get_list('Patient Appointment')
+ assert len(appointments) == 1
+
+ frappe.set_user("Administrator")
+ appointments = frappe.get_list('Patient Appointment')
+ assert len(appointments) == 2
+
+def create_healthcare_docs(practitioner_name=None):
+ if not practitioner_name:
+ practitioner_name = '_Test Healthcare Practitioner'
+
patient = create_patient()
- practitioner = frappe.db.exists('Healthcare Practitioner', '_Test Healthcare Practitioner')
+ practitioner = frappe.db.exists('Healthcare Practitioner', practitioner_name)
medical_department = frappe.db.exists('Medical Department', '_Test Medical Department')
if not medical_department:
@@ -213,7 +239,7 @@
if not practitioner:
practitioner = frappe.new_doc('Healthcare Practitioner')
- practitioner.first_name = '_Test Healthcare Practitioner'
+ practitioner.first_name = practitioner_name
practitioner.gender = 'Female'
practitioner.department = medical_department
practitioner.op_consulting_charge = 500
@@ -319,3 +345,17 @@
'price_list': args.get('price_list') or frappe.db.get_value("Price List", {"selling": 1}),
'items': args.get('items') or items
}).insert()
+
+def create_user(email=None, roles=None):
+ if not email:
+ email = '{}@frappe.com'.format(frappe.utils.random_string(10))
+ user = frappe.db.exists('User', email)
+ if not user:
+ user = frappe.get_doc({
+ "doctype": "User",
+ "email": email,
+ "first_name": "test_user",
+ "password": "password",
+ "roles": roles,
+ }).insert()
+ return user
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 66e2394..3efbe88 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -26,17 +26,17 @@
self.set_status()
self.validate_operation_id()
self.validate_sequence_id()
- self.get_sub_operations()
+ self.set_sub_operations()
self.update_sub_operation_status()
- def get_sub_operations(self):
+ def set_sub_operations(self):
if self.operation:
self.sub_operations = []
- for row in frappe.get_all("Sub Operation",
- filters = {"parent": self.operation}, fields=["operation", "idx"]):
- row.status = "Pending"
+ for row in frappe.get_all('Sub Operation',
+ filters = {'parent': self.operation}, fields=['operation', 'idx'], order_by='idx'):
+ row.status = 'Pending'
row.sub_operation = row.operation
- self.append("sub_operations", row)
+ self.append('sub_operations', row)
def validate_time_logs(self):
self.total_time_in_mins = 0.0
@@ -690,7 +690,7 @@
target.set('time_logs', [])
target.set('employee', [])
target.set('items', [])
- target.get_sub_operations()
+ target.set_sub_operations()
target.get_required_items()
target.validate_time_logs()
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index bf0446b..0a6a8bd 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -295,5 +295,6 @@
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.reset_clearance_date_for_intracompany_payment_entries
erpnext.patches.v13_0.einvoicing_deprecation_warning
erpnext.patches.v14_0.delete_einvoicing_doctypes
diff --git a/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py b/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py
new file mode 100644
index 0000000..1da5275
--- /dev/null
+++ b/erpnext/patches/v13_0/reset_clearance_date_for_intracompany_payment_entries.py
@@ -0,0 +1,45 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+import frappe
+
+def execute():
+ """
+ Reset Clearance Date for Payment Entries of type Internal Transfer that have only been reconciled with one Bank Transaction.
+ This will allow the Payment Entries to be reconciled with the second Bank Transaction using the Bank Reconciliation Tool.
+ """
+
+ intra_company_pe = get_intra_company_payment_entries_with_clearance_dates()
+ reconciled_bank_transactions = get_reconciled_bank_transactions(intra_company_pe)
+
+ for payment_entry in reconciled_bank_transactions:
+ if len(reconciled_bank_transactions[payment_entry]) == 1:
+ frappe.db.set_value('Payment Entry', payment_entry, 'clearance_date', None)
+
+def get_intra_company_payment_entries_with_clearance_dates():
+ return frappe.get_all(
+ 'Payment Entry',
+ filters = {
+ 'payment_type': 'Internal Transfer',
+ 'clearance_date': ["not in", None]
+ },
+ pluck = 'name'
+ )
+
+def get_reconciled_bank_transactions(intra_company_pe):
+ """Returns dictionary where each key:value pair is Payment Entry : List of Bank Transactions reconciled with Payment Entry"""
+
+ reconciled_bank_transactions = {}
+
+ for payment_entry in intra_company_pe:
+ reconciled_bank_transactions[payment_entry] = frappe.get_all(
+ 'Bank Transaction Payments',
+ filters = {
+ 'payment_entry': payment_entry
+ },
+ pluck='parent'
+ )
+
+ return reconciled_bank_transactions
\ No newline at end of file
diff --git a/erpnext/regional/report/vat_audit_report/test_vat_audit_report.py b/erpnext/regional/report/vat_audit_report/test_vat_audit_report.py
new file mode 100644
index 0000000..dea17a6
--- /dev/null
+++ b/erpnext/regional/report/vat_audit_report/test_vat_audit_report.py
@@ -0,0 +1,193 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from unittest import TestCase
+from frappe.utils import today
+
+from erpnext.accounts.doctype.account.test_account import create_account
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
+
+from erpnext.regional.report.vat_audit_report.vat_audit_report import execute
+
+class TestVATAuditReport(TestCase):
+ def setUp(self):
+ frappe.set_user("Administrator")
+ make_company("_Test Company SA VAT", "_TCSV")
+
+ create_account(account_name="VAT - 0%", account_type="Tax",
+ parent_account="Duties and Taxes - _TCSV", company="_Test Company SA VAT")
+ create_account(account_name="VAT - 15%", account_type="Tax",
+ parent_account="Duties and Taxes - _TCSV", company="_Test Company SA VAT")
+ set_sa_vat_accounts()
+
+ make_item("_Test SA VAT Item")
+ make_item("_Test SA VAT Zero Rated Item", properties = {"is_zero_rated": 1})
+
+ make_customer()
+ make_supplier()
+
+ make_sales_invoices()
+ create_purchase_invoices()
+
+ def tearDown(self):
+ frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company SA VAT'")
+ frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company SA VAT'")
+
+ def test_vat_audit_report(self):
+ filters = {
+ "company": "_Test Company SA VAT",
+ "from_date": today(),
+ "to_date": today()
+ }
+ columns, data = execute(filters)
+ total_tax_amount = 0
+ total_row_tax = 0
+ for row in data:
+ keys = row.keys()
+ # skips total row tax_amount in if.. and skips section header in elif..
+ if 'voucher_no' in keys:
+ total_tax_amount = total_tax_amount + row['tax_amount']
+ elif 'tax_amount' in keys:
+ total_row_tax = total_row_tax + row['tax_amount']
+
+ self.assertEqual(total_tax_amount, total_row_tax)
+
+def make_company(company_name, abbr):
+ if not frappe.db.exists("Company", company_name):
+ company = frappe.get_doc({
+ "doctype": "Company",
+ "company_name": company_name,
+ "abbr": abbr,
+ "default_currency": "ZAR",
+ "country": "South Africa",
+ "create_chart_of_accounts_based_on": "Standard Template"
+ })
+ company.insert()
+ else:
+ company = frappe.get_doc("Company", company_name)
+
+ company.create_default_warehouses()
+
+ if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": company.name}):
+ company.create_default_cost_center()
+
+ company.save()
+
+ return company
+
+def set_sa_vat_accounts():
+ if not frappe.db.exists("South Africa VAT Settings", "_Test Company SA VAT"):
+ vat_accounts = frappe.get_all(
+ "Account",
+ fields=["name"],
+ filters = {
+ "company": "_Test Company SA VAT",
+ "is_group": 0,
+ "account_type": "Tax"
+ }
+ )
+
+ sa_vat_accounts = []
+ for account in vat_accounts:
+ sa_vat_accounts.append({
+ "doctype": "South Africa VAT Account",
+ "account": account.name
+ })
+
+ frappe.get_doc({
+ "company": "_Test Company SA VAT",
+ "vat_accounts": sa_vat_accounts,
+ "doctype": "South Africa VAT Settings",
+ }).insert()
+
+def make_customer():
+ if not frappe.db.exists("Customer", "_Test SA Customer"):
+ frappe.get_doc({
+ "doctype": "Customer",
+ "customer_name": "_Test SA Customer",
+ "customer_type": "Company",
+ }).insert()
+
+def make_supplier():
+ if not frappe.db.exists("Supplier", "_Test SA Supplier"):
+ frappe.get_doc({
+ "doctype": "Supplier",
+ "supplier_name": "_Test SA Supplier",
+ "supplier_type": "Company",
+ "supplier_group":"All Supplier Groups"
+ }).insert()
+
+def make_item(item_code, properties=None):
+ if not frappe.db.exists("Item", item_code):
+ item = frappe.get_doc({
+ "doctype": "Item",
+ "item_code": item_code,
+ "item_name": item_code,
+ "description": item_code,
+ "item_group": "Products"
+ })
+
+ if properties:
+ item.update(properties)
+
+ item.insert()
+
+def make_sales_invoices():
+ def make_sales_invoices_wrapper(item, rate, tax_account, tax_rate, tax=True):
+ si = create_sales_invoice(
+ company="_Test Company SA VAT",
+ customer = "_Test SA Customer",
+ currency = "ZAR",
+ item=item,
+ rate=rate,
+ warehouse = "Finished Goods - _TCSV",
+ debit_to = "Debtors - _TCSV",
+ income_account = "Sales - _TCSV",
+ expense_account = "Cost of Goods Sold - _TCSV",
+ cost_center = "Main - _TCSV",
+ do_not_save=1
+ )
+ if tax:
+ si.append("taxes", {
+ "charge_type": "On Net Total",
+ "account_head": tax_account,
+ "cost_center": "Main - _TCSV",
+ "description": "VAT 15% @ 15.0",
+ "rate": tax_rate
+ })
+
+ si.submit()
+
+ test_item = "_Test SA VAT Item"
+ test_zero_rated_item = "_Test SA VAT Zero Rated Item"
+
+ make_sales_invoices_wrapper(test_item, 100.0, "VAT - 15% - _TCSV", 15.0)
+ make_sales_invoices_wrapper(test_zero_rated_item, 100.0, "VAT - 0% - _TCSV", 0.0)
+
+def create_purchase_invoices():
+ pi = make_purchase_invoice(
+ company = "_Test Company SA VAT",
+ supplier = "_Test SA Supplier",
+ supplier_warehouse = "Finished Goods - _TCSV",
+ warehouse = "Finished Goods - _TCSV",
+ currency = "ZAR",
+ cost_center = "Main - _TCSV",
+ expense_account = "Cost of Goods Sold - _TCSV",
+ item = "_Test SA VAT Item",
+ qty = 1,
+ rate = 100,
+ uom = "Nos",
+ do_not_save = 1
+ )
+ pi.append("taxes", {
+ "charge_type": "On Net Total",
+ "account_head": "VAT - 15% - _TCSV",
+ "cost_center": "Main - _TCSV",
+ "description": "VAT 15% @ 15.0",
+ "rate": 15.0
+ })
+
+ pi.submit()
diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py
index 17aca17..88f6b92 100644
--- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py
+++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py
@@ -1,11 +1,11 @@
-# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import json
from frappe import _
-from frappe.utils import formatdate
+from frappe.utils import formatdate, get_link_to_form
def execute(filters=None):
return VATAuditReport(filters).run()
@@ -42,7 +42,8 @@
self.sa_vat_accounts = frappe.get_list("South Africa VAT Account",
filters = {"parent": self.filters.company}, pluck="account")
if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate:
- frappe.throw(_("Please set VAT Accounts in South Africa VAT Settings"))
+ link_to_settings = get_link_to_form("South Africa VAT Settings", "", label="South Africa VAT Settings")
+ frappe.throw(_("Please set VAT Accounts in {0}").format(link_to_settings))
def get_invoice_data(self, doctype):
conditions = self.get_conditions()
@@ -69,7 +70,7 @@
items = frappe.db.sql("""
SELECT
- item_code, parent, taxable_value, base_net_amount, is_zero_rated
+ item_code, parent, base_net_amount, is_zero_rated
FROM
`tab%s Item`
WHERE
@@ -79,7 +80,7 @@
if d.item_code not in self.invoice_items.get(d.parent, {}):
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {
'net_amount': 0.0})
- self.invoice_items[d.parent][d.item_code]['net_amount'] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
+ self.invoice_items[d.parent][d.item_code]['net_amount'] += d.get('base_net_amount', 0)
self.invoice_items[d.parent][d.item_code]['is_zero_rated'] = d.is_zero_rated
def get_items_based_on_tax_rate(self, doctype):
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index 2f06e98..cb00019 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -111,7 +111,6 @@
}
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Customer'}
- frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
if(!frm.doc.__islocal) {
frappe.contacts.render_address_and_contact(frm);
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index 0d839fc..2acc64c 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -268,6 +268,7 @@
"options": "fa fa-map-marker"
},
{
+ "depends_on": "eval: !doc.__islocal",
"fieldname": "address_html",
"fieldtype": "HTML",
"label": "Address HTML",
@@ -284,6 +285,7 @@
"width": "50%"
},
{
+ "depends_on": "eval: !doc.__islocal",
"fieldname": "contact_html",
"fieldtype": "HTML",
"label": "Contact HTML",
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index eddd048..27feec1 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -332,6 +332,7 @@
where
item_code = %(item_code)s
and warehouse = %(warehouse)s
+ and is_cancelled = 0
and timestamp(posting_date, time_format(posting_time, %(time_format)s)) = timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s))
order by