Merge pull request #27124 from GangaManoj/gross-profit-product-bundle
feat: Improve Product Bundle handling
This commit is contained in:
commit
a158b825fc
@ -622,6 +622,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "packed_items",
|
||||||
"fieldname": "packing_list",
|
"fieldname": "packing_list",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Packing List",
|
"label": "Packing List",
|
||||||
@ -629,6 +630,7 @@
|
|||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "packed_items",
|
||||||
"fieldname": "packed_items",
|
"fieldname": "packed_items",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Packed Items",
|
"label": "Packed Items",
|
||||||
@ -1564,7 +1566,7 @@
|
|||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-08-24 18:19:20.728433",
|
"modified": "2021-08-27 20:12:57.306772",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice",
|
"name": "POS Invoice",
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"item_code",
|
"item_code",
|
||||||
|
"product_bundle",
|
||||||
"col_break1",
|
"col_break1",
|
||||||
"item_name",
|
"item_name",
|
||||||
"description_section",
|
"description_section",
|
||||||
@ -857,12 +858,19 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Discount Account",
|
"label": "Discount Account",
|
||||||
"options": "Account"
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "product_bundle",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Product Bundle",
|
||||||
|
"options": "Product Bundle",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-08-12 20:14:48.506639",
|
"modified": "2021-09-01 16:04:03.538643",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Item",
|
"name": "Purchase Invoice Item",
|
||||||
|
@ -578,6 +578,9 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
frm.add_fetch('payment_term', 'invoice_portion', 'invoice_portion');
|
frm.add_fetch('payment_term', 'invoice_portion', 'invoice_portion');
|
||||||
frm.add_fetch('payment_term', 'description', 'description');
|
frm.add_fetch('payment_term', 'description', 'description');
|
||||||
|
|
||||||
|
frm.set_df_property('packed_items', 'cannot_add_rows', true);
|
||||||
|
frm.set_df_property('packed_items', 'cannot_delete_rows', true);
|
||||||
|
|
||||||
frm.set_query("account_for_change_amount", function() {
|
frm.set_query("account_for_change_amount", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
|
@ -247,7 +247,7 @@
|
|||||||
"depends_on": "customer",
|
"depends_on": "customer",
|
||||||
"fetch_from": "customer.customer_name",
|
"fetch_from": "customer.customer_name",
|
||||||
"fieldname": "customer_name",
|
"fieldname": "customer_name",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Data",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
@ -695,7 +695,6 @@
|
|||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "Scan Barcode",
|
"label": "Scan Barcode",
|
||||||
"length": 1,
|
|
||||||
"options": "Barcode"
|
"options": "Barcode"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -727,6 +726,7 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "packed_items",
|
||||||
"fieldname": "packing_list",
|
"fieldname": "packing_list",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
@ -736,6 +736,7 @@
|
|||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "packed_items",
|
||||||
"fieldname": "packed_items",
|
"fieldname": "packed_items",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
@ -1060,7 +1061,6 @@
|
|||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "Apply Additional Discount On",
|
"label": "Apply Additional Discount On",
|
||||||
"length": 15,
|
|
||||||
"options": "\nGrand Total\nNet Total",
|
"options": "\nGrand Total\nNet Total",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
@ -1147,7 +1147,7 @@
|
|||||||
{
|
{
|
||||||
"description": "In Words will be visible once you save the Sales Invoice.",
|
"description": "In Words will be visible once you save the Sales Invoice.",
|
||||||
"fieldname": "base_in_words",
|
"fieldname": "base_in_words",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Data",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "In Words (Company Currency)",
|
"label": "In Words (Company Currency)",
|
||||||
@ -1207,7 +1207,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "in_words",
|
"fieldname": "in_words",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Data",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "In Words",
|
"label": "In Words",
|
||||||
@ -1560,7 +1560,6 @@
|
|||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "Print Language",
|
"label": "Print Language",
|
||||||
"length": 6,
|
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@ -1648,7 +1647,6 @@
|
|||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"length": 30,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer",
|
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
@ -1708,7 +1706,6 @@
|
|||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "Is Opening Entry",
|
"label": "Is Opening Entry",
|
||||||
"length": 4,
|
|
||||||
"oldfieldname": "is_opening",
|
"oldfieldname": "is_opening",
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"options": "No\nYes",
|
"options": "No\nYes",
|
||||||
@ -1720,7 +1717,6 @@
|
|||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "C-Form Applicable",
|
"label": "C-Form Applicable",
|
||||||
"length": 4,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "No\nYes",
|
"options": "No\nYes",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
@ -2021,7 +2017,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2021-08-25 14:46:05.279588",
|
"modified": "2021-08-27 20:13:40.456462",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
@ -36,5 +36,20 @@ frappe.query_reports["Gross Profit"] = {
|
|||||||
"options": "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject",
|
"options": "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject",
|
||||||
"default": "Invoice"
|
"default": "Invoice"
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
"tree": true,
|
||||||
|
"name_field": "parent",
|
||||||
|
"parent_field": "parent_invoice",
|
||||||
|
"initial_depth": 3,
|
||||||
|
"formatter": function(value, row, column, data, default_formatter) {
|
||||||
|
value = default_formatter(value, row, column, data);
|
||||||
|
|
||||||
|
if (data && data.indent == 0.0) {
|
||||||
|
value = $(`<span>${value}</span>`);
|
||||||
|
var $value = $(value).css("font-weight", "bold");
|
||||||
|
value = $value.wrap("<p></p>").parent().html();
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,34 @@ def execute(filters=None):
|
|||||||
|
|
||||||
columns = get_columns(group_wise_columns, filters)
|
columns = get_columns(group_wise_columns, filters)
|
||||||
|
|
||||||
|
if filters.group_by == 'Invoice':
|
||||||
|
get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_wise_columns, data)
|
||||||
|
|
||||||
|
else:
|
||||||
|
get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_columns, data)
|
||||||
|
|
||||||
|
return columns, data
|
||||||
|
|
||||||
|
def get_data_when_grouped_by_invoice(columns, gross_profit_data, filters, group_wise_columns, data):
|
||||||
|
column_names = get_column_names()
|
||||||
|
|
||||||
|
# to display item as Item Code: Item Name
|
||||||
|
columns[0] = 'Sales Invoice:Link/Item:300'
|
||||||
|
# removing Item Code and Item Name columns
|
||||||
|
del columns[4:6]
|
||||||
|
|
||||||
|
for src in gross_profit_data.si_list:
|
||||||
|
row = frappe._dict()
|
||||||
|
row.indent = src.indent
|
||||||
|
row.parent_invoice = src.parent_invoice
|
||||||
|
row.currency = filters.currency
|
||||||
|
|
||||||
|
for col in group_wise_columns.get(scrub(filters.group_by)):
|
||||||
|
row[column_names[col]] = src.get(col)
|
||||||
|
|
||||||
|
data.append(row)
|
||||||
|
|
||||||
|
def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_columns, data):
|
||||||
for idx, src in enumerate(gross_profit_data.grouped_data):
|
for idx, src in enumerate(gross_profit_data.grouped_data):
|
||||||
row = []
|
row = []
|
||||||
for col in group_wise_columns.get(scrub(filters.group_by)):
|
for col in group_wise_columns.get(scrub(filters.group_by)):
|
||||||
@ -51,8 +79,6 @@ def execute(filters=None):
|
|||||||
row[0] = frappe.bold("Total")
|
row[0] = frappe.bold("Total")
|
||||||
data.append(row)
|
data.append(row)
|
||||||
|
|
||||||
return columns, data
|
|
||||||
|
|
||||||
def get_columns(group_wise_columns, filters):
|
def get_columns(group_wise_columns, filters):
|
||||||
columns = []
|
columns = []
|
||||||
column_map = frappe._dict({
|
column_map = frappe._dict({
|
||||||
@ -93,12 +119,38 @@ def get_columns(group_wise_columns, filters):
|
|||||||
|
|
||||||
return columns
|
return columns
|
||||||
|
|
||||||
|
def get_column_names():
|
||||||
|
return frappe._dict({
|
||||||
|
'parent': 'sales_invoice',
|
||||||
|
'customer': 'customer',
|
||||||
|
'customer_group': 'customer_group',
|
||||||
|
'posting_date': 'posting_date',
|
||||||
|
'item_code': 'item_code',
|
||||||
|
'item_name': 'item_name',
|
||||||
|
'item_group': 'item_group',
|
||||||
|
'brand': 'brand',
|
||||||
|
'description': 'description',
|
||||||
|
'warehouse': 'warehouse',
|
||||||
|
'qty': 'qty',
|
||||||
|
'base_rate': 'avg._selling_rate',
|
||||||
|
'buying_rate': 'valuation_rate',
|
||||||
|
'base_amount': 'selling_amount',
|
||||||
|
'buying_amount': 'buying_amount',
|
||||||
|
'gross_profit': 'gross_profit',
|
||||||
|
'gross_profit_percent': 'gross_profit_%',
|
||||||
|
'project': 'project'
|
||||||
|
})
|
||||||
|
|
||||||
class GrossProfitGenerator(object):
|
class GrossProfitGenerator(object):
|
||||||
def __init__(self, filters=None):
|
def __init__(self, filters=None):
|
||||||
self.data = []
|
self.data = []
|
||||||
self.average_buying_rate = {}
|
self.average_buying_rate = {}
|
||||||
self.filters = frappe._dict(filters)
|
self.filters = frappe._dict(filters)
|
||||||
self.load_invoice_items()
|
self.load_invoice_items()
|
||||||
|
|
||||||
|
if filters.group_by == 'Invoice':
|
||||||
|
self.group_items_by_invoice()
|
||||||
|
|
||||||
self.load_stock_ledger_entries()
|
self.load_stock_ledger_entries()
|
||||||
self.load_product_bundle()
|
self.load_product_bundle()
|
||||||
self.load_non_stock_items()
|
self.load_non_stock_items()
|
||||||
@ -112,7 +164,12 @@ class GrossProfitGenerator(object):
|
|||||||
self.currency_precision = cint(frappe.db.get_default("currency_precision")) or 3
|
self.currency_precision = cint(frappe.db.get_default("currency_precision")) or 3
|
||||||
self.float_precision = cint(frappe.db.get_default("float_precision")) or 2
|
self.float_precision = cint(frappe.db.get_default("float_precision")) or 2
|
||||||
|
|
||||||
for row in self.si_list:
|
grouped_by_invoice = True if self.filters.get("group_by") == "Invoice" else False
|
||||||
|
|
||||||
|
if grouped_by_invoice:
|
||||||
|
buying_amount = 0
|
||||||
|
|
||||||
|
for row in reversed(self.si_list):
|
||||||
if self.skip_row(row, self.product_bundles):
|
if self.skip_row(row, self.product_bundles):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -134,12 +191,20 @@ class GrossProfitGenerator(object):
|
|||||||
row.buying_amount = flt(self.get_buying_amount(row, row.item_code),
|
row.buying_amount = flt(self.get_buying_amount(row, row.item_code),
|
||||||
self.currency_precision)
|
self.currency_precision)
|
||||||
|
|
||||||
|
if grouped_by_invoice:
|
||||||
|
if row.indent == 1.0:
|
||||||
|
buying_amount += row.buying_amount
|
||||||
|
elif row.indent == 0.0:
|
||||||
|
row.buying_amount = buying_amount
|
||||||
|
buying_amount = 0
|
||||||
|
|
||||||
# get buying rate
|
# get buying rate
|
||||||
if row.qty:
|
if flt(row.qty):
|
||||||
row.buying_rate = flt(row.buying_amount / row.qty, self.float_precision)
|
row.buying_rate = flt(row.buying_amount / flt(row.qty), self.float_precision)
|
||||||
row.base_rate = flt(row.base_amount / row.qty, self.float_precision)
|
row.base_rate = flt(row.base_amount / flt(row.qty), self.float_precision)
|
||||||
else:
|
else:
|
||||||
row.buying_rate, row.base_rate = 0.0, 0.0
|
if self.is_not_invoice_row(row):
|
||||||
|
row.buying_rate, row.base_rate = 0.0, 0.0
|
||||||
|
|
||||||
# calculate gross profit
|
# calculate gross profit
|
||||||
row.gross_profit = flt(row.base_amount - row.buying_amount, self.currency_precision)
|
row.gross_profit = flt(row.base_amount - row.buying_amount, self.currency_precision)
|
||||||
@ -171,7 +236,7 @@ class GrossProfitGenerator(object):
|
|||||||
if i==0:
|
if i==0:
|
||||||
new_row = row
|
new_row = row
|
||||||
else:
|
else:
|
||||||
new_row.qty += row.qty
|
new_row.qty += flt(row.qty)
|
||||||
new_row.buying_amount += flt(row.buying_amount, self.currency_precision)
|
new_row.buying_amount += flt(row.buying_amount, self.currency_precision)
|
||||||
new_row.base_amount += flt(row.base_amount, self.currency_precision)
|
new_row.base_amount += flt(row.base_amount, self.currency_precision)
|
||||||
new_row = self.set_average_rate(new_row)
|
new_row = self.set_average_rate(new_row)
|
||||||
@ -183,16 +248,19 @@ class GrossProfitGenerator(object):
|
|||||||
and row.item_code in self.returned_invoices[row.parent]:
|
and row.item_code in self.returned_invoices[row.parent]:
|
||||||
returned_item_rows = self.returned_invoices[row.parent][row.item_code]
|
returned_item_rows = self.returned_invoices[row.parent][row.item_code]
|
||||||
for returned_item_row in returned_item_rows:
|
for returned_item_row in returned_item_rows:
|
||||||
row.qty += returned_item_row.qty
|
row.qty += flt(returned_item_row.qty)
|
||||||
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
|
row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
|
||||||
row.buying_amount = flt(row.qty * row.buying_rate, self.currency_precision)
|
row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
|
||||||
if row.qty or row.base_amount:
|
if (flt(row.qty) or row.base_amount) and self.is_not_invoice_row(row):
|
||||||
row = self.set_average_rate(row)
|
row = self.set_average_rate(row)
|
||||||
self.grouped_data.append(row)
|
self.grouped_data.append(row)
|
||||||
self.add_to_totals(row)
|
self.add_to_totals(row)
|
||||||
self.set_average_gross_profit(self.totals)
|
self.set_average_gross_profit(self.totals)
|
||||||
self.grouped_data.append(self.totals)
|
self.grouped_data.append(self.totals)
|
||||||
|
|
||||||
|
def is_not_invoice_row(self, row):
|
||||||
|
return (self.filters.get("group_by") == "Invoice" and row.indent != 0.0) or self.filters.get("group_by") != "Invoice"
|
||||||
|
|
||||||
def set_average_rate(self, new_row):
|
def set_average_rate(self, new_row):
|
||||||
self.set_average_gross_profit(new_row)
|
self.set_average_gross_profit(new_row)
|
||||||
new_row.buying_rate = flt(new_row.buying_amount / new_row.qty, self.float_precision) if new_row.qty else 0
|
new_row.buying_rate = flt(new_row.buying_amount / new_row.qty, self.float_precision) if new_row.qty else 0
|
||||||
@ -354,6 +422,109 @@ class GrossProfitGenerator(object):
|
|||||||
.format(conditions=conditions, sales_person_cols=sales_person_cols,
|
.format(conditions=conditions, sales_person_cols=sales_person_cols,
|
||||||
sales_team_table=sales_team_table, match_cond = get_match_cond('Sales Invoice')), self.filters, as_dict=1)
|
sales_team_table=sales_team_table, match_cond = get_match_cond('Sales Invoice')), self.filters, as_dict=1)
|
||||||
|
|
||||||
|
def group_items_by_invoice(self):
|
||||||
|
"""
|
||||||
|
Turns list of Sales Invoice Items to a tree of Sales Invoices with their Items as children.
|
||||||
|
"""
|
||||||
|
|
||||||
|
parents = []
|
||||||
|
|
||||||
|
for row in self.si_list:
|
||||||
|
if row.parent not in parents:
|
||||||
|
parents.append(row.parent)
|
||||||
|
|
||||||
|
parents_index = 0
|
||||||
|
for index, row in enumerate(self.si_list):
|
||||||
|
if parents_index < len(parents) and row.parent == parents[parents_index]:
|
||||||
|
invoice = self.get_invoice_row(row)
|
||||||
|
self.si_list.insert(index, invoice)
|
||||||
|
parents_index += 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
# skipping the bundle items rows
|
||||||
|
if not row.indent:
|
||||||
|
row.indent = 1.0
|
||||||
|
row.parent_invoice = row.parent
|
||||||
|
row.parent = row.item_code
|
||||||
|
|
||||||
|
if frappe.db.exists('Product Bundle', row.item_code):
|
||||||
|
self.add_bundle_items(row, index)
|
||||||
|
|
||||||
|
def get_invoice_row(self, row):
|
||||||
|
return frappe._dict({
|
||||||
|
'parent_invoice': "",
|
||||||
|
'indent': 0.0,
|
||||||
|
'parent': row.parent,
|
||||||
|
'posting_date': row.posting_date,
|
||||||
|
'posting_time': row.posting_time,
|
||||||
|
'project': row.project,
|
||||||
|
'update_stock': row.update_stock,
|
||||||
|
'customer': row.customer,
|
||||||
|
'customer_group': row.customer_group,
|
||||||
|
'item_code': None,
|
||||||
|
'item_name': None,
|
||||||
|
'description': None,
|
||||||
|
'warehouse': None,
|
||||||
|
'item_group': None,
|
||||||
|
'brand': None,
|
||||||
|
'dn_detail': None,
|
||||||
|
'delivery_note': None,
|
||||||
|
'qty': None,
|
||||||
|
'item_row': None,
|
||||||
|
'is_return': row.is_return,
|
||||||
|
'cost_center': row.cost_center,
|
||||||
|
'base_net_amount': frappe.db.get_value('Sales Invoice', row.parent, 'base_net_total')
|
||||||
|
})
|
||||||
|
|
||||||
|
def add_bundle_items(self, product_bundle, index):
|
||||||
|
bundle_items = self.get_bundle_items(product_bundle)
|
||||||
|
|
||||||
|
for i, item in enumerate(bundle_items):
|
||||||
|
bundle_item = self.get_bundle_item_row(product_bundle, item)
|
||||||
|
self.si_list.insert((index+i+1), bundle_item)
|
||||||
|
|
||||||
|
def get_bundle_items(self, product_bundle):
|
||||||
|
return frappe.get_all(
|
||||||
|
'Product Bundle Item',
|
||||||
|
filters = {
|
||||||
|
'parent': product_bundle.item_code
|
||||||
|
},
|
||||||
|
fields = ['item_code', 'qty']
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_bundle_item_row(self, product_bundle, item):
|
||||||
|
item_name, description, item_group, brand = self.get_bundle_item_details(item.item_code)
|
||||||
|
|
||||||
|
return frappe._dict({
|
||||||
|
'parent_invoice': product_bundle.item_code,
|
||||||
|
'indent': product_bundle.indent + 1,
|
||||||
|
'parent': item.item_code,
|
||||||
|
'posting_date': product_bundle.posting_date,
|
||||||
|
'posting_time': product_bundle.posting_time,
|
||||||
|
'project': product_bundle.project,
|
||||||
|
'customer': product_bundle.customer,
|
||||||
|
'customer_group': product_bundle.customer_group,
|
||||||
|
'item_code': item.item_code,
|
||||||
|
'item_name': item_name,
|
||||||
|
'description': description,
|
||||||
|
'warehouse': product_bundle.warehouse,
|
||||||
|
'item_group': item_group,
|
||||||
|
'brand': brand,
|
||||||
|
'dn_detail': product_bundle.dn_detail,
|
||||||
|
'delivery_note': product_bundle.delivery_note,
|
||||||
|
'qty': (flt(product_bundle.qty) * flt(item.qty)),
|
||||||
|
'item_row': None,
|
||||||
|
'is_return': product_bundle.is_return,
|
||||||
|
'cost_center': product_bundle.cost_center
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_bundle_item_details(self, item_code):
|
||||||
|
return frappe.db.get_value(
|
||||||
|
'Item',
|
||||||
|
item_code,
|
||||||
|
['item_name', 'description', 'item_group', 'brand']
|
||||||
|
)
|
||||||
|
|
||||||
def load_stock_ledger_entries(self):
|
def load_stock_ledger_entries(self):
|
||||||
res = frappe.db.sql("""select item_code, voucher_type, voucher_no,
|
res = frappe.db.sql("""select item_code, voucher_type, voucher_no,
|
||||||
voucher_detail_no, stock_value, warehouse, actual_qty as qty
|
voucher_detail_no, stock_value, warehouse, actual_qty as qty
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,7 @@
|
|||||||
"item_code",
|
"item_code",
|
||||||
"supplier_part_no",
|
"supplier_part_no",
|
||||||
"item_name",
|
"item_name",
|
||||||
|
"product_bundle",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"schedule_date",
|
"schedule_date",
|
||||||
"expected_delivery_date",
|
"expected_delivery_date",
|
||||||
@ -488,7 +489,6 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Sales Order",
|
"options": "Sales Order",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1,
|
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -830,13 +830,20 @@
|
|||||||
"label": "Production Plan Sub Assembly Item",
|
"label": "Production Plan Sub Assembly Item",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "product_bundle",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Product Bundle",
|
||||||
|
"options": "Product Bundle",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-06-28 19:22:22.715365",
|
"modified": "2021-08-30 20:06:26.712097",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order Item",
|
"name": "Purchase Order Item",
|
||||||
|
@ -18,6 +18,8 @@ frappe.ui.form.on('Quotation', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_df_property('packed_items', 'cannot_add_rows', true);
|
||||||
|
frm.set_df_property('packed_items', 'cannot_delete_rows', true);
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
|
@ -43,6 +43,8 @@
|
|||||||
"ignore_pricing_rule",
|
"ignore_pricing_rule",
|
||||||
"items_section",
|
"items_section",
|
||||||
"items",
|
"items",
|
||||||
|
"bundle_items_section",
|
||||||
|
"packed_items",
|
||||||
"pricing_rule_details",
|
"pricing_rule_details",
|
||||||
"pricing_rules",
|
"pricing_rules",
|
||||||
"sec_break23",
|
"sec_break23",
|
||||||
@ -926,6 +928,24 @@
|
|||||||
"label": "Lost Reasons",
|
"label": "Lost Reasons",
|
||||||
"options": "Quotation Lost Reason Detail",
|
"options": "Quotation Lost Reason Detail",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "packed_items",
|
||||||
|
"fieldname": "packed_items",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Bundle Items",
|
||||||
|
"options": "Packed Item",
|
||||||
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"collapsible_depends_on": "packed_items",
|
||||||
|
"depends_on": "packed_items",
|
||||||
|
"fieldname": "bundle_items_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Bundle Items",
|
||||||
|
"options": "fa fa-suitcase",
|
||||||
|
"print_hide": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-shopping-cart",
|
"icon": "fa fa-shopping-cart",
|
||||||
@ -933,7 +953,7 @@
|
|||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"max_attachments": 1,
|
"max_attachments": 1,
|
||||||
"modified": "2020-10-30 13:58:59.212060",
|
"modified": "2021-08-27 20:10:07.864951",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Quotation",
|
"name": "Quotation",
|
||||||
|
@ -31,6 +31,9 @@ class Quotation(SellingController):
|
|||||||
if self.items:
|
if self.items:
|
||||||
self.with_items = 1
|
self.with_items = 1
|
||||||
|
|
||||||
|
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
|
||||||
|
make_packing_list(self)
|
||||||
|
|
||||||
def validate_valid_till(self):
|
def validate_valid_till(self):
|
||||||
if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date):
|
if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date):
|
||||||
frappe.throw(_("Valid till date cannot be before transaction date"))
|
frappe.throw(_("Valid till date cannot be before transaction date"))
|
||||||
|
@ -226,9 +226,87 @@ class TestQuotation(unittest.TestCase):
|
|||||||
expired_quotation.reload()
|
expired_quotation.reload()
|
||||||
self.assertEqual(expired_quotation.status, "Expired")
|
self.assertEqual(expired_quotation.status, "Expired")
|
||||||
|
|
||||||
|
def test_product_bundle_mapping_on_creating_so(self):
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
|
||||||
|
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
||||||
|
|
||||||
|
make_item("_Test Product Bundle", {"is_stock_item": 0})
|
||||||
|
make_item("_Test Bundle Item 1", {"is_stock_item": 1})
|
||||||
|
make_item("_Test Bundle Item 2", {"is_stock_item": 1})
|
||||||
|
|
||||||
|
make_product_bundle("_Test Product Bundle",
|
||||||
|
["_Test Bundle Item 1", "_Test Bundle Item 2"])
|
||||||
|
|
||||||
|
quotation = make_quotation(item_code="_Test Product Bundle", qty=1, rate=100)
|
||||||
|
sales_order = make_sales_order(quotation.name)
|
||||||
|
|
||||||
|
quotation_item = [quotation.items[0].item_code, quotation.items[0].rate, quotation.items[0].qty, quotation.items[0].amount]
|
||||||
|
so_item = [sales_order.items[0].item_code, sales_order.items[0].rate, sales_order.items[0].qty, sales_order.items[0].amount]
|
||||||
|
|
||||||
|
self.assertEqual(quotation_item, so_item)
|
||||||
|
|
||||||
|
quotation_packed_items = [
|
||||||
|
[quotation.packed_items[0].parent_item, quotation.packed_items[0].item_code, quotation.packed_items[0].qty],
|
||||||
|
[quotation.packed_items[1].parent_item, quotation.packed_items[1].item_code, quotation.packed_items[1].qty]
|
||||||
|
]
|
||||||
|
so_packed_items = [
|
||||||
|
[sales_order.packed_items[0].parent_item, sales_order.packed_items[0].item_code, sales_order.packed_items[0].qty],
|
||||||
|
[sales_order.packed_items[1].parent_item, sales_order.packed_items[1].item_code, sales_order.packed_items[1].qty]
|
||||||
|
]
|
||||||
|
|
||||||
|
self.assertEqual(quotation_packed_items, so_packed_items)
|
||||||
|
|
||||||
|
def test_product_bundle_price_calculation_when_calculate_bundle_price_is_unchecked(self):
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
|
||||||
|
|
||||||
|
make_item("_Test Product Bundle", {"is_stock_item": 0})
|
||||||
|
bundle_item1 = make_item("_Test Bundle Item 1", {"is_stock_item": 1})
|
||||||
|
bundle_item2 = make_item("_Test Bundle Item 2", {"is_stock_item": 1})
|
||||||
|
|
||||||
|
make_product_bundle("_Test Product Bundle",
|
||||||
|
["_Test Bundle Item 1", "_Test Bundle Item 2"])
|
||||||
|
|
||||||
|
bundle_item1.valuation_rate = 100
|
||||||
|
bundle_item1.save()
|
||||||
|
|
||||||
|
bundle_item2.valuation_rate = 200
|
||||||
|
bundle_item2.save()
|
||||||
|
|
||||||
|
quotation = make_quotation(item_code="_Test Product Bundle", qty=2, rate=100)
|
||||||
|
self.assertEqual(quotation.items[0].amount, 200)
|
||||||
|
|
||||||
|
def test_product_bundle_price_calculation_when_calculate_bundle_price_is_checked(self):
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
|
||||||
|
|
||||||
|
make_item("_Test Product Bundle", {"is_stock_item": 0})
|
||||||
|
make_item("_Test Bundle Item 1", {"is_stock_item": 1})
|
||||||
|
make_item("_Test Bundle Item 2", {"is_stock_item": 1})
|
||||||
|
|
||||||
|
make_product_bundle("_Test Product Bundle",
|
||||||
|
["_Test Bundle Item 1", "_Test Bundle Item 2"])
|
||||||
|
|
||||||
|
enable_calculate_bundle_price()
|
||||||
|
|
||||||
|
quotation = make_quotation(item_code="_Test Product Bundle", qty=2, rate=100, do_not_submit=1)
|
||||||
|
quotation.packed_items[0].rate = 100
|
||||||
|
quotation.packed_items[1].rate = 200
|
||||||
|
quotation.save()
|
||||||
|
|
||||||
|
self.assertEqual(quotation.items[0].amount, 600)
|
||||||
|
self.assertEqual(quotation.items[0].rate, 300)
|
||||||
|
|
||||||
|
enable_calculate_bundle_price(enable=0)
|
||||||
|
|
||||||
test_records = frappe.get_test_records('Quotation')
|
test_records = frappe.get_test_records('Quotation')
|
||||||
|
|
||||||
|
def enable_calculate_bundle_price(enable=1):
|
||||||
|
selling_settings = frappe.get_doc("Selling Settings")
|
||||||
|
selling_settings.editable_bundle_item_rates = enable
|
||||||
|
selling_settings.save()
|
||||||
|
|
||||||
def get_quotation_dict(party_name=None, item_code=None):
|
def get_quotation_dict(party_name=None, item_code=None):
|
||||||
if not party_name:
|
if not party_name:
|
||||||
party_name = '_Test Customer'
|
party_name = '_Test Customer'
|
||||||
|
@ -43,6 +43,9 @@ frappe.ui.form.on("Sales Order", {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_df_property('packed_items', 'cannot_add_rows', true);
|
||||||
|
frm.set_df_property('packed_items', 'cannot_delete_rows', true);
|
||||||
},
|
},
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
if(frm.doc.docstatus === 1 && frm.doc.status !== 'Closed'
|
if(frm.doc.docstatus === 1 && frm.doc.status !== 'Closed'
|
||||||
|
@ -55,6 +55,8 @@
|
|||||||
"items_section",
|
"items_section",
|
||||||
"scan_barcode",
|
"scan_barcode",
|
||||||
"items",
|
"items",
|
||||||
|
"packing_list",
|
||||||
|
"packed_items",
|
||||||
"pricing_rule_details",
|
"pricing_rule_details",
|
||||||
"pricing_rules",
|
"pricing_rules",
|
||||||
"section_break_31",
|
"section_break_31",
|
||||||
@ -101,8 +103,6 @@
|
|||||||
"in_words",
|
"in_words",
|
||||||
"advance_paid",
|
"advance_paid",
|
||||||
"disable_rounded_total",
|
"disable_rounded_total",
|
||||||
"packing_list",
|
|
||||||
"packed_items",
|
|
||||||
"payment_schedule_section",
|
"payment_schedule_section",
|
||||||
"payment_terms_template",
|
"payment_terms_template",
|
||||||
"payment_schedule",
|
"payment_schedule",
|
||||||
@ -1019,6 +1019,7 @@
|
|||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"collapsible_depends_on": "packed_items",
|
"collapsible_depends_on": "packed_items",
|
||||||
|
"depends_on": "packed_items",
|
||||||
"fieldname": "packing_list",
|
"fieldname": "packing_list",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
@ -1029,14 +1030,14 @@
|
|||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "packed_items",
|
||||||
"fieldname": "packed_items",
|
"fieldname": "packed_items",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
"hide_seconds": 1,
|
"hide_seconds": 1,
|
||||||
"label": "Packed Items",
|
"label": "Packed Items",
|
||||||
"options": "Packed Item",
|
"options": "Packed Item",
|
||||||
"print_hide": 1,
|
"print_hide": 1
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "payment_schedule_section",
|
"fieldname": "payment_schedule_section",
|
||||||
@ -1511,7 +1512,7 @@
|
|||||||
"idx": 105,
|
"idx": 105,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-08-17 20:15:26.531553",
|
"modified": "2021-09-01 15:12:24.115483",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Sales Order",
|
"name": "Sales Order",
|
||||||
|
@ -947,11 +947,52 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None):
|
|||||||
"pricing_rules"
|
"pricing_rules"
|
||||||
],
|
],
|
||||||
"postprocess": update_item,
|
"postprocess": update_item,
|
||||||
"condition": lambda doc: doc.ordered_qty < doc.stock_qty and doc.item_code in items_to_map
|
"condition": lambda doc: doc.ordered_qty < doc.stock_qty and doc.item_code in items_to_map and not is_product_bundle(doc.item_code)
|
||||||
|
},
|
||||||
|
"Packed Item": {
|
||||||
|
"doctype": "Purchase Order Item",
|
||||||
|
"field_map": [
|
||||||
|
["parent", "sales_order"],
|
||||||
|
["uom", "uom"],
|
||||||
|
["conversion_factor", "conversion_factor"],
|
||||||
|
["parent_item", "product_bundle"],
|
||||||
|
["rate", "rate"]
|
||||||
|
],
|
||||||
|
"field_no_map": [
|
||||||
|
"price_list_rate",
|
||||||
|
"item_tax_template",
|
||||||
|
"discount_percentage",
|
||||||
|
"discount_amount",
|
||||||
|
"supplier",
|
||||||
|
"pricing_rules"
|
||||||
|
],
|
||||||
}
|
}
|
||||||
}, target_doc, set_missing_values)
|
}, target_doc, set_missing_values)
|
||||||
|
|
||||||
|
set_delivery_date(doc.items, source_name)
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
def set_delivery_date(items, sales_order):
|
||||||
|
delivery_dates = frappe.get_all(
|
||||||
|
'Sales Order Item',
|
||||||
|
filters = {
|
||||||
|
'parent': sales_order
|
||||||
|
},
|
||||||
|
fields = ['delivery_date', 'item_code']
|
||||||
|
)
|
||||||
|
|
||||||
|
delivery_by_item = frappe._dict()
|
||||||
|
for date in delivery_dates:
|
||||||
|
delivery_by_item[date.item_code] = date.delivery_date
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if item.product_bundle:
|
||||||
|
item.schedule_date = delivery_by_item[item.product_bundle]
|
||||||
|
|
||||||
|
def is_product_bundle(item_code):
|
||||||
|
return frappe.db.exists('Product Bundle', item_code)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_work_orders(items, sales_order, company, project=None):
|
def make_work_orders(items, sales_order, company, project=None):
|
||||||
'''Make Work Orders against the given Sales Order for the given `items`'''
|
'''Make Work Orders against the given Sales Order for the given `items`'''
|
||||||
|
@ -906,6 +906,38 @@ class TestSalesOrder(unittest.TestCase):
|
|||||||
self.assertEqual(purchase_orders[0].supplier, '_Test Supplier')
|
self.assertEqual(purchase_orders[0].supplier, '_Test Supplier')
|
||||||
self.assertEqual(purchase_orders[1].supplier, '_Test Supplier 1')
|
self.assertEqual(purchase_orders[1].supplier, '_Test Supplier 1')
|
||||||
|
|
||||||
|
def test_product_bundles_in_so_are_replaced_with_bundle_items_in_po(self):
|
||||||
|
"""
|
||||||
|
Tests if the the Product Bundles in the Items table of Sales Orders are replaced with
|
||||||
|
their child items(from the Packed Items table) on creating a Purchase Order from it.
|
||||||
|
"""
|
||||||
|
from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order
|
||||||
|
|
||||||
|
product_bundle = make_item("_Test Product Bundle", {"is_stock_item": 0})
|
||||||
|
make_item("_Test Bundle Item 1", {"is_stock_item": 1})
|
||||||
|
make_item("_Test Bundle Item 2", {"is_stock_item": 1})
|
||||||
|
|
||||||
|
make_product_bundle("_Test Product Bundle",
|
||||||
|
["_Test Bundle Item 1", "_Test Bundle Item 2"])
|
||||||
|
|
||||||
|
so_items = [
|
||||||
|
{
|
||||||
|
"item_code": product_bundle.item_code,
|
||||||
|
"warehouse": "",
|
||||||
|
"qty": 2,
|
||||||
|
"rate": 400,
|
||||||
|
"delivered_by_supplier": 1,
|
||||||
|
"supplier": '_Test Supplier'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
so = make_sales_order(item_list=so_items)
|
||||||
|
|
||||||
|
purchase_order = make_purchase_order(so.name, selected_items=so_items)
|
||||||
|
|
||||||
|
self.assertEqual(purchase_order.items[0].item_code, "_Test Bundle Item 1")
|
||||||
|
self.assertEqual(purchase_order.items[1].item_code, "_Test Bundle Item 2")
|
||||||
|
|
||||||
def test_reserved_qty_for_closing_so(self):
|
def test_reserved_qty_for_closing_so(self):
|
||||||
bin = frappe.get_all("Bin", filters={"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
|
bin = frappe.get_all("Bin", filters={"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
|
||||||
fields=["reserved_qty"])
|
fields=["reserved_qty"])
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
"maintain_same_rate_action",
|
"maintain_same_rate_action",
|
||||||
"editable_price_list_rate",
|
"editable_price_list_rate",
|
||||||
"validate_selling_price",
|
"validate_selling_price",
|
||||||
|
"editable_bundle_item_rates",
|
||||||
"sales_transactions_settings_section",
|
"sales_transactions_settings_section",
|
||||||
"so_required",
|
"so_required",
|
||||||
"dn_required",
|
"dn_required",
|
||||||
@ -191,6 +192,12 @@
|
|||||||
"fieldname": "sales_transactions_settings_section",
|
"fieldname": "sales_transactions_settings_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Transaction Settings"
|
"label": "Transaction Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "editable_bundle_item_rates",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Calculate Product Bundle Price based on Child Items' Rates"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-cog",
|
"icon": "fa fa-cog",
|
||||||
@ -198,7 +205,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-08-06 22:25:50.119458",
|
"modified": "2021-08-24 22:08:34.470897",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Selling Settings",
|
"name": "Selling Settings",
|
||||||
|
@ -15,6 +15,7 @@ from frappe.model.document import Document
|
|||||||
class SellingSettings(Document):
|
class SellingSettings(Document):
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
self.toggle_hide_tax_id()
|
self.toggle_hide_tax_id()
|
||||||
|
self.toggle_editable_rate_for_bundle_items()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
for key in ["cust_master_name", "campaign_naming_by", "customer_group", "territory",
|
for key in ["cust_master_name", "campaign_naming_by", "customer_group", "territory",
|
||||||
@ -33,6 +34,11 @@ class SellingSettings(Document):
|
|||||||
make_property_setter(doctype, "tax_id", "hidden", self.hide_tax_id, "Check", validate_fields_for_doctype=False)
|
make_property_setter(doctype, "tax_id", "hidden", self.hide_tax_id, "Check", validate_fields_for_doctype=False)
|
||||||
make_property_setter(doctype, "tax_id", "print_hide", self.hide_tax_id, "Check", validate_fields_for_doctype=False)
|
make_property_setter(doctype, "tax_id", "print_hide", self.hide_tax_id, "Check", validate_fields_for_doctype=False)
|
||||||
|
|
||||||
|
def toggle_editable_rate_for_bundle_items(self):
|
||||||
|
editable_bundle_item_rates = cint(self.editable_bundle_item_rates)
|
||||||
|
|
||||||
|
make_property_setter("Packed Item", "rate", "read_only", not(editable_bundle_item_rates), "Check", validate_fields_for_doctype=False)
|
||||||
|
|
||||||
def set_default_customer_group_and_territory(self):
|
def set_default_customer_group_and_territory(self):
|
||||||
if not self.customer_group:
|
if not self.customer_group:
|
||||||
self.customer_group = get_root_of('Customer Group')
|
self.customer_group = get_root_of('Customer Group')
|
||||||
|
@ -90,10 +90,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
|
|||||||
|
|
||||||
this.frm.toggle_display("customer_name",
|
this.frm.toggle_display("customer_name",
|
||||||
(this.frm.doc.customer_name && this.frm.doc.customer_name!==this.frm.doc.customer));
|
(this.frm.doc.customer_name && this.frm.doc.customer_name!==this.frm.doc.customer));
|
||||||
if(this.frm.fields_dict.packed_items) {
|
|
||||||
var packing_list_exists = (this.frm.doc.packed_items || []).length;
|
|
||||||
this.frm.toggle_display("packing_list", packing_list_exists ? true : false);
|
|
||||||
}
|
|
||||||
this.toggle_editable_price_list_rate();
|
this.toggle_editable_price_list_rate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -543,6 +543,7 @@
|
|||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"collapsible_depends_on": "packed_items",
|
"collapsible_depends_on": "packed_items",
|
||||||
|
"depends_on": "packed_items",
|
||||||
"fieldname": "packing_list",
|
"fieldname": "packing_list",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Packing List",
|
"label": "Packing List",
|
||||||
@ -551,6 +552,7 @@
|
|||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"depends_on": "packed_items",
|
||||||
"fieldname": "packed_items",
|
"fieldname": "packed_items",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Packed Items",
|
"label": "Packed Items",
|
||||||
@ -1306,7 +1308,7 @@
|
|||||||
"idx": 146,
|
"idx": 146,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-08-17 20:15:50.574966",
|
"modified": "2021-08-27 20:14:40.215231",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Delivery Note",
|
"name": "Delivery Note",
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
"conversion_factor",
|
"conversion_factor",
|
||||||
"column_break_9",
|
"column_break_9",
|
||||||
"qty",
|
"qty",
|
||||||
|
"rate",
|
||||||
"uom",
|
"uom",
|
||||||
"section_break_9",
|
"section_break_9",
|
||||||
"serial_no",
|
"serial_no",
|
||||||
@ -215,13 +216,23 @@
|
|||||||
"fieldname": "conversion_factor",
|
"fieldname": "conversion_factor",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Conversion Factor"
|
"label": "Conversion Factor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "item_code.valuation_rate",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
|
"fieldname": "rate",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Rate",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-05-26 07:08:05.111385",
|
"modified": "2021-09-01 15:10:29.646399",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Packed Item",
|
"name": "Packed Item",
|
||||||
|
@ -39,8 +39,10 @@ def update_packing_list_item(doc, packing_item_code, qty, main_item_row, descrip
|
|||||||
# check if exists
|
# check if exists
|
||||||
exists = 0
|
exists = 0
|
||||||
for d in doc.get("packed_items"):
|
for d in doc.get("packed_items"):
|
||||||
if d.parent_item == main_item_row.item_code and d.item_code == packing_item_code and\
|
if d.parent_item == main_item_row.item_code and d.item_code == packing_item_code:
|
||||||
d.parent_detail_docname == main_item_row.name:
|
if d.parent_detail_docname != main_item_row.name:
|
||||||
|
d.parent_detail_docname = main_item_row.name
|
||||||
|
|
||||||
pi, exists = d, 1
|
pi, exists = d, 1
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -86,6 +88,9 @@ def make_packing_list(doc):
|
|||||||
|
|
||||||
cleanup_packing_list(doc, parent_items)
|
cleanup_packing_list(doc, parent_items)
|
||||||
|
|
||||||
|
if frappe.db.get_single_value("Selling Settings", "editable_bundle_item_rates"):
|
||||||
|
update_product_bundle_price(doc, parent_items)
|
||||||
|
|
||||||
def cleanup_packing_list(doc, parent_items):
|
def cleanup_packing_list(doc, parent_items):
|
||||||
"""Remove all those child items which are no longer present in main item table"""
|
"""Remove all those child items which are no longer present in main item table"""
|
||||||
delete_list = []
|
delete_list = []
|
||||||
@ -103,6 +108,40 @@ def cleanup_packing_list(doc, parent_items):
|
|||||||
if d not in delete_list:
|
if d not in delete_list:
|
||||||
doc.append("packed_items", d)
|
doc.append("packed_items", d)
|
||||||
|
|
||||||
|
def update_product_bundle_price(doc, parent_items):
|
||||||
|
"""Updates the prices of Product Bundles based on the rates of the Items in the bundle."""
|
||||||
|
|
||||||
|
if not doc.get('items'):
|
||||||
|
return
|
||||||
|
|
||||||
|
parent_items_index = 0
|
||||||
|
bundle_price = 0
|
||||||
|
|
||||||
|
for bundle_item in doc.get("packed_items"):
|
||||||
|
if parent_items[parent_items_index][0] == bundle_item.parent_item:
|
||||||
|
bundle_item_rate = bundle_item.rate if bundle_item.rate else 0
|
||||||
|
bundle_price += bundle_item.qty * bundle_item_rate
|
||||||
|
else:
|
||||||
|
update_parent_item_price(doc, parent_items[parent_items_index][0], bundle_price)
|
||||||
|
|
||||||
|
bundle_price = 0
|
||||||
|
parent_items_index += 1
|
||||||
|
|
||||||
|
# for the last product bundle
|
||||||
|
if doc.get("packed_items"):
|
||||||
|
update_parent_item_price(doc, parent_items[parent_items_index][0], bundle_price)
|
||||||
|
|
||||||
|
def update_parent_item_price(doc, parent_item_code, bundle_price):
|
||||||
|
parent_item_doc = doc.get('items', {'item_code': parent_item_code})[0]
|
||||||
|
|
||||||
|
current_parent_item_price = parent_item_doc.amount
|
||||||
|
if current_parent_item_price != bundle_price:
|
||||||
|
parent_item_doc.amount = bundle_price
|
||||||
|
update_parent_item_rate(parent_item_doc, bundle_price)
|
||||||
|
|
||||||
|
def update_parent_item_rate(parent_item_doc, bundle_price):
|
||||||
|
parent_item_doc.rate = bundle_price/parent_item_doc.qty
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_items_from_product_bundle(args):
|
def get_items_from_product_bundle(args):
|
||||||
args = json.loads(args)
|
args = json.loads(args)
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"barcode",
|
"barcode",
|
||||||
"section_break_2",
|
"section_break_2",
|
||||||
"item_code",
|
"item_code",
|
||||||
|
"product_bundle",
|
||||||
"supplier_part_no",
|
"supplier_part_no",
|
||||||
"column_break_2",
|
"column_break_2",
|
||||||
"item_name",
|
"item_name",
|
||||||
@ -956,12 +957,19 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "product_bundle",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Product Bundle",
|
||||||
|
"options": "Product Bundle",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-03-29 04:17:00.336298",
|
"modified": "2021-09-01 16:02:40.338597",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt Item",
|
"name": "Purchase Receipt Item",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user