Merge pull request #35392 from frappe/version-14-hotfix

chore: release v14
This commit is contained in:
Deepesh Garg
2023-05-24 08:31:31 +05:30
committed by GitHub
28 changed files with 513 additions and 82 deletions

View File

@@ -905,7 +905,7 @@ frappe.ui.form.on('Payment Entry', {
function(d) { return flt(d.amount) }));
frm.set_value("difference_amount", difference_amount - total_deductions +
frm.doc.base_total_taxes_and_charges);
flt(frm.doc.base_total_taxes_and_charges));
frm.events.hide_unhide_fields(frm);
},

View File

@@ -1179,7 +1179,12 @@ class SalesInvoice(SellingController):
if self.is_return:
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name")
asset,
item.base_net_amount,
item.finance_book,
self.get("doctype"),
self.get("name"),
self.get("posting_date"),
)
asset.db_set("disposal_date", None)
@@ -1194,7 +1199,12 @@ class SalesInvoice(SellingController):
asset.reload()
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name")
asset,
item.base_net_amount,
item.finance_book,
self.get("doctype"),
self.get("name"),
self.get("posting_date"),
)
asset.db_set("disposal_date", self.posting_date)

View File

@@ -0,0 +1,41 @@
{
"creation": "2023-05-23 09:58:17.235916",
"docstatus": 0,
"doctype": "Form Tour",
"first_document": 0,
"idx": 0,
"include_name_field": 0,
"is_standard": 1,
"modified": "2023-05-23 13:10:56.227127",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
"owner": "Administrator",
"reference_doctype": "Sales Invoice",
"save_on_complete": 1,
"steps": [
{
"description": "Select a customer for whom this invoice is being prepared.",
"fieldname": "customer",
"fieldtype": "Link",
"has_next_condition": 1,
"is_table_field": 0,
"label": "Customer",
"next_step_condition": "eval: doc.customer",
"position": "Right",
"title": "Select Customer"
},
{
"child_doctype": "Sales Invoice Item",
"description": "Select item that you have sold along with quantity and rate.",
"fieldname": "items",
"fieldtype": "Table",
"has_next_condition": 0,
"is_table_field": 0,
"parent_fieldname": "items",
"position": "Top",
"title": "Select Item"
}
],
"title": "Sales Invoice"
}

View File

