Merge branch 'version-14-hotfix' into asset_bug_fixes_14

This commit is contained in:
Anand Baburajan
2023-01-21 20:04:17 +05:30
committed by GitHub
47 changed files with 794 additions and 478 deletions

View File

@@ -11,6 +11,6 @@
"root_login": "root",
"root_password": "travis",
"host_name": "http://test_site:8000",
"install_apps": ["erpnext"],
"install_apps": ["payments", "erpnext"],
"throttle_user_limit": 100
}

View File

@@ -137,8 +137,7 @@
"fieldname": "finance_book",
"fieldtype": "Link",
"label": "Finance Book",
"options": "Finance Book",
"read_only": 1
"options": "Finance Book"
},
{
"fieldname": "2_add_edit_gl_entries",
@@ -539,7 +538,7 @@
"idx": 176,
"is_submittable": 1,
"links": [],
"modified": "2022-11-28 17:40:01.241908",
"modified": "2023-01-17 12:53:53.280620",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",

View File

@@ -3,6 +3,7 @@
frappe.ui.form.on('Payment Gateway Account', {
refresh(frm) {
erpnext.utils.check_payments_app();
if(!frm.doc.__islocal) {
frm.set_df_property('payment_gateway', 'read_only', 1);
}

View File

@@ -9,7 +9,6 @@ from frappe import _
from frappe.model.document import Document
from frappe.utils import flt, get_url, nowdate
from frappe.utils.background_jobs import enqueue
from payments.utils import get_payment_gateway_controller
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
@@ -22,6 +21,14 @@ from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_pla
from erpnext.accounts.party import get_party_account, get_party_bank_account
from erpnext.accounts.utils import get_account_currency
from erpnext.erpnext_integrations.stripe_integration import create_stripe_subscription
from erpnext.utilities import payment_app_import_guard
def _get_payment_gateway_controller(*args, **kwargs):
with payment_app_import_guard():
from payments.utils import get_payment_gateway_controller
return get_payment_gateway_controller(*args, **kwargs)
class PaymentRequest(Document):
@@ -110,7 +117,7 @@ class PaymentRequest(Document):
self.request_phone_payment()
def request_phone_payment(self):
controller = get_payment_gateway_controller(self.payment_gateway)
controller = _get_payment_gateway_controller(self.payment_gateway)
request_amount = self.get_request_amount()
payment_record = dict(
@@ -159,7 +166,7 @@ class PaymentRequest(Document):
def payment_gateway_validation(self):
try:
controller = get_payment_gateway_controller(self.payment_gateway)
controller = _get_payment_gateway_controller(self.payment_gateway)
if hasattr(controller, "on_payment_request_submission"):
return controller.on_payment_request_submission(self)
else:
@@ -192,7 +199,7 @@ class PaymentRequest(Document):
)
data.update({"company": frappe.defaults.get_defaults().company})
controller = get_payment_gateway_controller(self.payment_gateway)
controller = _get_payment_gateway_controller(self.payment_gateway)
controller.validate_transaction_currency(self.currency)
if hasattr(controller, "validate_minimum_transaction_amount"):

View File

@@ -681,11 +681,21 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
def apply_pricing_rule_for_free_items(doc, pricing_rule_args):
if pricing_rule_args:
items = tuple((d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item)
args = {(d["item_code"], d["pricing_rules"]): d for d in pricing_rule_args}
for args in pricing_rule_args:
if not items or (args.get("item_code"), args.get("pricing_rules")) not in items:
doc.append("items", args)
for item in doc.items:
if not item.is_free_item:
continue
free_item_data = args.get((item.item_code, item.pricing_rules))
if free_item_data:
free_item_data.pop("item_name")
free_item_data.pop("description")
item.update(free_item_data)
args.pop((item.item_code, item.pricing_rules))
for free_item in args.values():
doc.append("items", free_item)
def get_pricing_rule_items(pr_doc, other_items=False) -> list:

View File

@@ -49,7 +49,6 @@
<br>
{% endif %}
{{ _("Against") }}: {{ row.against }}
<br>{{ _("Remarks") }}: {{ row.remarks }}
{% if row.bill_no %}
<br>{{ _("Supplier Invoice No") }}: {{ row.bill_no }}

View File

@@ -1166,6 +1166,46 @@ class TestSalesInvoice(unittest.TestCase):
frappe.db.sql("delete from `tabPOS Profile`")
def test_bin_details_of_packed_item(self):
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
from erpnext.stock.doctype.item.test_item import make_item
# test Update Items with product bundle
if not frappe.db.exists("Item", "_Test Product Bundle Item New"):
bundle_item = make_item("_Test Product Bundle Item New", {"is_stock_item": 0})
bundle_item.append(
"item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"}
)
bundle_item.save(ignore_permissions=True)
make_item("_Packed Item New 1", {"is_stock_item": 1})
make_product_bundle("_Test Product Bundle Item New", ["_Packed Item New 1"], 2)
si = create_sales_invoice(
item_code="_Test Product Bundle Item New",
update_stock=1,
warehouse="_Test Warehouse - _TC",
transaction_date=add_days(nowdate(), -1),
do_not_submit=1,
)
make_stock_entry(item="_Packed Item New 1", target="_Test Warehouse - _TC", qty=120, rate=100)
bin_details = frappe.db.get_value(
"Bin",
{"item_code": "_Packed Item New 1", "warehouse": "_Test Warehouse - _TC"},
["actual_qty", "projected_qty", "ordered_qty"],
as_dict=1,
)
si.transaction_date = nowdate()
si.save()
packed_item = si.packed_items[0]
self.assertEqual(flt(bin_details.actual_qty), flt(packed_item.actual_qty))
self.assertEqual(flt(bin_details.projected_qty), flt(packed_item.projected_qty))
self.assertEqual(flt(bin_details.ordered_qty), flt(packed_item.ordered_qty))
def test_pos_si_without_payment(self):
make_pos_profile()

View File

@@ -5,5 +5,9 @@ frappe.ui.form.on('Subscription Plan', {
price_determination: function(frm) {
frm.toggle_reqd("cost", frm.doc.price_determination === 'Fixed rate');
frm.toggle_reqd("price_list", frm.doc.price_determination === 'Based on price list');
}
},
subscription_plan: function (frm) {
erpnext.utils.check_payments_app();
},
});

View File

@@ -1,6 +1,6 @@
{
"actions": [],
"autoname": "autoincrement",
"autoname": "hash",
"creation": "2022-09-13 16:18:59.404842",
"doctype": "DocType",
"editable_grid": 1,
@@ -36,11 +36,11 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-09-13 23:40:41.479208",
"modified": "2023-01-13 13:40:41.479208",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withheld Vouchers",
"naming_rule": "Autoincrement",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",

View File

@@ -259,9 +259,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
if tax_deducted:
net_total = inv.tax_withholding_net_total
if ldc:
tax_amount = get_tds_amount_from_ldc(
ldc, parties, pan_no, tax_details, posting_date, net_total
)
tax_amount = get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total)
else:
tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
@@ -538,7 +536,7 @@ def get_invoice_total_without_tcs(inv, tax_details):
return inv.grand_total - tcs_tax_row_amount
def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total):
def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total):
tds_amount = 0
limit_consumed = frappe.db.get_value(
"Purchase Invoice",

View File

@@ -378,15 +378,14 @@ class Deferred_Revenue_and_Expense_Report(object):
ret += [{}]
# add total row
if ret is not []:
if self.filters.type == "Revenue":
total_row = frappe._dict({"name": "Total Deferred Income"})
elif self.filters.type == "Expense":
total_row = frappe._dict({"name": "Total Deferred Expense"})
if self.filters.type == "Revenue":
total_row = frappe._dict({"name": "Total Deferred Income"})
elif self.filters.type == "Expense":
total_row = frappe._dict({"name": "Total Deferred Expense"})
for idx, period in enumerate(self.period_list, 0):
total_row[period.key] = self.period_total[idx].total
ret.append(total_row)
for idx, period in enumerate(self.period_list, 0):
total_row[period.key] = self.period_total[idx].total
ret.append(total_row)
return ret

View File

@@ -25,8 +25,8 @@
<thead>
<tr>
<th style="width: 12%">{%= __("Date") %}</th>
<th style="width: 15%">{%= __("Ref") %}</th>
<th style="width: 25%">{%= __("Party") %}</th>
<th style="width: 15%">{%= __("Reference") %}</th>
<th style="width: 25%">{%= __("Remarks") %}</th>
<th style="width: 15%">{%= __("Debit") %}</th>
<th style="width: 15%">{%= __("Credit") %}</th>
<th style="width: 18%">{%= __("Balance (Dr - Cr)") %}</th>
@@ -45,7 +45,6 @@
<br>
{% } %}
{{ __("Against") }}: {%= data[i].against %}
<br>{%= __("Remarks") %}: {%= data[i].remarks %}
{% if(data[i].bill_no) { %}
<br>{%= __("Supplier Invoice No") %}: {%= data[i].bill_no %}

View File

@@ -4,6 +4,7 @@
import frappe
from frappe import _
from frappe.utils import flt
def execute(filters=None):
@@ -65,6 +66,12 @@ def get_result(
else:
total_amount_credited += entry.credit
## Check if ldc is applied and show rate as per ldc
actual_rate = (tds_deducted / total_amount_credited) * 100
if flt(actual_rate) < flt(rate):
rate = actual_rate
if tds_deducted:
row = {
"pan"

View File

@@ -889,6 +889,11 @@ class TestPurchaseOrder(FrappeTestCase):
self.assertEqual(po.status, "Completed")
self.assertEqual(mr.status, "Received")
def test_variant_item_po(self):
po = create_purchase_order(item_code="_Test Variant Item", qty=1, rate=100, do_not_save=1)
self.assertRaises(frappe.ValidationError, po.save)
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
@@ -994,8 +999,8 @@ def create_purchase_order(**args):
},
)
po.set_missing_values()
if not args.do_not_save:
po.set_missing_values()
po.insert()
if not args.do_not_submit:
if po.is_subcontracted:

View File

@@ -25,7 +25,7 @@ class SellingController(StockController):
def onload(self):
super(SellingController, self).onload()
if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice"):
for item in self.get("items"):
for item in self.get("items") + (self.get("packed_items") or []):
item.update(get_bin_details(item.item_code, item.warehouse, include_child_warehouses=True))
def validate(self):

View File

@@ -58,7 +58,7 @@ status_map = {
"eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1",
],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
["On Hold", "eval:self.status=='On Hold'"],
],
"Purchase Order": [
@@ -79,7 +79,7 @@ status_map = {
["Delivered", "eval:self.status=='Delivered'"],
["Cancelled", "eval:self.docstatus==2"],
["On Hold", "eval:self.status=='On Hold'"],
["Closed", "eval:self.status=='Closed'"],
["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
],
"Delivery Note": [
["Draft", None],
@@ -87,7 +87,7 @@ status_map = {
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
],
"Purchase Receipt": [
["Draft", None],
@@ -95,7 +95,7 @@ status_map = {
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
],
"Material Request": [
["Draft", None],

View File

@@ -48,5 +48,11 @@ frappe.ui.form.on("E Commerce Settings", {
frm.set_value('default_customer_group', '');
frm.set_value('quotation_series', '');
}
},
enable_checkout: function(frm) {
if (frm.doc.enable_checkout) {
erpnext.utils.check_payments_app();
}
}
});

View File

@@ -174,7 +174,10 @@ class TestWebsiteItem(unittest.TestCase):
# Website Item Portal Tests Begin
def test_website_item_breadcrumbs(self):
"Check if breadcrumbs include homepage, product listing navigation page, parent item group(s) and item group."
"""
Check if breadcrumbs include homepage, product listing navigation page,
parent item group(s) and item group
"""
from erpnext.setup.doctype.item_group.item_group import get_parent_item_groups
item_code = "Test Breadcrumb Item"
@@ -197,7 +200,7 @@ class TestWebsiteItem(unittest.TestCase):
breadcrumbs = get_parent_item_groups(item.item_group)
self.assertEqual(breadcrumbs[0]["name"], "Home")
self.assertEqual(breadcrumbs[1]["name"], "Shop by Category")
self.assertEqual(breadcrumbs[1]["name"], "All Products")
self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group
self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1")

View File

@@ -2,4 +2,7 @@
// For license information, please see license.txt
frappe.ui.form.on('GoCardless Settings', {
refresh: function(frm) {
erpnext.utils.check_payments_app();
}
});

View File

@@ -173,7 +173,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-02-12 14:18:47.209114",
"modified": "2022-02-12 14:18:47.209114",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "GoCardless Settings",
@@ -201,7 +201,6 @@
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,

View File

@@ -10,7 +10,8 @@ from frappe import _
from frappe.integrations.utils import create_request_log
from frappe.model.document import Document
from frappe.utils import call_hook_method, cint, flt, get_url
from payments.utils import create_payment_gateway
from erpnext.utilities import payment_app_import_guard
class GoCardlessSettings(Document):
@@ -30,6 +31,9 @@ class GoCardlessSettings(Document):
frappe.throw(e)
def on_update(self):
with payment_app_import_guard():
from payments.utils import create_payment_gateway
create_payment_gateway(
"GoCardless-" + self.gateway_name, settings="GoCardLess Settings", controller=self.gateway_name
)

View File

@@ -7,6 +7,8 @@ frappe.ui.form.on('Mpesa Settings', {
},
refresh: function(frm) {
erpnext.utils.check_payments_app();
frappe.realtime.on("refresh_mpesa_dashboard", function(){
frm.reload_doc();
frm.events.setup_account_balance_html(frm);

View File

@@ -9,13 +9,13 @@ from frappe import _
from frappe.integrations.utils import create_request_log
from frappe.model.document import Document
from frappe.utils import call_hook_method, fmt_money, get_request_site_address
from payments.utils import create_payment_gateway
from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_connector import MpesaConnector
from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_custom_fields import (
create_custom_pos_fields,
)
from erpnext.erpnext_integrations.utils import create_mode_of_payment
from erpnext.utilities import payment_app_import_guard
class MpesaSettings(Document):
@@ -30,6 +30,9 @@ class MpesaSettings(Document):
)
def on_update(self):
with payment_app_import_guard():
from payments.utils import create_payment_gateway
create_custom_pos_fields()
create_payment_gateway(
"Mpesa-" + self.payment_gateway_name,

View File

@@ -2,12 +2,16 @@
# For license information, please see license.txt
import frappe
import stripe
from frappe import _
from frappe.integrations.utils import create_request_log
from erpnext.utilities import payment_app_import_guard
def create_stripe_subscription(gateway_controller, data):
with payment_app_import_guard():
import stripe
stripe_settings = frappe.get_doc("Stripe Settings", gateway_controller)
stripe_settings.data = frappe._dict(data)
@@ -35,6 +39,9 @@ def create_stripe_subscription(gateway_controller, data):
def create_subscription_on_stripe(stripe_settings):
with payment_app_import_guard():
import stripe
items = []
for payment_plan in stripe_settings.payment_plans:
plan = frappe.db.get_value("Subscription Plan", payment_plan.plan, "product_price_id")

View File

@@ -10,7 +10,6 @@ app_email = "info@erpnext.com"
app_license = "GNU General Public License (v3)"
source_link = "https://github.com/frappe/erpnext"
app_logo_url = "/assets/erpnext/images/erpnext-logo.svg"
required_apps = ["payments"]
develop_version = "14.x.x-develop"

View File

@@ -65,7 +65,21 @@ frappe.ui.form.on("BOM", {
});
},
onload_post_render(frm) {
validate: function(frm) {
if (frm.doc.fg_based_operating_cost && frm.doc.with_operations) {
frappe.throw({message: __("Please check either with operations or FG Based Operating Cost."), title: __("Mandatory")});
}
},
with_operations: function(frm) {
frm.set_df_property("fg_based_operating_cost", "hidden", frm.doc.with_operations ? 1 : 0);
},
fg_based_operating_cost: function(frm) {
frm.set_df_property("with_operations", "hidden", frm.doc.fg_based_operating_cost ? 1 : 0);
},
onload_post_render: function(frm) {
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
},
@@ -532,18 +546,25 @@ erpnext.bom.update_cost = function(doc) {
};
erpnext.bom.calculate_op_cost = function(doc) {
var op = doc.operations || [];
doc.operating_cost = 0.0;
doc.base_operating_cost = 0.0;
for(var i=0;i<op.length;i++) {
var operating_cost = flt(flt(op[i].hour_rate) * flt(op[i].time_in_mins) / 60, 2);
var base_operating_cost = flt(operating_cost * doc.conversion_rate, 2);
frappe.model.set_value('BOM Operation',op[i].name, "operating_cost", operating_cost);
frappe.model.set_value('BOM Operation',op[i].name, "base_operating_cost", base_operating_cost);
if(doc.with_operations) {
doc.operations.forEach((item) => {
let operating_cost = flt(flt(item.hour_rate) * flt(item.time_in_mins) / 60, 2);
let base_operating_cost = flt(operating_cost * doc.conversion_rate, 2);
frappe.model.set_value('BOM Operation',item.name, {
"operating_cost": operating_cost,
"base_operating_cost": base_operating_cost
});
doc.operating_cost += operating_cost;
doc.base_operating_cost += base_operating_cost;
doc.operating_cost += operating_cost;
doc.base_operating_cost += base_operating_cost;
});
} else if(doc.fg_based_operating_cost) {
let total_operating_cost = doc.quantity * flt(doc.operating_cost_per_bom_quantity);
doc.operating_cost = total_operating_cost;
doc.base_operating_cost = flt(total_operating_cost * doc.conversion_rate, 2);
}
refresh_field(['operating_cost', 'base_operating_cost']);
};

View File

@@ -33,6 +33,9 @@
"column_break_23",
"transfer_material_against",
"routing",
"fg_based_operating_cost",
"fg_based_section_section",
"operating_cost_per_bom_quantity",
"operations_section",
"operations",
"materials_section",
@@ -575,7 +578,26 @@
{
"fieldname": "scrap_items_section",
"fieldtype": "Section Break",
"hide_border": 1,
"label": "Scrap Items"
},
{
"default": "0",
"fieldname": "fg_based_operating_cost",
"fieldtype": "Check",
"label": "FG based Operating Cost"
},
{
"depends_on": "fg_based_operating_cost",
"fieldname": "fg_based_section_section",
"fieldtype": "Section Break",
"label": "FG Based Operating Cost Section"
},
{
"depends_on": "fg_based_operating_cost",
"fieldname": "operating_cost_per_bom_quantity",
"fieldtype": "Currency",
"label": "Operating Cost Per BOM Quantity"
}
],
"icon": "fa fa-sitemap",
@@ -583,7 +605,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
"modified": "2023-01-03 18:42:27.732107",
"modified": "2023-01-10 07:47:08.652616",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",

View File

@@ -614,18 +614,26 @@ class BOM(WebsiteGenerator):
"""Update workstation rate and calculates totals"""
self.operating_cost = 0
self.base_operating_cost = 0
for d in self.get("operations"):
if d.workstation:
self.update_rate_and_time(d, update_hour_rate)
if self.get("with_operations"):
for d in self.get("operations"):
if d.workstation:
self.update_rate_and_time(d, update_hour_rate)
operating_cost = d.operating_cost
base_operating_cost = d.base_operating_cost
if d.set_cost_based_on_bom_qty:
operating_cost = flt(d.cost_per_unit) * flt(self.quantity)
base_operating_cost = flt(d.base_cost_per_unit) * flt(self.quantity)
operating_cost = d.operating_cost
base_operating_cost = d.base_operating_cost
if d.set_cost_based_on_bom_qty:
operating_cost = flt(d.cost_per_unit) * flt(self.quantity)
base_operating_cost = flt(d.base_cost_per_unit) * flt(self.quantity)
self.operating_cost += flt(operating_cost)
self.base_operating_cost += flt(base_operating_cost)
self.operating_cost += flt(operating_cost)
self.base_operating_cost += flt(base_operating_cost)
elif self.get("fg_based_operating_cost"):
total_operating_cost = flt(self.get("quantity")) * flt(
self.get("operating_cost_per_bom_quantity")
)
self.operating_cost = total_operating_cost
self.base_operating_cost = flt(total_operating_cost * self.conversion_rate, 2)
def update_rate_and_time(self, row, update_hour_rate=False):
if not row.hour_rate or update_hour_rate:

View File

@@ -202,6 +202,33 @@ class TestBOM(FrappeTestCase):
self.assertEqual(bom.items[0].rate, 20)
def test_bom_cost_with_fg_based_operating_cost(self):
bom = frappe.copy_doc(test_records[4])
bom.insert()
raw_material_cost = 0.0
op_cost = 0.0
op_cost = bom.quantity * bom.operating_cost_per_bom_quantity
for row in bom.items:
raw_material_cost += row.amount
base_raw_material_cost = raw_material_cost * flt(
bom.conversion_rate, bom.precision("conversion_rate")
)
base_op_cost = op_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
# test amounts in selected currency, almostEqual checks for 7 digits by default
self.assertAlmostEqual(bom.operating_cost, op_cost)
self.assertAlmostEqual(bom.raw_material_cost, raw_material_cost)
self.assertAlmostEqual(bom.total_cost, raw_material_cost + op_cost)
# test amounts in selected currency
self.assertAlmostEqual(bom.base_operating_cost, base_op_cost)
self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost)
self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
def test_subcontractor_sourced_item(self):
item_code = "_Test Subcontracted FG Item 1"
set_backflush_based_on("Material Transferred for Subcontract")

View File

@@ -162,5 +162,31 @@
"item": "_Test Variant Item",
"quantity": 1.0,
"with_operations": 1
},
{
"items": [
{
"amount": 5000.0,
"doctype": "BOM Item",
"item_code": "_Test Item",
"parentfield": "items",
"qty": 2.0,
"rate": 3000.0,
"uom": "_Test UOM",
"stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC",
"include_item_in_manufacturing": 1
}
],
"docstatus": 1,
"doctype": "BOM",
"is_active": 1,
"is_default": 1,
"currency": "USD",
"item": "_Test Variant Item",
"quantity": 1.0,
"with_operations": 0,
"fg_based_operating_cost": 1,
"operating_cost_per_bom_quantity": 140
}
]

View File

@@ -268,6 +268,7 @@ erpnext.patches.v13_0.show_india_localisation_deprecation_warning
erpnext.patches.v13_0.show_hr_payroll_deprecation_warning
erpnext.patches.v13_0.reset_corrupt_defaults
erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair
erpnext.patches.v14_0.update_reference_due_date_in_journal_entry
[post_model_sync]
execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
@@ -323,3 +324,4 @@ erpnext.patches.v14_0.create_incoterms_and_migrate_shipment
erpnext.patches.v14_0.setup_clear_repost_logs
erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request
erpnext.patches.v14_0.update_entry_type_for_journal_entry
erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers

View File

@@ -0,0 +1,12 @@
import frappe
def execute():
if (
frappe.db.sql(
"""select data_type FROM information_schema.columns
where column_name = 'name' and table_name = 'tabTax Withheld Vouchers'"""
)[0][0]
== "bigint"
):
frappe.db.change_column_type("Tax Withheld Vouchers", "name", "varchar(140)")

View File

@@ -0,0 +1,12 @@
import frappe
def execute():
if frappe.db.get_value("Journal Entry Account", {"reference_due_date": ""}):
frappe.db.sql(
"""
UPDATE `tabJournal Entry Account`
SET reference_due_date = NULL
WHERE reference_due_date = ''
"""
)

View File

@@ -97,7 +97,7 @@ class Task(NestedSet):
if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"):
frappe.throw(
_(
"Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled."
"Cannot complete task {0} as its dependant task {1} are not completed / cancelled."
).format(frappe.bold(self.name), frappe.bold(d.task))
)

View File

@@ -1,7 +1,7 @@
frappe.provide("erpnext.accounts.bank_reconciliation");
erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
constructor(company, bank_account) {
constructor(company, bank_account, bank_statement_from_date, bank_statement_to_date, filter_by_reference_date, from_reference_date, to_reference_date) {
this.bank_account = bank_account;
this.company = company;
this.make_dialog();

View File

@@ -122,24 +122,16 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
calculate_item_values() {
var me = this;
if (!this.discount_amount_applied) {
$.each(this.frm.doc["items"] || [], function(i, item) {
for (item of this.frm.doc.items || []) {
frappe.model.round_floats_in(item);
item.net_rate = item.rate;
if ((!item.qty) && me.frm.doc.is_return) {
item.amount = flt(item.rate * -1, precision("amount", item));
} else if ((!item.qty) && me.frm.doc.is_debit_note) {
item.amount = flt(item.rate, precision("amount", item));
} else {
item.amount = flt(item.rate * item.qty, precision("amount", item));
}
item.net_amount = item.amount;
item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty;
item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item));
item.item_tax_amount = 0.0;
item.total_weight = flt(item.weight_per_unit * item.stock_qty);
me.set_in_company_currency(item, ["price_list_rate", "rate", "amount", "net_rate", "net_amount"]);
});
}
}
}

View File

@@ -1473,6 +1473,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
"parenttype": d.parenttype,
"parent": d.parent,
"pricing_rules": d.pricing_rules,
"is_free_item": d.is_free_item,
"warehouse": d.warehouse,
"serial_no": d.serial_no,
"batch_no": d.batch_no,

View File

@@ -333,8 +333,18 @@ $.extend(erpnext.utils, {
}
frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
});
}
},
// check if payments app is installed on site, if not warn user.
check_payments_app: () => {
if (frappe.boot.versions && !frappe.boot.versions.payments) {
const marketplace_link = '<a href="https://frappecloud.com/marketplace/apps/payments">Marketplace</a>'
const github_link = '<a href="https://github.com/frappe/payments/">GitHub</a>'
const msg = __("payments app is not installed. Please install it from {0} or {1}", [marketplace_link, github_link])
frappe.msgprint(msg);
}
},
});
erpnext.utils.select_alternate_items = function(opts) {

View File

@@ -552,6 +552,42 @@ class TestSalesOrder(FrappeTestCase):
workflow.is_active = 0
workflow.save()
def test_bin_details_of_packed_item(self):
# test Update Items with product bundle
if not frappe.db.exists("Item", "_Test Product Bundle Item New"):
bundle_item = make_item("_Test Product Bundle Item New", {"is_stock_item": 0})
bundle_item.append(
"item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"}
)
bundle_item.save(ignore_permissions=True)
make_item("_Packed Item New 1", {"is_stock_item": 1})
make_product_bundle("_Test Product Bundle Item New", ["_Packed Item New 1"], 2)
so = make_sales_order(
item_code="_Test Product Bundle Item New",
warehouse="_Test Warehouse - _TC",
transaction_date=add_days(nowdate(), -1),
do_not_submit=1,
)
make_stock_entry(item="_Packed Item New 1", target="_Test Warehouse - _TC", qty=120, rate=100)
bin_details = frappe.db.get_value(
"Bin",
{"item_code": "_Packed Item New 1", "warehouse": "_Test Warehouse - _TC"},
["actual_qty", "projected_qty", "ordered_qty"],
as_dict=1,
)
so.transaction_date = nowdate()
so.save()
packed_item = so.packed_items[0]
self.assertEqual(flt(bin_details.actual_qty), flt(packed_item.actual_qty))
self.assertEqual(flt(bin_details.projected_qty), flt(packed_item.projected_qty))
self.assertEqual(flt(bin_details.ordered_qty), flt(packed_item.ordered_qty))
def test_update_child_product_bundle(self):
# test Update Items with product bundle
if not frappe.db.exists("Item", "_Product Bundle Item"):

