mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-25 07:54:46 +00:00
Merge pull request #34239 from frappe/version-13-hotfix
chore: release v13
This commit is contained in:
@@ -161,7 +161,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
|
|
||||||
bold_item_name = frappe.bold(item.item_name)
|
bold_item_name = frappe.bold(item.item_name)
|
||||||
bold_extra_batch_qty_needed = frappe.bold(
|
bold_extra_batch_qty_needed = frappe.bold(
|
||||||
abs(available_batch_qty - reserved_batch_qty - item.qty)
|
abs(available_batch_qty - reserved_batch_qty - item.stock_qty)
|
||||||
)
|
)
|
||||||
bold_invalid_batch_no = frappe.bold(item.batch_no)
|
bold_invalid_batch_no = frappe.bold(item.batch_no)
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
).format(item.idx, bold_invalid_batch_no, bold_item_name),
|
).format(item.idx, bold_invalid_batch_no, bold_item_name),
|
||||||
title=_("Item Unavailable"),
|
title=_("Item Unavailable"),
|
||||||
)
|
)
|
||||||
elif (available_batch_qty - reserved_batch_qty - item.qty) < 0:
|
elif (available_batch_qty - reserved_batch_qty - item.stock_qty) < 0:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Row #{}: Batch No. {} of item {} has less than required stock available, {} more required"
|
"Row #{}: Batch No. {} of item {} has less than required stock available, {} more required"
|
||||||
@@ -246,7 +246,7 @@ class POSInvoice(SalesInvoice):
|
|||||||
),
|
),
|
||||||
title=_("Item Unavailable"),
|
title=_("Item Unavailable"),
|
||||||
)
|
)
|
||||||
elif is_stock_item and flt(available_stock) < flt(d.qty):
|
elif is_stock_item and flt(available_stock) < flt(d.stock_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 {}."
|
||||||
@@ -652,7 +652,7 @@ def get_bundle_availability(bundle_item_code, warehouse):
|
|||||||
item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse)
|
item_pos_reserved_qty = get_pos_reserved_qty(item.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.stock_qty
|
||||||
if bundle_bin_qty > max_available_bundles and frappe.get_value(
|
if bundle_bin_qty > max_available_bundles and frappe.get_value(
|
||||||
"Item", item.item_code, "is_stock_item"
|
"Item", item.item_code, "is_stock_item"
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -1078,7 +1078,7 @@ var select_loyalty_program = function(frm, loyalty_programs) {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
dialog.set_primary_action(__("Set"), function() {
|
dialog.set_primary_action(__("Set Loyalty Program"), function() {
|
||||||
dialog.hide();
|
dialog.hide();
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
method: "frappe.client.set_value",
|
method: "frappe.client.set_value",
|
||||||
|
|||||||
@@ -364,6 +364,7 @@ def get_column_names():
|
|||||||
|
|
||||||
class GrossProfitGenerator(object):
|
class GrossProfitGenerator(object):
|
||||||
def __init__(self, filters=None):
|
def __init__(self, filters=None):
|
||||||
|
self.sle = {}
|
||||||
self.data = []
|
self.data = []
|
||||||
self.average_buying_rate = {}
|
self.average_buying_rate = {}
|
||||||
self.filters = frappe._dict(filters)
|
self.filters = frappe._dict(filters)
|
||||||
@@ -373,7 +374,6 @@ class GrossProfitGenerator(object):
|
|||||||
if filters.group_by == "Invoice":
|
if filters.group_by == "Invoice":
|
||||||
self.group_items_by_invoice()
|
self.group_items_by_invoice()
|
||||||
|
|
||||||
self.load_stock_ledger_entries()
|
|
||||||
self.load_product_bundle()
|
self.load_product_bundle()
|
||||||
self.load_non_stock_items()
|
self.load_non_stock_items()
|
||||||
self.get_returned_invoice_items()
|
self.get_returned_invoice_items()
|
||||||
@@ -563,7 +563,7 @@ class GrossProfitGenerator(object):
|
|||||||
return flt(row.qty) * item_rate
|
return flt(row.qty) * item_rate
|
||||||
|
|
||||||
else:
|
else:
|
||||||
my_sle = self.sle.get((item_code, row.warehouse))
|
my_sle = self.get_stock_ledger_entries(item_code, row.warehouse)
|
||||||
if (row.update_stock or row.dn_detail) and my_sle:
|
if (row.update_stock or row.dn_detail) and my_sle:
|
||||||
parenttype, parent = row.parenttype, row.parent
|
parenttype, parent = row.parenttype, row.parent
|
||||||
if row.dn_detail:
|
if row.dn_detail:
|
||||||
@@ -581,7 +581,7 @@ class GrossProfitGenerator(object):
|
|||||||
dn["item_row"],
|
dn["item_row"],
|
||||||
dn["warehouse"],
|
dn["warehouse"],
|
||||||
)
|
)
|
||||||
my_sle = self.sle.get((item_code, warehouse))
|
my_sle = self.get_stock_ledger_entries(item_code, row.warehouse)
|
||||||
return self.calculate_buying_amount_from_sle(
|
return self.calculate_buying_amount_from_sle(
|
||||||
row, my_sle, parenttype, parent, item_row, item_code
|
row, my_sle, parenttype, parent, item_row, item_code
|
||||||
)
|
)
|
||||||
@@ -597,15 +597,12 @@ class GrossProfitGenerator(object):
|
|||||||
def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code):
|
def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code):
|
||||||
from frappe.query_builder.functions import Sum
|
from frappe.query_builder.functions import Sum
|
||||||
|
|
||||||
delivery_note = frappe.qb.DocType("Delivery Note")
|
|
||||||
delivery_note_item = frappe.qb.DocType("Delivery Note Item")
|
delivery_note_item = frappe.qb.DocType("Delivery Note Item")
|
||||||
|
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(delivery_note)
|
frappe.qb.from_(delivery_note_item)
|
||||||
.inner_join(delivery_note_item)
|
|
||||||
.on(delivery_note.name == delivery_note_item.parent)
|
|
||||||
.select(Sum(delivery_note_item.incoming_rate * delivery_note_item.stock_qty))
|
.select(Sum(delivery_note_item.incoming_rate * delivery_note_item.stock_qty))
|
||||||
.where(delivery_note.docstatus == 1)
|
.where(delivery_note_item.docstatus == 1)
|
||||||
.where(delivery_note_item.item_code == item_code)
|
.where(delivery_note_item.item_code == item_code)
|
||||||
.where(delivery_note_item.against_sales_order == sales_order)
|
.where(delivery_note_item.against_sales_order == sales_order)
|
||||||
.where(delivery_note_item.so_detail == so_detail)
|
.where(delivery_note_item.so_detail == so_detail)
|
||||||
@@ -840,24 +837,36 @@ class GrossProfitGenerator(object):
|
|||||||
"Item", item_code, ["item_name", "description", "item_group", "brand"]
|
"Item", item_code, ["item_name", "description", "item_group", "brand"]
|
||||||
)
|
)
|
||||||
|
|
||||||
def load_stock_ledger_entries(self):
|
def get_stock_ledger_entries(self, item_code, warehouse):
|
||||||
res = frappe.db.sql(
|
if item_code and warehouse:
|
||||||
"""select item_code, voucher_type, voucher_no,
|
if (item_code, warehouse) not in self.sle:
|
||||||
voucher_detail_no, stock_value, warehouse, actual_qty as qty
|
sle = qb.DocType("Stock Ledger Entry")
|
||||||
from `tabStock Ledger Entry`
|
res = (
|
||||||
where company=%(company)s and is_cancelled = 0
|
qb.from_(sle)
|
||||||
order by
|
.select(
|
||||||
item_code desc, warehouse desc, posting_date desc,
|
sle.item_code,
|
||||||
posting_time desc, creation desc""",
|
sle.voucher_type,
|
||||||
self.filters,
|
sle.voucher_no,
|
||||||
as_dict=True,
|
sle.voucher_detail_no,
|
||||||
)
|
sle.stock_value,
|
||||||
self.sle = {}
|
sle.warehouse,
|
||||||
for r in res:
|
sle.actual_qty.as_("qty"),
|
||||||
if (r.item_code, r.warehouse) not in self.sle:
|
)
|
||||||
self.sle[(r.item_code, r.warehouse)] = []
|
.where(
|
||||||
|
(sle.company == self.filters.company)
|
||||||
|
& (sle.item_code == item_code)
|
||||||
|
& (sle.warehouse == warehouse)
|
||||||
|
& (sle.is_cancelled == 0)
|
||||||
|
)
|
||||||
|
.orderby(sle.item_code)
|
||||||
|
.orderby(sle.warehouse, sle.posting_date, sle.posting_time, sle.creation, order=Order.desc)
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
|
||||||
self.sle[(r.item_code, r.warehouse)].append(r)
|
self.sle[(item_code, warehouse)] = res
|
||||||
|
|
||||||
|
return self.sle[(item_code, warehouse)]
|
||||||
|
return []
|
||||||
|
|
||||||
def load_product_bundle(self):
|
def load_product_bundle(self):
|
||||||
self.product_bundles = {}
|
self.product_bundles = {}
|
||||||
|
|||||||
@@ -296,10 +296,6 @@ frappe.ui.form.on('Asset', {
|
|||||||
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
|
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
|
||||||
},
|
},
|
||||||
|
|
||||||
opening_accumulated_depreciation: function(frm) {
|
|
||||||
erpnext.asset.set_accumulated_depreciation(frm);
|
|
||||||
},
|
|
||||||
|
|
||||||
make_schedules_editable: function(frm) {
|
make_schedules_editable: function(frm) {
|
||||||
if (frm.doc.finance_books) {
|
if (frm.doc.finance_books) {
|
||||||
var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0
|
var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0
|
||||||
@@ -519,19 +515,23 @@ frappe.ui.form.on('Depreciation Schedule', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
depreciation_amount: function(frm, cdt, cdn) {
|
depreciation_amount: function(frm, cdt, cdn) {
|
||||||
erpnext.asset.set_accumulated_depreciation(frm);
|
erpnext.asset.set_accumulated_depreciation(frm, locals[cdt][cdn].finance_book_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
});
|
||||||
|
|
||||||
erpnext.asset.set_accumulated_depreciation = function(frm) {
|
erpnext.asset.set_accumulated_depreciation = function(frm, finance_book_id) {
|
||||||
if(frm.doc.depreciation_method != "Manual") return;
|
var depreciation_method = frm.doc.finance_books[Number(finance_book_id) - 1].depreciation_method;
|
||||||
|
|
||||||
|
if(depreciation_method != "Manual") return;
|
||||||
|
|
||||||
var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation);
|
var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation);
|
||||||
|
|
||||||
$.each(frm.doc.schedules || [], function(i, row) {
|
$.each(frm.doc.schedules || [], function(i, row) {
|
||||||
accumulated_depreciation += flt(row.depreciation_amount);
|
if (row.finance_book_id === finance_book_id) {
|
||||||
frappe.model.set_value(row.doctype, row.name,
|
accumulated_depreciation += flt(row.depreciation_amount);
|
||||||
"accumulated_depreciation_amount", accumulated_depreciation);
|
frappe.model.set_value(row.doctype, row.name, "accumulated_depreciation_amount", accumulated_depreciation);
|
||||||
|
};
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -84,14 +84,55 @@ class Asset(AccountsController):
|
|||||||
if self.calculate_depreciation:
|
if self.calculate_depreciation:
|
||||||
self.value_after_depreciation = 0
|
self.value_after_depreciation = 0
|
||||||
self.set_depreciation_rate()
|
self.set_depreciation_rate()
|
||||||
self.make_depreciation_schedule(date_of_disposal)
|
if self.should_prepare_depreciation_schedule():
|
||||||
self.set_accumulated_depreciation(date_of_disposal, date_of_return)
|
self.make_depreciation_schedule(date_of_disposal)
|
||||||
|
self.set_accumulated_depreciation(date_of_disposal, date_of_return)
|
||||||
else:
|
else:
|
||||||
self.finance_books = []
|
self.finance_books = []
|
||||||
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
|
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
|
||||||
self.opening_accumulated_depreciation
|
self.opening_accumulated_depreciation
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def should_prepare_depreciation_schedule(self):
|
||||||
|
if not self.get("schedules"):
|
||||||
|
return True
|
||||||
|
|
||||||
|
old_asset_doc = self.get_doc_before_save()
|
||||||
|
|
||||||
|
if not old_asset_doc:
|
||||||
|
return True
|
||||||
|
|
||||||
|
have_asset_details_been_modified = (
|
||||||
|
old_asset_doc.gross_purchase_amount != self.gross_purchase_amount
|
||||||
|
or old_asset_doc.opening_accumulated_depreciation != self.opening_accumulated_depreciation
|
||||||
|
or old_asset_doc.number_of_depreciations_booked != self.number_of_depreciations_booked
|
||||||
|
)
|
||||||
|
|
||||||
|
if have_asset_details_been_modified:
|
||||||
|
return True
|
||||||
|
|
||||||
|
manual_fb_idx = -1
|
||||||
|
for d in self.finance_books:
|
||||||
|
if d.depreciation_method == "Manual":
|
||||||
|
manual_fb_idx = d.idx - 1
|
||||||
|
|
||||||
|
no_manual_depr_or_have_manual_depr_details_been_modified = (
|
||||||
|
manual_fb_idx == -1
|
||||||
|
or old_asset_doc.finance_books[manual_fb_idx].total_number_of_depreciations
|
||||||
|
!= self.finance_books[manual_fb_idx].total_number_of_depreciations
|
||||||
|
or old_asset_doc.finance_books[manual_fb_idx].frequency_of_depreciation
|
||||||
|
!= self.finance_books[manual_fb_idx].frequency_of_depreciation
|
||||||
|
or old_asset_doc.finance_books[manual_fb_idx].depreciation_start_date
|
||||||
|
!= getdate(self.finance_books[manual_fb_idx].depreciation_start_date)
|
||||||
|
or old_asset_doc.finance_books[manual_fb_idx].expected_value_after_useful_life
|
||||||
|
!= self.finance_books[manual_fb_idx].expected_value_after_useful_life
|
||||||
|
)
|
||||||
|
|
||||||
|
if no_manual_depr_or_have_manual_depr_details_been_modified:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def validate_item(self):
|
def validate_item(self):
|
||||||
item = frappe.get_cached_value(
|
item = frappe.get_cached_value(
|
||||||
"Item", self.item_code, ["is_fixed_asset", "is_stock_item", "disabled"], as_dict=1
|
"Item", self.item_code, ["is_fixed_asset", "is_stock_item", "disabled"], as_dict=1
|
||||||
@@ -225,9 +266,7 @@ class Asset(AccountsController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def make_depreciation_schedule(self, date_of_disposal):
|
def make_depreciation_schedule(self, date_of_disposal):
|
||||||
if "Manual" not in [d.depreciation_method for d in self.finance_books] and not self.get(
|
if not self.get("schedules"):
|
||||||
"schedules"
|
|
||||||
):
|
|
||||||
self.schedules = []
|
self.schedules = []
|
||||||
|
|
||||||
if not self.available_for_use_date:
|
if not self.available_for_use_date:
|
||||||
@@ -545,9 +584,7 @@ class Asset(AccountsController):
|
|||||||
def set_accumulated_depreciation(
|
def set_accumulated_depreciation(
|
||||||
self, date_of_disposal=None, date_of_return=None, ignore_booked_entry=False
|
self, date_of_disposal=None, date_of_return=None, ignore_booked_entry=False
|
||||||
):
|
):
|
||||||
straight_line_idx = [
|
straight_line_idx = []
|
||||||
d.idx for d in self.get("schedules") if d.depreciation_method == "Straight Line"
|
|
||||||
]
|
|
||||||
finance_books = []
|
finance_books = []
|
||||||
|
|
||||||
for i, d in enumerate(self.get("schedules")):
|
for i, d in enumerate(self.get("schedules")):
|
||||||
@@ -555,6 +592,12 @@ class Asset(AccountsController):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if int(d.finance_book_id) not in finance_books:
|
if int(d.finance_book_id) not in finance_books:
|
||||||
|
straight_line_idx = [
|
||||||
|
s.idx
|
||||||
|
for s in self.get("schedules")
|
||||||
|
if s.finance_book_id == d.finance_book_id
|
||||||
|
and (s.depreciation_method == "Straight Line" or s.depreciation_method == "Manual")
|
||||||
|
]
|
||||||
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
|
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
|
||||||
value_after_depreciation = flt(
|
value_after_depreciation = flt(
|
||||||
self.get("finance_books")[cint(d.finance_book_id) - 1].value_after_depreciation
|
self.get("finance_books")[cint(d.finance_book_id) - 1].value_after_depreciation
|
||||||
|
|||||||
@@ -817,7 +817,9 @@ def get_leave_balance_on(
|
|||||||
allocation = allocation_records.get(leave_type, frappe._dict())
|
allocation = allocation_records.get(leave_type, frappe._dict())
|
||||||
|
|
||||||
end_date = allocation.to_date if cint(consider_all_leaves_in_the_allocation_period) else date
|
end_date = allocation.to_date if cint(consider_all_leaves_in_the_allocation_period) else date
|
||||||
cf_expiry = get_allocation_expiry_for_cf_leaves(employee, leave_type, to_date, date)
|
cf_expiry = get_allocation_expiry_for_cf_leaves(
|
||||||
|
employee, leave_type, to_date, allocation.from_date
|
||||||
|
)
|
||||||
|
|
||||||
leaves_taken = get_leaves_for_period(employee, leave_type, allocation.from_date, end_date)
|
leaves_taken = get_leaves_for_period(employee, leave_type, allocation.from_date, end_date)
|
||||||
|
|
||||||
@@ -832,6 +834,7 @@ def get_leave_balance_on(
|
|||||||
def get_leave_allocation_records(employee, date, leave_type=None):
|
def get_leave_allocation_records(employee, date, leave_type=None):
|
||||||
"""Returns the total allocated leaves and carry forwarded leaves based on ledger entries"""
|
"""Returns the total allocated leaves and carry forwarded leaves based on ledger entries"""
|
||||||
Ledger = frappe.qb.DocType("Leave Ledger Entry")
|
Ledger = frappe.qb.DocType("Leave Ledger Entry")
|
||||||
|
LeaveAllocation = frappe.qb.DocType("Leave Allocation")
|
||||||
|
|
||||||
cf_leave_case = (
|
cf_leave_case = (
|
||||||
frappe.qb.terms.Case().when(Ledger.is_carry_forward == "1", Ledger.leaves).else_(0)
|
frappe.qb.terms.Case().when(Ledger.is_carry_forward == "1", Ledger.leaves).else_(0)
|
||||||
@@ -845,6 +848,8 @@ def get_leave_allocation_records(employee, date, leave_type=None):
|
|||||||
|
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(Ledger)
|
frappe.qb.from_(Ledger)
|
||||||
|
.inner_join(LeaveAllocation)
|
||||||
|
.on(Ledger.transaction_name == LeaveAllocation.name)
|
||||||
.select(
|
.select(
|
||||||
sum_cf_leaves,
|
sum_cf_leaves,
|
||||||
sum_new_leaves,
|
sum_new_leaves,
|
||||||
@@ -854,12 +859,21 @@ def get_leave_allocation_records(employee, date, leave_type=None):
|
|||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
(Ledger.from_date <= date)
|
(Ledger.from_date <= date)
|
||||||
& (Ledger.to_date >= date)
|
|
||||||
& (Ledger.docstatus == 1)
|
& (Ledger.docstatus == 1)
|
||||||
& (Ledger.transaction_type == "Leave Allocation")
|
& (Ledger.transaction_type == "Leave Allocation")
|
||||||
& (Ledger.employee == employee)
|
& (Ledger.employee == employee)
|
||||||
& (Ledger.is_expired == 0)
|
& (Ledger.is_expired == 0)
|
||||||
& (Ledger.is_lwp == 0)
|
& (Ledger.is_lwp == 0)
|
||||||
|
& (
|
||||||
|
# newly allocated leave's end date is same as the leave allocation's to date
|
||||||
|
((Ledger.is_carry_forward == 0) & (Ledger.to_date >= date))
|
||||||
|
# carry forwarded leave's end date won't be same as the leave allocation's to date
|
||||||
|
# it's between the leave allocation's from and to date
|
||||||
|
| (
|
||||||
|
(Ledger.is_carry_forward == 1)
|
||||||
|
& (Ledger.to_date.between(LeaveAllocation.from_date, LeaveAllocation.to_date))
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -925,8 +939,12 @@ def get_remaining_leaves(
|
|||||||
|
|
||||||
# balance for carry forwarded leaves
|
# balance for carry forwarded leaves
|
||||||
if cf_expiry and allocation.unused_leaves:
|
if cf_expiry and allocation.unused_leaves:
|
||||||
cf_leaves = flt(allocation.unused_leaves) + flt(leaves_taken)
|
if getdate(date) > getdate(cf_expiry):
|
||||||
remaining_cf_leaves = _get_remaining_leaves(cf_leaves, cf_expiry)
|
# carry forwarded leave expiry date passed
|
||||||
|
cf_leaves = remaining_cf_leaves = 0
|
||||||
|
else:
|
||||||
|
cf_leaves = flt(allocation.unused_leaves) + flt(leaves_taken)
|
||||||
|
remaining_cf_leaves = _get_remaining_leaves(cf_leaves, cf_expiry)
|
||||||
|
|
||||||
leave_balance = flt(allocation.new_leaves_allocated) + flt(cf_leaves)
|
leave_balance = flt(allocation.new_leaves_allocated) + flt(cf_leaves)
|
||||||
leave_balance_for_consumption = flt(allocation.new_leaves_allocated) + flt(remaining_cf_leaves)
|
leave_balance_for_consumption = flt(allocation.new_leaves_allocated) + flt(remaining_cf_leaves)
|
||||||
|
|||||||
@@ -698,8 +698,7 @@ class TestLeaveApplication(unittest.TestCase):
|
|||||||
leave_type_name="_Test_CF_leave_expiry",
|
leave_type_name="_Test_CF_leave_expiry",
|
||||||
is_carry_forward=1,
|
is_carry_forward=1,
|
||||||
expire_carry_forwarded_leaves_after_days=90,
|
expire_carry_forwarded_leaves_after_days=90,
|
||||||
)
|
).insert()
|
||||||
leave_type.insert()
|
|
||||||
|
|
||||||
create_carry_forwarded_allocation(employee, leave_type)
|
create_carry_forwarded_allocation(employee, leave_type)
|
||||||
details = get_leave_balance_on(
|
details = get_leave_balance_on(
|
||||||
@@ -992,17 +991,51 @@ class TestLeaveApplication(unittest.TestCase):
|
|||||||
self.assertEqual(leave_allocation, expected)
|
self.assertEqual(leave_allocation, expected)
|
||||||
|
|
||||||
@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
|
@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
|
||||||
def test_get_leave_allocation_records(self):
|
def test_leave_details_with_expired_cf_leaves(self):
|
||||||
employee = get_employee()
|
employee = get_employee()
|
||||||
leave_type = create_leave_type(
|
leave_type = create_leave_type(
|
||||||
leave_type_name="_Test_CF_leave_expiry",
|
leave_type_name="_Test_CF_leave_expiry",
|
||||||
is_carry_forward=1,
|
is_carry_forward=1,
|
||||||
expire_carry_forwarded_leaves_after_days=90,
|
expire_carry_forwarded_leaves_after_days=90,
|
||||||
)
|
).insert()
|
||||||
leave_type.insert()
|
|
||||||
|
|
||||||
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
|
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
|
||||||
details = get_leave_allocation_records(employee.name, getdate(), leave_type.name)
|
cf_expiry = frappe.db.get_value(
|
||||||
|
"Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date"
|
||||||
|
)
|
||||||
|
|
||||||
|
# all leaves available before cf leave expiry
|
||||||
|
leave_details = get_leave_details(employee.name, add_days(cf_expiry, -1))
|
||||||
|
self.assertEqual(leave_details["leave_allocation"][leave_type.name]["remaining_leaves"], 30.0)
|
||||||
|
|
||||||
|
# cf leaves expired
|
||||||
|
leave_details = get_leave_details(employee.name, add_days(cf_expiry, 1))
|
||||||
|
expected_data = {
|
||||||
|
"total_leaves": 30.0,
|
||||||
|
"expired_leaves": 15.0,
|
||||||
|
"leaves_taken": 0.0,
|
||||||
|
"leaves_pending_approval": 0.0,
|
||||||
|
"remaining_leaves": 15.0,
|
||||||
|
}
|
||||||
|
self.assertEqual(leave_details["leave_allocation"][leave_type.name], expected_data)
|
||||||
|
|
||||||
|
@set_holiday_list("Salary Slip Test Holiday List", "_Test Company")
|
||||||
|
def test_get_leave_allocation_records(self):
|
||||||
|
"""Tests if total leaves allocated before and after carry forwarded leave expiry is same"""
|
||||||
|
employee = get_employee()
|
||||||
|
leave_type = create_leave_type(
|
||||||
|
leave_type_name="_Test_CF_leave_expiry",
|
||||||
|
is_carry_forward=1,
|
||||||
|
expire_carry_forwarded_leaves_after_days=90,
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
leave_alloc = create_carry_forwarded_allocation(employee, leave_type)
|
||||||
|
cf_expiry = frappe.db.get_value(
|
||||||
|
"Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date"
|
||||||
|
)
|
||||||
|
|
||||||
|
# test total leaves allocated before cf leave expiry
|
||||||
|
details = get_leave_allocation_records(employee.name, add_days(cf_expiry, -1), leave_type.name)
|
||||||
expected_data = {
|
expected_data = {
|
||||||
"from_date": getdate(leave_alloc.from_date),
|
"from_date": getdate(leave_alloc.from_date),
|
||||||
"to_date": getdate(leave_alloc.to_date),
|
"to_date": getdate(leave_alloc.to_date),
|
||||||
@@ -1013,6 +1046,11 @@ class TestLeaveApplication(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
self.assertEqual(details.get(leave_type.name), expected_data)
|
self.assertEqual(details.get(leave_type.name), expected_data)
|
||||||
|
|
||||||
|
# test leaves allocated after carry forwarded leaves expiry, should be same thoroughout allocation period
|
||||||
|
# cf leaves should show up under expired or taken leaves later
|
||||||
|
details = get_leave_allocation_records(employee.name, add_days(cf_expiry, 1), leave_type.name)
|
||||||
|
self.assertEqual(details.get(leave_type.name), expected_data)
|
||||||
|
|
||||||
|
|
||||||
def create_carry_forwarded_allocation(employee, leave_type):
|
def create_carry_forwarded_allocation(employee, leave_type):
|
||||||
# initial leave allocation
|
# initial leave allocation
|
||||||
|
|||||||
@@ -64,8 +64,6 @@
|
|||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "prevdoc_detail_docname.sales_person",
|
|
||||||
"fetch_if_empty": 1,
|
|
||||||
"fieldname": "service_person",
|
"fieldname": "service_person",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -110,13 +108,15 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-05-27 17:47:21.474282",
|
"modified": "2023-02-27 11:09:33.114458",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Maintenance",
|
"module": "Maintenance",
|
||||||
"name": "Maintenance Visit Purpose",
|
"name": "Maintenance Visit Purpose",
|
||||||
|
"naming_rule": "Random",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,61 @@
|
|||||||
import frappe
|
import frappe
|
||||||
|
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||||
from erpnext.regional.india.setup import make_custom_fields
|
|
||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
if frappe.get_all("Company", filters={"country": "India"}):
|
if frappe.get_all("Company", filters={"country": "India"}):
|
||||||
frappe.reload_doc("accounts", "doctype", "POS Invoice")
|
custom_fields = get_non_profit_custom_fields()
|
||||||
frappe.reload_doc("accounts", "doctype", "POS Invoice Item")
|
create_custom_fields(custom_fields, update=True)
|
||||||
|
|
||||||
make_custom_fields()
|
|
||||||
|
|
||||||
if not frappe.db.exists("Party Type", "Donor"):
|
if not frappe.db.exists("Party Type", "Donor"):
|
||||||
frappe.get_doc(
|
frappe.get_doc(
|
||||||
{"doctype": "Party Type", "party_type": "Donor", "account_type": "Receivable"}
|
{"doctype": "Party Type", "party_type": "Donor", "account_type": "Receivable"}
|
||||||
).insert(ignore_permissions=True)
|
).insert(ignore_permissions=True, ignore_mandatory=True)
|
||||||
|
|
||||||
|
|
||||||
|
def get_non_profit_custom_fields():
|
||||||
|
return {
|
||||||
|
"Company": [
|
||||||
|
{
|
||||||
|
"fieldname": "non_profit_section",
|
||||||
|
"label": "Non Profit Settings",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"insert_after": "asset_received_but_not_billed",
|
||||||
|
"collapsible": 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company_80g_number",
|
||||||
|
"label": "80G Number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"insert_after": "non_profit_section",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "with_effect_from",
|
||||||
|
"label": "80G With Effect From",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"insert_after": "company_80g_number",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "pan_details",
|
||||||
|
"label": "PAN Number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"insert_after": "with_effect_from",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"Member": [
|
||||||
|
{
|
||||||
|
"fieldname": "pan_number",
|
||||||
|
"label": "PAN Details",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"insert_after": "email_id",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"Donor": [
|
||||||
|
{
|
||||||
|
"fieldname": "pan_number",
|
||||||
|
"label": "PAN Details",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"insert_after": "email",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|||||||
@@ -124,8 +124,8 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
|||||||
item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item));
|
item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let qty = item.qty || 1;
|
// allow for '0' qty on Credit/Debit notes
|
||||||
qty = me.frm.doc.is_return ? -1 * qty : qty;
|
let qty = item.qty || -1
|
||||||
item.net_amount = item.amount = flt(item.rate * qty, precision("amount", item));
|
item.net_amount = item.amount = flt(item.rate * qty, precision("amount", item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -280,9 +280,12 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
|||||||
|
|
||||||
make_work_order() {
|
make_work_order() {
|
||||||
var me = this;
|
var me = this;
|
||||||
this.frm.call({
|
me.frm.call({
|
||||||
doc: this.frm.doc,
|
method: "erpnext.selling.doctype.sales_order.sales_order.get_work_order_items",
|
||||||
method: 'get_work_order_items',
|
args: {
|
||||||
|
sales_order: this.frm.docname,
|
||||||
|
},
|
||||||
|
freeze: true,
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(!r.message) {
|
if(!r.message) {
|
||||||
frappe.msgprint({
|
frappe.msgprint({
|
||||||
@@ -292,14 +295,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if(!r.message) {
|
else {
|
||||||
frappe.msgprint({
|
|
||||||
title: __('Work Order not created'),
|
|
||||||
message: __('Work Order already created for all items with BOM'),
|
|
||||||
indicator: 'orange'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
const fields = [{
|
const fields = [{
|
||||||
label: 'Items',
|
label: 'Items',
|
||||||
fieldtype: 'Table',
|
fieldtype: 'Table',
|
||||||
@@ -400,9 +396,9 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
|||||||
make_raw_material_request: function() {
|
make_raw_material_request: function() {
|
||||||
var me = this;
|
var me = this;
|
||||||
this.frm.call({
|
this.frm.call({
|
||||||
doc: this.frm.doc,
|
method: "erpnext.selling.doctype.sales_order.sales_order.get_work_order_items",
|
||||||
method: 'get_work_order_items',
|
|
||||||
args: {
|
args: {
|
||||||
|
sales_order: this.frm.docname,
|
||||||
for_raw_material_request: 1
|
for_raw_material_request: 1
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
@@ -421,6 +417,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
|||||||
},
|
},
|
||||||
|
|
||||||
make_raw_material_request_dialog: function(r) {
|
make_raw_material_request_dialog: function(r) {
|
||||||
|
var me = this;
|
||||||
var fields = [
|
var fields = [
|
||||||
{fieldtype:'Check', fieldname:'include_exploded_items',
|
{fieldtype:'Check', fieldname:'include_exploded_items',
|
||||||
label: __('Include Exploded Items')},
|
label: __('Include Exploded Items')},
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import json
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
import frappe.utils
|
import frappe.utils
|
||||||
from frappe import _
|
from frappe import _, qb
|
||||||
from frappe.contacts.doctype.address.address import get_company_address
|
from frappe.contacts.doctype.address.address import get_company_address
|
||||||
from frappe.desk.notifications import clear_doctype_notifications
|
from frappe.desk.notifications import clear_doctype_notifications
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.model.utils import get_fetch_values
|
from frappe.model.utils import get_fetch_values
|
||||||
|
from frappe.query_builder.functions import Sum
|
||||||
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, nowdate, strip_html
|
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, nowdate, strip_html
|
||||||
from six import string_types
|
from six import string_types
|
||||||
|
|
||||||
@@ -481,51 +482,6 @@ class SalesOrder(SellingController):
|
|||||||
self.indicator_color = "green"
|
self.indicator_color = "green"
|
||||||
self.indicator_title = _("Paid")
|
self.indicator_title = _("Paid")
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def get_work_order_items(self, for_raw_material_request=0):
|
|
||||||
"""Returns items with BOM that already do not have a linked work order"""
|
|
||||||
items = []
|
|
||||||
item_codes = [i.item_code for i in self.items]
|
|
||||||
product_bundle_parents = [
|
|
||||||
pb.new_item_code
|
|
||||||
for pb in frappe.get_all(
|
|
||||||
"Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"]
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
for table in [self.items, self.packed_items]:
|
|
||||||
for i in table:
|
|
||||||
bom = get_default_bom(i.item_code)
|
|
||||||
stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty
|
|
||||||
|
|
||||||
if not for_raw_material_request:
|
|
||||||
total_work_order_qty = flt(
|
|
||||||
frappe.db.sql(
|
|
||||||
"""select sum(qty) from `tabWork Order`
|
|
||||||
where production_item=%s and sales_order=%s and sales_order_item = %s and docstatus<2""",
|
|
||||||
(i.item_code, self.name, i.name),
|
|
||||||
)[0][0]
|
|
||||||
)
|
|
||||||
pending_qty = stock_qty - total_work_order_qty
|
|
||||||
else:
|
|
||||||
pending_qty = stock_qty
|
|
||||||
|
|
||||||
if pending_qty and i.item_code not in product_bundle_parents:
|
|
||||||
items.append(
|
|
||||||
dict(
|
|
||||||
name=i.name,
|
|
||||||
item_code=i.item_code,
|
|
||||||
description=i.description,
|
|
||||||
bom=bom or "",
|
|
||||||
warehouse=i.warehouse,
|
|
||||||
pending_qty=pending_qty,
|
|
||||||
required_qty=pending_qty if for_raw_material_request else 0,
|
|
||||||
sales_order_item=i.name,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
def on_recurring(self, reference_doc, auto_repeat_doc):
|
def on_recurring(self, reference_doc, auto_repeat_doc):
|
||||||
def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date):
|
def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date):
|
||||||
delivery_date = auto_repeat_doc.get_next_schedule_date(schedule_date=ref_doc_delivery_date)
|
delivery_date = auto_repeat_doc.get_next_schedule_date(schedule_date=ref_doc_delivery_date)
|
||||||
@@ -1399,3 +1355,57 @@ def update_produced_qty_in_so_item(sales_order, sales_order_item):
|
|||||||
return
|
return
|
||||||
|
|
||||||
frappe.db.set_value("Sales Order Item", sales_order_item, "produced_qty", total_produced_qty)
|
frappe.db.set_value("Sales Order Item", sales_order_item, "produced_qty", total_produced_qty)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_work_order_items(sales_order, for_raw_material_request=0):
|
||||||
|
"""Returns items with BOM that already do not have a linked work order"""
|
||||||
|
if sales_order:
|
||||||
|
so = frappe.get_doc("Sales Order", sales_order)
|
||||||
|
|
||||||
|
wo = qb.DocType("Work Order")
|
||||||
|
|
||||||
|
items = []
|
||||||
|
item_codes = [i.item_code for i in so.items]
|
||||||
|
product_bundle_parents = [
|
||||||
|
pb.new_item_code
|
||||||
|
for pb in frappe.get_all(
|
||||||
|
"Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
for table in [so.items, so.packed_items]:
|
||||||
|
for i in table:
|
||||||
|
bom = get_default_bom(i.item_code)
|
||||||
|
stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty
|
||||||
|
|
||||||
|
if not for_raw_material_request:
|
||||||
|
total_work_order_qty = flt(
|
||||||
|
qb.from_(wo)
|
||||||
|
.select(Sum(wo.qty))
|
||||||
|
.where(
|
||||||
|
(wo.production_item == i.item_code)
|
||||||
|
& (wo.sales_order == so.name) * (wo.sales_order_item == i.name)
|
||||||
|
& (wo.docstatus.lte(2))
|
||||||
|
)
|
||||||
|
.run()[0][0]
|
||||||
|
)
|
||||||
|
pending_qty = stock_qty - total_work_order_qty
|
||||||
|
else:
|
||||||
|
pending_qty = stock_qty
|
||||||
|
|
||||||
|
if pending_qty and i.item_code not in product_bundle_parents:
|
||||||
|
items.append(
|
||||||
|
dict(
|
||||||
|
name=i.name,
|
||||||
|
item_code=i.item_code,
|
||||||
|
description=i.description,
|
||||||
|
bom=bom or "",
|
||||||
|
warehouse=i.warehouse,
|
||||||
|
pending_qty=pending_qty,
|
||||||
|
required_qty=pending_qty if for_raw_material_request else 0,
|
||||||
|
sales_order_item=i.name,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return items
|
||||||
|
|||||||
@@ -1211,6 +1211,8 @@ class TestSalesOrder(FrappeTestCase):
|
|||||||
self.assertTrue(si.get("payment_schedule"))
|
self.assertTrue(si.get("payment_schedule"))
|
||||||
|
|
||||||
def test_make_work_order(self):
|
def test_make_work_order(self):
|
||||||
|
from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items
|
||||||
|
|
||||||
# Make a new Sales Order
|
# Make a new Sales Order
|
||||||
so = make_sales_order(
|
so = make_sales_order(
|
||||||
**{
|
**{
|
||||||
@@ -1224,7 +1226,7 @@ class TestSalesOrder(FrappeTestCase):
|
|||||||
# Raise Work Orders
|
# Raise Work Orders
|
||||||
po_items = []
|
po_items = []
|
||||||
so_item_name = {}
|
so_item_name = {}
|
||||||
for item in so.get_work_order_items():
|
for item in get_work_order_items(so.name):
|
||||||
po_items.append(
|
po_items.append(
|
||||||
{
|
{
|
||||||
"warehouse": item.get("warehouse"),
|
"warehouse": item.get("warehouse"),
|
||||||
@@ -1415,6 +1417,7 @@ class TestSalesOrder(FrappeTestCase):
|
|||||||
|
|
||||||
from erpnext.controllers.item_variant import create_variant
|
from erpnext.controllers.item_variant import create_variant
|
||||||
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||||
|
from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items
|
||||||
|
|
||||||
make_item( # template item
|
make_item( # template item
|
||||||
"Test-WO-Tshirt",
|
"Test-WO-Tshirt",
|
||||||
@@ -1454,7 +1457,7 @@ class TestSalesOrder(FrappeTestCase):
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
wo_items = so.get_work_order_items()
|
wo_items = get_work_order_items(so.name)
|
||||||
|
|
||||||
self.assertEqual(wo_items[0].get("item_code"), "Test-WO-Tshirt-R")
|
self.assertEqual(wo_items[0].get("item_code"), "Test-WO-Tshirt-R")
|
||||||
self.assertEqual(wo_items[0].get("bom"), red_var_bom.name)
|
self.assertEqual(wo_items[0].get("bom"), red_var_bom.name)
|
||||||
@@ -1464,6 +1467,8 @@ class TestSalesOrder(FrappeTestCase):
|
|||||||
self.assertEqual(wo_items[1].get("bom"), template_bom.name)
|
self.assertEqual(wo_items[1].get("bom"), template_bom.name)
|
||||||
|
|
||||||
def test_request_for_raw_materials(self):
|
def test_request_for_raw_materials(self):
|
||||||
|
from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items
|
||||||
|
|
||||||
item = make_item(
|
item = make_item(
|
||||||
"_Test Finished Item",
|
"_Test Finished Item",
|
||||||
{
|
{
|
||||||
@@ -1496,7 +1501,7 @@ class TestSalesOrder(FrappeTestCase):
|
|||||||
so = make_sales_order(**{"item_list": [{"item_code": item.item_code, "qty": 1, "rate": 1000}]})
|
so = make_sales_order(**{"item_list": [{"item_code": item.item_code, "qty": 1, "rate": 1000}]})
|
||||||
so.submit()
|
so.submit()
|
||||||
mr_dict = frappe._dict()
|
mr_dict = frappe._dict()
|
||||||
items = so.get_work_order_items(1)
|
items = get_work_order_items(so.name, 1)
|
||||||
mr_dict["items"] = items
|
mr_dict["items"] = items
|
||||||
mr_dict["include_exploded_items"] = 0
|
mr_dict["include_exploded_items"] = 0
|
||||||
mr_dict["ignore_existing_ordered_qty"] = 1
|
mr_dict["ignore_existing_ordered_qty"] = 1
|
||||||
|
|||||||
@@ -522,7 +522,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
|
|
||||||
const from_selector = field === 'qty' && value === "+1";
|
const from_selector = field === 'qty' && value === "+1";
|
||||||
if (from_selector)
|
if (from_selector)
|
||||||
value = flt(item_row.qty) + flt(value);
|
value = flt(item_row.stock_qty) + flt(value);
|
||||||
|
|
||||||
if (item_row_exists) {
|
if (item_row_exists) {
|
||||||
if (field === 'qty')
|
if (field === 'qty')
|
||||||
|
|||||||
@@ -418,8 +418,6 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
|
|||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(r.message) {
|
if(r.message) {
|
||||||
frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message);
|
frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message);
|
||||||
} else {
|
|
||||||
frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ frappe.ui.form.on("Item", {
|
|||||||
'Material Request': () => {
|
'Material Request': () => {
|
||||||
open_form(frm, "Material Request", "Material Request Item", "items");
|
open_form(frm, "Material Request", "Material Request Item", "items");
|
||||||
},
|
},
|
||||||
|
'Stock Entry': () => {
|
||||||
|
open_form(frm, "Stock Entry", "Stock Entry Detail", "items");
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
},
|
},
|
||||||
@@ -848,6 +851,9 @@ function open_form(frm, doctype, child_doctype, parentfield) {
|
|||||||
new_child_doc.item_name = frm.doc.item_name;
|
new_child_doc.item_name = frm.doc.item_name;
|
||||||
new_child_doc.uom = frm.doc.stock_uom;
|
new_child_doc.uom = frm.doc.stock_uom;
|
||||||
new_child_doc.description = frm.doc.description;
|
new_child_doc.description = frm.doc.description;
|
||||||
|
if (!new_child_doc.qty) {
|
||||||
|
new_child_doc.qty = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
frappe.run_serially([
|
frappe.run_serially([
|
||||||
() => frappe.ui.form.make_quick_entry(doctype, null, null, new_doc),
|
() => frappe.ui.form.make_quick_entry(doctype, null, null, new_doc),
|
||||||
|
|||||||
@@ -2,7 +2,18 @@
|
|||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
frappe.ui.form.on("Item Price", {
|
frappe.ui.form.on("Item Price", {
|
||||||
onload: function (frm) {
|
setup(frm) {
|
||||||
|
frm.set_query("item_code", function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"disabled": 0,
|
||||||
|
"has_variants": 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onload(frm) {
|
||||||
// Fetch price list details
|
// Fetch price list details
|
||||||
frm.add_fetch("price_list", "buying", "buying");
|
frm.add_fetch("price_list", "buying", "buying");
|
||||||
frm.add_fetch("price_list", "selling", "selling");
|
frm.add_fetch("price_list", "selling", "selling");
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _, bold
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ class ItemPrice(Document):
|
|||||||
self.update_price_list_details()
|
self.update_price_list_details()
|
||||||
self.update_item_details()
|
self.update_item_details()
|
||||||
self.check_duplicates()
|
self.check_duplicates()
|
||||||
|
self.validate_item_template()
|
||||||
|
|
||||||
def validate_item(self):
|
def validate_item(self):
|
||||||
if not frappe.db.exists("Item", self.item_code):
|
if not frappe.db.exists("Item", self.item_code):
|
||||||
@@ -47,6 +48,12 @@ class ItemPrice(Document):
|
|||||||
"Item", self.item_code, ["item_name", "description"]
|
"Item", self.item_code, ["item_name", "description"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_item_template(self):
|
||||||
|
if frappe.get_cached_value("Item", self.item_code, "has_variants"):
|
||||||
|
msg = f"Item Price cannot be created for the template item {bold(self.item_code)}"
|
||||||
|
|
||||||
|
frappe.throw(_(msg))
|
||||||
|
|
||||||
def check_duplicates(self):
|
def check_duplicates(self):
|
||||||
conditions = (
|
conditions = (
|
||||||
"""where item_code = %(item_code)s and price_list = %(price_list)s and name != %(name)s"""
|
"""where item_code = %(item_code)s and price_list = %(price_list)s and name != %(name)s"""
|
||||||
|
|||||||
@@ -16,6 +16,28 @@ class TestItemPrice(FrappeTestCase):
|
|||||||
frappe.db.sql("delete from `tabItem Price`")
|
frappe.db.sql("delete from `tabItem Price`")
|
||||||
make_test_records_for_doctype("Item Price", force=True)
|
make_test_records_for_doctype("Item Price", force=True)
|
||||||
|
|
||||||
|
def test_template_item_price(self):
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
|
||||||
|
item = make_item(
|
||||||
|
"Test Template Item 1",
|
||||||
|
{
|
||||||
|
"has_variants": 1,
|
||||||
|
"variant_based_on": "Manufacturer",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
doc = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Item Price",
|
||||||
|
"price_list": "_Test Price List",
|
||||||
|
"item_code": item.name,
|
||||||
|
"price_list_rate": 100,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, doc.save)
|
||||||
|
|
||||||
def test_duplicate_item(self):
|
def test_duplicate_item(self):
|
||||||
doc = frappe.copy_doc(test_records[0])
|
doc = frappe.copy_doc(test_records[0])
|
||||||
self.assertRaises(ItemPriceDuplicateItem, doc.save)
|
self.assertRaises(ItemPriceDuplicateItem, doc.save)
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ class LandedCostVoucher(Document):
|
|||||||
self.get_items_from_purchase_receipts()
|
self.get_items_from_purchase_receipts()
|
||||||
|
|
||||||
self.set_applicable_charges_on_item()
|
self.set_applicable_charges_on_item()
|
||||||
self.validate_applicable_charges_for_item()
|
|
||||||
|
|
||||||
def check_mandatory(self):
|
def check_mandatory(self):
|
||||||
if not self.get("purchase_receipts"):
|
if not self.get("purchase_receipts"):
|
||||||
@@ -115,6 +114,13 @@ class LandedCostVoucher(Document):
|
|||||||
total_item_cost += item.get(based_on_field)
|
total_item_cost += item.get(based_on_field)
|
||||||
|
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
|
if not total_item_cost and not item.get(based_on_field):
|
||||||
|
frappe.throw(
|
||||||
|
_(
|
||||||
|
"It's not possible to distribute charges equally when total amount is zero, please set 'Distribute Charges Based On' as 'Quantity'"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
item.applicable_charges = flt(
|
item.applicable_charges = flt(
|
||||||
flt(item.get(based_on_field)) * (flt(self.total_taxes_and_charges) / flt(total_item_cost)),
|
flt(item.get(based_on_field)) * (flt(self.total_taxes_and_charges) / flt(total_item_cost)),
|
||||||
item.precision("applicable_charges"),
|
item.precision("applicable_charges"),
|
||||||
@@ -162,6 +168,7 @@ class LandedCostVoucher(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
|
self.validate_applicable_charges_for_item()
|
||||||
self.update_landed_cost()
|
self.update_landed_cost()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
|
|||||||
@@ -175,6 +175,59 @@ class TestLandedCostVoucher(FrappeTestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 50.0)
|
self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 50.0)
|
||||||
|
|
||||||
|
def test_landed_cost_voucher_for_zero_purchase_rate(self):
|
||||||
|
"Test impact of LCV on future stock balances."
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
|
||||||
|
item = make_item("LCV Stock Item", {"is_stock_item": 1})
|
||||||
|
warehouse = "Stores - _TC"
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
item_code=item.name,
|
||||||
|
warehouse=warehouse,
|
||||||
|
qty=10,
|
||||||
|
rate=0,
|
||||||
|
posting_date=add_days(frappe.utils.nowdate(), -2),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0},
|
||||||
|
"stock_value_difference",
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
|
lcv = make_landed_cost_voucher(
|
||||||
|
company=pr.company,
|
||||||
|
receipt_document_type="Purchase Receipt",
|
||||||
|
receipt_document=pr.name,
|
||||||
|
charges=100,
|
||||||
|
distribute_charges_based_on="Distribute Manually",
|
||||||
|
do_not_save=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
lcv.get_items_from_purchase_receipts()
|
||||||
|
lcv.items[0].applicable_charges = 100
|
||||||
|
lcv.save()
|
||||||
|
lcv.submit()
|
||||||
|
|
||||||
|
self.assertTrue(
|
||||||
|
frappe.db.exists(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0},
|
||||||
|
"stock_value_difference",
|
||||||
|
),
|
||||||
|
100,
|
||||||
|
)
|
||||||
|
|
||||||
def test_landed_cost_voucher_against_purchase_invoice(self):
|
def test_landed_cost_voucher_against_purchase_invoice(self):
|
||||||
|
|
||||||
pi = make_purchase_invoice(
|
pi = make_purchase_invoice(
|
||||||
@@ -516,7 +569,7 @@ def make_landed_cost_voucher(**args):
|
|||||||
|
|
||||||
lcv = frappe.new_doc("Landed Cost Voucher")
|
lcv = frappe.new_doc("Landed Cost Voucher")
|
||||||
lcv.company = args.company or "_Test Company"
|
lcv.company = args.company or "_Test Company"
|
||||||
lcv.distribute_charges_based_on = "Amount"
|
lcv.distribute_charges_based_on = args.distribute_charges_based_on or "Amount"
|
||||||
|
|
||||||
lcv.set(
|
lcv.set(
|
||||||
"purchase_receipts",
|
"purchase_receipts",
|
||||||
|
|||||||
@@ -594,6 +594,9 @@ def make_stock_entry(source_name, target_doc=None):
|
|||||||
|
|
||||||
def set_missing_values(source, target):
|
def set_missing_values(source, target):
|
||||||
target.purpose = source.material_request_type
|
target.purpose = source.material_request_type
|
||||||
|
target.from_warehouse = source.set_from_warehouse
|
||||||
|
target.to_warehouse = source.set_warehouse
|
||||||
|
|
||||||
if source.job_card:
|
if source.job_card:
|
||||||
target.purpose = "Material Transfer for Manufacture"
|
target.purpose = "Material Transfer for Manufacture"
|
||||||
|
|
||||||
|
|||||||
@@ -1064,13 +1064,25 @@ def get_item_account_wise_additional_cost(purchase_document):
|
|||||||
account.expense_account, {"amount": 0.0, "base_amount": 0.0}
|
account.expense_account, {"amount": 0.0, "base_amount": 0.0}
|
||||||
)
|
)
|
||||||
|
|
||||||
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account][
|
if total_item_cost > 0:
|
||||||
"amount"
|
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
|
||||||
] += (account.amount * item.get(based_on_field) / total_item_cost)
|
account.expense_account
|
||||||
|
]["amount"] += (
|
||||||
|
account.amount * item.get(based_on_field) / total_item_cost
|
||||||
|
)
|
||||||
|
|
||||||
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account][
|
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
|
||||||
"base_amount"
|
account.expense_account
|
||||||
] += (account.base_amount * item.get(based_on_field) / total_item_cost)
|
]["base_amount"] += (
|
||||||
|
account.base_amount * item.get(based_on_field) / total_item_cost
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
|
||||||
|
account.expense_account
|
||||||
|
]["amount"] += item.applicable_charges
|
||||||
|
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
|
||||||
|
account.expense_account
|
||||||
|
]["base_amount"] += item.applicable_charges
|
||||||
|
|
||||||
return item_account_wise_cost
|
return item_account_wise_cost
|
||||||
|
|
||||||
|
|||||||
@@ -4047,7 +4047,7 @@ Server Error,Serverfehler,
|
|||||||
Service Level Agreement has been changed to {0}.,Service Level Agreement wurde in {0} geändert.,
|
Service Level Agreement has been changed to {0}.,Service Level Agreement wurde in {0} geändert.,
|
||||||
Service Level Agreement was reset.,Service Level Agreement wurde zurückgesetzt.,
|
Service Level Agreement was reset.,Service Level Agreement wurde zurückgesetzt.,
|
||||||
Service Level Agreement with Entity Type {0} and Entity {1} already exists.,Service Level Agreement mit Entitätstyp {0} und Entität {1} ist bereits vorhanden.,
|
Service Level Agreement with Entity Type {0} and Entity {1} already exists.,Service Level Agreement mit Entitätstyp {0} und Entität {1} ist bereits vorhanden.,
|
||||||
Set,Menge,
|
Set Loyalty Program,Treueprogramm eintragen,
|
||||||
Set Meta Tags,Festlegen von Meta-Tags,
|
Set Meta Tags,Festlegen von Meta-Tags,
|
||||||
Set {0} in company {1},{0} in Firma {1} festlegen,
|
Set {0} in company {1},{0} in Firma {1} festlegen,
|
||||||
Setup,Einstellungen,
|
Setup,Einstellungen,
|
||||||
@@ -4227,10 +4227,8 @@ To date cannot be before From date,Bis-Datum kann nicht vor Von-Datum liegen,
|
|||||||
Write Off,Abschreiben,
|
Write Off,Abschreiben,
|
||||||
{0} Created,{0} Erstellt,
|
{0} Created,{0} Erstellt,
|
||||||
Email Id,E-Mail-ID,
|
Email Id,E-Mail-ID,
|
||||||
No,Kein,
|
|
||||||
Reference Doctype,Referenz-DocType,
|
Reference Doctype,Referenz-DocType,
|
||||||
User Id,Benutzeridentifikation,
|
User Id,Benutzeridentifikation,
|
||||||
Yes,Ja,
|
|
||||||
Actual ,Tatsächlich,
|
Actual ,Tatsächlich,
|
||||||
Add to cart,In den Warenkorb legen,
|
Add to cart,In den Warenkorb legen,
|
||||||
Budget,Budget,
|
Budget,Budget,
|
||||||
|
|||||||
|
Can't render this file because it is too large.
|
Reference in New Issue
Block a user