@@ -343,7 +343,7 @@ class Asset(AccountsController):
# if asset is being sold
if date_of_disposal:
from_date = self.get_from_date(finance_book.finance_book)
from_date = self.get_from_date_for_disposal(finance_book)
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book,
depreciation_amount,
@@ -425,7 +425,7 @@ class Asset(AccountsController):
depreciation_amount += value_after_depreciation - finance_book.expected_value_after_useful_life
skip_row = True
if depreciation_amount > 0:
if flt(depreciation_amount, self.precision("gross_purchase_amount")) > 0:
self._add_depreciation_row(
schedule_date,
depreciation_amount,
@@ -500,16 +500,19 @@ class Asset(AccountsController):
return start
def get_from_date(self, finance_book):
def get_from_date_for_disposal(self, finance_book):
if not self.get("schedules"):
return self.available_for_use_date
return add_months(
getdate(self.available_for_use_date),
(self.number_of_depreciations_booked * finance_book.frequency_of_depreciation),
)
if len(self.finance_books) == 1:
return self.schedules[-1].schedule_date
from_date = ""
for schedule in self.get("schedules"):
if schedule.finance_book == finance_book:
if schedule.finance_book == finance_book.finance_book:
from_date = schedule.schedule_date
if from_date:
@@ -1287,9 +1290,11 @@ def get_straight_line_or_manual_depr_amount(asset, row):
)
# if the Depreciation Schedule is being prepared for the first time
else:
return (flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)) / flt(
row.total_number_of_depreciations
)
return (
flt(asset.gross_purchase_amount)
- flt(asset.opening_accumulated_depreciation)
- flt(row.expected_value_after_useful_life)
) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
def get_wdv_or_dd_depr_amount(

View File

@@ -272,7 +272,7 @@ def scrap_asset(asset_name):
je.company = asset.company
je.remark = "Scrap Entry for asset {0}".format(asset_name)
for entry in get_gl_entries_on_asset_disposal(asset):
for entry in get_gl_entries_on_asset_disposal(asset, date):
entry.update({"reference_type": "Asset", "reference_name": asset_name})
je.append("accounts", entry)
@@ -395,8 +395,11 @@ def disposal_happens_in_the_future(posting_date_of_disposal):
def get_gl_entries_on_asset_regain(
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None, date=None
):
if not date:
date = getdate()
(
fixed_asset_account,
asset,
@@ -414,7 +417,7 @@ def get_gl_entries_on_asset_regain(
"debit_in_account_currency": asset.gross_purchase_amount,
"debit": asset.gross_purchase_amount,
"cost_center": depreciation_cost_center,
"posting_date": getdate(),
"posting_date": date,
},
item=asset,
),
@@ -424,7 +427,7 @@ def get_gl_entries_on_asset_regain(
"credit_in_account_currency": accumulated_depr_amount,
"credit": accumulated_depr_amount,
"cost_center": depreciation_cost_center,
"posting_date": getdate(),
"posting_date": date,
},
item=asset,
),
@@ -433,7 +436,7 @@ def get_gl_entries_on_asset_regain(
profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount))
if profit_amount:
get_profit_gl_entries(
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center, date
)
if voucher_type and voucher_no:
@@ -445,8 +448,11 @@ def get_gl_entries_on_asset_regain(
def get_gl_entries_on_asset_disposal(
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None, date=None
):
if not date:
date = getdate()
(
fixed_asset_account,
asset,
@@ -464,7 +470,7 @@ def get_gl_entries_on_asset_disposal(
"credit_in_account_currency": asset.gross_purchase_amount,
"credit": asset.gross_purchase_amount,
"cost_center": depreciation_cost_center,
"posting_date": getdate(),
"posting_date": date,
},
item=asset,
),
@@ -474,7 +480,7 @@ def get_gl_entries_on_asset_disposal(
"debit_in_account_currency": accumulated_depr_amount,
"debit": accumulated_depr_amount,
"cost_center": depreciation_cost_center,
"posting_date": getdate(),
"posting_date": date,
},
item=asset,
),
@@ -483,7 +489,7 @@ def get_gl_entries_on_asset_disposal(
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
if profit_amount:
get_profit_gl_entries(
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center, date
)
if voucher_type and voucher_no:
@@ -517,8 +523,12 @@ def get_asset_details(asset, finance_book=None):
def get_profit_gl_entries(
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center, date=None
):
if not date:
date = getdate()
debit_or_credit = "debit" if profit_amount < 0 else "credit"
gl_entries.append(
asset.get_gl_dict(
@@ -527,7 +537,7 @@ def get_profit_gl_entries(
"cost_center": depreciation_cost_center,
debit_or_credit: abs(profit_amount),
debit_or_credit + "_in_account_currency": abs(profit_amount),
"posting_date": getdate(),
"posting_date": date,
},
item=asset,
)

View File

@@ -327,6 +327,79 @@ class TestAsset(AssetSetup):
si.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
def test_gle_made_by_asset_sale_for_existing_asset(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
asset = create_asset(
calculate_depreciation=1,
available_for_use_date="2020-04-01",
purchase_date="2020-04-01",
expected_value_after_useful_life=0,
total_number_of_depreciations=5,
number_of_depreciations_booked=2,
frequency_of_depreciation=12,
depreciation_start_date="2023-03-31",
opening_accumulated_depreciation=24000,
gross_purchase_amount=60000,
submit=1,
)
expected_depr_values = [
["2023-03-31", 12000, 36000],
["2024-03-31", 12000, 48000],
["2025-03-31", 12000, 60000],
]
for i, schedule in enumerate(asset.schedules):
self.assertEqual(getdate(expected_depr_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_depr_values[i][1], schedule.depreciation_amount)
self.assertEqual(expected_depr_values[i][2], schedule.accumulated_depreciation_amount)
post_depreciation_entries(date="2023-03-31")
si = create_sales_invoice(
item_code="Macbook Pro", asset=asset.name, qty=1, rate=40000, posting_date=getdate("2023-05-23")
)
asset.load_from_db()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
expected_values = [["2023-03-31", 12000, 36000], ["2023-05-23", 1742.47, 37742.47]]
for i, schedule in enumerate(asset.schedules):
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
self.assertTrue(schedule.journal_entry)
expected_gle = (
(
"_Test Accumulated Depreciations - _TC",
37742.47,
0.0,
),
(
"_Test Fixed Asset - _TC",
0.0,
60000.0,
),
(
"_Test Gain/Loss on Asset Disposal - _TC",
0.0,
17742.47,
),
("Debtors - _TC", 40000.0, 0.0),
)
gle = frappe.db.sql(
"""select account, debit, credit from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no = %s
order by account""",
si.name,
)
self.assertSequenceEqual(gle, expected_gle)
def test_asset_with_maintenance_required_status_after_sale(self):
asset = create_asset(
calculate_depreciation=1,
@@ -649,7 +722,7 @@ class TestDepreciationMethods(AssetSetup):
)
self.assertEqual(asset.status, "Draft")
expected_schedules = [["2032-12-31", 30000.0, 77095.89], ["2033-06-06", 12904.11, 90000.0]]
expected_schedules = [["2032-12-31", 42904.11, 90000.0]]
schedules = [
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
for d in asset.get("schedules")

View File

@@ -436,6 +436,7 @@ class AssetCapitalization(StockController):
item.get("finance_book") or self.get("finance_book"),
self.get("doctype"),
self.get("name"),
self.get("posting_date"),
)
asset.db_set("disposal_date", self.posting_date)

View File

@@ -689,7 +689,6 @@ class SubcontractingController(StockController):
"actual_qty": flt(item.rejected_qty) * flt(item.conversion_factor),
"serial_no": cstr(item.rejected_serial_no).strip(),
"incoming_rate": 0.0,
"recalculate_rate": 1,
},
)
)

View File

@@ -439,7 +439,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2022-11-09 15:02:44.490731",
"modified": "2023-05-23 09:56:43.826602",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",

View File

@@ -730,7 +730,7 @@ class JobCard(Document):
self.status = {0: "Open", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0]
if self.docstatus < 2:
if self.for_quantity <= self.transferred_qty:
if flt(self.for_quantity) <= flt(self.transferred_qty):
self.status = "Material Transferred"
if self.time_logs:

View File

@@ -35,8 +35,12 @@
"section_break_25",
"prod_plan_references",
"section_break_24",
"get_sub_assembly_items",
"combine_sub_items",
"section_break_ucc4",
"skip_available_sub_assembly_item",
"column_break_igxl",
"get_sub_assembly_items",
"section_break_g4ip",
"sub_assembly_items",
"download_materials_request_plan_section_section",
"download_materials_required",
@@ -351,12 +355,12 @@
{
"fieldname": "section_break_24",
"fieldtype": "Section Break",
"hide_border": 1
"hide_border": 1,
"label": "Sub Assembly Items"
},
{
"fieldname": "sub_assembly_items",
"fieldtype": "Table",
"label": "Sub Assembly Items",
"no_copy": 1,
"options": "Production Plan Sub Assembly Item"
},
@@ -392,13 +396,33 @@
"fieldname": "download_materials_request_plan_section_section",
"fieldtype": "Section Break",
"label": "Download Materials Request Plan Section"
},
{
"default": "0",
"description": "System consider the projected quantity to check available or will be available sub-assembly items ",
"fieldname": "skip_available_sub_assembly_item",
"fieldtype": "Check",
"label": "Skip Available Sub Assembly Items"
},
{
"fieldname": "section_break_ucc4",
"fieldtype": "Column Break",
"hide_border": 1
},
{
"fieldname": "section_break_g4ip",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_igxl",
"fieldtype": "Column Break"
}
],
"icon": "fa fa-calendar",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-03-31 10:30:48.118932",
"modified": "2023-05-22 23:36:31.770517",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",