View File

@@ -148,12 +148,12 @@ def get_item_for_list_in_html(context):
def get_parent_item_groups(item_group_name, from_item=False):
base_nav_page = {"name": _("Shop by Category"), "route": "/shop-by-category"}
base_nav_page = {"name": _("All Products"), "route": "/all-products"}
if from_item and frappe.request.environ.get("HTTP_REFERER"):
# base page after 'Home' will vary on Item page
last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0]
if last_page and last_page in ("shop-by-category", "all-products"):
if last_page and last_page == "shop-by-category":
base_nav_page_title = " ".join(last_page.split("-")).title()
base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page}

View File

@@ -490,6 +490,46 @@ class TestDeliveryNote(FrappeTestCase):
self.assertEqual(gle_warehouse_amount, 1400)
def test_bin_details_of_packed_item(self):
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
from erpnext.stock.doctype.item.test_item import make_item
# test Update Items with product bundle
if not frappe.db.exists("Item", "_Test Product Bundle Item New"):
bundle_item = make_item("_Test Product Bundle Item New", {"is_stock_item": 0})
bundle_item.append(
"item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"}
)
bundle_item.save(ignore_permissions=True)
make_item("_Packed Item New 1", {"is_stock_item": 1})
make_product_bundle("_Test Product Bundle Item New", ["_Packed Item New 1"], 2)
si = create_delivery_note(
item_code="_Test Product Bundle Item New",
update_stock=1,
warehouse="_Test Warehouse - _TC",
transaction_date=add_days(nowdate(), -1),
do_not_submit=1,
)
make_stock_entry(item="_Packed Item New 1", target="_Test Warehouse - _TC", qty=120, rate=100)
bin_details = frappe.db.get_value(
"Bin",
{"item_code": "_Packed Item New 1", "warehouse": "_Test Warehouse - _TC"},
["actual_qty", "projected_qty", "ordered_qty"],
as_dict=1,
)
si.transaction_date = nowdate()
si.save()
packed_item = si.packed_items[0]
self.assertEqual(flt(bin_details.actual_qty), flt(packed_item.actual_qty))
self.assertEqual(flt(bin_details.projected_qty), flt(packed_item.projected_qty))
self.assertEqual(flt(bin_details.ordered_qty), flt(packed_item.ordered_qty))
def test_return_for_serialized_items(self):
se = make_serialized_item()
serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
@@ -650,6 +690,11 @@ class TestDeliveryNote(FrappeTestCase):
update_delivery_note_status(dn.name, "Closed")
self.assertEqual(frappe.db.get_value("Delivery Note", dn.name, "Status"), "Closed")
# Check cancelling closed delivery note
dn.load_from_db()
dn.cancel()
self.assertEqual(dn.status, "Cancelled")
def test_dn_billing_status_case1(self):
# SO -> DN -> SI
so = make_sales_order()

