mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-04 20:59:11 +00:00
Merge branch 'version-13-hotfix' into fix-payment-reconciliation-jv-cost-center
This commit is contained in:
@@ -1572,7 +1572,7 @@
|
|||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-03-22 13:00:24.166684",
|
"modified": "2022-09-27 13:00:24.166684",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice",
|
"name": "POS Invoice",
|
||||||
|
|||||||
@@ -239,14 +239,14 @@ class POSInvoice(SalesInvoice):
|
|||||||
frappe.bold(d.warehouse),
|
frappe.bold(d.warehouse),
|
||||||
frappe.bold(d.qty),
|
frappe.bold(d.qty),
|
||||||
)
|
)
|
||||||
if flt(available_stock) <= 0:
|
if is_stock_item and flt(available_stock) <= 0:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Row #{}: Item Code: {} is not available under warehouse {}.").format(
|
_("Row #{}: Item Code: {} is not available under warehouse {}.").format(
|
||||||
d.idx, item_code, warehouse
|
d.idx, item_code, warehouse
|
||||||
),
|
),
|
||||||
title=_("Item Unavailable"),
|
title=_("Item Unavailable"),
|
||||||
)
|
)
|
||||||
elif flt(available_stock) < flt(d.qty):
|
elif is_stock_item and flt(available_stock) < flt(d.qty):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}."
|
"Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}."
|
||||||
@@ -634,11 +634,12 @@ def get_stock_availability(item_code, warehouse):
|
|||||||
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
|
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
|
||||||
return bin_qty - pos_sales_qty, is_stock_item
|
return bin_qty - pos_sales_qty, is_stock_item
|
||||||
else:
|
else:
|
||||||
is_stock_item = False
|
is_stock_item = True
|
||||||
if frappe.db.exists("Product Bundle", item_code):
|
if frappe.db.exists("Product Bundle", item_code):
|
||||||
return get_bundle_availability(item_code, warehouse), is_stock_item
|
return get_bundle_availability(item_code, warehouse), is_stock_item
|
||||||
else:
|
else:
|
||||||
# Is a service item
|
is_stock_item = False
|
||||||
|
# Is a service item or non_stock item
|
||||||
return 0, is_stock_item
|
return 0, is_stock_item
|
||||||
|
|
||||||
|
|
||||||
@@ -652,7 +653,9 @@ def get_bundle_availability(bundle_item_code, warehouse):
|
|||||||
available_qty = item_bin_qty - item_pos_reserved_qty
|
available_qty = item_bin_qty - item_pos_reserved_qty
|
||||||
|
|
||||||
max_available_bundles = available_qty / item.qty
|
max_available_bundles = available_qty / item.qty
|
||||||
if bundle_bin_qty > max_available_bundles:
|
if bundle_bin_qty > max_available_bundles and frappe.get_value(
|
||||||
|
"Item", item.item_code, "is_stock_item"
|
||||||
|
):
|
||||||
bundle_bin_qty = max_available_bundles
|
bundle_bin_qty = max_available_bundles
|
||||||
|
|
||||||
pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse)
|
pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse)
|
||||||
@@ -744,3 +747,7 @@ def add_return_modes(doc, pos_profile):
|
|||||||
]:
|
]:
|
||||||
payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
|
payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
|
||||||
append_payment(payment_mode[0])
|
append_payment(payment_mode[0])
|
||||||
|
|
||||||
|
|
||||||
|
def on_doctype_update():
|
||||||
|
frappe.db.add_index("POS Invoice", ["return_against"])
|
||||||
|
|||||||
@@ -1549,6 +1549,37 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
|||||||
pi.save()
|
pi.save()
|
||||||
self.assertEqual(pi.items[0].conversion_factor, 1000)
|
self.assertEqual(pi.items[0].conversion_factor, 1000)
|
||||||
|
|
||||||
|
def test_batch_expiry_for_purchase_invoice(self):
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
|
||||||
|
item = self.make_item(
|
||||||
|
"_Test Batch Item For Return Check",
|
||||||
|
{
|
||||||
|
"is_purchase_item": 1,
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "TBIRC.#####",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
pi = make_purchase_invoice(
|
||||||
|
qty=1,
|
||||||
|
item_code=item.name,
|
||||||
|
update_stock=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
pi.load_from_db()
|
||||||
|
batch_no = pi.items[0].batch_no
|
||||||
|
self.assertTrue(batch_no)
|
||||||
|
|
||||||
|
frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(nowdate(), -1))
|
||||||
|
|
||||||
|
return_pi = make_return_doc(pi.doctype, pi.name)
|
||||||
|
return_pi.save().submit()
|
||||||
|
|
||||||
|
self.assertTrue(return_pi.docstatus == 1)
|
||||||
|
|
||||||
|
|
||||||
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||||
gl_entries = frappe.db.sql(
|
gl_entries = frappe.db.sql(
|
||||||
|
|||||||
@@ -326,6 +326,9 @@ def get_advance_vouchers(
|
|||||||
"party": ["in", parties],
|
"party": ["in", parties],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if party_type == "Customer":
|
||||||
|
filters.update({"against_voucher": ["is", "not set"]})
|
||||||
|
|
||||||
if company:
|
if company:
|
||||||
filters["company"] = company
|
filters["company"] = company
|
||||||
if from_date and to_date:
|
if from_date and to_date:
|
||||||
|
|||||||
@@ -280,9 +280,9 @@ def get_conditions(filters):
|
|||||||
or filters.get("party")
|
or filters.get("party")
|
||||||
or filters.get("group_by") in ["Group by Account", "Group by Party"]
|
or filters.get("group_by") in ["Group by Account", "Group by Party"]
|
||||||
):
|
):
|
||||||
conditions.append("posting_date >=%(from_date)s")
|
conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')")
|
||||||
|
|
||||||
conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')")
|
conditions.append("(posting_date <=%(to_date)s)")
|
||||||
|
|
||||||
if filters.get("project"):
|
if filters.get("project"):
|
||||||
conditions.append("project in %(project)s")
|
conditions.append("project in %(project)s")
|
||||||
|
|||||||
@@ -19,14 +19,19 @@ def execute(filters=None):
|
|||||||
return _execute(filters)
|
return _execute(filters)
|
||||||
|
|
||||||
|
|
||||||
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
|
def _execute(
|
||||||
|
filters=None,
|
||||||
|
additional_table_columns=None,
|
||||||
|
additional_query_columns=None,
|
||||||
|
additional_conditions=None,
|
||||||
|
):
|
||||||
if not filters:
|
if not filters:
|
||||||
filters = {}
|
filters = {}
|
||||||
columns = get_columns(additional_table_columns, filters)
|
columns = get_columns(additional_table_columns, filters)
|
||||||
|
|
||||||
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
|
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
|
||||||
|
|
||||||
item_list = get_items(filters, additional_query_columns)
|
item_list = get_items(filters, additional_query_columns, additional_conditions)
|
||||||
if item_list:
|
if item_list:
|
||||||
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
|
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
|
||||||
|
|
||||||
@@ -328,7 +333,7 @@ def get_columns(additional_table_columns, filters):
|
|||||||
return columns
|
return columns
|
||||||
|
|
||||||
|
|
||||||
def get_conditions(filters):
|
def get_conditions(filters, additional_conditions=None):
|
||||||
conditions = ""
|
conditions = ""
|
||||||
|
|
||||||
for opts in (
|
for opts in (
|
||||||
@@ -341,6 +346,9 @@ def get_conditions(filters):
|
|||||||
if filters.get(opts[0]):
|
if filters.get(opts[0]):
|
||||||
conditions += opts[1]
|
conditions += opts[1]
|
||||||
|
|
||||||
|
if additional_conditions:
|
||||||
|
conditions += additional_conditions
|
||||||
|
|
||||||
if filters.get("mode_of_payment"):
|
if filters.get("mode_of_payment"):
|
||||||
conditions += """ and exists(select name from `tabSales Invoice Payment`
|
conditions += """ and exists(select name from `tabSales Invoice Payment`
|
||||||
where parent=`tabSales Invoice`.name
|
where parent=`tabSales Invoice`.name
|
||||||
@@ -376,8 +384,8 @@ def get_group_by_conditions(filters, doctype):
|
|||||||
return "ORDER BY `tab{0}`.{1}".format(doctype, frappe.scrub(filters.get("group_by")))
|
return "ORDER BY `tab{0}`.{1}".format(doctype, frappe.scrub(filters.get("group_by")))
|
||||||
|
|
||||||
|
|
||||||
def get_items(filters, additional_query_columns):
|
def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||||
conditions = get_conditions(filters)
|
conditions = get_conditions(filters, additional_conditions)
|
||||||
|
|
||||||
if additional_query_columns:
|
if additional_query_columns:
|
||||||
additional_query_columns = ", " + ", ".join(additional_query_columns)
|
additional_query_columns = ", " + ", ".join(additional_query_columns)
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ def get_rootwise_opening_balances(filters, report_type):
|
|||||||
query_filters = {
|
query_filters = {
|
||||||
"company": filters.company,
|
"company": filters.company,
|
||||||
"from_date": filters.from_date,
|
"from_date": filters.from_date,
|
||||||
|
"to_date": filters.to_date,
|
||||||
"report_type": report_type,
|
"report_type": report_type,
|
||||||
"year_start_date": filters.year_start_date,
|
"year_start_date": filters.year_start_date,
|
||||||
"project": filters.project,
|
"project": filters.project,
|
||||||
@@ -200,7 +201,7 @@ def get_rootwise_opening_balances(filters, report_type):
|
|||||||
where
|
where
|
||||||
company=%(company)s
|
company=%(company)s
|
||||||
{additional_conditions}
|
{additional_conditions}
|
||||||
and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes')
|
and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s))
|
||||||
and account in (select name from `tabAccount` where report_type=%(report_type)s)
|
and account in (select name from `tabAccount` where report_type=%(report_type)s)
|
||||||
and is_cancelled = 0
|
and is_cancelled = 0
|
||||||
group by account""".format(
|
group by account""".format(
|
||||||
|
|||||||
@@ -106,12 +106,17 @@ def get_opening_balances(filters):
|
|||||||
where company=%(company)s
|
where company=%(company)s
|
||||||
and is_cancelled=0
|
and is_cancelled=0
|
||||||
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
|
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
|
||||||
and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes')
|
and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s))
|
||||||
{account_filter}
|
{account_filter}
|
||||||
group by party""".format(
|
group by party""".format(
|
||||||
account_filter=account_filter
|
account_filter=account_filter
|
||||||
),
|
),
|
||||||
{"company": filters.company, "from_date": filters.from_date, "party_type": filters.party_type},
|
{
|
||||||
|
"company": filters.company,
|
||||||
|
"from_date": filters.from_date,
|
||||||
|
"to_date": filters.to_date,
|
||||||
|
"party_type": filters.party_type,
|
||||||
|
},
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -478,7 +478,6 @@ scheduler_events = {
|
|||||||
],
|
],
|
||||||
"hourly": [
|
"hourly": [
|
||||||
"erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails",
|
"erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails",
|
||||||
"erpnext.accounts.doctype.subscription.subscription.process_all",
|
|
||||||
"erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details",
|
"erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details",
|
||||||
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
|
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
|
||||||
"erpnext.projects.doctype.project.project.hourly_reminder",
|
"erpnext.projects.doctype.project.project.hourly_reminder",
|
||||||
@@ -487,6 +486,7 @@ scheduler_events = {
|
|||||||
"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
|
"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
|
||||||
],
|
],
|
||||||
"hourly_long": [
|
"hourly_long": [
|
||||||
|
"erpnext.accounts.doctype.subscription.subscription.process_all",
|
||||||
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
|
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
|
||||||
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
|
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -181,7 +181,6 @@ def add_data(
|
|||||||
total_l += 1
|
total_l += 1
|
||||||
elif status == "Half Day":
|
elif status == "Half Day":
|
||||||
total_p += 0.5
|
total_p += 0.5
|
||||||
total_a += 0.5
|
|
||||||
total_l += 0.5
|
total_l += 0.5
|
||||||
elif not status:
|
elif not status:
|
||||||
total_um += 1
|
total_um += 1
|
||||||
|
|||||||
@@ -557,37 +557,52 @@ erpnext.work_order = {
|
|||||||
|
|
||||||
if(!frm.doc.skip_transfer){
|
if(!frm.doc.skip_transfer){
|
||||||
// If "Material Consumption is check in Manufacturing Settings, allow Material Consumption
|
// If "Material Consumption is check in Manufacturing Settings, allow Material Consumption
|
||||||
if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))
|
if (flt(doc.material_transferred_for_manufacturing) > 0 && frm.doc.status != 'Stopped') {
|
||||||
&& frm.doc.status != 'Stopped') {
|
if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) {
|
||||||
frm.has_finish_btn = true;
|
frm.has_finish_btn = true;
|
||||||
|
|
||||||
if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) {
|
if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) {
|
||||||
// Only show "Material Consumption" when required_qty > consumed_qty
|
// Only show "Material Consumption" when required_qty > consumed_qty
|
||||||
var counter = 0;
|
var counter = 0;
|
||||||
var tbl = frm.doc.required_items || [];
|
var tbl = frm.doc.required_items || [];
|
||||||
var tbl_lenght = tbl.length;
|
var tbl_lenght = tbl.length;
|
||||||
for (var i = 0, len = tbl_lenght; i < len; i++) {
|
for (var i = 0, len = tbl_lenght; i < len; i++) {
|
||||||
let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
|
let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
|
||||||
if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
|
if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
|
||||||
counter += 1;
|
counter += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (counter > 0) {
|
||||||
|
var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() {
|
||||||
|
const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on;
|
||||||
|
erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on);
|
||||||
|
});
|
||||||
|
consumption_btn.addClass('btn-primary');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (counter > 0) {
|
|
||||||
var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() {
|
var finish_btn = frm.add_custom_button(__('Finish'), function() {
|
||||||
const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on;
|
erpnext.work_order.make_se(frm, 'Manufacture');
|
||||||
erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on);
|
});
|
||||||
});
|
|
||||||
consumption_btn.addClass('btn-primary');
|
if(doc.material_transferred_for_manufacturing>=doc.qty) {
|
||||||
|
// all materials transferred for manufacturing, make this primary
|
||||||
|
finish_btn.addClass('btn-primary');
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
frappe.db.get_doc("Manufacturing Settings").then((doc) => {
|
||||||
|
let allowance_percentage = doc.overproduction_percentage_for_work_order;
|
||||||
|
|
||||||
var finish_btn = frm.add_custom_button(__('Finish'), function() {
|
if (allowance_percentage > 0) {
|
||||||
erpnext.work_order.make_se(frm, 'Manufacture');
|
let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty);
|
||||||
});
|
|
||||||
|
|
||||||
if(doc.material_transferred_for_manufacturing>=doc.qty) {
|
if ((flt(doc.produced_qty) < allowed_qty)) {
|
||||||
// all materials transferred for manufacturing, make this primary
|
frm.add_custom_button(__('Finish'), function() {
|
||||||
finish_btn.addClass('btn-primary');
|
erpnext.work_order.make_se(frm, 'Manufacture');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ def execute():
|
|||||||
"mode_of_payment": loan.mode_of_payment,
|
"mode_of_payment": loan.mode_of_payment,
|
||||||
"loan_account": loan.loan_account,
|
"loan_account": loan.loan_account,
|
||||||
"payment_account": loan.payment_account,
|
"payment_account": loan.payment_account,
|
||||||
|
"disbursement_account": loan.payment_account,
|
||||||
"interest_income_account": loan.interest_income_account,
|
"interest_income_account": loan.interest_income_account,
|
||||||
"penalty_income_account": loan.penalty_income_account,
|
"penalty_income_account": loan.penalty_income_account,
|
||||||
},
|
},
|
||||||
@@ -190,6 +191,7 @@ def create_loan_type(loan, loan_type_name, penalty_account):
|
|||||||
loan_type_doc.company = loan.company
|
loan_type_doc.company = loan.company
|
||||||
loan_type_doc.mode_of_payment = loan.mode_of_payment
|
loan_type_doc.mode_of_payment = loan.mode_of_payment
|
||||||
loan_type_doc.payment_account = loan.payment_account
|
loan_type_doc.payment_account = loan.payment_account
|
||||||
|
loan_type_doc.disbursement_account = loan.payment_account
|
||||||
loan_type_doc.loan_account = loan.loan_account
|
loan_type_doc.loan_account = loan.loan_account
|
||||||
loan_type_doc.interest_income_account = loan.interest_income_account
|
loan_type_doc.interest_income_account = loan.interest_income_account
|
||||||
loan_type_doc.penalty_income_account = penalty_account
|
loan_type_doc.penalty_income_account = penalty_account
|
||||||
|
|||||||
@@ -15,12 +15,6 @@ filters = filters.concat({
|
|||||||
"placeholder":"Company GSTIN",
|
"placeholder":"Company GSTIN",
|
||||||
"options": [""],
|
"options": [""],
|
||||||
"width": "80"
|
"width": "80"
|
||||||
}, {
|
|
||||||
"fieldname":"invoice_type",
|
|
||||||
"label": __("Invoice Type"),
|
|
||||||
"fieldtype": "Select",
|
|
||||||
"placeholder":"Invoice Type",
|
|
||||||
"options": ["", "Regular", "SEZ", "Export", "Deemed Export"]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle company on change
|
// Handle company on change
|
||||||
|
|||||||
@@ -5,31 +5,48 @@
|
|||||||
from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import _execute
|
from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import _execute
|
||||||
|
|
||||||
|
|
||||||
|
def get_conditions(filters, additional_query_columns):
|
||||||
|
conditions = ""
|
||||||
|
|
||||||
|
for opts in additional_query_columns:
|
||||||
|
if filters.get(opts):
|
||||||
|
conditions += f" and {opts}=%({opts})s"
|
||||||
|
|
||||||
|
return conditions
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
|
additional_table_columns = [
|
||||||
|
dict(fieldtype="Data", label="Customer GSTIN", fieldname="customer_gstin", width=120),
|
||||||
|
dict(
|
||||||
|
fieldtype="Data", label="Billing Address GSTIN", fieldname="billing_address_gstin", width=140
|
||||||
|
),
|
||||||
|
dict(fieldtype="Data", label="Company GSTIN", fieldname="company_gstin", width=120),
|
||||||
|
dict(fieldtype="Data", label="Place of Supply", fieldname="place_of_supply", width=120),
|
||||||
|
dict(fieldtype="Data", label="Reverse Charge", fieldname="reverse_charge", width=120),
|
||||||
|
dict(fieldtype="Data", label="GST Category", fieldname="gst_category", width=120),
|
||||||
|
dict(fieldtype="Data", label="Export Type", fieldname="export_type", width=120),
|
||||||
|
dict(fieldtype="Data", label="E-Commerce GSTIN", fieldname="ecommerce_gstin", width=130),
|
||||||
|
dict(fieldtype="Data", label="HSN Code", fieldname="gst_hsn_code", width=120),
|
||||||
|
]
|
||||||
|
|
||||||
|
additional_query_columns = [
|
||||||
|
"customer_gstin",
|
||||||
|
"billing_address_gstin",
|
||||||
|
"company_gstin",
|
||||||
|
"place_of_supply",
|
||||||
|
"reverse_charge",
|
||||||
|
"gst_category",
|
||||||
|
"export_type",
|
||||||
|
"ecommerce_gstin",
|
||||||
|
"gst_hsn_code",
|
||||||
|
]
|
||||||
|
|
||||||
|
additional_conditions = get_conditions(filters, additional_query_columns)
|
||||||
|
|
||||||
return _execute(
|
return _execute(
|
||||||
filters,
|
filters,
|
||||||
additional_table_columns=[
|
additional_table_columns=additional_table_columns,
|
||||||
dict(fieldtype="Data", label="Customer GSTIN", fieldname="customer_gstin", width=120),
|
additional_query_columns=additional_query_columns,
|
||||||
dict(
|
additional_conditions=additional_conditions,
|
||||||
fieldtype="Data", label="Billing Address GSTIN", fieldname="billing_address_gstin", width=140
|
|
||||||
),
|
|
||||||
dict(fieldtype="Data", label="Company GSTIN", fieldname="company_gstin", width=120),
|
|
||||||
dict(fieldtype="Data", label="Place of Supply", fieldname="place_of_supply", width=120),
|
|
||||||
dict(fieldtype="Data", label="Reverse Charge", fieldname="reverse_charge", width=120),
|
|
||||||
dict(fieldtype="Data", label="GST Category", fieldname="gst_category", width=120),
|
|
||||||
dict(fieldtype="Data", label="Export Type", fieldname="export_type", width=120),
|
|
||||||
dict(fieldtype="Data", label="E-Commerce GSTIN", fieldname="ecommerce_gstin", width=130),
|
|
||||||
dict(fieldtype="Data", label="HSN Code", fieldname="gst_hsn_code", width=120),
|
|
||||||
],
|
|
||||||
additional_query_columns=[
|
|
||||||
"customer_gstin",
|
|
||||||
"billing_address_gstin",
|
|
||||||
"company_gstin",
|
|
||||||
"place_of_supply",
|
|
||||||
"reverse_charge",
|
|
||||||
"gst_category",
|
|
||||||
"export_type",
|
|
||||||
"ecommerce_gstin",
|
|
||||||
"gst_hsn_code",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -660,7 +660,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (available_qty < qty_needed) {
|
} else if (is_stock_item && available_qty < qty_needed) {
|
||||||
frappe.throw({
|
frappe.throw({
|
||||||
message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]),
|
message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]),
|
||||||
indicator: 'orange'
|
indicator: 'orange'
|
||||||
@@ -694,7 +694,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
callback(res) {
|
callback(res) {
|
||||||
if (!me.item_stock_map[item_code])
|
if (!me.item_stock_map[item_code])
|
||||||
me.item_stock_map[item_code] = {};
|
me.item_stock_map[item_code] = {};
|
||||||
me.item_stock_map[item_code][warehouse] = res.message[0];
|
me.item_stock_map[item_code][warehouse] = res.message;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,13 +242,14 @@ erpnext.PointOfSale.ItemDetails = class {
|
|||||||
if (this.value) {
|
if (this.value) {
|
||||||
me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => {
|
me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => {
|
||||||
me.item_stock_map = me.events.get_item_stock_map();
|
me.item_stock_map = me.events.get_item_stock_map();
|
||||||
const available_qty = me.item_stock_map[me.item_row.item_code][this.value];
|
const available_qty = me.item_stock_map[me.item_row.item_code][this.value][0];
|
||||||
|
const is_stock_item = Boolean(me.item_stock_map[me.item_row.item_code][this.value][1]);
|
||||||
if (available_qty === undefined) {
|
if (available_qty === undefined) {
|
||||||
me.events.get_available_stock(me.item_row.item_code, this.value).then(() => {
|
me.events.get_available_stock(me.item_row.item_code, this.value).then(() => {
|
||||||
// item stock map is updated now reset warehouse
|
// item stock map is updated now reset warehouse
|
||||||
me.warehouse_control.set_value(this.value);
|
me.warehouse_control.set_value(this.value);
|
||||||
})
|
})
|
||||||
} else if (available_qty === 0) {
|
} else if (available_qty === 0 && is_stock_item) {
|
||||||
me.warehouse_control.set_value('');
|
me.warehouse_control.set_value('');
|
||||||
const bold_item_code = me.item_row.item_code.bold();
|
const bold_item_code = me.item_row.item_code.bold();
|
||||||
const bold_warehouse = this.value.bold();
|
const bold_warehouse = this.value.bold();
|
||||||
|
|||||||
@@ -1446,6 +1446,37 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(query[0].value, 0)
|
self.assertEqual(query[0].value, 0)
|
||||||
|
|
||||||
|
def test_batch_expiry_for_purchase_receipt(self):
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
|
||||||
|
item = make_item(
|
||||||
|
"_Test Batch Item For Return Check",
|
||||||
|
{
|
||||||
|
"is_purchase_item": 1,
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "TBIRC.#####",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
pi = make_purchase_receipt(
|
||||||
|
qty=1,
|
||||||
|
item_code=item.name,
|
||||||
|
update_stock=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
pi.load_from_db()
|
||||||
|
batch_no = pi.items[0].batch_no
|
||||||
|
self.assertTrue(batch_no)
|
||||||
|
|
||||||
|
frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(today(), -1))
|
||||||
|
|
||||||
|
return_pi = make_return_doc(pi.doctype, pi.name)
|
||||||
|
return_pi.save().submit()
|
||||||
|
|
||||||
|
self.assertTrue(return_pi.docstatus == 1)
|
||||||
|
|
||||||
|
|
||||||
def prepare_data_for_internal_transfer():
|
def prepare_data_for_internal_transfer():
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||||
|
|||||||
@@ -1015,8 +1015,8 @@ class StockEntry(StockController):
|
|||||||
# No work order could mean independent Manufacture entry, if so skip validation
|
# No work order could mean independent Manufacture entry, if so skip validation
|
||||||
if self.work_order and self.fg_completed_qty > allowed_qty:
|
if self.work_order and self.fg_completed_qty > allowed_qty:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("For quantity {0} should not be greater than work order quantity {1}").format(
|
_("For quantity {0} should not be greater than allowed quantity {1}").format(
|
||||||
flt(self.fg_completed_qty), wo_qty
|
flt(self.fg_completed_qty), allowed_qty
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -153,6 +153,9 @@ class StockLedgerEntry(Document):
|
|||||||
|
|
||||||
def validate_batch(self):
|
def validate_batch(self):
|
||||||
if self.batch_no and self.voucher_type != "Stock Entry":
|
if self.batch_no and self.voucher_type != "Stock Entry":
|
||||||
|
if self.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and self.actual_qty < 0:
|
||||||
|
return
|
||||||
|
|
||||||
expiry_date = frappe.db.get_value("Batch", self.batch_no, "expiry_date")
|
expiry_date = frappe.db.get_value("Batch", self.batch_no, "expiry_date")
|
||||||
if expiry_date:
|
if expiry_date:
|
||||||
if getdate(self.posting_date) > getdate(expiry_date):
|
if getdate(self.posting_date) > getdate(expiry_date):
|
||||||
|
|||||||
@@ -62,22 +62,28 @@ def get_data(filters, columns):
|
|||||||
|
|
||||||
|
|
||||||
def get_item_price_qty_data(filters):
|
def get_item_price_qty_data(filters):
|
||||||
conditions = ""
|
item_price = frappe.qb.DocType("Item Price")
|
||||||
if filters.get("item_code"):
|
bin = frappe.qb.DocType("Bin")
|
||||||
conditions += "where a.item_code=%(item_code)s"
|
|
||||||
|
|
||||||
item_results = frappe.db.sql(
|
query = (
|
||||||
"""select a.item_code, a.item_name, a.name as price_list_name,
|
frappe.qb.from_(item_price)
|
||||||
a.brand as brand, b.warehouse as warehouse, b.actual_qty as actual_qty
|
.left_join(bin)
|
||||||
from `tabItem Price` a left join `tabBin` b
|
.on(item_price.item_code == bin.item_code)
|
||||||
ON a.item_code = b.item_code
|
.select(
|
||||||
{conditions}""".format(
|
item_price.item_code,
|
||||||
conditions=conditions
|
item_price.item_name,
|
||||||
),
|
item_price.name.as_("price_list_name"),
|
||||||
filters,
|
item_price.brand.as_("brand"),
|
||||||
as_dict=1,
|
bin.warehouse.as_("warehouse"),
|
||||||
|
bin.actual_qty.as_("actual_qty"),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if filters.get("item_code"):
|
||||||
|
query = query.where(item_price.item_code == filters.get("item_code"))
|
||||||
|
|
||||||
|
item_results = query.run(as_dict=True)
|
||||||
|
|
||||||
price_list_names = list(set(item.price_list_name for item in item_results))
|
price_list_names = list(set(item.price_list_name for item in item_results))
|
||||||
|
|
||||||
buying_price_map = get_price_map(price_list_names, buying=1)
|
buying_price_map = get_price_map(price_list_names, buying=1)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<b class="caret"></b>
|
<b class="caret"></b>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||||
{% if doc.doctype == 'Purchase Order' %}
|
{% if doc.doctype == 'Purchase Order' and show_make_pi_button %}
|
||||||
<a class="dropdown-item" href="/api/method/erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice_from_portal?purchase_order_name={{ doc.name }}" data-action="make_purchase_invoice">{{ _("Make Purchase Invoice") }}</a>
|
<a class="dropdown-item" href="/api/method/erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice_from_portal?purchase_order_name={{ doc.name }}" data-action="make_purchase_invoice">{{ _("Make Purchase Invoice") }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a class="dropdown-item" href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}'
|
<a class="dropdown-item" href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}'
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ def get_context(context):
|
|||||||
)
|
)
|
||||||
context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points"))
|
context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points"))
|
||||||
|
|
||||||
|
# show Make Purchase Invoice button based on permission
|
||||||
|
context.show_make_pi_button = frappe.has_permission("Purchase Invoice", "create")
|
||||||
|
|
||||||
|
|
||||||
def get_attachments(dt, dn):
|
def get_attachments(dt, dn):
|
||||||
return frappe.get_all(
|
return frappe.get_all(
|
||||||
|
|||||||
Reference in New Issue
Block a user