View File

@@ -718,7 +718,9 @@ class ProductionPlan(Document):
frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx))
bom_data = []
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty)
warehouse = row.warehouse if self.skip_available_sub_assembly_item else None
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty, self.company, warehouse=warehouse)
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
sub_assembly_items_store.extend(bom_data)
@@ -894,7 +896,9 @@ def download_raw_materials(doc, warehouses=None):
build_csv_response(item_list, doc.name)
def get_exploded_items(item_details, company, bom_no, include_non_stock_items, planned_qty=1):
def get_exploded_items(
item_details, company, bom_no, include_non_stock_items, planned_qty=1, doc=None
):
bei = frappe.qb.DocType("BOM Explosion Item")
bom = frappe.qb.DocType("BOM")
item = frappe.qb.DocType("Item")
@@ -1271,6 +1275,12 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
include_safety_stock = doc.get("include_safety_stock")
so_item_details = frappe._dict()
sub_assembly_items = {}
if doc.get("skip_available_sub_assembly_item"):
for d in doc.get("sub_assembly_items"):
sub_assembly_items.setdefault((d.get("production_item"), d.get("bom_no")), d.get("qty"))
for data in po_items:
if not data.get("include_exploded_items") and doc.get("sub_assembly_items"):
data["include_exploded_items"] = 1
@@ -1296,10 +1306,24 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get("idx")))
if bom_no:
if data.get("include_exploded_items") and include_subcontracted_items:
if (
data.get("include_exploded_items")
and doc.get("sub_assembly_items")
and doc.get("skip_available_sub_assembly_item")
):
item_details = get_raw_materials_of_sub_assembly_items(
item_details,
company,
bom_no,
include_non_stock_items,
sub_assembly_items,
planned_qty=planned_qty,
)
elif data.get("include_exploded_items") and include_subcontracted_items:
# fetch exploded items from BOM
item_details = get_exploded_items(
item_details, company, bom_no, include_non_stock_items, planned_qty=planned_qty
item_details, company, bom_no, include_non_stock_items, planned_qty=planned_qty, doc=doc
)
else:
item_details = get_subitems(
@@ -1456,12 +1480,22 @@ def get_item_data(item_code):
}
def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0):
def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, company, warehouse=None, indent=0):
data = get_bom_children(parent=bom_no)
for d in data:
if d.expandable:
parent_item_code = frappe.get_cached_value("BOM", bom_no, "item")
stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
if warehouse:
bin_dict = get_bin_details(d, company, for_warehouse=warehouse)
if bin_dict and bin_dict[0].projected_qty > 0:
if bin_dict[0].projected_qty > stock_qty:
continue
else:
stock_qty = stock_qty - bin_dict[0].projected_qty
bom_data.append(
frappe._dict(
{
@@ -1481,7 +1515,7 @@ def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0):
)
if d.value:
get_sub_assembly_items(d.value, bom_data, stock_qty, indent=indent + 1)
get_sub_assembly_items(d.value, bom_data, stock_qty, company, warehouse, indent=indent + 1)
def set_default_warehouses(row, default_warehouses):
@@ -1519,3 +1553,68 @@ def get_reserved_qty_for_production_plan(item_code, warehouse):
)
return reserved_qty_for_production_plan - reserved_qty_for_production
def get_raw_materials_of_sub_assembly_items(
item_details, company, bom_no, include_non_stock_items, sub_assembly_items, planned_qty=1
):
bei = frappe.qb.DocType("BOM Item")
bom = frappe.qb.DocType("BOM")
item = frappe.qb.DocType("Item")
item_default = frappe.qb.DocType("Item Default")
item_uom = frappe.qb.DocType("UOM Conversion Detail")
items = (
frappe.qb.from_(bei)
.join(bom)
.on(bom.name == bei.parent)
.join(item)
.on(item.name == bei.item_code)
.left_join(item_default)
.on((item_default.parent == item.name) & (item_default.company == company))
.left_join(item_uom)
.on((item.name == item_uom.parent) & (item_uom.uom == item.purchase_uom))
.select(
(IfNull(Sum(bei.stock_qty / IfNull(bom.quantity, 1)), 0) * planned_qty).as_("qty"),
item.item_name,
item.name.as_("item_code"),
bei.description,
bei.stock_uom,
bei.bom_no,
item.min_order_qty,
bei.source_warehouse,
item.default_material_request_type,
item.min_order_qty,
item_default.default_warehouse,
item.purchase_uom,
item_uom.conversion_factor,
item.safety_stock,
)
.where(
(bei.docstatus == 1)
& (bom.name == bom_no)
& (item.is_stock_item.isin([0, 1]) if include_non_stock_items else item.is_stock_item == 1)
)
.groupby(bei.item_code, bei.stock_uom)
).run(as_dict=True)
for item in items:
key = (item.item_code, item.bom_no)
if item.bom_no and key in sub_assembly_items:
planned_qty = flt(sub_assembly_items[key])
get_raw_materials_of_sub_assembly_items(
item_details,
company,
item.bom_no,
include_non_stock_items,
sub_assembly_items,
planned_qty=planned_qty,
)
else:
if not item.conversion_factor and item.purchase_uom:
item.conversion_factor = get_uom_conversion_factor(item.item_code, item.purchase_uom)
item_details.setdefault(item.get("item_code"), item)
return item_details