View File

@@ -74,11 +74,10 @@ class ItemAttribute(Document):
def validate_duplication(self):
values, abbrs = [], []
for d in self.item_attribute_values:
d.abbr = d.abbr.upper()
if d.attribute_value in values:
frappe.throw(_("{0} must appear only once").format(d.attribute_value))
if d.attribute_value.lower() in map(str.lower, values):
frappe.throw(_("Attribute value: {0} must appear only once").format(d.attribute_value.title()))
values.append(d.attribute_value)
if d.abbr in abbrs:
frappe.throw(_("{0} must appear only once").format(d.abbr))
if d.abbr.lower() in map(str.lower, abbrs):
frappe.throw(_("Abbreviation: {0} must appear only once").format(d.abbr.title()))
abbrs.append(d.abbr)

View File

@@ -11,7 +11,7 @@ from frappe import _
from frappe.model.document import Document
from frappe.model.mapper import map_child_doc
from frappe.query_builder import Case
from frappe.query_builder.functions import Locate
from frappe.query_builder.functions import IfNull, Locate, Sum
from frappe.utils import cint, floor, flt, today
from frappe.utils.nestedset import get_descendants_of
@@ -503,42 +503,30 @@ def get_available_item_locations_for_serialized_item(
def get_available_item_locations_for_batched_item(
item_code, from_warehouses, required_qty, company
):
warehouse_condition = "and warehouse in %(warehouses)s" if from_warehouses else ""
batch_locations = frappe.db.sql(
"""
SELECT
sle.`warehouse`,
sle.`batch_no`,
SUM(sle.`actual_qty`) AS `qty`
FROM
`tabStock Ledger Entry` sle, `tabBatch` batch
WHERE
sle.batch_no = batch.name
and sle.`item_code`=%(item_code)s
and sle.`company` = %(company)s
and batch.disabled = 0
and sle.is_cancelled=0
and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
{warehouse_condition}
GROUP BY
sle.`warehouse`,
sle.`batch_no`,
sle.`item_code`
HAVING `qty` > 0
ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`, sle.`batch_no`, sle.`warehouse`
""".format(
warehouse_condition=warehouse_condition
),
{ # nosec
"item_code": item_code,
"company": company,
"today": today(),
"warehouses": from_warehouses,
},
as_dict=1,
sle = frappe.qb.DocType("Stock Ledger Entry")
batch = frappe.qb.DocType("Batch")
query = (
frappe.qb.from_(sle)
.from_(batch)
.select(sle.warehouse, sle.batch_no, Sum(sle.actual_qty).as_("qty"))
.where(
(sle.batch_no == batch.name)
& (sle.item_code == item_code)
& (sle.company == company)
& (batch.disabled == 0)
& (sle.is_cancelled == 0)
& (IfNull(batch.expiry_date, "2200-01-01") > today())
)
.groupby(sle.warehouse, sle.batch_no, sle.item_code)
.having(Sum(sle.actual_qty) > 0)
.orderby(IfNull(batch.expiry_date, "2200-01-01"), batch.creation, sle.batch_no, sle.warehouse)
)
return batch_locations
if from_warehouses:
query = query.where(sle.warehouse.isin(from_warehouses))
return query.run(as_dict=True)
def get_available_item_locations_for_serial_and_batched_item(

View File

@@ -236,8 +236,10 @@ def validate_item_details(args, item):
validate_end_of_life(item.name, item.end_of_life, item.disabled)
if args.transaction_type == "selling" and cint(item.has_variants):
throw(_("Item {0} is a template, please select one of its variants").format(item.name))
if cint(item.has_variants):
msg = f"Item {item.name} is a template, please select one of its variants"
throw(_(msg), title=_("Template Item Selected"))
elif args.transaction_type == "buying" and args.doctype != "Material Request":
if args.get("is_subcontracted"):

View File

@@ -1,352 +1,353 @@
{
"actions": [],
"autoname": "hash",
"creation": "2022-04-01 19:26:31.475015",
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"item_code",
"item_name",
"bom",
"include_exploded_items",
"column_break_3",
"schedule_date",
"expected_delivery_date",
"description_section",
"description",
"column_break_8",
"image",
"image_view",
"quantity_and_rate_section",
"qty",
"received_qty",
"returned_qty",
"column_break_13",
"stock_uom",
"conversion_factor",
"section_break_16",
"rate",
"amount",
"column_break_19",
"rm_cost_per_qty",
"service_cost_per_qty",
"additional_cost_per_qty",
"warehouse_section",
"warehouse",
"accounting_details_section",
"expense_account",
"manufacture_section",
"manufacturer",
"manufacturer_part_no",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"section_break_34",
"page_break"
],
"fields": [
{
"bold": 1,
"columns": 2,
"fieldname": "item_code",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item Code",
"options": "Item",
"read_only": 1,
"reqd": 1,
"search_index": 1
},
{
"fetch_from": "item_code.item_name",
"fetch_if_empty": 1,
"fieldname": "item_name",
"fieldtype": "Data",
"in_global_search": 1,
"label": "Item Name",
"print_hide": 1,
"reqd": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"bold": 1,
"columns": 2,
"fieldname": "schedule_date",
"fieldtype": "Date",
"label": "Required By",
"print_hide": 1,
"read_only": 1
},
{
"allow_on_submit": 1,
"bold": 1,
"fieldname": "expected_delivery_date",
"fieldtype": "Date",
"label": "Expected Delivery Date",
"search_index": 1
},
{
"collapsible": 1,
"fieldname": "description_section",
"fieldtype": "Section Break",
"label": "Description"
},
{
"fetch_from": "item_code.description",
"fetch_if_empty": 1,
"fieldname": "description",
"fieldtype": "Text Editor",
"label": "Description",
"print_width": "300px",
"reqd": 1,
"width": "300px"
},
{
"fieldname": "column_break_8",
"fieldtype": "Column Break"
},
{
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
"label": "Image"
},
{
"fieldname": "image_view",
"fieldtype": "Image",
"label": "Image View",
"options": "image",
"print_hide": 1
},
{
"fieldname": "quantity_and_rate_section",
"fieldtype": "Section Break",
"label": "Quantity and Rate"
},
{
"bold": 1,
"columns": 1,
"default": "1",
"fieldname": "qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Quantity",
"print_width": "60px",
"read_only": 1,
"reqd": 1,
"width": "60px"
},
{
"fieldname": "column_break_13",
"fieldtype": "Column Break",
"print_hide": 1
},
{
"fieldname": "stock_uom",
"fieldtype": "Link",
"label": "Stock UOM",
"options": "UOM",
"print_width": "100px",
"read_only": 1,
"reqd": 1,
"width": "100px"
},
{
"default": "1",
"fieldname": "conversion_factor",
"fieldtype": "Float",
"hidden": 1,
"label": "Conversion Factor",
"read_only": 1
},
{
"fieldname": "section_break_16",
"fieldtype": "Section Break"
},
{
"bold": 1,
"columns": 2,
"fetch_from": "item_code.standard_rate",
"fetch_if_empty": 1,
"fieldname": "rate",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Rate",
"options": "currency",
"read_only": 1,
"reqd": 1
},
{
"fieldname": "column_break_19",
"fieldtype": "Column Break"
},
{
"columns": 2,
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
"options": "currency",
"read_only": 1,
"reqd": 1
},
{
"fieldname": "warehouse_section",
"fieldtype": "Section Break",
"label": "Warehouse Details"
},
{
"fieldname": "warehouse",
"fieldtype": "Link",
"label": "Warehouse",
"options": "Warehouse",
"print_hide": 1,
"reqd": 1
},
{
"collapsible": 1,
"fieldname": "accounting_details_section",
"fieldtype": "Section Break",
"label": "Accounting Details"
},
{
"fieldname": "expense_account",
"fieldtype": "Link",
"label": "Expense Account",
"options": "Account",
"print_hide": 1
},
{
"collapsible": 1,
"fieldname": "manufacture_section",
"fieldtype": "Section Break",
"label": "Manufacture"
},
{
"fieldname": "manufacturer",
"fieldtype": "Link",
"label": "Manufacturer",
"options": "Manufacturer"
},
{
"fieldname": "manufacturer_part_no",
"fieldtype": "Data",
"label": "Manufacturer Part Number"
},
{
"depends_on": "item_code",
"fetch_from": "item_code.default_bom",
"fieldname": "bom",
"fieldtype": "Link",
"in_list_view": 1,
"label": "BOM",
"options": "BOM",
"print_hide": 1,
"reqd": 1
},
{
"default": "0",
"fieldname": "include_exploded_items",
"fieldtype": "Check",
"label": "Include Exploded Items",
"print_hide": 1
},
{
"fieldname": "service_cost_per_qty",
"fieldtype": "Currency",
"label": "Service Cost Per Qty",
"read_only": 1,
"reqd": 1
},
{
"default": "0",
"fieldname": "additional_cost_per_qty",
"fieldtype": "Currency",
"label": "Additional Cost Per Qty",
"read_only": 1
},
{
"fieldname": "rm_cost_per_qty",
"fieldtype": "Currency",
"label": "Raw Material Cost Per Qty",
"no_copy": 1,
"read_only": 1
},
{
"allow_on_submit": 1,
"default": "0",
"fieldname": "page_break",
"fieldtype": "Check",
"label": "Page Break",
"no_copy": 1,
"print_hide": 1
},
{
"fieldname": "section_break_34",
"fieldtype": "Section Break"
},
{
"depends_on": "received_qty",
"fieldname": "received_qty",
"fieldtype": "Float",
"label": "Received Qty",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"depends_on": "returned_qty",
"fieldname": "returned_qty",
"fieldtype": "Float",
"label": "Returned Qty",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-08-15 14:25:45.177703",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Order Item",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"search_fields": "item_name",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
"actions": [],
"autoname": "hash",
"creation": "2022-04-01 19:26:31.475015",
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"item_code",
"item_name",
"bom",
"include_exploded_items",
"column_break_3",
"schedule_date",
"expected_delivery_date",
"description_section",
"description",
"column_break_8",
"image",
"image_view",
"quantity_and_rate_section",
"qty",
"received_qty",
"returned_qty",
"column_break_13",
"stock_uom",
"conversion_factor",
"section_break_16",
"rate",
"amount",
"column_break_19",
"rm_cost_per_qty",
"service_cost_per_qty",
"additional_cost_per_qty",
"warehouse_section",
"warehouse",
"accounting_details_section",
"expense_account",
"manufacture_section",
"manufacturer",
"manufacturer_part_no",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"section_break_34",
"page_break"
],
"fields": [
{
"bold": 1,
"columns": 2,
"fieldname": "item_code",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item Code",
"options": "Item",
"read_only": 1,
"reqd": 1,
"search_index": 1
},
{
"fetch_from": "item_code.item_name",
"fetch_if_empty": 1,
"fieldname": "item_name",
"fieldtype": "Data",
"in_global_search": 1,
"label": "Item Name",
"print_hide": 1,
"reqd": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"bold": 1,
"columns": 2,
"fieldname": "schedule_date",
"fieldtype": "Date",
"label": "Required By",
"print_hide": 1,
"read_only": 1
},
{
"allow_on_submit": 1,
"bold": 1,
"fieldname": "expected_delivery_date",
"fieldtype": "Date",
"label": "Expected Delivery Date",
"search_index": 1
},
{
"collapsible": 1,
"fieldname": "description_section",
"fieldtype": "Section Break",
"label": "Description"
},
{
"fetch_from": "item_code.description",
"fetch_if_empty": 1,
"fieldname": "description",
"fieldtype": "Text Editor",
"label": "Description",
"print_width": "300px",
"reqd": 1,
"width": "300px"
},
{
"fieldname": "column_break_8",
"fieldtype": "Column Break"
},
{
"fieldname": "image",
"fieldtype": "Attach",
"hidden": 1,
"label": "Image"
},
{
"fieldname": "image_view",
"fieldtype": "Image",
"label": "Image View",
"options": "image",
"print_hide": 1
},
{
"fieldname": "quantity_and_rate_section",
"fieldtype": "Section Break",
"label": "Quantity and Rate"
},
{
"bold": 1,
"columns": 1,
"default": "1",
"fieldname": "qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Quantity",
"print_width": "60px",
"read_only": 1,
"reqd": 1,
"width": "60px"
},
{
"fieldname": "column_break_13",
"fieldtype": "Column Break",
"print_hide": 1
},
{
"fieldname": "stock_uom",
"fieldtype": "Link",
"label": "Stock UOM",
"options": "UOM",
"print_width": "100px",
"read_only": 1,
"reqd": 1,
"width": "100px"
},
{
"default": "1",
"fieldname": "conversion_factor",
"fieldtype": "Float",
"hidden": 1,
"label": "Conversion Factor",
"read_only": 1
},
{
"fieldname": "section_break_16",
"fieldtype": "Section Break"
},
{
"bold": 1,
"columns": 2,
"fetch_from": "item_code.standard_rate",
"fetch_if_empty": 1,
"fieldname": "rate",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Rate",
"options": "currency",
"read_only": 1,
"reqd": 1
},
{
"fieldname": "column_break_19",
"fieldtype": "Column Break"
},
{
"columns": 2,
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount",
"options": "currency",
"read_only": 1,
"reqd": 1
},
{
"fieldname": "warehouse_section",
"fieldtype": "Section Break",
"label": "Warehouse Details"
},
{
"fieldname": "warehouse",
"fieldtype": "Link",
"label": "Warehouse",
"options": "Warehouse",
"print_hide": 1,
"reqd": 1
},
{
"collapsible": 1,
"fieldname": "accounting_details_section",
"fieldtype": "Section Break",
"label": "Accounting Details"
},
{
"fieldname": "expense_account",
"fieldtype": "Link",
"label": "Expense Account",
"options": "Account",
"print_hide": 1
},
{
"collapsible": 1,
"fieldname": "manufacture_section",
"fieldtype": "Section Break",
"label": "Manufacture"
},
{
"fieldname": "manufacturer",
"fieldtype": "Link",
"label": "Manufacturer",
"options": "Manufacturer"
},
{
"fieldname": "manufacturer_part_no",
"fieldtype": "Data",
"label": "Manufacturer Part Number"
},
{
"depends_on": "item_code",
"fetch_from": "item_code.default_bom",
"fetch_if_empty": 1,
"fieldname": "bom",
"fieldtype": "Link",
"in_list_view": 1,
"label": "BOM",
"options": "BOM",
"print_hide": 1,
"reqd": 1
},
{
"default": "0",
"fieldname": "include_exploded_items",
"fieldtype": "Check",
"label": "Include Exploded Items",
"print_hide": 1
},
{
"fieldname": "service_cost_per_qty",
"fieldtype": "Currency",
"label": "Service Cost Per Qty",
"read_only": 1,
"reqd": 1
},
{
"default": "0",
"fieldname": "additional_cost_per_qty",
"fieldtype": "Currency",
"label": "Additional Cost Per Qty",
"read_only": 1
},
{
"fieldname": "rm_cost_per_qty",
"fieldtype": "Currency",
"label": "Raw Material Cost Per Qty",
"no_copy": 1,
"read_only": 1
},
{
"allow_on_submit": 1,
"default": "0",
"fieldname": "page_break",
"fieldtype": "Check",
"label": "Page Break",
"no_copy": 1,
"print_hide": 1
},
{
"fieldname": "section_break_34",
"fieldtype": "Section Break"
},
{
"depends_on": "received_qty",
"fieldname": "received_qty",
"fieldtype": "Float",
"label": "Received Qty",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"depends_on": "returned_qty",
"fieldname": "returned_qty",
"fieldtype": "Float",
"label": "Returned Qty",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-01-20 23:25:45.363281",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Order Item",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"search_fields": "item_name",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -262,15 +262,17 @@ class SubcontractingReceipt(SubcontractingController):
def get_gl_entries(self, warehouse_account=None):
from erpnext.accounts.general_ledger import process_gl_map
if not erpnext.is_perpetual_inventory_enabled(self.company):
return []
gl_entries = []
self.make_item_gl_entries(gl_entries, warehouse_account)
return process_gl_map(gl_entries)
def make_item_gl_entries(self, gl_entries, warehouse_account=None):
if erpnext.is_perpetual_inventory_enabled(self.company):
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
warehouse_with_no_account = []

View File

@@ -1,6 +1,9 @@
## temp utility
from contextlib import contextmanager
import frappe
from frappe import _
from frappe.utils import cstr
from erpnext.utilities.activation import get_level
@@ -35,3 +38,16 @@ def get_site_info(site_info):
domain = frappe.get_cached_value("Company", cstr(company), "domain")
return {"company": company, "domain": domain, "activation": get_level()}
@contextmanager
def payment_app_import_guard():
marketplace_link = '<a href="https://frappecloud.com/marketplace/apps/payments">Marketplace</a>'
github_link = '<a href="https://github.com/frappe/payments/">GitHub</a>'
msg = _("payments app is not installed. Please install it from {} or {}").format(
marketplace_link, github_link
)
try:
yield
except ImportError:
frappe.throw(msg, title=_("Missing Payments App"))