mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-25 07:54:46 +00:00
Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-28822
This commit is contained in:
@@ -24,6 +24,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
|||||||
get_accounting_dimensions,
|
get_accounting_dimensions,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
|
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
|
||||||
|
from erpnext.accounts.party import get_party_account_currency
|
||||||
|
|
||||||
|
|
||||||
class Subscription(Document):
|
class Subscription(Document):
|
||||||
@@ -356,6 +357,9 @@ class Subscription(Document):
|
|||||||
if frappe.db.get_value('Supplier', self.party, 'tax_withholding_category'):
|
if frappe.db.get_value('Supplier', self.party, 'tax_withholding_category'):
|
||||||
invoice.apply_tds = 1
|
invoice.apply_tds = 1
|
||||||
|
|
||||||
|
### Add party currency to invoice
|
||||||
|
invoice.currency = get_party_account_currency(self.party_type, self.party, self.company)
|
||||||
|
|
||||||
## Add dimensions in invoice for subscription:
|
## Add dimensions in invoice for subscription:
|
||||||
accounting_dimensions = get_accounting_dimensions()
|
accounting_dimensions = get_accounting_dimensions()
|
||||||
|
|
||||||
|
|||||||
@@ -60,15 +60,38 @@ def create_plan():
|
|||||||
plan.billing_interval_count = 3
|
plan.billing_interval_count = 3
|
||||||
plan.insert()
|
plan.insert()
|
||||||
|
|
||||||
|
if not frappe.db.exists('Subscription Plan', '_Test Plan Multicurrency'):
|
||||||
|
plan = frappe.new_doc('Subscription Plan')
|
||||||
|
plan.plan_name = '_Test Plan Multicurrency'
|
||||||
|
plan.item = '_Test Non Stock Item'
|
||||||
|
plan.price_determination = "Fixed Rate"
|
||||||
|
plan.cost = 50
|
||||||
|
plan.currency = 'USD'
|
||||||
|
plan.billing_interval = 'Month'
|
||||||
|
plan.billing_interval_count = 1
|
||||||
|
plan.insert()
|
||||||
|
|
||||||
|
def create_parties():
|
||||||
if not frappe.db.exists('Supplier', '_Test Supplier'):
|
if not frappe.db.exists('Supplier', '_Test Supplier'):
|
||||||
supplier = frappe.new_doc('Supplier')
|
supplier = frappe.new_doc('Supplier')
|
||||||
supplier.supplier_name = '_Test Supplier'
|
supplier.supplier_name = '_Test Supplier'
|
||||||
supplier.supplier_group = 'All Supplier Groups'
|
supplier.supplier_group = 'All Supplier Groups'
|
||||||
supplier.insert()
|
supplier.insert()
|
||||||
|
|
||||||
|
if not frappe.db.exists('Customer', '_Test Subscription Customer'):
|
||||||
|
customer = frappe.new_doc('Customer')
|
||||||
|
customer.customer_name = '_Test Subscription Customer'
|
||||||
|
customer.billing_currency = 'USD'
|
||||||
|
customer.append('accounts', {
|
||||||
|
'company': '_Test Company',
|
||||||
|
'account': '_Test Receivable USD - _TC'
|
||||||
|
})
|
||||||
|
customer.insert()
|
||||||
|
|
||||||
class TestSubscription(unittest.TestCase):
|
class TestSubscription(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
create_plan()
|
create_plan()
|
||||||
|
create_parties()
|
||||||
|
|
||||||
def test_create_subscription_with_trial_with_correct_period(self):
|
def test_create_subscription_with_trial_with_correct_period(self):
|
||||||
subscription = frappe.new_doc('Subscription')
|
subscription = frappe.new_doc('Subscription')
|
||||||
@@ -637,3 +660,22 @@ class TestSubscription(unittest.TestCase):
|
|||||||
|
|
||||||
subscription.process()
|
subscription.process()
|
||||||
self.assertEqual(len(subscription.invoices), 1)
|
self.assertEqual(len(subscription.invoices), 1)
|
||||||
|
|
||||||
|
def test_multicurrency_subscription(self):
|
||||||
|
subscription = frappe.new_doc('Subscription')
|
||||||
|
subscription.party_type = 'Customer'
|
||||||
|
subscription.party = '_Test Subscription Customer'
|
||||||
|
subscription.generate_invoice_at_period_start = 1
|
||||||
|
subscription.company = '_Test Company'
|
||||||
|
# select subscription start date as '2018-01-15'
|
||||||
|
subscription.start_date = '2018-01-01'
|
||||||
|
subscription.append('plans', {'plan': '_Test Plan Multicurrency', 'qty': 1})
|
||||||
|
subscription.save()
|
||||||
|
|
||||||
|
subscription.process()
|
||||||
|
self.assertEqual(len(subscription.invoices), 1)
|
||||||
|
self.assertEqual(subscription.status, 'Unpaid')
|
||||||
|
|
||||||
|
# Check the currency of the created invoice
|
||||||
|
currency = frappe.db.get_value('Sales Invoice', subscription.invoices[0].invoice, 'currency')
|
||||||
|
self.assertEqual(currency, 'USD')
|
||||||
@@ -56,9 +56,14 @@ class TestMaintenanceSchedule(unittest.TestCase):
|
|||||||
|
|
||||||
ms.submit()
|
ms.submit()
|
||||||
s_id = ms.get_pending_data(data_type = "id", item_name = i.item_name, s_date = expected_dates[1])
|
s_id = ms.get_pending_data(data_type = "id", item_name = i.item_name, s_date = expected_dates[1])
|
||||||
test = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id)
|
|
||||||
|
# Check if item is mapped in visit.
|
||||||
|
test_map_visit = make_maintenance_visit(source_name = ms.name, item_name = "_Test Item", s_id = s_id)
|
||||||
|
self.assertEqual(len(test_map_visit.purposes), 1)
|
||||||
|
self.assertEqual(test_map_visit.purposes[0].item_name, "_Test Item")
|
||||||
|
|
||||||
visit = frappe.new_doc('Maintenance Visit')
|
visit = frappe.new_doc('Maintenance Visit')
|
||||||
visit = test
|
visit = test_map_visit
|
||||||
visit.maintenance_schedule = ms.name
|
visit.maintenance_schedule = ms.name
|
||||||
visit.maintenance_schedule_detail = s_id
|
visit.maintenance_schedule_detail = s_id
|
||||||
visit.completion_status = "Partially Completed"
|
visit.completion_status = "Partially Completed"
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ frappe.ui.form.on('Maintenance Visit', {
|
|||||||
frm.set_value({ status: 'Draft' });
|
frm.set_value({ status: 'Draft' });
|
||||||
}
|
}
|
||||||
if (frm.doc.__islocal) {
|
if (frm.doc.__islocal) {
|
||||||
frm.clear_table("purposes");
|
frm.doc.maintenance_type == 'Unscheduled' && frm.clear_table("purposes");
|
||||||
frm.set_value({ mntc_date: frappe.datetime.get_today() });
|
frm.set_value({ mntc_date: frappe.datetime.get_today() });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -336,4 +336,4 @@ erpnext.patches.v13_0.item_naming_series_not_mandatory
|
|||||||
erpnext.patches.v13_0.update_category_in_ltds_certificate
|
erpnext.patches.v13_0.update_category_in_ltds_certificate
|
||||||
erpnext.patches.v13_0.create_ksa_vat_custom_fields
|
erpnext.patches.v13_0.create_ksa_vat_custom_fields
|
||||||
erpnext.patches.v13_0.rename_ksa_qr_field
|
erpnext.patches.v13_0.rename_ksa_qr_field
|
||||||
erpnext.patches.v13_0.disable_ksa_print_format_for_others
|
erpnext.patches.v13_0.disable_ksa_print_format_for_others # 16-12-2021
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
|
from erpnext.regional.saudi_arabia.setup import add_print_formats
|
||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
company = frappe.get_all('Company', filters = {'country': 'Saudi Arabia'})
|
company = frappe.get_all('Company', filters = {'country': 'Saudi Arabia'})
|
||||||
if company:
|
if company:
|
||||||
|
add_print_formats()
|
||||||
return
|
return
|
||||||
|
|
||||||
if frappe.db.exists('DocType', 'Print Format'):
|
if frappe.db.exists('DocType', 'Print Format'):
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ def get_stock_ledger_entries(filters, items):
|
|||||||
sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference,
|
sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference,
|
||||||
sle.item_code as name, sle.voucher_no, sle.stock_value, sle.batch_no
|
sle.item_code as name, sle.voucher_no, sle.stock_value, sle.batch_no
|
||||||
from
|
from
|
||||||
`tabStock Ledger Entry` sle force index (posting_sort_index)
|
`tabStock Ledger Entry` sle
|
||||||
where sle.docstatus < 2 %s %s
|
where sle.docstatus < 2 %s %s
|
||||||
and is_cancelled = 0
|
and is_cancelled = 0
|
||||||
order by sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty""" % #nosec
|
order by sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty""" % #nosec
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
const DIFFERNCE_FIELD_NAMES = [
|
||||||
|
"difference_in_qty",
|
||||||
|
"fifo_qty_diff",
|
||||||
|
"fifo_value_diff",
|
||||||
|
"fifo_valuation_diff",
|
||||||
|
"valuation_diff",
|
||||||
|
"fifo_difference_diff"
|
||||||
|
];
|
||||||
|
|
||||||
|
frappe.query_reports["Stock Ledger Invariant Check"] = {
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Item",
|
||||||
|
"mandatory": 1,
|
||||||
|
"options": "Item",
|
||||||
|
get_query: function() {
|
||||||
|
return {
|
||||||
|
filters: {is_stock_item: 1, has_serial_no: 0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Warehouse",
|
||||||
|
"mandatory": 1,
|
||||||
|
"options": "Warehouse",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
formatter (value, row, column, data, default_formatter) {
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
if (DIFFERNCE_FIELD_NAMES.includes(column.fieldname) && Math.abs(data[column.fieldname]) > 0.001) {
|
||||||
|
value = "<span style='color:red'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"add_total_row": 0,
|
||||||
|
"columns": [],
|
||||||
|
"creation": "2021-12-16 06:31:23.290916",
|
||||||
|
"disable_prepared_report": 0,
|
||||||
|
"disabled": 0,
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
|
"idx": 0,
|
||||||
|
"is_standard": "Yes",
|
||||||
|
"modified": "2021-12-16 09:55:58.341764",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Stock",
|
||||||
|
"name": "Stock Ledger Invariant Check",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Stock Ledger Entry",
|
||||||
|
"report_name": "Stock Ledger Invariant Check",
|
||||||
|
"report_type": "Script Report",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "System Manager"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# License: GNU GPL v3. See LICENSE
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
SLE_FIELDS = (
|
||||||
|
"name",
|
||||||
|
"posting_date",
|
||||||
|
"posting_time",
|
||||||
|
"creation",
|
||||||
|
"voucher_type",
|
||||||
|
"voucher_no",
|
||||||
|
"actual_qty",
|
||||||
|
"qty_after_transaction",
|
||||||
|
"incoming_rate",
|
||||||
|
"outgoing_rate",
|
||||||
|
"stock_queue",
|
||||||
|
"batch_no",
|
||||||
|
"stock_value",
|
||||||
|
"stock_value_difference",
|
||||||
|
"valuation_rate",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def execute(filters=None):
|
||||||
|
columns = get_columns()
|
||||||
|
data = get_data(filters)
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
|
||||||
|
def get_data(filters):
|
||||||
|
sles = get_stock_ledger_entries(filters)
|
||||||
|
return add_invariant_check_fields(sles)
|
||||||
|
|
||||||
|
|
||||||
|
def get_stock_ledger_entries(filters):
|
||||||
|
return frappe.get_all(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
fields=SLE_FIELDS,
|
||||||
|
filters={
|
||||||
|
"item_code": filters.item_code,
|
||||||
|
"warehouse": filters.warehouse,
|
||||||
|
"is_cancelled": 0
|
||||||
|
},
|
||||||
|
order_by="timestamp(posting_date, posting_time), creation",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_invariant_check_fields(sles):
|
||||||
|
balance_qty = 0.0
|
||||||
|
for idx, sle in enumerate(sles):
|
||||||
|
queue = json.loads(sle.stock_queue)
|
||||||
|
|
||||||
|
fifo_qty = 0.0
|
||||||
|
fifo_value = 0.0
|
||||||
|
for qty, rate in queue:
|
||||||
|
fifo_qty += qty
|
||||||
|
fifo_value += qty * rate
|
||||||
|
|
||||||
|
balance_qty += sle.actual_qty
|
||||||
|
if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no:
|
||||||
|
balance_qty = sle.qty_after_transaction
|
||||||
|
|
||||||
|
sle.fifo_queue_qty = fifo_qty
|
||||||
|
sle.fifo_stock_value = fifo_value
|
||||||
|
sle.fifo_valuation_rate = fifo_value / fifo_qty if fifo_qty else None
|
||||||
|
sle.balance_value_by_qty = (
|
||||||
|
sle.stock_value / sle.qty_after_transaction if sle.qty_after_transaction else None
|
||||||
|
)
|
||||||
|
sle.expected_qty_after_transaction = balance_qty
|
||||||
|
|
||||||
|
# set difference fields
|
||||||
|
sle.difference_in_qty = sle.qty_after_transaction - sle.expected_qty_after_transaction
|
||||||
|
sle.fifo_qty_diff = sle.qty_after_transaction - fifo_qty
|
||||||
|
sle.fifo_value_diff = sle.stock_value - fifo_value
|
||||||
|
sle.fifo_valuation_diff = (
|
||||||
|
sle.valuation_rate - sle.fifo_valuation_rate if sle.fifo_valuation_rate else None
|
||||||
|
)
|
||||||
|
sle.valuation_diff = (
|
||||||
|
sle.valuation_rate - sle.balance_value_by_qty if sle.balance_value_by_qty else None
|
||||||
|
)
|
||||||
|
|
||||||
|
if idx > 0:
|
||||||
|
sle.fifo_stock_diff = sle.fifo_stock_value - sles[idx - 1].fifo_stock_value
|
||||||
|
sle.fifo_difference_diff = sle.fifo_stock_diff - sle.stock_value_difference
|
||||||
|
|
||||||
|
return sles
|
||||||
|
|
||||||
|
|
||||||
|
def get_columns():
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"fieldname": "name",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Stock Ledger Entry",
|
||||||
|
"options": "Stock Ledger Entry",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Posting Date",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_time",
|
||||||
|
"fieldtype": "Time",
|
||||||
|
"label": "Posting Time",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "creation",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"label": "Creation",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "voucher_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Voucher Type",
|
||||||
|
"options": "DocType",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "voucher_no",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Voucher No",
|
||||||
|
"options": "voucher_type",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "batch_no",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Batch",
|
||||||
|
"options": "Batch",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "actual_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Qty Change",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "incoming_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Incoming Rate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "outgoing_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Outgoing Rate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "qty_after_transaction",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(A) Qty After Transaction",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "expected_qty_after_transaction",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(B) Expected Qty After Transaction",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "difference_in_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "A - B",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "stock_queue",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "FIFO Queue",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_queue_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(C) Total qty in queue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_qty_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "A - C",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "stock_value",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(D) Balance Stock Value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_stock_value",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(E) Balance Stock Value in Queue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_value_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "D - E",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"fieldname": "stock_value_difference",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(F) Stock Value Difference",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_stock_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(G) Stock Value difference (FIFO queue)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_difference_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "F - G",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "valuation_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(H) Valuation Rate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_valuation_rate",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(I) Valuation Rate as per FIFO",
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"fieldname": "fifo_valuation_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "H - I",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "balance_value_by_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "(J) Valuation = Value (D) ÷ Qty (A)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "valuation_diff",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "H - J",
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -41,6 +41,12 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [
|
|||||||
("Total Stock Summary", {"group_by": "warehouse",}),
|
("Total Stock Summary", {"group_by": "warehouse",}),
|
||||||
("Batch Item Expiry Status", {}),
|
("Batch Item Expiry Status", {}),
|
||||||
("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}),
|
("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}),
|
||||||
|
("Stock Ledger Invariant Check",
|
||||||
|
{
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"item": "_Test Item"
|
||||||
|
}
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
OPTIONAL_FILTERS = {
|
OPTIONAL_FILTERS = {
|
||||||
|
|||||||
Reference in New Issue
Block a user