View File

@@ -926,6 +926,50 @@ class TestProductionPlan(FrappeTestCase):
self.assertEqual(after_qty, before_qty)
def test_skip_available_qty_for_sub_assembly_items(self):
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
bom_tree = {
"Fininshed Goods1 For SUB Test": {
"SubAssembly1 For SUB Test": {"ChildPart1 For SUB Test": {}},
"SubAssembly2 For SUB Test": {},
}
}
parent_bom = create_nested_bom(bom_tree, prefix="")
plan = create_production_plan(
item_code=parent_bom.item,
planned_qty=10,
ignore_existing_ordered_qty=1,
do_not_submit=1,
skip_available_sub_assembly_item=1,
warehouse="_Test Warehouse - _TC",
)
make_stock_entry(
item_code="SubAssembly1 For SUB Test",
qty=5,
rate=100,
target="_Test Warehouse - _TC",
)
self.assertTrue(plan.skip_available_sub_assembly_item)
plan.get_sub_assembly_items()
for row in plan.sub_assembly_items:
if row.production_item == "SubAssembly1 For SUB Test":
self.assertEqual(row.qty, 5)
mr_items = get_items_for_material_requests(plan.as_dict())
for row in mr_items:
row = frappe._dict(row)
if row.item_code == "ChildPart1 For SUB Test":
self.assertEqual(row.quantity, 5)
if row.item_code == "SubAssembly2 For SUB Test":
self.assertEqual(row.quantity, 10)
def create_production_plan(**args):
"""
@@ -945,6 +989,7 @@ def create_production_plan(**args):
"include_subcontracted_items": args.include_subcontracted_items or 0,
"ignore_existing_ordered_qty": args.ignore_existing_ordered_qty or 0,
"get_items_from": "Sales Order",
"skip_available_sub_assembly_item": args.skip_available_sub_assembly_item or 0,
}
)
@@ -958,6 +1003,7 @@ def create_production_plan(**args):
"planned_qty": args.planned_qty or 1,
"planned_start_date": args.planned_start_date or now_datetime(),
"stock_uom": args.stock_uom or "Nos",
"warehouse": args.warehouse,
},
)

View File

@@ -28,7 +28,11 @@
"uom",
"stock_uom",
"column_break_22",
"description"
"description",
"section_break_4rxf",
"actual_qty",
"column_break_xfhm",
"projected_qty"
],
"fields": [
{
@@ -183,12 +187,34 @@
"fieldtype": "Datetime",
"in_list_view": 1,
"label": "Schedule Date"
},
{
"fieldname": "section_break_4rxf",
"fieldtype": "Section Break"
},
{
"fieldname": "actual_qty",
"fieldtype": "Float",
"label": "Actual Qty",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "column_break_xfhm",
"fieldtype": "Column Break"
},
{
"fieldname": "projected_qty",
"fieldtype": "Float",
"label": "Projected Qty",
"no_copy": 1,
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-11-28 13:50:15.116082",
"modified": "2023-05-22 17:52:34.708879",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Sub Assembly Item",

View File

@@ -7,4 +7,6 @@ def execute():
frappe.reload_doc("manufacturing", "doctype", "work_order")
frappe.reload_doc("manufacturing", "doctype", "work_order_item")
frappe.db.sql("""UPDATE `tabWork Order Item` SET amount = rate * required_qty""")
frappe.db.sql(
"""UPDATE `tabWork Order Item` SET amount = ifnull(rate, 0.0) * ifnull(required_qty, 0.0)"""
)

View File

@@ -91,6 +91,12 @@ frappe.ui.form.on("Sales Invoice", {
});
frappe.ui.form.on('Purchase Invoice', {
setup: (frm) => {
frm.make_methods = {
'Landed Cost Voucher': function () { frm.trigger('create_landedcost_voucher') },
}
},
mode_of_payment: function(frm) {
get_payment_mode_account(frm, frm.doc.mode_of_payment, function(account){
frm.set_value('cash_bank_account', account);
@@ -99,6 +105,20 @@ frappe.ui.form.on('Purchase Invoice', {
payment_terms_template: function() {
cur_frm.trigger("disable_due_date");
},
create_landedcost_voucher: function (frm) {
let lcv = frappe.model.get_new_doc('Landed Cost Voucher');
lcv.company = frm.doc.company;
let lcv_receipt = frappe.model.get_new_doc('Landed Cost Purchase Invoice');
lcv_receipt.receipt_document_type = 'Purchase Invoice';
lcv_receipt.receipt_document = frm.doc.name;
lcv_receipt.supplier = frm.doc.supplier;
lcv_receipt.grand_total = frm.doc.grand_total;
lcv.purchase_receipts = [lcv_receipt];
frappe.set_route("Form", lcv.doctype, lcv.name);
}
});

View File

@@ -288,7 +288,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
)
# sales team
for d in customer.get("sales_team"):
for d in customer.get("sales_team") or []:
target.append(
"sales_team",
{

View File

@@ -2,9 +2,11 @@
"creation": "2021-11-23 12:00:36.138824",
"docstatus": 0,
"doctype": "Form Tour",
"first_document": 0,
"idx": 0,
"include_name_field": 0,
"is_standard": 1,
"modified": "2021-11-23 12:02:48.010298",
"modified": "2023-05-23 12:51:48.684517",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",
@@ -14,51 +16,43 @@
"steps": [
{
"description": "Select a customer or lead for whom this quotation is being prepared. Let's select a Customer.",
"field": "",
"fieldname": "quotation_to",
"fieldtype": "Link",
"has_next_condition": 0,
"is_table_field": 0,
"label": "Quotation To",
"parent_field": "",
"position": "Right",
"title": "Quotation To"
},
{
"description": "Select a specific Customer to whom this quotation will be sent.",
"field": "",
"fieldname": "party_name",
"fieldtype": "Dynamic Link",
"has_next_condition": 0,
"is_table_field": 0,
"label": "Party",
"parent_field": "",
"position": "Right",
"title": "Party"
},
{
"child_doctype": "Quotation Item",
"description": "Select an item for which you will be quoting a price.",
"field": "",
"fieldname": "items",
"fieldtype": "Table",
"has_next_condition": 0,
"is_table_field": 0,
"label": "Items",
"parent_field": "",
"parent_fieldname": "items",
"position": "Bottom",
"title": "Items"
},
{
"description": "You can select pre-populated Sales Taxes and Charges from here.",
"field": "",
"fieldname": "taxes",
"fieldtype": "Table",
"has_next_condition": 0,
"is_table_field": 0,
"label": "Sales Taxes and Charges",
"parent_field": "",
"position": "Bottom",
"title": "Sales Taxes and Charges"
}

View File

@@ -25,15 +25,12 @@
"documentation_url": "https://docs.erpnext.com/docs/v14/user/manual/en/setting-up/company-setup",
"idx": 0,
"is_complete": 0,
"modified": "2023-05-16 13:13:24.043792",
"modified": "2023-05-23 13:20:19.703506",
"modified_by": "Administrator",
"module": "Setup",
"name": "Home",
"owner": "Administrator",
"steps": [
{
"step": "Navigation Help"
},
{
"step": "Create an Item"
},
@@ -41,13 +38,10 @@
"step": "Create a Customer"
},
{
"step": "Create a Supplier"
},
{
"step": "Create a Quotation"
"step": "Create Your First Sales Invoice"
}
],
"subtitle": "Item, Customer, Supplier, Navigation Help and Quotation",
"subtitle": "Item, Customer, Supplier and Quotation",
"success_message": "You're ready to start your journey with ERPNext",
"title": "Let's begin your journey with ERPNext"
}

View File

@@ -9,7 +9,7 @@
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2023-05-16 12:54:54.112364",
"modified": "2023-05-23 12:45:55.138580",
"modified_by": "Administrator",
"name": "Create a Customer",
"owner": "Administrator",

View File

@@ -9,7 +9,7 @@
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2023-05-16 12:55:08.610113",
"modified": "2023-05-19 15:32:55.069257",
"modified_by": "Administrator",
"name": "Create a Supplier",
"owner": "Administrator",

View File

@@ -11,7 +11,7 @@
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2023-05-16 12:56:40.355878",
"modified": "2023-05-23 12:43:08.484206",
"modified_by": "Administrator",
"name": "Create an Item",
"owner": "Administrator",

View File

@@ -0,0 +1,20 @@
{
"action": "Create Entry",
"creation": "2020-05-14 17:48:21.019019",
"description": "# All about sales invoice\n\nA Sales Invoice is a bill that you send to your Customers against which the Customer makes the payment. Sales Invoice is an accounting transaction. On submission of Sales Invoice, the system updates the receivable and books income against a Customer Account.",
"docstatus": 0,
"doctype": "Onboarding Step",
"idx": 0,
"is_complete": 0,
"is_single": 0,
"is_skipped": 0,
"modified": "2023-05-22 21:20:15.589644",
"modified_by": "Administrator",
"name": "Create Your First Sales Invoice",
"owner": "Administrator",
"reference_document": "Sales Invoice",
"show_form_tour": 1,
"show_full_form": 1,
"title": "Create Your First Sales Invoice ",
"validate_action": 1
}

View File

@@ -29,6 +29,7 @@ class PickList(Document):
self.validate_for_qty()
def before_save(self):
self.update_status()
self.set_item_locations()
# set percentage picked in SO
@@ -89,20 +90,20 @@ class PickList(Document):
self.update_reference_qty()
self.update_sales_order_picking_status()
def update_status(self, status=None, update_modified=True):
def update_status(self, status=None):
if not status:
if self.docstatus == 0:
status = "Draft"
elif self.docstatus == 1:
if self.status == "Draft":
status = "Open"
elif target_document_exists(self.name, self.purpose):
if target_document_exists(self.name, self.purpose):
status = "Completed"
else:
status = "Open"
elif self.docstatus == 2:
status = "Cancelled"
if status:
frappe.db.set_value("Pick List", self.name, "status", status, update_modified=update_modified)
self.db_set("status", status)
def update_reference_qty(self):
packed_items = []
@@ -459,7 +460,7 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus)
item_doc.qty if (docstatus == 1 and item_doc.stock_qty == 0) else item_doc.stock_qty
)
while remaining_stock_qty > 0 and available_locations:
while flt(remaining_stock_qty) > 0 and available_locations:
item_location = available_locations.pop(0)
item_location = frappe._dict(item_location)

View File

@@ -33,5 +33,40 @@ frappe.query_reports["Stock and Account Value Comparison"] = {
"fieldtype": "Date",
"default": frappe.datetime.get_today(),
},
]
],
get_datatable_options(options) {
return Object.assign(options, {
checkboxColumn: true,
});
},
onload(report) {
report.page.add_inner_button(__("Create Reposting Entries"), function() {
let message = `<div>
<p>
Reposting Entries will change the value of
accounts Stock In Hand, and Stock Expenses
in the Trial Balance report and will also change
the Balance Value in the Stock Balance report.
</p>
<p>Are you sure you want to create Reposting Entries?</p>
</div>
`;
frappe.confirm(__(message), () => {
let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows();
let selected_rows = indexes.map(i => frappe.query_report.data[i]);
frappe.call({
method: "erpnext.stock.report.stock_and_account_value_comparison.stock_and_account_value_comparison.create_reposting_entries",
args: {
rows: selected_rows,
company: frappe.query_report.get_filter_values().company
}
});
});
});
}
};

View File

@@ -4,6 +4,7 @@
import frappe
from frappe import _
from frappe.utils import get_link_to_form, parse_json
import erpnext
from erpnext.accounts.utils import get_currency_precision, get_stock_accounts
@@ -134,3 +135,35 @@ def get_columns(filters):
"width": "120",
},
]
@frappe.whitelist()
def create_reposting_entries(rows, company):
if isinstance(rows, str):
rows = parse_json(rows)
entries = []
for row in rows:
row = frappe._dict(row)
try:
doc = frappe.get_doc(
{
"doctype": "Repost Item Valuation",
"based_on": "Transaction",
"status": "Queued",
"voucher_type": row.voucher_type,
"voucher_no": row.voucher_no,
"posting_date": row.posting_date,
"company": company,
"allow_nagative_stock": 1,
}
).submit()
entries.append(get_link_to_form("Repost Item Valuation", doc.name))
except frappe.DuplicateEntryError:
pass
if entries:
entries = ", ".join(entries)
frappe.msgprint(_(f"Reposting entries created: {entries}"))

View File

@@ -76,26 +76,14 @@ frappe.ui.form.on('Subcontracting Receipt', {
}
});
let batch_no_field = frm.get_docfield("items", "batch_no");
let batch_no_field = frm.get_docfield('items', 'batch_no');
if (batch_no_field) {
batch_no_field.get_route_options_for_new_doc = function(row) {
return {
"item": row.doc.item_code
'item': row.doc.item_code
}
};
}
frappe.db.get_single_value('Buying Settings', 'backflush_raw_materials_of_subcontract_based_on').then(val => {
if (val == 'Material Transferred for Subcontract') {
frm.fields_dict['supplied_items'].grid.grid_rows.forEach((grid_row) => {
grid_row.docfields.forEach((df) => {
if (df.fieldname == 'consumed_qty') {
df.read_only = 0;
}
});
});
}
});
},
refresh: (frm) => {
@@ -157,6 +145,8 @@ frappe.ui.form.on('Subcontracting Receipt', {
}
});
}, __('Get Items From'));
frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty', 'read_only', frm.doc.__onload && frm.doc.__onload.backflush_based_on === 'BOM');
}
},

View File

@@ -28,6 +28,14 @@ class SubcontractingReceipt(SubcontractingController):
},
]
def onload(self):
self.set_onload(
"backflush_based_on",
frappe.db.get_single_value(
"Buying Settings", "backflush_raw_materials_of_subcontract_based_on"
),
)
def update_status_updater_args(self):
if cint(self.is_return):
self.status_updater.extend(