mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-19 13:09:17 +00:00
style: bulk format code with black
v13 port because otherwise backports will result in conflicts always
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -20,12 +20,11 @@ from erpnext.stock.utils import get_incoming_rate
|
||||
class QtyMismatchError(ValidationError):
|
||||
pass
|
||||
|
||||
class BuyingController(StockController, Subcontracting):
|
||||
|
||||
class BuyingController(StockController, Subcontracting):
|
||||
def get_feed(self):
|
||||
if self.get("supplier_name"):
|
||||
return _("From {0} | {1} {2}").format(self.supplier_name, self.currency,
|
||||
self.grand_total)
|
||||
return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total)
|
||||
|
||||
def validate(self):
|
||||
super(BuyingController, self).validate()
|
||||
@@ -40,16 +39,18 @@ class BuyingController(StockController, Subcontracting):
|
||||
self.set_supplier_address()
|
||||
self.validate_asset_return()
|
||||
|
||||
if self.doctype=="Purchase Invoice":
|
||||
if self.doctype == "Purchase Invoice":
|
||||
self.validate_purchase_receipt_if_update_stock()
|
||||
|
||||
if self.doctype=="Purchase Receipt" or (self.doctype=="Purchase Invoice" and self.update_stock):
|
||||
if self.doctype == "Purchase Receipt" or (
|
||||
self.doctype == "Purchase Invoice" and self.update_stock
|
||||
):
|
||||
# self.validate_purchase_return()
|
||||
self.validate_rejected_warehouse()
|
||||
self.validate_accepted_rejected_qty()
|
||||
validate_for_items(self)
|
||||
|
||||
#sub-contracting
|
||||
# sub-contracting
|
||||
self.validate_for_subcontracting()
|
||||
self.create_raw_materials_supplied("supplied_items")
|
||||
self.set_landed_cost_voucher_amount()
|
||||
@@ -59,8 +60,12 @@ class BuyingController(StockController, Subcontracting):
|
||||
|
||||
def onload(self):
|
||||
super(BuyingController, self).onload()
|
||||
self.set_onload("backflush_based_on", frappe.db.get_single_value('Buying Settings',
|
||||
'backflush_raw_materials_of_subcontract_based_on'))
|
||||
self.set_onload(
|
||||
"backflush_based_on",
|
||||
frappe.db.get_single_value(
|
||||
"Buying Settings", "backflush_raw_materials_of_subcontract_based_on"
|
||||
),
|
||||
)
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
super(BuyingController, self).set_missing_values(for_validate)
|
||||
@@ -77,9 +82,9 @@ class BuyingController(StockController, Subcontracting):
|
||||
doctype=self.doctype,
|
||||
company=self.company,
|
||||
party_address=self.get("supplier_address"),
|
||||
shipping_address=self.get('shipping_address'),
|
||||
fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template'),
|
||||
ignore_permissions=self.flags.ignore_permissions
|
||||
shipping_address=self.get("shipping_address"),
|
||||
fetch_payment_terms_template=not self.get("ignore_default_payment_terms_template"),
|
||||
ignore_permissions=self.flags.ignore_permissions,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -88,14 +93,16 @@ class BuyingController(StockController, Subcontracting):
|
||||
def set_supplier_from_item_default(self):
|
||||
if self.meta.get_field("supplier") and not self.supplier:
|
||||
for d in self.get("items"):
|
||||
supplier = frappe.db.get_value("Item Default",
|
||||
{"parent": d.item_code, "company": self.company}, "default_supplier")
|
||||
supplier = frappe.db.get_value(
|
||||
"Item Default", {"parent": d.item_code, "company": self.company}, "default_supplier"
|
||||
)
|
||||
if supplier:
|
||||
self.supplier = supplier
|
||||
else:
|
||||
item_group = frappe.db.get_value("Item", d.item_code, "item_group")
|
||||
supplier = frappe.db.get_value("Item Default",
|
||||
{"parent": item_group, "company": self.company}, "default_supplier")
|
||||
supplier = frappe.db.get_value(
|
||||
"Item Default", {"parent": item_group, "company": self.company}, "default_supplier"
|
||||
)
|
||||
if supplier:
|
||||
self.supplier = supplier
|
||||
break
|
||||
@@ -106,55 +113,71 @@ class BuyingController(StockController, Subcontracting):
|
||||
self.update_tax_category(msg)
|
||||
|
||||
def update_tax_category(self, msg):
|
||||
tax_for_valuation = [d for d in self.get("taxes")
|
||||
if d.category in ["Valuation", "Valuation and Total"]]
|
||||
tax_for_valuation = [
|
||||
d for d in self.get("taxes") if d.category in ["Valuation", "Valuation and Total"]
|
||||
]
|
||||
|
||||
if tax_for_valuation:
|
||||
for d in tax_for_valuation:
|
||||
d.category = 'Total'
|
||||
d.category = "Total"
|
||||
|
||||
msgprint(msg)
|
||||
|
||||
def validate_asset_return(self):
|
||||
if self.doctype not in ['Purchase Receipt', 'Purchase Invoice'] or not self.is_return:
|
||||
if self.doctype not in ["Purchase Receipt", "Purchase Invoice"] or not self.is_return:
|
||||
return
|
||||
|
||||
purchase_doc_field = 'purchase_receipt' if self.doctype == 'Purchase Receipt' else 'purchase_invoice'
|
||||
not_cancelled_asset = [d.name for d in frappe.db.get_all("Asset", {
|
||||
purchase_doc_field: self.return_against,
|
||||
"docstatus": 1
|
||||
})]
|
||||
purchase_doc_field = (
|
||||
"purchase_receipt" if self.doctype == "Purchase Receipt" else "purchase_invoice"
|
||||
)
|
||||
not_cancelled_asset = [
|
||||
d.name
|
||||
for d in frappe.db.get_all("Asset", {purchase_doc_field: self.return_against, "docstatus": 1})
|
||||
]
|
||||
if self.is_return and len(not_cancelled_asset):
|
||||
frappe.throw(_("{} has submitted assets linked to it. You need to cancel the assets to create purchase return.")
|
||||
.format(self.return_against), title=_("Not Allowed"))
|
||||
frappe.throw(
|
||||
_(
|
||||
"{} has submitted assets linked to it. You need to cancel the assets to create purchase return."
|
||||
).format(self.return_against),
|
||||
title=_("Not Allowed"),
|
||||
)
|
||||
|
||||
def get_asset_items(self):
|
||||
if self.doctype not in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
|
||||
if self.doctype not in ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]:
|
||||
return []
|
||||
|
||||
return [d.item_code for d in self.items if d.is_fixed_asset]
|
||||
|
||||
def set_landed_cost_voucher_amount(self):
|
||||
for d in self.get("items"):
|
||||
lc_voucher_data = frappe.db.sql("""select sum(applicable_charges), cost_center
|
||||
lc_voucher_data = frappe.db.sql(
|
||||
"""select sum(applicable_charges), cost_center
|
||||
from `tabLanded Cost Item`
|
||||
where docstatus = 1 and purchase_receipt_item = %s""", d.name)
|
||||
where docstatus = 1 and purchase_receipt_item = %s""",
|
||||
d.name,
|
||||
)
|
||||
d.landed_cost_voucher_amount = lc_voucher_data[0][0] if lc_voucher_data else 0.0
|
||||
if not d.cost_center and lc_voucher_data and lc_voucher_data[0][1]:
|
||||
d.db_set('cost_center', lc_voucher_data[0][1])
|
||||
d.db_set("cost_center", lc_voucher_data[0][1])
|
||||
|
||||
def validate_from_warehouse(self):
|
||||
for item in self.get('items'):
|
||||
if item.get('from_warehouse') and (item.get('from_warehouse') == item.get('warehouse')):
|
||||
frappe.throw(_("Row #{0}: Accepted Warehouse and Supplier Warehouse cannot be same").format(item.idx))
|
||||
for item in self.get("items"):
|
||||
if item.get("from_warehouse") and (item.get("from_warehouse") == item.get("warehouse")):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Accepted Warehouse and Supplier Warehouse cannot be same").format(item.idx)
|
||||
)
|
||||
|
||||
if item.get('from_warehouse') and self.get('is_subcontracted') == 'Yes':
|
||||
frappe.throw(_("Row #{0}: Cannot select Supplier Warehouse while suppling raw materials to subcontractor").format(item.idx))
|
||||
if item.get("from_warehouse") and self.get("is_subcontracted") == "Yes":
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: Cannot select Supplier Warehouse while suppling raw materials to subcontractor"
|
||||
).format(item.idx)
|
||||
)
|
||||
|
||||
def set_supplier_address(self):
|
||||
address_dict = {
|
||||
'supplier_address': 'address_display',
|
||||
'shipping_address': 'shipping_address_display'
|
||||
"supplier_address": "address_display",
|
||||
"shipping_address": "shipping_address_display",
|
||||
}
|
||||
|
||||
for address_field, address_display_field in address_dict.items():
|
||||
@@ -163,6 +186,7 @@ class BuyingController(StockController, Subcontracting):
|
||||
|
||||
def set_total_in_words(self):
|
||||
from frappe.utils import money_in_words
|
||||
|
||||
if self.meta.get_field("base_in_words"):
|
||||
if self.meta.get_field("base_rounded_total") and not self.is_rounded_total_disabled():
|
||||
amount = self.base_rounded_total
|
||||
@@ -181,10 +205,10 @@ class BuyingController(StockController, Subcontracting):
|
||||
# update valuation rate
|
||||
def update_valuation_rate(self, reset_outgoing_rate=True):
|
||||
"""
|
||||
item_tax_amount is the total tax amount applied on that item
|
||||
stored for valuation
|
||||
item_tax_amount is the total tax amount applied on that item
|
||||
stored for valuation
|
||||
|
||||
TODO: rename item_tax_amount to valuation_tax_amount
|
||||
TODO: rename item_tax_amount to valuation_tax_amount
|
||||
"""
|
||||
stock_and_asset_items = []
|
||||
stock_and_asset_items = self.get_stock_items() + self.get_asset_items()
|
||||
@@ -192,36 +216,50 @@ class BuyingController(StockController, Subcontracting):
|
||||
stock_and_asset_items_qty, stock_and_asset_items_amount = 0, 0
|
||||
last_item_idx = 1
|
||||
for d in self.get("items"):
|
||||
if (d.item_code and d.item_code in stock_and_asset_items):
|
||||
if d.item_code and d.item_code in stock_and_asset_items:
|
||||
stock_and_asset_items_qty += flt(d.qty)
|
||||
stock_and_asset_items_amount += flt(d.base_net_amount)
|
||||
last_item_idx = d.idx
|
||||
|
||||
total_valuation_amount = sum(flt(d.base_tax_amount_after_discount_amount) for d in self.get("taxes")
|
||||
if d.category in ["Valuation", "Valuation and Total"])
|
||||
total_valuation_amount = sum(
|
||||
flt(d.base_tax_amount_after_discount_amount)
|
||||
for d in self.get("taxes")
|
||||
if d.category in ["Valuation", "Valuation and Total"]
|
||||
)
|
||||
|
||||
valuation_amount_adjustment = total_valuation_amount
|
||||
for i, item in enumerate(self.get("items")):
|
||||
if item.item_code and item.qty and item.item_code in stock_and_asset_items:
|
||||
item_proportion = flt(item.base_net_amount) / stock_and_asset_items_amount if stock_and_asset_items_amount \
|
||||
item_proportion = (
|
||||
flt(item.base_net_amount) / stock_and_asset_items_amount
|
||||
if stock_and_asset_items_amount
|
||||
else flt(item.qty) / stock_and_asset_items_qty
|
||||
)
|
||||
|
||||
if i == (last_item_idx - 1):
|
||||
item.item_tax_amount = flt(valuation_amount_adjustment,
|
||||
self.precision("item_tax_amount", item))
|
||||
item.item_tax_amount = flt(
|
||||
valuation_amount_adjustment, self.precision("item_tax_amount", item)
|
||||
)
|
||||
else:
|
||||
item.item_tax_amount = flt(item_proportion * total_valuation_amount,
|
||||
self.precision("item_tax_amount", item))
|
||||
item.item_tax_amount = flt(
|
||||
item_proportion * total_valuation_amount, self.precision("item_tax_amount", item)
|
||||
)
|
||||
valuation_amount_adjustment -= item.item_tax_amount
|
||||
|
||||
self.round_floats_in(item)
|
||||
if flt(item.conversion_factor)==0.0:
|
||||
item.conversion_factor = get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
|
||||
if flt(item.conversion_factor) == 0.0:
|
||||
item.conversion_factor = (
|
||||
get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
|
||||
)
|
||||
|
||||
qty_in_stock_uom = flt(item.qty * item.conversion_factor)
|
||||
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
|
||||
item.valuation_rate = ((item.base_net_amount + item.item_tax_amount + item.rm_supp_cost
|
||||
+ flt(item.landed_cost_voucher_amount)) / qty_in_stock_uom)
|
||||
item.valuation_rate = (
|
||||
item.base_net_amount
|
||||
+ item.item_tax_amount
|
||||
+ item.rm_supp_cost
|
||||
+ flt(item.landed_cost_voucher_amount)
|
||||
) / qty_in_stock_uom
|
||||
else:
|
||||
item.valuation_rate = 0.0
|
||||
|
||||
@@ -242,44 +280,53 @@ class BuyingController(StockController, Subcontracting):
|
||||
# Get outgoing rate based on original item cost based on valuation method
|
||||
|
||||
if not d.get(frappe.scrub(ref_doctype)):
|
||||
outgoing_rate = get_incoming_rate({
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.get('from_warehouse'),
|
||||
"posting_date": self.get('posting_date') or self.get('transation_date'),
|
||||
"posting_time": self.get('posting_time'),
|
||||
"qty": -1 * flt(d.get('stock_qty')),
|
||||
"serial_no": d.get('serial_no'),
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"allow_zero_valuation": d.get("allow_zero_valuation")
|
||||
}, raise_error_if_no_rate=False)
|
||||
outgoing_rate = get_incoming_rate(
|
||||
{
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.get("from_warehouse"),
|
||||
"posting_date": self.get("posting_date") or self.get("transation_date"),
|
||||
"posting_time": self.get("posting_time"),
|
||||
"qty": -1 * flt(d.get("stock_qty")),
|
||||
"serial_no": d.get("serial_no"),
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"allow_zero_valuation": d.get("allow_zero_valuation"),
|
||||
},
|
||||
raise_error_if_no_rate=False,
|
||||
)
|
||||
|
||||
rate = flt(outgoing_rate * d.conversion_factor, d.precision('rate'))
|
||||
rate = flt(outgoing_rate * d.conversion_factor, d.precision("rate"))
|
||||
else:
|
||||
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), 'rate')
|
||||
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), "rate")
|
||||
|
||||
if self.is_internal_transfer():
|
||||
if rate != d.rate:
|
||||
d.rate = rate
|
||||
d.discount_percentage = 0
|
||||
d.discount_amount = 0
|
||||
frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer")
|
||||
.format(d.idx), alert=1)
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
|
||||
).format(d.idx),
|
||||
alert=1,
|
||||
)
|
||||
|
||||
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
|
||||
supplied_items_cost = 0.0
|
||||
for d in self.get("supplied_items"):
|
||||
if d.reference_name == item_row_id:
|
||||
if reset_outgoing_rate and frappe.get_cached_value('Item', d.rm_item_code, 'is_stock_item'):
|
||||
rate = get_incoming_rate({
|
||||
"item_code": d.rm_item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"qty": -1 * d.consumed_qty,
|
||||
"serial_no": d.serial_no
|
||||
})
|
||||
if reset_outgoing_rate and frappe.get_cached_value("Item", d.rm_item_code, "is_stock_item"):
|
||||
rate = get_incoming_rate(
|
||||
{
|
||||
"item_code": d.rm_item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"qty": -1 * d.consumed_qty,
|
||||
"serial_no": d.serial_no,
|
||||
}
|
||||
)
|
||||
|
||||
if rate > 0:
|
||||
d.rate = rate
|
||||
@@ -314,7 +361,7 @@ class BuyingController(StockController, Subcontracting):
|
||||
item.bom = None
|
||||
|
||||
def create_raw_materials_supplied(self, raw_material_table):
|
||||
if self.is_subcontracted=="Yes":
|
||||
if self.is_subcontracted == "Yes":
|
||||
self.set_materials_for_subcontracted_items(raw_material_table)
|
||||
|
||||
elif self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
@@ -322,19 +369,17 @@ class BuyingController(StockController, Subcontracting):
|
||||
item.rm_supp_cost = 0.0
|
||||
|
||||
if self.is_subcontracted == "No" and self.get("supplied_items"):
|
||||
self.set('supplied_items', [])
|
||||
self.set("supplied_items", [])
|
||||
|
||||
@property
|
||||
def sub_contracted_items(self):
|
||||
if not hasattr(self, "_sub_contracted_items"):
|
||||
self._sub_contracted_items = []
|
||||
item_codes = list(set(item.item_code for item in
|
||||
self.get("items")))
|
||||
item_codes = list(set(item.item_code for item in self.get("items")))
|
||||
if item_codes:
|
||||
items = frappe.get_all('Item', filters={
|
||||
'name': ['in', item_codes],
|
||||
'is_sub_contracted_item': 1
|
||||
})
|
||||
items = frappe.get_all(
|
||||
"Item", filters={"name": ["in", item_codes], "is_sub_contracted_item": 1}
|
||||
)
|
||||
self._sub_contracted_items = [item.name for item in items]
|
||||
|
||||
return self._sub_contracted_items
|
||||
@@ -348,9 +393,11 @@ class BuyingController(StockController, Subcontracting):
|
||||
frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx))
|
||||
d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
|
||||
|
||||
if self.doctype=="Purchase Receipt" and d.meta.get_field("received_stock_qty"):
|
||||
if self.doctype == "Purchase Receipt" and d.meta.get_field("received_stock_qty"):
|
||||
# Set Received Qty in Stock UOM
|
||||
d.received_stock_qty = flt(d.received_qty) * flt(d.conversion_factor, d.precision("conversion_factor"))
|
||||
d.received_stock_qty = flt(d.received_qty) * flt(
|
||||
d.conversion_factor, d.precision("conversion_factor")
|
||||
)
|
||||
|
||||
def validate_purchase_return(self):
|
||||
for d in self.get("items"):
|
||||
@@ -366,20 +413,26 @@ class BuyingController(StockController, Subcontracting):
|
||||
d.rejected_warehouse = self.rejected_warehouse
|
||||
|
||||
if not d.rejected_warehouse:
|
||||
frappe.throw(_("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format(d.idx, d.item_code))
|
||||
frappe.throw(
|
||||
_("Row #{0}: Rejected Warehouse is mandatory against rejected Item {1}").format(
|
||||
d.idx, d.item_code
|
||||
)
|
||||
)
|
||||
|
||||
# validate accepted and rejected qty
|
||||
def validate_accepted_rejected_qty(self):
|
||||
for d in self.get("items"):
|
||||
self.validate_negative_quantity(d, ["received_qty","qty", "rejected_qty"])
|
||||
self.validate_negative_quantity(d, ["received_qty", "qty", "rejected_qty"])
|
||||
|
||||
if not flt(d.received_qty) and (flt(d.qty) or flt(d.rejected_qty)):
|
||||
d.received_qty = flt(d.qty) + flt(d.rejected_qty)
|
||||
|
||||
# Check Received Qty = Accepted Qty + Rejected Qty
|
||||
val = flt(d.qty) + flt(d.rejected_qty)
|
||||
if (flt(val, d.precision("received_qty")) != flt(d.received_qty, d.precision("received_qty"))):
|
||||
message = _("Row #{0}: Received Qty must be equal to Accepted + Rejected Qty for Item {1}").format(d.idx, d.item_code)
|
||||
if flt(val, d.precision("received_qty")) != flt(d.received_qty, d.precision("received_qty")):
|
||||
message = _(
|
||||
"Row #{0}: Received Qty must be equal to Accepted + Rejected Qty for Item {1}"
|
||||
).format(d.idx, d.item_code)
|
||||
frappe.throw(msg=message, title=_("Mismatch"), exc=QtyMismatchError)
|
||||
|
||||
def validate_negative_quantity(self, item_row, field_list):
|
||||
@@ -389,15 +442,20 @@ class BuyingController(StockController, Subcontracting):
|
||||
item_row = item_row.as_dict()
|
||||
for fieldname in field_list:
|
||||
if flt(item_row[fieldname]) < 0:
|
||||
frappe.throw(_("Row #{0}: {1} can not be negative for item {2}").format(item_row['idx'],
|
||||
frappe.get_meta(item_row.doctype).get_label(fieldname), item_row['item_code']))
|
||||
frappe.throw(
|
||||
_("Row #{0}: {1} can not be negative for item {2}").format(
|
||||
item_row["idx"],
|
||||
frappe.get_meta(item_row.doctype).get_label(fieldname),
|
||||
item_row["item_code"],
|
||||
)
|
||||
)
|
||||
|
||||
def check_for_on_hold_or_closed_status(self, ref_doctype, ref_fieldname):
|
||||
for d in self.get("items"):
|
||||
if d.get(ref_fieldname):
|
||||
status = frappe.db.get_value(ref_doctype, d.get(ref_fieldname), "status")
|
||||
if status in ("Closed", "On Hold"):
|
||||
frappe.throw(_("{0} {1} is {2}").format(ref_doctype,d.get(ref_fieldname), status))
|
||||
frappe.throw(_("{0} {1} is {2}").format(ref_doctype, d.get(ref_fieldname), status))
|
||||
|
||||
def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False):
|
||||
self.update_ordered_and_reserved_qty()
|
||||
@@ -405,76 +463,88 @@ class BuyingController(StockController, Subcontracting):
|
||||
sl_entries = []
|
||||
stock_items = self.get_stock_items()
|
||||
|
||||
for d in self.get('items'):
|
||||
for d in self.get("items"):
|
||||
if d.item_code in stock_items and d.warehouse:
|
||||
pr_qty = flt(d.qty) * flt(d.conversion_factor)
|
||||
|
||||
if pr_qty:
|
||||
|
||||
if d.from_warehouse and ((not cint(self.is_return) and self.docstatus==1)
|
||||
or (cint(self.is_return) and self.docstatus==2)):
|
||||
from_warehouse_sle = self.get_sl_entries(d, {
|
||||
"actual_qty": -1 * pr_qty,
|
||||
"warehouse": d.from_warehouse,
|
||||
"outgoing_rate": d.rate,
|
||||
"recalculate_rate": 1,
|
||||
"dependant_sle_voucher_detail_no": d.name
|
||||
})
|
||||
if d.from_warehouse and (
|
||||
(not cint(self.is_return) and self.docstatus == 1)
|
||||
or (cint(self.is_return) and self.docstatus == 2)
|
||||
):
|
||||
from_warehouse_sle = self.get_sl_entries(
|
||||
d,
|
||||
{
|
||||
"actual_qty": -1 * pr_qty,
|
||||
"warehouse": d.from_warehouse,
|
||||
"outgoing_rate": d.rate,
|
||||
"recalculate_rate": 1,
|
||||
"dependant_sle_voucher_detail_no": d.name,
|
||||
},
|
||||
)
|
||||
|
||||
sl_entries.append(from_warehouse_sle)
|
||||
|
||||
sle = self.get_sl_entries(d, {
|
||||
"actual_qty": flt(pr_qty),
|
||||
"serial_no": cstr(d.serial_no).strip()
|
||||
})
|
||||
sle = self.get_sl_entries(
|
||||
d, {"actual_qty": flt(pr_qty), "serial_no": cstr(d.serial_no).strip()}
|
||||
)
|
||||
if self.is_return:
|
||||
outgoing_rate = get_rate_for_return(self.doctype, self.name, d.item_code, self.return_against, item_row=d)
|
||||
outgoing_rate = get_rate_for_return(
|
||||
self.doctype, self.name, d.item_code, self.return_against, item_row=d
|
||||
)
|
||||
|
||||
sle.update({
|
||||
"outgoing_rate": outgoing_rate,
|
||||
"recalculate_rate": 1
|
||||
})
|
||||
sle.update({"outgoing_rate": outgoing_rate, "recalculate_rate": 1})
|
||||
if d.from_warehouse:
|
||||
sle.dependant_sle_voucher_detail_no = d.name
|
||||
else:
|
||||
val_rate_db_precision = 6 if cint(self.precision("valuation_rate", d)) <= 6 else 9
|
||||
incoming_rate = flt(d.valuation_rate, val_rate_db_precision)
|
||||
sle.update({
|
||||
"incoming_rate": incoming_rate,
|
||||
"recalculate_rate": 1 if (self.is_subcontracted and d.bom) or d.from_warehouse else 0
|
||||
})
|
||||
sle.update(
|
||||
{
|
||||
"incoming_rate": incoming_rate,
|
||||
"recalculate_rate": 1 if (self.is_subcontracted and d.bom) or d.from_warehouse else 0,
|
||||
}
|
||||
)
|
||||
sl_entries.append(sle)
|
||||
|
||||
if d.from_warehouse and ((not cint(self.is_return) and self.docstatus==2)
|
||||
or (cint(self.is_return) and self.docstatus==1)):
|
||||
from_warehouse_sle = self.get_sl_entries(d, {
|
||||
"actual_qty": -1 * pr_qty,
|
||||
"warehouse": d.from_warehouse,
|
||||
"recalculate_rate": 1
|
||||
})
|
||||
if d.from_warehouse and (
|
||||
(not cint(self.is_return) and self.docstatus == 2)
|
||||
or (cint(self.is_return) and self.docstatus == 1)
|
||||
):
|
||||
from_warehouse_sle = self.get_sl_entries(
|
||||
d, {"actual_qty": -1 * pr_qty, "warehouse": d.from_warehouse, "recalculate_rate": 1}
|
||||
)
|
||||
|
||||
sl_entries.append(from_warehouse_sle)
|
||||
|
||||
if flt(d.rejected_qty) != 0:
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
"warehouse": d.rejected_warehouse,
|
||||
"actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor),
|
||||
"serial_no": cstr(d.rejected_serial_no).strip(),
|
||||
"incoming_rate": 0.0
|
||||
}))
|
||||
sl_entries.append(
|
||||
self.get_sl_entries(
|
||||
d,
|
||||
{
|
||||
"warehouse": d.rejected_warehouse,
|
||||
"actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor),
|
||||
"serial_no": cstr(d.rejected_serial_no).strip(),
|
||||
"incoming_rate": 0.0,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
self.make_sl_entries_for_supplier_warehouse(sl_entries)
|
||||
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock,
|
||||
via_landed_cost_voucher=via_landed_cost_voucher)
|
||||
self.make_sl_entries(
|
||||
sl_entries,
|
||||
allow_negative_stock=allow_negative_stock,
|
||||
via_landed_cost_voucher=via_landed_cost_voucher,
|
||||
)
|
||||
|
||||
def update_ordered_and_reserved_qty(self):
|
||||
po_map = {}
|
||||
for d in self.get("items"):
|
||||
if self.doctype=="Purchase Receipt" \
|
||||
and d.purchase_order:
|
||||
po_map.setdefault(d.purchase_order, []).append(d.purchase_order_item)
|
||||
if self.doctype == "Purchase Receipt" and d.purchase_order:
|
||||
po_map.setdefault(d.purchase_order, []).append(d.purchase_order_item)
|
||||
|
||||
elif self.doctype=="Purchase Invoice" and d.purchase_order and d.po_detail:
|
||||
elif self.doctype == "Purchase Invoice" and d.purchase_order and d.po_detail:
|
||||
po_map.setdefault(d.purchase_order, []).append(d.po_detail)
|
||||
|
||||
for po, po_item_rows in po_map.items():
|
||||
@@ -482,68 +552,78 @@ class BuyingController(StockController, Subcontracting):
|
||||
po_obj = frappe.get_doc("Purchase Order", po)
|
||||
|
||||
if po_obj.status in ["Closed", "Cancelled"]:
|
||||
frappe.throw(_("{0} {1} is cancelled or closed").format(_("Purchase Order"), po),
|
||||
frappe.InvalidStatusError)
|
||||
frappe.throw(
|
||||
_("{0} {1} is cancelled or closed").format(_("Purchase Order"), po),
|
||||
frappe.InvalidStatusError,
|
||||
)
|
||||
|
||||
po_obj.update_ordered_qty(po_item_rows)
|
||||
if self.is_subcontracted:
|
||||
po_obj.update_reserved_qty_for_subcontract()
|
||||
|
||||
def make_sl_entries_for_supplier_warehouse(self, sl_entries):
|
||||
if hasattr(self, 'supplied_items'):
|
||||
for d in self.get('supplied_items'):
|
||||
if hasattr(self, "supplied_items"):
|
||||
for d in self.get("supplied_items"):
|
||||
# negative quantity is passed, as raw material qty has to be decreased
|
||||
# when PR is submitted and it has to be increased when PR is cancelled
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
"item_code": d.rm_item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"actual_qty": -1*flt(d.consumed_qty),
|
||||
"dependant_sle_voucher_detail_no": d.reference_name
|
||||
}))
|
||||
sl_entries.append(
|
||||
self.get_sl_entries(
|
||||
d,
|
||||
{
|
||||
"item_code": d.rm_item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"actual_qty": -1 * flt(d.consumed_qty),
|
||||
"dependant_sle_voucher_detail_no": d.reference_name,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
def on_submit(self):
|
||||
if self.get('is_return'):
|
||||
if self.get("is_return"):
|
||||
return
|
||||
|
||||
if self.doctype in ['Purchase Receipt', 'Purchase Invoice']:
|
||||
field = 'purchase_invoice' if self.doctype == 'Purchase Invoice' else 'purchase_receipt'
|
||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
field = "purchase_invoice" if self.doctype == "Purchase Invoice" else "purchase_receipt"
|
||||
|
||||
self.process_fixed_asset()
|
||||
self.update_fixed_asset(field)
|
||||
|
||||
if self.doctype in ['Purchase Order', 'Purchase Receipt']:
|
||||
update_last_purchase_rate(self, is_submit = 1)
|
||||
if self.doctype in ["Purchase Order", "Purchase Receipt"]:
|
||||
update_last_purchase_rate(self, is_submit=1)
|
||||
|
||||
def on_cancel(self):
|
||||
super(BuyingController, self).on_cancel()
|
||||
|
||||
if self.get('is_return'):
|
||||
if self.get("is_return"):
|
||||
return
|
||||
|
||||
if self.doctype in ['Purchase Order', 'Purchase Receipt']:
|
||||
update_last_purchase_rate(self, is_submit = 0)
|
||||
if self.doctype in ["Purchase Order", "Purchase Receipt"]:
|
||||
update_last_purchase_rate(self, is_submit=0)
|
||||
|
||||
if self.doctype in ['Purchase Receipt', 'Purchase Invoice']:
|
||||
field = 'purchase_invoice' if self.doctype == 'Purchase Invoice' else 'purchase_receipt'
|
||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
field = "purchase_invoice" if self.doctype == "Purchase Invoice" else "purchase_receipt"
|
||||
|
||||
self.delete_linked_asset()
|
||||
self.update_fixed_asset(field, delete_asset=True)
|
||||
|
||||
def validate_budget(self):
|
||||
if self.docstatus == 1:
|
||||
for data in self.get('items'):
|
||||
for data in self.get("items"):
|
||||
args = data.as_dict()
|
||||
args.update({
|
||||
'doctype': self.doctype,
|
||||
'company': self.company,
|
||||
'posting_date': (self.schedule_date
|
||||
if self.doctype == 'Material Request' else self.transaction_date)
|
||||
})
|
||||
args.update(
|
||||
{
|
||||
"doctype": self.doctype,
|
||||
"company": self.company,
|
||||
"posting_date": (
|
||||
self.schedule_date if self.doctype == "Material Request" else self.transaction_date
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
validate_expense_against_budget(args)
|
||||
|
||||
def process_fixed_asset(self):
|
||||
if self.doctype == 'Purchase Invoice' and not self.update_stock:
|
||||
if self.doctype == "Purchase Invoice" and not self.update_stock:
|
||||
return
|
||||
|
||||
asset_items = self.get_asset_items()
|
||||
@@ -558,10 +638,10 @@ class BuyingController(StockController, Subcontracting):
|
||||
if d.is_fixed_asset:
|
||||
item_data = items_data.get(d.item_code)
|
||||
|
||||
if item_data.get('auto_create_assets'):
|
||||
if item_data.get("auto_create_assets"):
|
||||
# If asset has to be auto created
|
||||
# Check for asset naming series
|
||||
if item_data.get('asset_naming_series'):
|
||||
if item_data.get("asset_naming_series"):
|
||||
created_assets = []
|
||||
|
||||
for qty in range(cint(d.qty)):
|
||||
@@ -570,21 +650,31 @@ class BuyingController(StockController, Subcontracting):
|
||||
|
||||
if len(created_assets) > 5:
|
||||
# dont show asset form links if more than 5 assets are created
|
||||
messages.append(_('{} Assets created for {}').format(len(created_assets), frappe.bold(d.item_code)))
|
||||
else:
|
||||
assets_link = list(map(lambda d: frappe.utils.get_link_to_form('Asset', d), created_assets))
|
||||
assets_link = frappe.bold(','.join(assets_link))
|
||||
|
||||
is_plural = 's' if len(created_assets) != 1 else ''
|
||||
messages.append(
|
||||
_('Asset{} {assets_link} created for {}').format(is_plural, frappe.bold(d.item_code), assets_link=assets_link)
|
||||
_("{} Assets created for {}").format(len(created_assets), frappe.bold(d.item_code))
|
||||
)
|
||||
else:
|
||||
assets_link = list(map(lambda d: frappe.utils.get_link_to_form("Asset", d), created_assets))
|
||||
assets_link = frappe.bold(",".join(assets_link))
|
||||
|
||||
is_plural = "s" if len(created_assets) != 1 else ""
|
||||
messages.append(
|
||||
_("Asset{} {assets_link} created for {}").format(
|
||||
is_plural, frappe.bold(d.item_code), assets_link=assets_link
|
||||
)
|
||||
)
|
||||
else:
|
||||
frappe.throw(_("Row {}: Asset Naming Series is mandatory for the auto creation for item {}")
|
||||
.format(d.idx, frappe.bold(d.item_code)))
|
||||
frappe.throw(
|
||||
_("Row {}: Asset Naming Series is mandatory for the auto creation for item {}").format(
|
||||
d.idx, frappe.bold(d.item_code)
|
||||
)
|
||||
)
|
||||
else:
|
||||
messages.append(_("Assets not created for {0}. You will have to create asset manually.")
|
||||
.format(frappe.bold(d.item_code)))
|
||||
messages.append(
|
||||
_("Assets not created for {0}. You will have to create asset manually.").format(
|
||||
frappe.bold(d.item_code)
|
||||
)
|
||||
)
|
||||
|
||||
for message in messages:
|
||||
frappe.msgprint(message, title="Success", indicator="green")
|
||||
@@ -593,26 +683,29 @@ class BuyingController(StockController, Subcontracting):
|
||||
if not row.asset_location:
|
||||
frappe.throw(_("Row {0}: Enter location for the asset item {1}").format(row.idx, row.item_code))
|
||||
|
||||
item_data = frappe.db.get_value('Item',
|
||||
row.item_code, ['asset_naming_series', 'asset_category'], as_dict=1)
|
||||
item_data = frappe.db.get_value(
|
||||
"Item", row.item_code, ["asset_naming_series", "asset_category"], as_dict=1
|
||||
)
|
||||
|
||||
purchase_amount = flt(row.base_rate + row.item_tax_amount)
|
||||
asset = frappe.get_doc({
|
||||
'doctype': 'Asset',
|
||||
'item_code': row.item_code,
|
||||
'asset_name': row.item_name,
|
||||
'naming_series': item_data.get('asset_naming_series') or 'AST',
|
||||
'asset_category': item_data.get('asset_category'),
|
||||
'location': row.asset_location,
|
||||
'company': self.company,
|
||||
'supplier': self.supplier,
|
||||
'purchase_date': self.posting_date,
|
||||
'calculate_depreciation': 1,
|
||||
'purchase_receipt_amount': purchase_amount,
|
||||
'gross_purchase_amount': purchase_amount,
|
||||
'purchase_receipt': self.name if self.doctype == 'Purchase Receipt' else None,
|
||||
'purchase_invoice': self.name if self.doctype == 'Purchase Invoice' else None
|
||||
})
|
||||
asset = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Asset",
|
||||
"item_code": row.item_code,
|
||||
"asset_name": row.item_name,
|
||||
"naming_series": item_data.get("asset_naming_series") or "AST",
|
||||
"asset_category": item_data.get("asset_category"),
|
||||
"location": row.asset_location,
|
||||
"company": self.company,
|
||||
"supplier": self.supplier,
|
||||
"purchase_date": self.posting_date,
|
||||
"calculate_depreciation": 1,
|
||||
"purchase_receipt_amount": purchase_amount,
|
||||
"gross_purchase_amount": purchase_amount,
|
||||
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
|
||||
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
|
||||
}
|
||||
)
|
||||
|
||||
asset.flags.ignore_validate = True
|
||||
asset.flags.ignore_mandatory = True
|
||||
@@ -621,22 +714,25 @@ class BuyingController(StockController, Subcontracting):
|
||||
|
||||
return asset.name
|
||||
|
||||
def update_fixed_asset(self, field, delete_asset = False):
|
||||
def update_fixed_asset(self, field, delete_asset=False):
|
||||
for d in self.get("items"):
|
||||
if d.is_fixed_asset:
|
||||
is_auto_create_enabled = frappe.db.get_value('Item', d.item_code, 'auto_create_assets')
|
||||
assets = frappe.db.get_all('Asset', filters={ field : self.name, 'item_code' : d.item_code })
|
||||
is_auto_create_enabled = frappe.db.get_value("Item", d.item_code, "auto_create_assets")
|
||||
assets = frappe.db.get_all("Asset", filters={field: self.name, "item_code": d.item_code})
|
||||
|
||||
for asset in assets:
|
||||
asset = frappe.get_doc('Asset', asset.name)
|
||||
asset = frappe.get_doc("Asset", asset.name)
|
||||
if delete_asset and is_auto_create_enabled:
|
||||
# need to delete movements to delete assets otherwise throws link exists error
|
||||
movements = frappe.db.sql(
|
||||
"""SELECT asm.name
|
||||
FROM `tabAsset Movement` asm, `tabAsset Movement Item` asm_item
|
||||
WHERE asm_item.parent=asm.name and asm_item.asset=%s""", asset.name, as_dict=1)
|
||||
WHERE asm_item.parent=asm.name and asm_item.asset=%s""",
|
||||
asset.name,
|
||||
as_dict=1,
|
||||
)
|
||||
for movement in movements:
|
||||
frappe.delete_doc('Asset Movement', movement.name, force=1)
|
||||
frappe.delete_doc("Asset Movement", movement.name, force=1)
|
||||
frappe.delete_doc("Asset", asset.name, force=1)
|
||||
continue
|
||||
|
||||
@@ -649,8 +745,11 @@ class BuyingController(StockController, Subcontracting):
|
||||
asset.set(field, None)
|
||||
asset.supplier = None
|
||||
if asset.docstatus == 1 and delete_asset:
|
||||
frappe.throw(_('Cannot cancel this document as it is linked with submitted asset {0}. Please cancel it to continue.')
|
||||
.format(frappe.utils.get_link_to_form('Asset', asset.name)))
|
||||
frappe.throw(
|
||||
_(
|
||||
"Cannot cancel this document as it is linked with submitted asset {0}. Please cancel it to continue."
|
||||
).format(frappe.utils.get_link_to_form("Asset", asset.name))
|
||||
)
|
||||
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
asset.flags.ignore_mandatory = True
|
||||
@@ -660,7 +759,7 @@ class BuyingController(StockController, Subcontracting):
|
||||
asset.save()
|
||||
|
||||
def delete_linked_asset(self):
|
||||
if self.doctype == 'Purchase Invoice' and not self.get('update_stock'):
|
||||
if self.doctype == "Purchase Invoice" and not self.get("update_stock"):
|
||||
return
|
||||
|
||||
frappe.db.sql("delete from `tabAsset Movement` where reference_name=%s", self.name)
|
||||
@@ -671,37 +770,47 @@ class BuyingController(StockController, Subcontracting):
|
||||
|
||||
if any(d.schedule_date for d in self.get("items")):
|
||||
# Select earliest schedule_date.
|
||||
self.schedule_date = min(d.schedule_date for d in self.get("items")
|
||||
if d.schedule_date is not None)
|
||||
self.schedule_date = min(
|
||||
d.schedule_date for d in self.get("items") if d.schedule_date is not None
|
||||
)
|
||||
|
||||
if self.schedule_date:
|
||||
for d in self.get('items'):
|
||||
for d in self.get("items"):
|
||||
if not d.schedule_date:
|
||||
d.schedule_date = self.schedule_date
|
||||
|
||||
if (d.schedule_date and self.transaction_date and
|
||||
getdate(d.schedule_date) < getdate(self.transaction_date)):
|
||||
if (
|
||||
d.schedule_date
|
||||
and self.transaction_date
|
||||
and getdate(d.schedule_date) < getdate(self.transaction_date)
|
||||
):
|
||||
frappe.throw(_("Row #{0}: Reqd by Date cannot be before Transaction Date").format(d.idx))
|
||||
else:
|
||||
frappe.throw(_("Please enter Reqd by Date"))
|
||||
|
||||
def validate_items(self):
|
||||
# validate items to see if they have is_purchase_item or is_subcontracted_item enabled
|
||||
if self.doctype=="Material Request": return
|
||||
if self.doctype == "Material Request":
|
||||
return
|
||||
|
||||
if hasattr(self, "is_subcontracted") and self.is_subcontracted == 'Yes':
|
||||
if hasattr(self, "is_subcontracted") and self.is_subcontracted == "Yes":
|
||||
validate_item_type(self, "is_sub_contracted_item", "subcontracted")
|
||||
else:
|
||||
validate_item_type(self, "is_purchase_item", "purchase")
|
||||
|
||||
|
||||
def get_asset_item_details(asset_items):
|
||||
asset_items_data = {}
|
||||
for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"],
|
||||
filters = {'name': ('in', asset_items)}):
|
||||
for d in frappe.get_all(
|
||||
"Item",
|
||||
fields=["name", "auto_create_assets", "asset_naming_series"],
|
||||
filters={"name": ("in", asset_items)},
|
||||
):
|
||||
asset_items_data.setdefault(d.name, d)
|
||||
|
||||
return asset_items_data
|
||||
|
||||
|
||||
def validate_item_type(doc, fieldname, message):
|
||||
# iterate through items and check if they are valid sales or purchase items
|
||||
items = [d.item_code for d in doc.items if d.item_code]
|
||||
@@ -712,16 +821,28 @@ def validate_item_type(doc, fieldname, message):
|
||||
|
||||
item_list = ", ".join(["%s" % frappe.db.escape(d) for d in items])
|
||||
|
||||
invalid_items = [d[0] for d in frappe.db.sql("""
|
||||
invalid_items = [
|
||||
d[0]
|
||||
for d in frappe.db.sql(
|
||||
"""
|
||||
select item_code from tabItem where name in ({0}) and {1}=0
|
||||
""".format(item_list, fieldname), as_list=True)]
|
||||
""".format(
|
||||
item_list, fieldname
|
||||
),
|
||||
as_list=True,
|
||||
)
|
||||
]
|
||||
|
||||
if invalid_items:
|
||||
items = ", ".join([d for d in invalid_items])
|
||||
|
||||
if len(invalid_items) > 1:
|
||||
error_message = _("Following items {0} are not marked as {1} item. You can enable them as {1} item from its Item master").format(items, message)
|
||||
error_message = _(
|
||||
"Following items {0} are not marked as {1} item. You can enable them as {1} item from its Item master"
|
||||
).format(items, message)
|
||||
else:
|
||||
error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master").format(items, message)
|
||||
error_message = _(
|
||||
"Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master"
|
||||
).format(items, message)
|
||||
|
||||
frappe.throw(error_message)
|
||||
|
||||
@@ -11,24 +11,30 @@ from frappe.utils import cstr, flt
|
||||
from six import string_types
|
||||
|
||||
|
||||
class ItemVariantExistsError(frappe.ValidationError): pass
|
||||
class InvalidItemAttributeValueError(frappe.ValidationError): pass
|
||||
class ItemTemplateCannotHaveStock(frappe.ValidationError): pass
|
||||
class ItemVariantExistsError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidItemAttributeValueError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class ItemTemplateCannotHaveStock(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_variant(template, args=None, variant=None, manufacturer=None,
|
||||
manufacturer_part_no=None):
|
||||
def get_variant(template, args=None, variant=None, manufacturer=None, manufacturer_part_no=None):
|
||||
"""Validates Attributes and their Values, then looks for an exactly
|
||||
matching Item Variant
|
||||
matching Item Variant
|
||||
|
||||
:param item: Template Item
|
||||
:param args: A dictionary with "Attribute" as key and "Attribute Value" as value
|
||||
:param item: Template Item
|
||||
:param args: A dictionary with "Attribute" as key and "Attribute Value" as value
|
||||
"""
|
||||
item_template = frappe.get_doc('Item', template)
|
||||
item_template = frappe.get_doc("Item", template)
|
||||
|
||||
if item_template.variant_based_on=='Manufacturer' and manufacturer:
|
||||
return make_variant_based_on_manufacturer(item_template, manufacturer,
|
||||
manufacturer_part_no)
|
||||
if item_template.variant_based_on == "Manufacturer" and manufacturer:
|
||||
return make_variant_based_on_manufacturer(item_template, manufacturer, manufacturer_part_no)
|
||||
else:
|
||||
if isinstance(args, string_types):
|
||||
args = json.loads(args)
|
||||
@@ -37,28 +43,30 @@ def get_variant(template, args=None, variant=None, manufacturer=None,
|
||||
frappe.throw(_("Please specify at least one attribute in the Attributes table"))
|
||||
return find_variant(template, args, variant)
|
||||
|
||||
|
||||
def make_variant_based_on_manufacturer(template, manufacturer, manufacturer_part_no):
|
||||
'''Make and return a new variant based on manufacturer and
|
||||
manufacturer part no'''
|
||||
"""Make and return a new variant based on manufacturer and
|
||||
manufacturer part no"""
|
||||
from frappe.model.naming import append_number_if_name_exists
|
||||
|
||||
variant = frappe.new_doc('Item')
|
||||
variant = frappe.new_doc("Item")
|
||||
|
||||
copy_attributes_to_variant(template, variant)
|
||||
|
||||
variant.manufacturer = manufacturer
|
||||
variant.manufacturer_part_no = manufacturer_part_no
|
||||
|
||||
variant.item_code = append_number_if_name_exists('Item', template.name)
|
||||
variant.item_code = append_number_if_name_exists("Item", template.name)
|
||||
|
||||
return variant
|
||||
|
||||
|
||||
def validate_item_variant_attributes(item, args=None):
|
||||
if isinstance(item, string_types):
|
||||
item = frappe.get_doc('Item', item)
|
||||
item = frappe.get_doc("Item", item)
|
||||
|
||||
if not args:
|
||||
args = {d.attribute.lower():d.attribute_value for d in item.attributes}
|
||||
args = {d.attribute.lower(): d.attribute_value for d in item.attributes}
|
||||
|
||||
attribute_values, numeric_values = get_attribute_values(item)
|
||||
|
||||
@@ -74,6 +82,7 @@ def validate_item_variant_attributes(item, args=None):
|
||||
attributes_list = attribute_values.get(attribute.lower(), [])
|
||||
validate_item_attribute_value(attributes_list, attribute, value, item.name, from_variant=True)
|
||||
|
||||
|
||||
def validate_is_incremental(numeric_attribute, attribute, value, item):
|
||||
from_range = numeric_attribute.from_range
|
||||
to_range = numeric_attribute.to_range
|
||||
@@ -85,30 +94,48 @@ def validate_is_incremental(numeric_attribute, attribute, value, item):
|
||||
|
||||
is_in_range = from_range <= flt(value) <= to_range
|
||||
precision = max(len(cstr(v).split(".")[-1].rstrip("0")) for v in (value, increment))
|
||||
#avoid precision error by rounding the remainder
|
||||
# avoid precision error by rounding the remainder
|
||||
remainder = flt((flt(value) - from_range) % increment, precision)
|
||||
|
||||
is_incremental = remainder==0 or remainder==increment
|
||||
is_incremental = remainder == 0 or remainder == increment
|
||||
|
||||
if not (is_in_range and is_incremental):
|
||||
frappe.throw(_("Value for Attribute {0} must be within the range of {1} to {2} in the increments of {3} for Item {4}")\
|
||||
.format(attribute, from_range, to_range, increment, item),
|
||||
InvalidItemAttributeValueError, title=_('Invalid Attribute'))
|
||||
frappe.throw(
|
||||
_(
|
||||
"Value for Attribute {0} must be within the range of {1} to {2} in the increments of {3} for Item {4}"
|
||||
).format(attribute, from_range, to_range, increment, item),
|
||||
InvalidItemAttributeValueError,
|
||||
title=_("Invalid Attribute"),
|
||||
)
|
||||
|
||||
def validate_item_attribute_value(attributes_list, attribute, attribute_value, item, from_variant=True):
|
||||
allow_rename_attribute_value = frappe.db.get_single_value('Item Variant Settings', 'allow_rename_attribute_value')
|
||||
|
||||
def validate_item_attribute_value(
|
||||
attributes_list, attribute, attribute_value, item, from_variant=True
|
||||
):
|
||||
allow_rename_attribute_value = frappe.db.get_single_value(
|
||||
"Item Variant Settings", "allow_rename_attribute_value"
|
||||
)
|
||||
if allow_rename_attribute_value:
|
||||
pass
|
||||
elif attribute_value not in attributes_list:
|
||||
if from_variant:
|
||||
frappe.throw(_("{0} is not a valid Value for Attribute {1} of Item {2}.").format(
|
||||
frappe.bold(attribute_value), frappe.bold(attribute), frappe.bold(item)), InvalidItemAttributeValueError, title=_("Invalid Value"))
|
||||
frappe.throw(
|
||||
_("{0} is not a valid Value for Attribute {1} of Item {2}.").format(
|
||||
frappe.bold(attribute_value), frappe.bold(attribute), frappe.bold(item)
|
||||
),
|
||||
InvalidItemAttributeValueError,
|
||||
title=_("Invalid Value"),
|
||||
)
|
||||
else:
|
||||
msg = _("The value {0} is already assigned to an existing Item {1}.").format(
|
||||
frappe.bold(attribute_value), frappe.bold(item))
|
||||
msg += "<br>" + _("To still proceed with editing this Attribute Value, enable {0} in Item Variant Settings.").format(frappe.bold("Allow Rename Attribute Value"))
|
||||
frappe.bold(attribute_value), frappe.bold(item)
|
||||
)
|
||||
msg += "<br>" + _(
|
||||
"To still proceed with editing this Attribute Value, enable {0} in Item Variant Settings."
|
||||
).format(frappe.bold("Allow Rename Attribute Value"))
|
||||
|
||||
frappe.throw(msg, InvalidItemAttributeValueError, title=_("Edit Not Allowed"))
|
||||
|
||||
frappe.throw(msg, InvalidItemAttributeValueError, title=_('Edit Not Allowed'))
|
||||
|
||||
def get_attribute_values(item):
|
||||
if not frappe.flags.attribute_values:
|
||||
@@ -117,9 +144,11 @@ def get_attribute_values(item):
|
||||
for t in frappe.get_all("Item Attribute Value", fields=["parent", "attribute_value"]):
|
||||
attribute_values.setdefault(t.parent.lower(), []).append(t.attribute_value)
|
||||
|
||||
for t in frappe.get_all('Item Variant Attribute',
|
||||
for t in frappe.get_all(
|
||||
"Item Variant Attribute",
|
||||
fields=["attribute", "from_range", "to_range", "increment"],
|
||||
filters={'numeric_values': 1, 'parent': item.variant_of}):
|
||||
filters={"numeric_values": 1, "parent": item.variant_of},
|
||||
):
|
||||
numeric_values[t.attribute.lower()] = t
|
||||
|
||||
frappe.flags.attribute_values = attribute_values
|
||||
@@ -127,14 +156,22 @@ def get_attribute_values(item):
|
||||
|
||||
return frappe.flags.attribute_values, frappe.flags.numeric_values
|
||||
|
||||
|
||||
def find_variant(template, args, variant_item_code=None):
|
||||
conditions = ["""(iv_attribute.attribute={0} and iv_attribute.attribute_value={1})"""\
|
||||
.format(frappe.db.escape(key), frappe.db.escape(cstr(value))) for key, value in args.items()]
|
||||
conditions = [
|
||||
"""(iv_attribute.attribute={0} and iv_attribute.attribute_value={1})""".format(
|
||||
frappe.db.escape(key), frappe.db.escape(cstr(value))
|
||||
)
|
||||
for key, value in args.items()
|
||||
]
|
||||
|
||||
conditions = " or ".join(conditions)
|
||||
|
||||
from erpnext.e_commerce.variant_selector.utils import get_item_codes_by_attributes
|
||||
possible_variants = [i for i in get_item_codes_by_attributes(args, template) if i != variant_item_code]
|
||||
|
||||
possible_variants = [
|
||||
i for i in get_item_codes_by_attributes(args, template) if i != variant_item_code
|
||||
]
|
||||
|
||||
for variant in possible_variants:
|
||||
variant = frappe.get_doc("Item", variant)
|
||||
@@ -146,7 +183,7 @@ def find_variant(template, args, variant_item_code=None):
|
||||
|
||||
for attribute, value in args.items():
|
||||
for row in variant.attributes:
|
||||
if row.attribute==attribute and row.attribute_value== cstr(value):
|
||||
if row.attribute == attribute and row.attribute_value == cstr(value):
|
||||
# this row matches
|
||||
match_count += 1
|
||||
break
|
||||
@@ -154,6 +191,7 @@ def find_variant(template, args, variant_item_code=None):
|
||||
if match_count == len(args.keys()):
|
||||
return variant.name
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_variant(item, args):
|
||||
if isinstance(args, string_types):
|
||||
@@ -161,14 +199,11 @@ def create_variant(item, args):
|
||||
|
||||
template = frappe.get_doc("Item", item)
|
||||
variant = frappe.new_doc("Item")
|
||||
variant.variant_based_on = 'Item Attribute'
|
||||
variant.variant_based_on = "Item Attribute"
|
||||
variant_attributes = []
|
||||
|
||||
for d in template.attributes:
|
||||
variant_attributes.append({
|
||||
"attribute": d.attribute,
|
||||
"attribute_value": args.get(d.attribute)
|
||||
})
|
||||
variant_attributes.append({"attribute": d.attribute, "attribute_value": args.get(d.attribute)})
|
||||
|
||||
variant.set("attributes", variant_attributes)
|
||||
copy_attributes_to_variant(template, variant)
|
||||
@@ -176,6 +211,7 @@ def create_variant(item, args):
|
||||
|
||||
return variant
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def enqueue_multiple_variant_creation(item, args):
|
||||
# There can be innumerable attribute combinations, enqueue
|
||||
@@ -190,9 +226,14 @@ def enqueue_multiple_variant_creation(item, args):
|
||||
if total_variants < 10:
|
||||
return create_multiple_variants(item, args)
|
||||
else:
|
||||
frappe.enqueue("erpnext.controllers.item_variant.create_multiple_variants",
|
||||
item=item, args=args, now=frappe.flags.in_test);
|
||||
return 'queued'
|
||||
frappe.enqueue(
|
||||
"erpnext.controllers.item_variant.create_multiple_variants",
|
||||
item=item,
|
||||
args=args,
|
||||
now=frappe.flags.in_test,
|
||||
)
|
||||
return "queued"
|
||||
|
||||
|
||||
def create_multiple_variants(item, args):
|
||||
count = 0
|
||||
@@ -205,26 +246,27 @@ def create_multiple_variants(item, args):
|
||||
if not get_variant(item, args=attribute_values):
|
||||
variant = create_variant(item, attribute_values)
|
||||
variant.save()
|
||||
count +=1
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
|
||||
def generate_keyed_value_combinations(args):
|
||||
"""
|
||||
From this:
|
||||
|
||||
args = {"attr1": ["a", "b", "c"], "attr2": ["1", "2"], "attr3": ["A"]}
|
||||
args = {"attr1": ["a", "b", "c"], "attr2": ["1", "2"], "attr3": ["A"]}
|
||||
|
||||
To this:
|
||||
|
||||
[
|
||||
{u'attr1': u'a', u'attr2': u'1', u'attr3': u'A'},
|
||||
{u'attr1': u'b', u'attr2': u'1', u'attr3': u'A'},
|
||||
{u'attr1': u'c', u'attr2': u'1', u'attr3': u'A'},
|
||||
{u'attr1': u'a', u'attr2': u'2', u'attr3': u'A'},
|
||||
{u'attr1': u'b', u'attr2': u'2', u'attr3': u'A'},
|
||||
{u'attr1': u'c', u'attr2': u'2', u'attr3': u'A'}
|
||||
]
|
||||
[
|
||||
{u'attr1': u'a', u'attr2': u'1', u'attr3': u'A'},
|
||||
{u'attr1': u'b', u'attr2': u'1', u'attr3': u'A'},
|
||||
{u'attr1': u'c', u'attr2': u'1', u'attr3': u'A'},
|
||||
{u'attr1': u'a', u'attr2': u'2', u'attr3': u'A'},
|
||||
{u'attr1': u'b', u'attr2': u'2', u'attr3': u'A'},
|
||||
{u'attr1': u'c', u'attr2': u'2', u'attr3': u'A'}
|
||||
]
|
||||
|
||||
"""
|
||||
# Return empty list if empty
|
||||
@@ -260,17 +302,27 @@ def generate_keyed_value_combinations(args):
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def copy_attributes_to_variant(item, variant):
|
||||
# copy non no-copy fields
|
||||
|
||||
exclude_fields = ["naming_series", "item_code", "item_name", "published_in_website",
|
||||
"opening_stock", "variant_of", "valuation_rate", "has_variants", "attributes"]
|
||||
exclude_fields = [
|
||||
"naming_series",
|
||||
"item_code",
|
||||
"item_name",
|
||||
"published_in_website",
|
||||
"opening_stock",
|
||||
"variant_of",
|
||||
"valuation_rate",
|
||||
"has_variants",
|
||||
"attributes",
|
||||
]
|
||||
|
||||
if item.variant_based_on=='Manufacturer':
|
||||
if item.variant_based_on == "Manufacturer":
|
||||
# don't copy manufacturer values if based on part no
|
||||
exclude_fields += ['manufacturer', 'manufacturer_part_no']
|
||||
exclude_fields += ["manufacturer", "manufacturer_part_no"]
|
||||
|
||||
allow_fields = [d.field_name for d in frappe.get_all("Variant Field", fields = ['field_name'])]
|
||||
allow_fields = [d.field_name for d in frappe.get_all("Variant Field", fields=["field_name"])]
|
||||
if "variant_based_on" not in allow_fields:
|
||||
allow_fields.append("variant_based_on")
|
||||
for field in item.meta.fields:
|
||||
@@ -289,11 +341,11 @@ def copy_attributes_to_variant(item, variant):
|
||||
|
||||
variant.variant_of = item.name
|
||||
|
||||
if 'description' not in allow_fields:
|
||||
if "description" not in allow_fields:
|
||||
if not variant.description:
|
||||
variant.description = ""
|
||||
variant.description = ""
|
||||
else:
|
||||
if item.variant_based_on=='Item Attribute':
|
||||
if item.variant_based_on == "Item Attribute":
|
||||
if variant.attributes:
|
||||
attributes_description = item.description + " "
|
||||
for d in variant.attributes:
|
||||
@@ -302,6 +354,7 @@ def copy_attributes_to_variant(item, variant):
|
||||
if attributes_description not in variant.description:
|
||||
variant.description = attributes_description
|
||||
|
||||
|
||||
def make_variant_item_code(template_item_code, template_item_name, variant):
|
||||
"""Uses template's item code and abbreviations to make variant's item code"""
|
||||
if variant.item_code:
|
||||
@@ -309,13 +362,14 @@ def make_variant_item_code(template_item_code, template_item_name, variant):
|
||||
|
||||
abbreviations = []
|
||||
for attr in variant.attributes:
|
||||
item_attribute = frappe.db.sql("""select i.numeric_values, v.abbr
|
||||
item_attribute = frappe.db.sql(
|
||||
"""select i.numeric_values, v.abbr
|
||||
from `tabItem Attribute` i left join `tabItem Attribute Value` v
|
||||
on (i.name=v.parent)
|
||||
where i.name=%(attribute)s and (v.attribute_value=%(attribute_value)s or i.numeric_values = 1)""", {
|
||||
"attribute": attr.attribute,
|
||||
"attribute_value": attr.attribute_value
|
||||
}, as_dict=True)
|
||||
where i.name=%(attribute)s and (v.attribute_value=%(attribute_value)s or i.numeric_values = 1)""",
|
||||
{"attribute": attr.attribute, "attribute_value": attr.attribute_value},
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
if not item_attribute:
|
||||
continue
|
||||
@@ -323,13 +377,16 @@ def make_variant_item_code(template_item_code, template_item_name, variant):
|
||||
# frappe.bold(attr.attribute_value)), title=_('Invalid Attribute'),
|
||||
# exc=InvalidItemAttributeValueError)
|
||||
|
||||
abbr_or_value = cstr(attr.attribute_value) if item_attribute[0].numeric_values else item_attribute[0].abbr
|
||||
abbr_or_value = (
|
||||
cstr(attr.attribute_value) if item_attribute[0].numeric_values else item_attribute[0].abbr
|
||||
)
|
||||
abbreviations.append(abbr_or_value)
|
||||
|
||||
if abbreviations:
|
||||
variant.item_code = "{0}-{1}".format(template_item_code, "-".join(abbreviations))
|
||||
variant.item_name = "{0}-{1}".format(template_item_name, "-".join(abbreviations))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_variant_doc_for_quick_entry(template, args):
|
||||
variant_based_on = frappe.db.get_value("Item", template, "variant_based_on")
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
|
||||
def set_print_templates_for_item_table(doc, settings):
|
||||
doc.print_templates = {
|
||||
"items": "templates/print_formats/includes/items.html",
|
||||
@@ -20,16 +19,21 @@ def set_print_templates_for_item_table(doc, settings):
|
||||
doc.flags.compact_item_fields = ["description", "qty", "rate", "amount"]
|
||||
|
||||
if settings.compact_item_print:
|
||||
doc.child_print_templates["items"]["description"] =\
|
||||
"templates/print_formats/includes/item_table_description.html"
|
||||
doc.child_print_templates["items"][
|
||||
"description"
|
||||
] = "templates/print_formats/includes/item_table_description.html"
|
||||
doc.flags.format_columns = format_columns
|
||||
|
||||
|
||||
def set_print_templates_for_taxes(doc, settings):
|
||||
doc.flags.show_inclusive_tax_in_print = doc.is_inclusive_tax()
|
||||
doc.print_templates.update({
|
||||
"total": "templates/print_formats/includes/total.html",
|
||||
"taxes": "templates/print_formats/includes/taxes.html"
|
||||
})
|
||||
doc.print_templates.update(
|
||||
{
|
||||
"total": "templates/print_formats/includes/total.html",
|
||||
"taxes": "templates/print_formats/includes/taxes.html",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def format_columns(display_columns, compact_fields):
|
||||
compact_fields = compact_fields + ["image", "item_code", "item_name"]
|
||||
|
||||
@@ -21,7 +21,8 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions = []
|
||||
fields = get_fields("Employee", ["name", "employee_name"])
|
||||
|
||||
return frappe.db.sql("""select {fields} from `tabEmployee`
|
||||
return frappe.db.sql(
|
||||
"""select {fields} from `tabEmployee`
|
||||
where status in ('Active', 'Suspended')
|
||||
and docstatus < 2
|
||||
and ({key} like %(txt)s
|
||||
@@ -32,17 +33,16 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
if(locate(%(_txt)s, employee_name), locate(%(_txt)s, employee_name), 99999),
|
||||
idx desc,
|
||||
name, employee_name
|
||||
limit %(start)s, %(page_len)s""".format(**{
|
||||
'fields': ", ".join(fields),
|
||||
'key': searchfield,
|
||||
'fcond': get_filters_cond(doctype, filters, conditions),
|
||||
'mcond': get_match_cond(doctype)
|
||||
}), {
|
||||
'txt': "%%%s%%" % txt,
|
||||
'_txt': txt.replace("%", ""),
|
||||
'start': start,
|
||||
'page_len': page_len
|
||||
})
|
||||
limit %(start)s, %(page_len)s""".format(
|
||||
**{
|
||||
"fields": ", ".join(fields),
|
||||
"key": searchfield,
|
||||
"fcond": get_filters_cond(doctype, filters, conditions),
|
||||
"mcond": get_match_cond(doctype),
|
||||
}
|
||||
),
|
||||
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
|
||||
)
|
||||
|
||||
|
||||
# searches for leads which are not converted
|
||||
@@ -51,7 +51,8 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
fields = get_fields("Lead", ["name", "lead_name", "company_name"])
|
||||
|
||||
return frappe.db.sql("""select {fields} from `tabLead`
|
||||
return frappe.db.sql(
|
||||
"""select {fields} from `tabLead`
|
||||
where docstatus < 2
|
||||
and ifnull(status, '') != 'Converted'
|
||||
and ({key} like %(txt)s
|
||||
@@ -64,19 +65,15 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
if(locate(%(_txt)s, company_name), locate(%(_txt)s, company_name), 99999),
|
||||
idx desc,
|
||||
name, lead_name
|
||||
limit %(start)s, %(page_len)s""".format(**{
|
||||
'fields': ", ".join(fields),
|
||||
'key': searchfield,
|
||||
'mcond':get_match_cond(doctype)
|
||||
}), {
|
||||
'txt': "%%%s%%" % txt,
|
||||
'_txt': txt.replace("%", ""),
|
||||
'start': start,
|
||||
'page_len': page_len
|
||||
})
|
||||
limit %(start)s, %(page_len)s""".format(
|
||||
**{"fields": ", ".join(fields), "key": searchfield, "mcond": get_match_cond(doctype)}
|
||||
),
|
||||
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
|
||||
)
|
||||
|
||||
# searches for customer
|
||||
|
||||
|
||||
# searches for customer
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
@@ -93,7 +90,8 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
searchfields = frappe.get_meta("Customer").get_search_fields()
|
||||
searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
|
||||
|
||||
return frappe.db.sql("""select {fields} from `tabCustomer`
|
||||
return frappe.db.sql(
|
||||
"""select {fields} from `tabCustomer`
|
||||
where docstatus < 2
|
||||
and ({scond}) and disabled=0
|
||||
{fcond} {mcond}
|
||||
@@ -102,17 +100,16 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
if(locate(%(_txt)s, customer_name), locate(%(_txt)s, customer_name), 99999),
|
||||
idx desc,
|
||||
name, customer_name
|
||||
limit %(start)s, %(page_len)s""".format(**{
|
||||
"fields": ", ".join(fields),
|
||||
"scond": searchfields,
|
||||
"mcond": get_match_cond(doctype),
|
||||
"fcond": get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
|
||||
}), {
|
||||
'txt': "%%%s%%" % txt,
|
||||
'_txt': txt.replace("%", ""),
|
||||
'start': start,
|
||||
'page_len': page_len
|
||||
})
|
||||
limit %(start)s, %(page_len)s""".format(
|
||||
**{
|
||||
"fields": ", ".join(fields),
|
||||
"scond": searchfields,
|
||||
"mcond": get_match_cond(doctype),
|
||||
"fcond": get_filters_cond(doctype, filters, conditions).replace("%", "%%"),
|
||||
}
|
||||
),
|
||||
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
|
||||
)
|
||||
|
||||
|
||||
# searches for supplier
|
||||
@@ -128,7 +125,8 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
fields = get_fields("Supplier", fields)
|
||||
|
||||
return frappe.db.sql("""select {field} from `tabSupplier`
|
||||
return frappe.db.sql(
|
||||
"""select {field} from `tabSupplier`
|
||||
where docstatus < 2
|
||||
and ({key} like %(txt)s
|
||||
or supplier_name like %(txt)s) and disabled=0
|
||||
@@ -139,29 +137,25 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
if(locate(%(_txt)s, supplier_name), locate(%(_txt)s, supplier_name), 99999),
|
||||
idx desc,
|
||||
name, supplier_name
|
||||
limit %(start)s, %(page_len)s """.format(**{
|
||||
'field': ', '.join(fields),
|
||||
'key': searchfield,
|
||||
'mcond':get_match_cond(doctype)
|
||||
}), {
|
||||
'txt': "%%%s%%" % txt,
|
||||
'_txt': txt.replace("%", ""),
|
||||
'start': start,
|
||||
'page_len': page_len
|
||||
})
|
||||
limit %(start)s, %(page_len)s """.format(
|
||||
**{"field": ", ".join(fields), "key": searchfield, "mcond": get_match_cond(doctype)}
|
||||
),
|
||||
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
company_currency = erpnext.get_company_currency(filters.get('company'))
|
||||
company_currency = erpnext.get_company_currency(filters.get("company"))
|
||||
|
||||
def get_accounts(with_account_type_filter):
|
||||
account_type_condition = ''
|
||||
account_type_condition = ""
|
||||
if with_account_type_filter:
|
||||
account_type_condition = "AND account_type in %(account_types)s"
|
||||
|
||||
accounts = frappe.db.sql("""
|
||||
accounts = frappe.db.sql(
|
||||
"""
|
||||
SELECT name, parent_account
|
||||
FROM `tabAccount`
|
||||
WHERE `tabAccount`.docstatus!=2
|
||||
@@ -176,7 +170,7 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
""".format(
|
||||
account_type_condition=account_type_condition,
|
||||
searchfield=searchfield,
|
||||
mcond=get_match_cond(doctype)
|
||||
mcond=get_match_cond(doctype),
|
||||
),
|
||||
dict(
|
||||
account_types=filters.get("account_type"),
|
||||
@@ -184,8 +178,8 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
currency=company_currency,
|
||||
txt="%{}%".format(txt),
|
||||
offset=start,
|
||||
limit=page_len
|
||||
)
|
||||
limit=page_len,
|
||||
),
|
||||
)
|
||||
|
||||
return accounts
|
||||
@@ -206,7 +200,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
||||
if isinstance(filters, str):
|
||||
filters = json.loads(filters)
|
||||
|
||||
#Get searchfields from meta and use in Item Link field query
|
||||
# Get searchfields from meta and use in Item Link field query
|
||||
meta = frappe.get_meta("Item", cached=True)
|
||||
searchfields = meta.get_search_fields()
|
||||
|
||||
@@ -216,49 +210,56 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
||||
if ignored_field in searchfields:
|
||||
searchfields.remove(ignored_field)
|
||||
|
||||
columns = ''
|
||||
extra_searchfields = [field for field in searchfields
|
||||
if not field in ["name", "item_group", "description", "item_name"]]
|
||||
columns = ""
|
||||
extra_searchfields = [
|
||||
field
|
||||
for field in searchfields
|
||||
if not field in ["name", "item_group", "description", "item_name"]
|
||||
]
|
||||
|
||||
if extra_searchfields:
|
||||
columns = ", " + ", ".join(extra_searchfields)
|
||||
|
||||
searchfields = searchfields + [field for field in[searchfield or "name", "item_code", "item_group", "item_name"]
|
||||
if not field in searchfields]
|
||||
searchfields = searchfields + [
|
||||
field
|
||||
for field in [searchfield or "name", "item_code", "item_group", "item_name"]
|
||||
if not field in searchfields
|
||||
]
|
||||
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
||||
|
||||
if filters and isinstance(filters, dict):
|
||||
if filters.get('customer') or filters.get('supplier'):
|
||||
party = filters.get('customer') or filters.get('supplier')
|
||||
item_rules_list = frappe.get_all('Party Specific Item',
|
||||
filters = {'party': party}, fields = ['restrict_based_on', 'based_on_value'])
|
||||
if filters.get("customer") or filters.get("supplier"):
|
||||
party = filters.get("customer") or filters.get("supplier")
|
||||
item_rules_list = frappe.get_all(
|
||||
"Party Specific Item", filters={"party": party}, fields=["restrict_based_on", "based_on_value"]
|
||||
)
|
||||
|
||||
filters_dict = {}
|
||||
for rule in item_rules_list:
|
||||
if rule['restrict_based_on'] == 'Item':
|
||||
rule['restrict_based_on'] = 'name'
|
||||
if rule["restrict_based_on"] == "Item":
|
||||
rule["restrict_based_on"] = "name"
|
||||
filters_dict[rule.restrict_based_on] = []
|
||||
|
||||
for rule in item_rules_list:
|
||||
filters_dict[rule.restrict_based_on].append(rule.based_on_value)
|
||||
|
||||
for filter in filters_dict:
|
||||
filters[scrub(filter)] = ['in', filters_dict[filter]]
|
||||
filters[scrub(filter)] = ["in", filters_dict[filter]]
|
||||
|
||||
if filters.get('customer'):
|
||||
del filters['customer']
|
||||
if filters.get("customer"):
|
||||
del filters["customer"]
|
||||
else:
|
||||
del filters['supplier']
|
||||
del filters["supplier"]
|
||||
else:
|
||||
filters.pop('customer', None)
|
||||
filters.pop('supplier', None)
|
||||
filters.pop("customer", None)
|
||||
filters.pop("supplier", None)
|
||||
|
||||
|
||||
description_cond = ''
|
||||
if frappe.db.count('Item', cache=True) < 50000:
|
||||
description_cond = ""
|
||||
if frappe.db.count("Item", cache=True) < 50000:
|
||||
# scan description only if items are less than 50000
|
||||
description_cond = 'or tabItem.description LIKE %(txt)s'
|
||||
return frappe.db.sql("""select
|
||||
description_cond = "or tabItem.description LIKE %(txt)s"
|
||||
return frappe.db.sql(
|
||||
"""select
|
||||
tabItem.name, tabItem.item_name, tabItem.item_group,
|
||||
if(length(tabItem.description) > 40, \
|
||||
concat(substr(tabItem.description, 1, 40), "..."), description) as description
|
||||
@@ -279,16 +280,19 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
||||
limit %(start)s, %(page_len)s """.format(
|
||||
columns=columns,
|
||||
scond=searchfields,
|
||||
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
|
||||
mcond=get_match_cond(doctype).replace('%', '%%'),
|
||||
description_cond = description_cond),
|
||||
{
|
||||
"today": nowdate(),
|
||||
"txt": "%%%s%%" % txt,
|
||||
"_txt": txt.replace("%", ""),
|
||||
"start": start,
|
||||
"page_len": page_len
|
||||
}, as_dict=as_dict)
|
||||
fcond=get_filters_cond(doctype, filters, conditions).replace("%", "%%"),
|
||||
mcond=get_match_cond(doctype).replace("%", "%%"),
|
||||
description_cond=description_cond,
|
||||
),
|
||||
{
|
||||
"today": nowdate(),
|
||||
"txt": "%%%s%%" % txt,
|
||||
"_txt": txt.replace("%", ""),
|
||||
"start": start,
|
||||
"page_len": page_len,
|
||||
},
|
||||
as_dict=as_dict,
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -297,7 +301,8 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions = []
|
||||
fields = get_fields("BOM", ["name", "item"])
|
||||
|
||||
return frappe.db.sql("""select {fields}
|
||||
return frappe.db.sql(
|
||||
"""select {fields}
|
||||
from tabBOM
|
||||
where tabBOM.docstatus=1
|
||||
and tabBOM.is_active=1
|
||||
@@ -308,30 +313,35 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
idx desc, name
|
||||
limit %(start)s, %(page_len)s """.format(
|
||||
fields=", ".join(fields),
|
||||
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
|
||||
mcond=get_match_cond(doctype).replace('%', '%%'),
|
||||
key=searchfield),
|
||||
fcond=get_filters_cond(doctype, filters, conditions).replace("%", "%%"),
|
||||
mcond=get_match_cond(doctype).replace("%", "%%"),
|
||||
key=searchfield,
|
||||
),
|
||||
{
|
||||
'txt': '%' + txt + '%',
|
||||
'_txt': txt.replace("%", ""),
|
||||
'start': start or 0,
|
||||
'page_len': page_len or 20
|
||||
})
|
||||
"txt": "%" + txt + "%",
|
||||
"_txt": txt.replace("%", ""),
|
||||
"start": start or 0,
|
||||
"page_len": page_len or 20,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
cond = ''
|
||||
if filters and filters.get('customer'):
|
||||
cond = ""
|
||||
if filters and filters.get("customer"):
|
||||
cond = """(`tabProject`.customer = %s or
|
||||
ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer")))
|
||||
ifnull(`tabProject`.customer,"")="") and""" % (
|
||||
frappe.db.escape(filters.get("customer"))
|
||||
)
|
||||
|
||||
fields = get_fields("Project", ["name", "project_name"])
|
||||
searchfields = frappe.get_meta("Project").get_search_fields()
|
||||
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
||||
|
||||
return frappe.db.sql("""select {fields} from `tabProject`
|
||||
return frappe.db.sql(
|
||||
"""select {fields} from `tabProject`
|
||||
where
|
||||
`tabProject`.status not in ("Completed", "Cancelled")
|
||||
and {cond} {scond} {match_cond}
|
||||
@@ -340,15 +350,15 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
idx desc,
|
||||
`tabProject`.name asc
|
||||
limit {start}, {page_len}""".format(
|
||||
fields=", ".join(['`tabProject`.{0}'.format(f) for f in fields]),
|
||||
fields=", ".join(["`tabProject`.{0}".format(f) for f in fields]),
|
||||
cond=cond,
|
||||
scond=searchfields,
|
||||
match_cond=get_match_cond(doctype),
|
||||
start=start,
|
||||
page_len=page_len), {
|
||||
"txt": "%{0}%".format(txt),
|
||||
"_txt": txt.replace('%', '')
|
||||
})
|
||||
page_len=page_len,
|
||||
),
|
||||
{"txt": "%{0}%".format(txt), "_txt": txt.replace("%", "")},
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -356,7 +366,8 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict):
|
||||
fields = get_fields("Delivery Note", ["name", "customer", "posting_date"])
|
||||
|
||||
return frappe.db.sql("""
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select %(fields)s
|
||||
from `tabDelivery Note`
|
||||
where `tabDelivery Note`.`%(key)s` like %(txt)s and
|
||||
@@ -371,15 +382,19 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
|
||||
)
|
||||
)
|
||||
%(mcond)s order by `tabDelivery Note`.`%(key)s` asc limit %(start)s, %(page_len)s
|
||||
""" % {
|
||||
"fields": ", ".join(["`tabDelivery Note`.{0}".format(f) for f in fields]),
|
||||
"key": searchfield,
|
||||
"fcond": get_filters_cond(doctype, filters, []),
|
||||
"mcond": get_match_cond(doctype),
|
||||
"start": start,
|
||||
"page_len": page_len,
|
||||
"txt": "%(txt)s"
|
||||
}, {"txt": ("%%%s%%" % txt)}, as_dict=as_dict)
|
||||
"""
|
||||
% {
|
||||
"fields": ", ".join(["`tabDelivery Note`.{0}".format(f) for f in fields]),
|
||||
"key": searchfield,
|
||||
"fcond": get_filters_cond(doctype, filters, []),
|
||||
"mcond": get_match_cond(doctype),
|
||||
"start": start,
|
||||
"page_len": page_len,
|
||||
"txt": "%(txt)s",
|
||||
},
|
||||
{"txt": ("%%%s%%" % txt)},
|
||||
as_dict=as_dict,
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -391,12 +406,12 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
batch_nos = None
|
||||
args = {
|
||||
'item_code': filters.get("item_code"),
|
||||
'warehouse': filters.get("warehouse"),
|
||||
'posting_date': filters.get('posting_date'),
|
||||
'txt': "%{0}%".format(txt),
|
||||
"item_code": filters.get("item_code"),
|
||||
"warehouse": filters.get("warehouse"),
|
||||
"posting_date": filters.get("posting_date"),
|
||||
"txt": "%{0}%".format(txt),
|
||||
"start": start,
|
||||
"page_len": page_len
|
||||
"page_len": page_len,
|
||||
}
|
||||
|
||||
having_clause = "having sum(sle.actual_qty) > 0"
|
||||
@@ -406,20 +421,21 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
meta = frappe.get_meta("Batch", cached=True)
|
||||
searchfields = meta.get_search_fields()
|
||||
|
||||
search_columns = ''
|
||||
search_cond = ''
|
||||
search_columns = ""
|
||||
search_cond = ""
|
||||
|
||||
if searchfields:
|
||||
search_columns = ", " + ", ".join(searchfields)
|
||||
search_cond = " or " + " or ".join([field + " like %(txt)s" for field in searchfields])
|
||||
|
||||
if args.get('warehouse'):
|
||||
searchfields = ['batch.' + field for field in searchfields]
|
||||
if args.get("warehouse"):
|
||||
searchfields = ["batch." + field for field in searchfields]
|
||||
if searchfields:
|
||||
search_columns = ", " + ", ".join(searchfields)
|
||||
search_cond = " or " + " or ".join([field + " like %(txt)s" for field in searchfields])
|
||||
|
||||
batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom,
|
||||
batch_nos = frappe.db.sql(
|
||||
"""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom,
|
||||
concat('MFG-',batch.manufacturing_date), concat('EXP-',batch.expiry_date)
|
||||
{search_columns}
|
||||
from `tabStock Ledger Entry` sle
|
||||
@@ -439,16 +455,19 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
group by batch_no {having_clause}
|
||||
order by batch.expiry_date, sle.batch_no desc
|
||||
limit %(start)s, %(page_len)s""".format(
|
||||
search_columns = search_columns,
|
||||
search_columns=search_columns,
|
||||
cond=cond,
|
||||
match_conditions=get_match_cond(doctype),
|
||||
having_clause = having_clause,
|
||||
search_cond = search_cond
|
||||
), args)
|
||||
having_clause=having_clause,
|
||||
search_cond=search_cond,
|
||||
),
|
||||
args,
|
||||
)
|
||||
|
||||
return batch_nos
|
||||
else:
|
||||
return frappe.db.sql("""select name, concat('MFG-', manufacturing_date), concat('EXP-',expiry_date)
|
||||
return frappe.db.sql(
|
||||
"""select name, concat('MFG-', manufacturing_date), concat('EXP-',expiry_date)
|
||||
{search_columns}
|
||||
from `tabBatch` batch
|
||||
where batch.disabled = 0
|
||||
@@ -462,8 +481,14 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
{match_conditions}
|
||||
|
||||
order by expiry_date, name desc
|
||||
limit %(start)s, %(page_len)s""".format(cond, search_columns = search_columns,
|
||||
search_cond = search_cond, match_conditions=get_match_cond(doctype)), args)
|
||||
limit %(start)s, %(page_len)s""".format(
|
||||
cond,
|
||||
search_columns=search_columns,
|
||||
search_cond=search_cond,
|
||||
match_conditions=get_match_cond(doctype),
|
||||
),
|
||||
args,
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -486,25 +511,33 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
if searchfield and txt:
|
||||
filter_list.append([doctype, searchfield, "like", "%%%s%%" % txt])
|
||||
|
||||
return frappe.desk.reportview.execute("Account", filters = filter_list,
|
||||
fields = ["name", "parent_account"],
|
||||
limit_start=start, limit_page_length=page_len, as_list=True)
|
||||
return frappe.desk.reportview.execute(
|
||||
"Account",
|
||||
filters=filter_list,
|
||||
fields=["name", "parent_account"],
|
||||
limit_start=start,
|
||||
limit_page_length=page_len,
|
||||
as_list=True,
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date
|
||||
return frappe.db.sql(
|
||||
"""select distinct bo.name, bo.blanket_order_type, bo.to_date
|
||||
from `tabBlanket Order` bo, `tabBlanket Order Item` boi
|
||||
where
|
||||
boi.parent = bo.name
|
||||
and boi.item_code = {item_code}
|
||||
and bo.blanket_order_type = '{blanket_order_type}'
|
||||
and bo.company = {company}
|
||||
and bo.docstatus = 1"""
|
||||
.format(item_code = frappe.db.escape(filters.get("item")),
|
||||
blanket_order_type = filters.get("blanket_order_type"),
|
||||
company = frappe.db.escape(filters.get("company"))
|
||||
))
|
||||
and bo.docstatus = 1""".format(
|
||||
item_code=frappe.db.escape(filters.get("item")),
|
||||
blanket_order_type=filters.get("blanket_order_type"),
|
||||
company=frappe.db.escape(filters.get("company")),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -515,23 +548,26 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
# income account can be any Credit account,
|
||||
# but can also be a Asset account with account_type='Income Account' in special circumstances.
|
||||
# Hence the first condition is an "OR"
|
||||
if not filters: filters = {}
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
condition = ""
|
||||
if filters.get("company"):
|
||||
condition += "and tabAccount.company = %(company)s"
|
||||
|
||||
return frappe.db.sql("""select tabAccount.name from `tabAccount`
|
||||
return frappe.db.sql(
|
||||
"""select tabAccount.name from `tabAccount`
|
||||
where (tabAccount.report_type = "Profit and Loss"
|
||||
or tabAccount.account_type in ("Income Account", "Temporary"))
|
||||
and tabAccount.is_group=0
|
||||
and tabAccount.`{key}` LIKE %(txt)s
|
||||
{condition} {match_condition}
|
||||
order by idx desc, name"""
|
||||
.format(condition=condition, match_condition=get_match_cond(doctype), key=searchfield), {
|
||||
'txt': '%' + txt + '%',
|
||||
'company': filters.get("company", "")
|
||||
})
|
||||
order by idx desc, name""".format(
|
||||
condition=condition, match_condition=get_match_cond(doctype), key=searchfield
|
||||
),
|
||||
{"txt": "%" + txt + "%", "company": filters.get("company", "")},
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
@@ -539,68 +575,73 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters)
|
||||
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
|
||||
get_dimension_filter_map,
|
||||
)
|
||||
|
||||
dimension_filters = get_dimension_filter_map()
|
||||
dimension_filters = dimension_filters.get((filters.get('dimension'),filters.get('account')))
|
||||
dimension_filters = dimension_filters.get((filters.get("dimension"), filters.get("account")))
|
||||
query_filters = []
|
||||
or_filters = []
|
||||
fields = ['name']
|
||||
fields = ["name"]
|
||||
|
||||
searchfields = frappe.get_meta(doctype).get_search_fields()
|
||||
|
||||
meta = frappe.get_meta(doctype)
|
||||
if meta.is_tree:
|
||||
query_filters.append(['is_group', '=', 0])
|
||||
query_filters.append(["is_group", "=", 0])
|
||||
|
||||
if meta.has_field('disabled'):
|
||||
query_filters.append(['disabled', '!=', 1])
|
||||
if meta.has_field("disabled"):
|
||||
query_filters.append(["disabled", "!=", 1])
|
||||
|
||||
if meta.has_field('company'):
|
||||
query_filters.append(['company', '=', filters.get('company')])
|
||||
if meta.has_field("company"):
|
||||
query_filters.append(["company", "=", filters.get("company")])
|
||||
|
||||
for field in searchfields:
|
||||
or_filters.append([field, 'LIKE', "%%%s%%" % txt])
|
||||
or_filters.append([field, "LIKE", "%%%s%%" % txt])
|
||||
fields.append(field)
|
||||
|
||||
if dimension_filters:
|
||||
if dimension_filters['allow_or_restrict'] == 'Allow':
|
||||
query_selector = 'in'
|
||||
if dimension_filters["allow_or_restrict"] == "Allow":
|
||||
query_selector = "in"
|
||||
else:
|
||||
query_selector = 'not in'
|
||||
query_selector = "not in"
|
||||
|
||||
if len(dimension_filters['allowed_dimensions']) == 1:
|
||||
dimensions = tuple(dimension_filters['allowed_dimensions'] * 2)
|
||||
if len(dimension_filters["allowed_dimensions"]) == 1:
|
||||
dimensions = tuple(dimension_filters["allowed_dimensions"] * 2)
|
||||
else:
|
||||
dimensions = tuple(dimension_filters['allowed_dimensions'])
|
||||
dimensions = tuple(dimension_filters["allowed_dimensions"])
|
||||
|
||||
query_filters.append(['name', query_selector, dimensions])
|
||||
query_filters.append(["name", query_selector, dimensions])
|
||||
|
||||
output = frappe.get_list(doctype, fields=fields, filters=query_filters, or_filters=or_filters, as_list=1)
|
||||
output = frappe.get_list(
|
||||
doctype, fields=fields, filters=query_filters, or_filters=or_filters, as_list=1
|
||||
)
|
||||
|
||||
return [tuple(d) for d in set(output)]
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
from erpnext.controllers.queries import get_match_cond
|
||||
|
||||
if not filters: filters = {}
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
condition = ""
|
||||
if filters.get("company"):
|
||||
condition += "and tabAccount.company = %(company)s"
|
||||
|
||||
return frappe.db.sql("""select tabAccount.name from `tabAccount`
|
||||
return frappe.db.sql(
|
||||
"""select tabAccount.name from `tabAccount`
|
||||
where (tabAccount.report_type = "Profit and Loss"
|
||||
or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed", "Capital Work in Progress"))
|
||||
and tabAccount.is_group=0
|
||||
and tabAccount.docstatus!=2
|
||||
and tabAccount.{key} LIKE %(txt)s
|
||||
{condition} {match_condition}"""
|
||||
.format(condition=condition, key=searchfield,
|
||||
match_condition=get_match_cond(doctype)), {
|
||||
'company': filters.get("company", ""),
|
||||
'txt': '%' + txt + '%'
|
||||
})
|
||||
{condition} {match_condition}""".format(
|
||||
condition=condition, key=searchfield, match_condition=get_match_cond(doctype)
|
||||
),
|
||||
{"company": filters.get("company", ""), "txt": "%" + txt + "%"},
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -621,14 +662,16 @@ def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
limit
|
||||
{start}, {page_len}
|
||||
""".format(
|
||||
bin_conditions=get_filters_cond(doctype, filter_dict.get("Bin"),bin_conditions, ignore_permissions=True),
|
||||
key=searchfield,
|
||||
fcond=get_filters_cond(doctype, filter_dict.get("Warehouse"), conditions),
|
||||
mcond=get_match_cond(doctype),
|
||||
start=start,
|
||||
page_len=page_len,
|
||||
txt=frappe.db.escape('%{0}%'.format(txt))
|
||||
)
|
||||
bin_conditions=get_filters_cond(
|
||||
doctype, filter_dict.get("Bin"), bin_conditions, ignore_permissions=True
|
||||
),
|
||||
key=searchfield,
|
||||
fcond=get_filters_cond(doctype, filter_dict.get("Warehouse"), conditions),
|
||||
mcond=get_match_cond(doctype),
|
||||
start=start,
|
||||
page_len=page_len,
|
||||
txt=frappe.db.escape("%{0}%".format(txt)),
|
||||
)
|
||||
|
||||
return frappe.db.sql(query)
|
||||
|
||||
@@ -647,10 +690,12 @@ def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters):
|
||||
query = """select batch_id from `tabBatch`
|
||||
where disabled = 0
|
||||
and (expiry_date >= CURDATE() or expiry_date IS NULL)
|
||||
and name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt)))
|
||||
and name like {txt}""".format(
|
||||
txt=frappe.db.escape("%{0}%".format(txt))
|
||||
)
|
||||
|
||||
if filters and filters.get('item'):
|
||||
query += " and item = {item}".format(item = frappe.db.escape(filters.get('item')))
|
||||
if filters and filters.get("item"):
|
||||
query += " and item = {item}".format(item=frappe.db.escape(filters.get("item")))
|
||||
|
||||
return frappe.db.sql(query, filters)
|
||||
|
||||
@@ -659,8 +704,8 @@ def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
item_filters = [
|
||||
['manufacturer', 'like', '%' + txt + '%'],
|
||||
['item_code', '=', filters.get("item_code")]
|
||||
["manufacturer", "like", "%" + txt + "%"],
|
||||
["item_code", "=", filters.get("item_code")],
|
||||
]
|
||||
|
||||
item_manufacturers = frappe.get_all(
|
||||
@@ -669,7 +714,7 @@ def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters)
|
||||
filters=item_filters,
|
||||
limit_start=start,
|
||||
limit_page_length=page_len,
|
||||
as_list=1
|
||||
as_list=1,
|
||||
)
|
||||
return item_manufacturers
|
||||
|
||||
@@ -681,10 +726,14 @@ def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters):
|
||||
select pr.name
|
||||
from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pritem
|
||||
where pr.docstatus = 1 and pritem.parent = pr.name
|
||||
and pr.name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt)))
|
||||
and pr.name like {txt}""".format(
|
||||
txt=frappe.db.escape("%{0}%".format(txt))
|
||||
)
|
||||
|
||||
if filters and filters.get('item_code'):
|
||||
query += " and pritem.item_code = {item_code}".format(item_code = frappe.db.escape(filters.get('item_code')))
|
||||
if filters and filters.get("item_code"):
|
||||
query += " and pritem.item_code = {item_code}".format(
|
||||
item_code=frappe.db.escape(filters.get("item_code"))
|
||||
)
|
||||
|
||||
return frappe.db.sql(query, filters)
|
||||
|
||||
@@ -696,10 +745,14 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
|
||||
select pi.name
|
||||
from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` piitem
|
||||
where pi.docstatus = 1 and piitem.parent = pi.name
|
||||
and pi.name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt)))
|
||||
and pi.name like {txt}""".format(
|
||||
txt=frappe.db.escape("%{0}%".format(txt))
|
||||
)
|
||||
|
||||
if filters and filters.get('item_code'):
|
||||
query += " and piitem.item_code = {item_code}".format(item_code = frappe.db.escape(filters.get('item_code')))
|
||||
if filters and filters.get("item_code"):
|
||||
query += " and piitem.item_code = {item_code}".format(
|
||||
item_code=frappe.db.escape(filters.get("item_code"))
|
||||
)
|
||||
|
||||
return frappe.db.sql(query, filters)
|
||||
|
||||
@@ -714,18 +767,22 @@ def get_healthcare_service_units(doctype, txt, searchfield, start, page_len, fil
|
||||
is_group = 0
|
||||
and company = {company}
|
||||
and name like {txt}""".format(
|
||||
company = frappe.db.escape(filters.get('company')), txt = frappe.db.escape('%{0}%'.format(txt)))
|
||||
company=frappe.db.escape(filters.get("company")), txt=frappe.db.escape("%{0}%".format(txt))
|
||||
)
|
||||
|
||||
if filters and filters.get('inpatient_record'):
|
||||
if filters and filters.get("inpatient_record"):
|
||||
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import (
|
||||
get_current_healthcare_service_unit,
|
||||
)
|
||||
service_unit = get_current_healthcare_service_unit(filters.get('inpatient_record'))
|
||||
|
||||
service_unit = get_current_healthcare_service_unit(filters.get("inpatient_record"))
|
||||
|
||||
# if the patient is admitted, then appointments should be allowed against the admission service unit,
|
||||
# inspite of it being an Inpatient Occupancy service unit
|
||||
if service_unit:
|
||||
query += " and (allow_appointments = 1 or name = {service_unit})".format(service_unit = frappe.db.escape(service_unit))
|
||||
query += " and (allow_appointments = 1 or name = {service_unit})".format(
|
||||
service_unit=frappe.db.escape(service_unit)
|
||||
)
|
||||
else:
|
||||
query += " and allow_appointments = 1"
|
||||
else:
|
||||
@@ -738,27 +795,29 @@ def get_healthcare_service_units(doctype, txt, searchfield, start, page_len, fil
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
item_doc = frappe.get_cached_doc('Item', filters.get('item_code'))
|
||||
item_group = filters.get('item_group')
|
||||
company = filters.get('company')
|
||||
item_doc = frappe.get_cached_doc("Item", filters.get("item_code"))
|
||||
item_group = filters.get("item_group")
|
||||
company = filters.get("company")
|
||||
taxes = item_doc.taxes or []
|
||||
|
||||
while item_group:
|
||||
item_group_doc = frappe.get_cached_doc('Item Group', item_group)
|
||||
item_group_doc = frappe.get_cached_doc("Item Group", item_group)
|
||||
taxes += item_group_doc.taxes or []
|
||||
item_group = item_group_doc.parent_item_group
|
||||
|
||||
if not taxes:
|
||||
return frappe.get_all('Item Tax Template', filters={'disabled': 0, 'company': company}, as_list=True)
|
||||
return frappe.get_all(
|
||||
"Item Tax Template", filters={"disabled": 0, "company": company}, as_list=True
|
||||
)
|
||||
else:
|
||||
valid_from = filters.get('valid_from')
|
||||
valid_from = filters.get("valid_from")
|
||||
valid_from = valid_from[1] if isinstance(valid_from, list) else valid_from
|
||||
|
||||
args = {
|
||||
'item_code': filters.get('item_code'),
|
||||
'posting_date': valid_from,
|
||||
'tax_category': filters.get('tax_category'),
|
||||
'company': company
|
||||
"item_code": filters.get("item_code"),
|
||||
"posting_date": valid_from,
|
||||
"tax_category": filters.get("tax_category"),
|
||||
"company": company,
|
||||
}
|
||||
|
||||
taxes = _get_item_tax_template(args, taxes, for_validate=True)
|
||||
|
||||
@@ -11,7 +11,9 @@ import erpnext
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
|
||||
|
||||
class StockOverReturnError(frappe.ValidationError): pass
|
||||
class StockOverReturnError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
def validate_return(doc):
|
||||
if not doc.meta.get_field("is_return") or not doc.is_return:
|
||||
@@ -21,32 +23,50 @@ def validate_return(doc):
|
||||
validate_return_against(doc)
|
||||
validate_returned_items(doc)
|
||||
|
||||
|
||||
def validate_return_against(doc):
|
||||
if not frappe.db.exists(doc.doctype, doc.return_against):
|
||||
frappe.throw(_("Invalid {0}: {1}")
|
||||
.format(doc.meta.get_label("return_against"), doc.return_against))
|
||||
frappe.throw(
|
||||
_("Invalid {0}: {1}").format(doc.meta.get_label("return_against"), doc.return_against)
|
||||
)
|
||||
else:
|
||||
ref_doc = frappe.get_doc(doc.doctype, doc.return_against)
|
||||
|
||||
party_type = "customer" if doc.doctype in ("Sales Invoice", "Delivery Note") else "supplier"
|
||||
|
||||
if ref_doc.company == doc.company and ref_doc.get(party_type) == doc.get(party_type) and ref_doc.docstatus == 1:
|
||||
if (
|
||||
ref_doc.company == doc.company
|
||||
and ref_doc.get(party_type) == doc.get(party_type)
|
||||
and ref_doc.docstatus == 1
|
||||
):
|
||||
# validate posting date time
|
||||
return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00")
|
||||
ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00")
|
||||
ref_posting_datetime = "%s %s" % (
|
||||
ref_doc.posting_date,
|
||||
ref_doc.get("posting_time") or "00:00:00",
|
||||
)
|
||||
|
||||
if get_datetime(return_posting_datetime) < get_datetime(ref_posting_datetime):
|
||||
frappe.throw(_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime)))
|
||||
frappe.throw(
|
||||
_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime))
|
||||
)
|
||||
|
||||
# validate same exchange rate
|
||||
if doc.conversion_rate != ref_doc.conversion_rate:
|
||||
frappe.throw(_("Exchange Rate must be same as {0} {1} ({2})")
|
||||
.format(doc.doctype, doc.return_against, ref_doc.conversion_rate))
|
||||
frappe.throw(
|
||||
_("Exchange Rate must be same as {0} {1} ({2})").format(
|
||||
doc.doctype, doc.return_against, ref_doc.conversion_rate
|
||||
)
|
||||
)
|
||||
|
||||
# validate update stock
|
||||
if doc.doctype == "Sales Invoice" and doc.update_stock and not ref_doc.update_stock:
|
||||
frappe.throw(_("'Update Stock' can not be checked because items are not delivered via {0}")
|
||||
.format(doc.return_against))
|
||||
frappe.throw(
|
||||
_("'Update Stock' can not be checked because items are not delivered via {0}").format(
|
||||
doc.return_against
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def validate_returned_items(doc):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
@@ -54,43 +74,61 @@ def validate_returned_items(doc):
|
||||
valid_items = frappe._dict()
|
||||
|
||||
select_fields = "item_code, qty, stock_qty, rate, parenttype, conversion_factor"
|
||||
if doc.doctype != 'Purchase Invoice':
|
||||
if doc.doctype != "Purchase Invoice":
|
||||
select_fields += ",serial_no, batch_no"
|
||||
|
||||
if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']:
|
||||
if doc.doctype in ["Purchase Invoice", "Purchase Receipt"]:
|
||||
select_fields += ",rejected_qty, received_qty"
|
||||
|
||||
for d in frappe.db.sql("""select {0} from `tab{1} Item` where parent = %s"""
|
||||
.format(select_fields, doc.doctype), doc.return_against, as_dict=1):
|
||||
valid_items = get_ref_item_dict(valid_items, d)
|
||||
for d in frappe.db.sql(
|
||||
"""select {0} from `tab{1} Item` where parent = %s""".format(select_fields, doc.doctype),
|
||||
doc.return_against,
|
||||
as_dict=1,
|
||||
):
|
||||
valid_items = get_ref_item_dict(valid_items, d)
|
||||
|
||||
if doc.doctype in ("Delivery Note", "Sales Invoice"):
|
||||
for d in frappe.db.sql("""select item_code, qty, serial_no, batch_no from `tabPacked Item`
|
||||
where parent = %s""", doc.return_against, as_dict=1):
|
||||
valid_items = get_ref_item_dict(valid_items, d)
|
||||
for d in frappe.db.sql(
|
||||
"""select item_code, qty, serial_no, batch_no from `tabPacked Item`
|
||||
where parent = %s""",
|
||||
doc.return_against,
|
||||
as_dict=1,
|
||||
):
|
||||
valid_items = get_ref_item_dict(valid_items, d)
|
||||
|
||||
already_returned_items = get_already_returned_items(doc)
|
||||
|
||||
# ( not mandatory when it is Purchase Invoice or a Sales Invoice without Update Stock )
|
||||
warehouse_mandatory = not ((doc.doctype=="Purchase Invoice" or doc.doctype=="Sales Invoice") and not doc.update_stock)
|
||||
warehouse_mandatory = not (
|
||||
(doc.doctype == "Purchase Invoice" or doc.doctype == "Sales Invoice") and not doc.update_stock
|
||||
)
|
||||
|
||||
items_returned = False
|
||||
for d in doc.get("items"):
|
||||
if d.item_code and (flt(d.qty) < 0 or flt(d.get('received_qty')) < 0):
|
||||
if d.item_code and (flt(d.qty) < 0 or flt(d.get("received_qty")) < 0):
|
||||
if d.item_code not in valid_items:
|
||||
frappe.throw(_("Row # {0}: Returned Item {1} does not exist in {2} {3}")
|
||||
.format(d.idx, d.item_code, doc.doctype, doc.return_against))
|
||||
frappe.throw(
|
||||
_("Row # {0}: Returned Item {1} does not exist in {2} {3}").format(
|
||||
d.idx, d.item_code, doc.doctype, doc.return_against
|
||||
)
|
||||
)
|
||||
else:
|
||||
ref = valid_items.get(d.item_code, frappe._dict())
|
||||
validate_quantity(doc, d, ref, valid_items, already_returned_items)
|
||||
|
||||
if ref.rate and doc.doctype in ("Delivery Note", "Sales Invoice") and flt(d.rate) > ref.rate:
|
||||
frappe.throw(_("Row # {0}: Rate cannot be greater than the rate used in {1} {2}")
|
||||
.format(d.idx, doc.doctype, doc.return_against))
|
||||
frappe.throw(
|
||||
_("Row # {0}: Rate cannot be greater than the rate used in {1} {2}").format(
|
||||
d.idx, doc.doctype, doc.return_against
|
||||
)
|
||||
)
|
||||
|
||||
elif ref.batch_no and d.batch_no not in ref.batch_no:
|
||||
frappe.throw(_("Row # {0}: Batch No must be same as {1} {2}")
|
||||
.format(d.idx, doc.doctype, doc.return_against))
|
||||
frappe.throw(
|
||||
_("Row # {0}: Batch No must be same as {1} {2}").format(
|
||||
d.idx, doc.doctype, doc.return_against
|
||||
)
|
||||
)
|
||||
|
||||
elif ref.serial_no:
|
||||
if not d.serial_no:
|
||||
@@ -99,11 +137,16 @@ def validate_returned_items(doc):
|
||||
serial_nos = get_serial_nos(d.serial_no)
|
||||
for s in serial_nos:
|
||||
if s not in ref.serial_no:
|
||||
frappe.throw(_("Row # {0}: Serial No {1} does not match with {2} {3}")
|
||||
.format(d.idx, s, doc.doctype, doc.return_against))
|
||||
frappe.throw(
|
||||
_("Row # {0}: Serial No {1} does not match with {2} {3}").format(
|
||||
d.idx, s, doc.doctype, doc.return_against
|
||||
)
|
||||
)
|
||||
|
||||
if (warehouse_mandatory and not d.get("warehouse") and
|
||||
frappe.db.get_value("Item", d.item_code, "is_stock_item")
|
||||
if (
|
||||
warehouse_mandatory
|
||||
and not d.get("warehouse")
|
||||
and frappe.db.get_value("Item", d.item_code, "is_stock_item")
|
||||
):
|
||||
frappe.throw(_("Warehouse is mandatory"))
|
||||
|
||||
@@ -115,21 +158,23 @@ def validate_returned_items(doc):
|
||||
if not items_returned:
|
||||
frappe.throw(_("Atleast one item should be entered with negative quantity in return document"))
|
||||
|
||||
|
||||
def validate_quantity(doc, args, ref, valid_items, already_returned_items):
|
||||
fields = ['stock_qty']
|
||||
if doc.doctype in ['Purchase Receipt', 'Purchase Invoice']:
|
||||
fields.extend(['received_qty', 'rejected_qty'])
|
||||
fields = ["stock_qty"]
|
||||
if doc.doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
fields.extend(["received_qty", "rejected_qty"])
|
||||
|
||||
already_returned_data = already_returned_items.get(args.item_code) or {}
|
||||
|
||||
company_currency = erpnext.get_company_currency(doc.company)
|
||||
stock_qty_precision = get_field_precision(frappe.get_meta(doc.doctype + " Item")
|
||||
.get_field("stock_qty"), company_currency)
|
||||
stock_qty_precision = get_field_precision(
|
||||
frappe.get_meta(doc.doctype + " Item").get_field("stock_qty"), company_currency
|
||||
)
|
||||
|
||||
for column in fields:
|
||||
returned_qty = flt(already_returned_data.get(column, 0)) if len(already_returned_data) > 0 else 0
|
||||
|
||||
if column == 'stock_qty':
|
||||
if column == "stock_qty":
|
||||
reference_qty = ref.get(column)
|
||||
current_stock_qty = args.get(column)
|
||||
else:
|
||||
@@ -137,38 +182,49 @@ def validate_quantity(doc, args, ref, valid_items, already_returned_items):
|
||||
current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0)
|
||||
|
||||
max_returnable_qty = flt(reference_qty, stock_qty_precision) - returned_qty
|
||||
label = column.replace('_', ' ').title()
|
||||
label = column.replace("_", " ").title()
|
||||
|
||||
if reference_qty:
|
||||
if flt(args.get(column)) > 0:
|
||||
frappe.throw(_("{0} must be negative in return document").format(label))
|
||||
elif returned_qty >= reference_qty and args.get(column):
|
||||
frappe.throw(_("Item {0} has already been returned")
|
||||
.format(args.item_code), StockOverReturnError)
|
||||
frappe.throw(
|
||||
_("Item {0} has already been returned").format(args.item_code), StockOverReturnError
|
||||
)
|
||||
elif abs(flt(current_stock_qty, stock_qty_precision)) > max_returnable_qty:
|
||||
frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}")
|
||||
.format(args.idx, max_returnable_qty, args.item_code), StockOverReturnError)
|
||||
frappe.throw(
|
||||
_("Row # {0}: Cannot return more than {1} for Item {2}").format(
|
||||
args.idx, max_returnable_qty, args.item_code
|
||||
),
|
||||
StockOverReturnError,
|
||||
)
|
||||
|
||||
|
||||
def get_ref_item_dict(valid_items, ref_item_row):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
valid_items.setdefault(ref_item_row.item_code, frappe._dict({
|
||||
"qty": 0,
|
||||
"rate": 0,
|
||||
"stock_qty": 0,
|
||||
"rejected_qty": 0,
|
||||
"received_qty": 0,
|
||||
"serial_no": [],
|
||||
"conversion_factor": ref_item_row.get("conversion_factor", 1),
|
||||
"batch_no": []
|
||||
}))
|
||||
valid_items.setdefault(
|
||||
ref_item_row.item_code,
|
||||
frappe._dict(
|
||||
{
|
||||
"qty": 0,
|
||||
"rate": 0,
|
||||
"stock_qty": 0,
|
||||
"rejected_qty": 0,
|
||||
"received_qty": 0,
|
||||
"serial_no": [],
|
||||
"conversion_factor": ref_item_row.get("conversion_factor", 1),
|
||||
"batch_no": [],
|
||||
}
|
||||
),
|
||||
)
|
||||
item_dict = valid_items[ref_item_row.item_code]
|
||||
item_dict["qty"] += ref_item_row.qty
|
||||
item_dict["stock_qty"] += ref_item_row.get('stock_qty', 0)
|
||||
item_dict["stock_qty"] += ref_item_row.get("stock_qty", 0)
|
||||
if ref_item_row.get("rate", 0) > item_dict["rate"]:
|
||||
item_dict["rate"] = ref_item_row.get("rate", 0)
|
||||
|
||||
if ref_item_row.parenttype in ['Purchase Invoice', 'Purchase Receipt']:
|
||||
if ref_item_row.parenttype in ["Purchase Invoice", "Purchase Receipt"]:
|
||||
item_dict["received_qty"] += ref_item_row.received_qty
|
||||
item_dict["rejected_qty"] += ref_item_row.rejected_qty
|
||||
|
||||
@@ -180,13 +236,15 @@ def get_ref_item_dict(valid_items, ref_item_row):
|
||||
|
||||
return valid_items
|
||||
|
||||
|
||||
def get_already_returned_items(doc):
|
||||
column = 'child.item_code, sum(abs(child.qty)) as qty, sum(abs(child.stock_qty)) as stock_qty'
|
||||
if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']:
|
||||
column = "child.item_code, sum(abs(child.qty)) as qty, sum(abs(child.stock_qty)) as stock_qty"
|
||||
if doc.doctype in ["Purchase Invoice", "Purchase Receipt"]:
|
||||
column += """, sum(abs(child.rejected_qty) * child.conversion_factor) as rejected_qty,
|
||||
sum(abs(child.received_qty) * child.conversion_factor) as received_qty"""
|
||||
|
||||
data = frappe.db.sql("""
|
||||
data = frappe.db.sql(
|
||||
"""
|
||||
select {0}
|
||||
from
|
||||
`tab{1} Item` child, `tab{2}` par
|
||||
@@ -194,62 +252,79 @@ def get_already_returned_items(doc):
|
||||
child.parent = par.name and par.docstatus = 1
|
||||
and par.is_return = 1 and par.return_against = %s
|
||||
group by item_code
|
||||
""".format(column, doc.doctype, doc.doctype), doc.return_against, as_dict=1)
|
||||
""".format(
|
||||
column, doc.doctype, doc.doctype
|
||||
),
|
||||
doc.return_against,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
items = {}
|
||||
|
||||
for d in data:
|
||||
items.setdefault(d.item_code, frappe._dict({
|
||||
"qty": d.get("qty"),
|
||||
"stock_qty": d.get("stock_qty"),
|
||||
"received_qty": d.get("received_qty"),
|
||||
"rejected_qty": d.get("rejected_qty")
|
||||
}))
|
||||
items.setdefault(
|
||||
d.item_code,
|
||||
frappe._dict(
|
||||
{
|
||||
"qty": d.get("qty"),
|
||||
"stock_qty": d.get("stock_qty"),
|
||||
"received_qty": d.get("received_qty"),
|
||||
"rejected_qty": d.get("rejected_qty"),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
return items
|
||||
|
||||
|
||||
def get_returned_qty_map_for_row(return_against, party, row_name, doctype):
|
||||
child_doctype = doctype + " Item"
|
||||
reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype)
|
||||
|
||||
if doctype in ('Purchase Receipt', 'Purchase Invoice'):
|
||||
party_type = 'supplier'
|
||||
if doctype in ("Purchase Receipt", "Purchase Invoice"):
|
||||
party_type = "supplier"
|
||||
else:
|
||||
party_type = 'customer'
|
||||
party_type = "customer"
|
||||
|
||||
fields = [
|
||||
"sum(abs(`tab{0}`.qty)) as qty".format(child_doctype),
|
||||
"sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype)
|
||||
"sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype),
|
||||
]
|
||||
|
||||
if doctype in ("Purchase Receipt", "Purchase Invoice"):
|
||||
fields += [
|
||||
"sum(abs(`tab{0}`.rejected_qty)) as rejected_qty".format(child_doctype),
|
||||
"sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype)
|
||||
"sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype),
|
||||
]
|
||||
|
||||
if doctype == "Purchase Receipt":
|
||||
fields += ["sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)]
|
||||
|
||||
# Used retrun against and supplier and is_retrun because there is an index added for it
|
||||
data = frappe.db.get_list(doctype,
|
||||
fields = fields,
|
||||
filters = [
|
||||
data = frappe.db.get_list(
|
||||
doctype,
|
||||
fields=fields,
|
||||
filters=[
|
||||
[doctype, "return_against", "=", return_against],
|
||||
[doctype, party_type, "=", party],
|
||||
[doctype, "docstatus", "=", 1],
|
||||
[doctype, "is_return", "=", 1],
|
||||
[child_doctype, reference_field, "=", row_name]
|
||||
])
|
||||
[child_doctype, reference_field, "=", row_name],
|
||||
],
|
||||
)
|
||||
|
||||
return data[0]
|
||||
|
||||
|
||||
def make_return_doc(doctype, source_name, target_doc=None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
company = frappe.db.get_value("Delivery Note", source_name, "company")
|
||||
default_warehouse_for_sales_return = frappe.db.get_value("Company", company, "default_warehouse_for_sales_return")
|
||||
default_warehouse_for_sales_return = frappe.db.get_value(
|
||||
"Company", company, "default_warehouse_for_sales_return"
|
||||
)
|
||||
|
||||
def set_missing_values(source, target):
|
||||
doc = frappe.get_doc(target)
|
||||
@@ -273,29 +348,34 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
tax.tax_amount = -1 * tax.tax_amount
|
||||
|
||||
if doc.get("is_return"):
|
||||
if doc.doctype == 'Sales Invoice' or doc.doctype == 'POS Invoice':
|
||||
if doc.doctype == "Sales Invoice" or doc.doctype == "POS Invoice":
|
||||
doc.consolidated_invoice = ""
|
||||
doc.set('payments', [])
|
||||
doc.set("payments", [])
|
||||
for data in source.payments:
|
||||
paid_amount = 0.00
|
||||
base_paid_amount = 0.00
|
||||
data.base_amount = flt(data.amount*source.conversion_rate, source.precision("base_paid_amount"))
|
||||
data.base_amount = flt(
|
||||
data.amount * source.conversion_rate, source.precision("base_paid_amount")
|
||||
)
|
||||
paid_amount += data.amount
|
||||
base_paid_amount += data.base_amount
|
||||
doc.append('payments', {
|
||||
'mode_of_payment': data.mode_of_payment,
|
||||
'type': data.type,
|
||||
'amount': -1 * paid_amount,
|
||||
'base_amount': -1 * base_paid_amount,
|
||||
'account': data.account,
|
||||
'default': data.default
|
||||
})
|
||||
doc.append(
|
||||
"payments",
|
||||
{
|
||||
"mode_of_payment": data.mode_of_payment,
|
||||
"type": data.type,
|
||||
"amount": -1 * paid_amount,
|
||||
"base_amount": -1 * base_paid_amount,
|
||||
"account": data.account,
|
||||
"default": data.default,
|
||||
},
|
||||
)
|
||||
if doc.is_pos:
|
||||
doc.paid_amount = -1 * source.paid_amount
|
||||
elif doc.doctype == 'Purchase Invoice':
|
||||
elif doc.doctype == "Purchase Invoice":
|
||||
doc.paid_amount = -1 * source.paid_amount
|
||||
doc.base_paid_amount = -1 * source.base_paid_amount
|
||||
doc.payment_terms_template = ''
|
||||
doc.payment_terms_template = ""
|
||||
doc.payment_schedule = []
|
||||
|
||||
if doc.get("is_return") and hasattr(doc, "packed_items"):
|
||||
@@ -312,16 +392,24 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
returned_serial_nos = get_returned_serial_nos(source_doc, source_parent)
|
||||
serial_nos = list(set(get_serial_nos(source_doc.serial_no)) - set(returned_serial_nos))
|
||||
if serial_nos:
|
||||
target_doc.serial_no = '\n'.join(serial_nos)
|
||||
target_doc.serial_no = "\n".join(serial_nos)
|
||||
|
||||
if doctype == "Purchase Receipt":
|
||||
returned_qty_map = get_returned_qty_map_for_row(source_parent.name, source_parent.supplier, source_doc.name, doctype)
|
||||
target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
|
||||
target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
|
||||
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
|
||||
returned_qty_map = get_returned_qty_map_for_row(
|
||||
source_parent.name, source_parent.supplier, source_doc.name, doctype
|
||||
)
|
||||
target_doc.received_qty = -1 * flt(
|
||||
source_doc.received_qty - (returned_qty_map.get("received_qty") or 0)
|
||||
)
|
||||
target_doc.rejected_qty = -1 * flt(
|
||||
source_doc.rejected_qty - (returned_qty_map.get("rejected_qty") or 0)
|
||||
)
|
||||
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0))
|
||||
|
||||
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
|
||||
target_doc.received_stock_qty = -1 * flt(source_doc.received_stock_qty - (returned_qty_map.get('received_stock_qty') or 0))
|
||||
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0))
|
||||
target_doc.received_stock_qty = -1 * flt(
|
||||
source_doc.received_stock_qty - (returned_qty_map.get("received_stock_qty") or 0)
|
||||
)
|
||||
|
||||
target_doc.purchase_order = source_doc.purchase_order
|
||||
target_doc.purchase_order_item = source_doc.purchase_order_item
|
||||
@@ -329,12 +417,18 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
target_doc.purchase_receipt_item = source_doc.name
|
||||
|
||||
elif doctype == "Purchase Invoice":
|
||||
returned_qty_map = get_returned_qty_map_for_row(source_parent.name, source_parent.supplier, source_doc.name, doctype)
|
||||
target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
|
||||
target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
|
||||
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
|
||||
returned_qty_map = get_returned_qty_map_for_row(
|
||||
source_parent.name, source_parent.supplier, source_doc.name, doctype
|
||||
)
|
||||
target_doc.received_qty = -1 * flt(
|
||||
source_doc.received_qty - (returned_qty_map.get("received_qty") or 0)
|
||||
)
|
||||
target_doc.rejected_qty = -1 * flt(
|
||||
source_doc.rejected_qty - (returned_qty_map.get("rejected_qty") or 0)
|
||||
)
|
||||
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0))
|
||||
|
||||
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
|
||||
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0))
|
||||
target_doc.purchase_order = source_doc.purchase_order
|
||||
target_doc.purchase_receipt = source_doc.purchase_receipt
|
||||
target_doc.rejected_warehouse = source_doc.rejected_warehouse
|
||||
@@ -343,9 +437,11 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
target_doc.purchase_invoice_item = source_doc.name
|
||||
|
||||
elif doctype == "Delivery Note":
|
||||
returned_qty_map = get_returned_qty_map_for_row(source_parent.name, source_parent.customer, source_doc.name, doctype)
|
||||
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
|
||||
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
|
||||
returned_qty_map = get_returned_qty_map_for_row(
|
||||
source_parent.name, source_parent.customer, source_doc.name, doctype
|
||||
)
|
||||
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0))
|
||||
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0))
|
||||
|
||||
target_doc.against_sales_order = source_doc.against_sales_order
|
||||
target_doc.against_sales_invoice = source_doc.against_sales_invoice
|
||||
@@ -356,9 +452,11 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
if default_warehouse_for_sales_return:
|
||||
target_doc.warehouse = default_warehouse_for_sales_return
|
||||
elif doctype == "Sales Invoice" or doctype == "POS Invoice":
|
||||
returned_qty_map = get_returned_qty_map_for_row(source_parent.name, source_parent.customer, source_doc.name, doctype)
|
||||
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
|
||||
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
|
||||
returned_qty_map = get_returned_qty_map_for_row(
|
||||
source_parent.name, source_parent.customer, source_doc.name, doctype
|
||||
)
|
||||
target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get("qty") or 0))
|
||||
target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get("stock_qty") or 0))
|
||||
|
||||
target_doc.sales_order = source_doc.sales_order
|
||||
target_doc.delivery_note = source_doc.delivery_note
|
||||
@@ -377,41 +475,56 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
def update_terms(source_doc, target_doc, source_parent):
|
||||
target_doc.payment_amount = -source_doc.payment_amount
|
||||
|
||||
doclist = get_mapped_doc(doctype, source_name, {
|
||||
doctype: {
|
||||
"doctype": doctype,
|
||||
|
||||
"validation": {
|
||||
"docstatus": ["=", 1],
|
||||
}
|
||||
},
|
||||
doctype +" Item": {
|
||||
"doctype": doctype + " Item",
|
||||
"field_map": {
|
||||
"serial_no": "serial_no",
|
||||
"batch_no": "batch_no"
|
||||
doclist = get_mapped_doc(
|
||||
doctype,
|
||||
source_name,
|
||||
{
|
||||
doctype: {
|
||||
"doctype": doctype,
|
||||
"validation": {
|
||||
"docstatus": ["=", 1],
|
||||
},
|
||||
},
|
||||
"postprocess": update_item
|
||||
doctype
|
||||
+ " Item": {
|
||||
"doctype": doctype + " Item",
|
||||
"field_map": {"serial_no": "serial_no", "batch_no": "batch_no"},
|
||||
"postprocess": update_item,
|
||||
},
|
||||
"Payment Schedule": {"doctype": "Payment Schedule", "postprocess": update_terms},
|
||||
},
|
||||
"Payment Schedule": {
|
||||
"doctype": "Payment Schedule",
|
||||
"postprocess": update_terms
|
||||
}
|
||||
}, target_doc, set_missing_values)
|
||||
target_doc,
|
||||
set_missing_values,
|
||||
)
|
||||
|
||||
doclist.set_onload('ignore_price_list', True)
|
||||
doclist.set_onload("ignore_price_list", True)
|
||||
|
||||
return doclist
|
||||
|
||||
def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None,
|
||||
item_row=None, voucher_detail_no=None, sle=None):
|
||||
|
||||
def get_rate_for_return(
|
||||
voucher_type,
|
||||
voucher_no,
|
||||
item_code,
|
||||
return_against=None,
|
||||
item_row=None,
|
||||
voucher_detail_no=None,
|
||||
sle=None,
|
||||
):
|
||||
if not return_against:
|
||||
return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against")
|
||||
|
||||
return_against_item_field = get_return_against_item_fields(voucher_type)
|
||||
|
||||
filters = get_filters(voucher_type, voucher_no, voucher_detail_no,
|
||||
return_against, item_code, return_against_item_field, item_row)
|
||||
filters = get_filters(
|
||||
voucher_type,
|
||||
voucher_no,
|
||||
voucher_detail_no,
|
||||
return_against,
|
||||
item_code,
|
||||
return_against_item_field,
|
||||
item_row,
|
||||
)
|
||||
|
||||
if voucher_type in ("Purchase Receipt", "Purchase Invoice"):
|
||||
select_field = "incoming_rate"
|
||||
@@ -419,52 +532,65 @@ def get_rate_for_return(voucher_type, voucher_no, item_code, return_against=None
|
||||
select_field = "abs(stock_value_difference / actual_qty)"
|
||||
|
||||
rate = flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
|
||||
if not (rate and return_against) and voucher_type in ['Sales Invoice', 'Delivery Note']:
|
||||
rate = frappe.db.get_value(f'{voucher_type} Item', voucher_detail_no, 'incoming_rate')
|
||||
if not (rate and return_against) and voucher_type in ["Sales Invoice", "Delivery Note"]:
|
||||
rate = frappe.db.get_value(f"{voucher_type} Item", voucher_detail_no, "incoming_rate")
|
||||
|
||||
if not rate and sle:
|
||||
rate = get_incoming_rate({
|
||||
"item_code": sle.item_code,
|
||||
"warehouse": sle.warehouse,
|
||||
"posting_date": sle.get('posting_date'),
|
||||
"posting_time": sle.get('posting_time'),
|
||||
"qty": sle.actual_qty,
|
||||
"serial_no": sle.get('serial_no'),
|
||||
"company": sle.company,
|
||||
"voucher_type": sle.voucher_type,
|
||||
"voucher_no": sle.voucher_no
|
||||
}, raise_error_if_no_rate=False)
|
||||
rate = get_incoming_rate(
|
||||
{
|
||||
"item_code": sle.item_code,
|
||||
"warehouse": sle.warehouse,
|
||||
"posting_date": sle.get("posting_date"),
|
||||
"posting_time": sle.get("posting_time"),
|
||||
"qty": sle.actual_qty,
|
||||
"serial_no": sle.get("serial_no"),
|
||||
"company": sle.company,
|
||||
"voucher_type": sle.voucher_type,
|
||||
"voucher_no": sle.voucher_no,
|
||||
},
|
||||
raise_error_if_no_rate=False,
|
||||
)
|
||||
|
||||
return rate
|
||||
|
||||
|
||||
def get_return_against_item_fields(voucher_type):
|
||||
return_against_item_fields = {
|
||||
"Purchase Receipt": "purchase_receipt_item",
|
||||
"Purchase Invoice": "purchase_invoice_item",
|
||||
"Delivery Note": "dn_detail",
|
||||
"Sales Invoice": "sales_invoice_item"
|
||||
"Sales Invoice": "sales_invoice_item",
|
||||
}
|
||||
return return_against_item_fields[voucher_type]
|
||||
|
||||
def get_filters(voucher_type, voucher_no, voucher_detail_no, return_against, item_code, return_against_item_field, item_row):
|
||||
filters = {
|
||||
"voucher_type": voucher_type,
|
||||
"voucher_no": return_against,
|
||||
"item_code": item_code
|
||||
}
|
||||
|
||||
def get_filters(
|
||||
voucher_type,
|
||||
voucher_no,
|
||||
voucher_detail_no,
|
||||
return_against,
|
||||
item_code,
|
||||
return_against_item_field,
|
||||
item_row,
|
||||
):
|
||||
filters = {"voucher_type": voucher_type, "voucher_no": return_against, "item_code": item_code}
|
||||
|
||||
if item_row:
|
||||
reference_voucher_detail_no = item_row.get(return_against_item_field)
|
||||
else:
|
||||
reference_voucher_detail_no = frappe.db.get_value(voucher_type + " Item", voucher_detail_no, return_against_item_field)
|
||||
reference_voucher_detail_no = frappe.db.get_value(
|
||||
voucher_type + " Item", voucher_detail_no, return_against_item_field
|
||||
)
|
||||
|
||||
if reference_voucher_detail_no:
|
||||
filters["voucher_detail_no"] = reference_voucher_detail_no
|
||||
|
||||
return filters
|
||||
|
||||
|
||||
def get_returned_serial_nos(child_doc, parent_doc):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
return_ref_field = frappe.scrub(child_doc.doctype)
|
||||
if child_doc.doctype == "Delivery Note Item":
|
||||
return_ref_field = "dn_detail"
|
||||
@@ -473,10 +599,14 @@ def get_returned_serial_nos(child_doc, parent_doc):
|
||||
|
||||
fields = ["`{0}`.`serial_no`".format("tab" + child_doc.doctype)]
|
||||
|
||||
filters = [[parent_doc.doctype, "return_against", "=", parent_doc.name], [parent_doc.doctype, "is_return", "=", 1],
|
||||
[child_doc.doctype, return_ref_field, "=", child_doc.name], [parent_doc.doctype, "docstatus", "=", 1]]
|
||||
filters = [
|
||||
[parent_doc.doctype, "return_against", "=", parent_doc.name],
|
||||
[parent_doc.doctype, "is_return", "=", 1],
|
||||
[child_doc.doctype, return_ref_field, "=", child_doc.name],
|
||||
[parent_doc.doctype, "docstatus", "=", 1],
|
||||
]
|
||||
|
||||
for row in frappe.get_all(parent_doc.doctype, fields = fields, filters=filters):
|
||||
for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
|
||||
serial_nos.extend(get_serial_nos(row.serial_no))
|
||||
|
||||
return serial_nos
|
||||
|
||||
@@ -17,8 +17,7 @@ from erpnext.stock.utils import get_incoming_rate
|
||||
|
||||
class SellingController(StockController):
|
||||
def get_feed(self):
|
||||
return _("To {0} | {1} {2}").format(self.customer_name, self.currency,
|
||||
self.grand_total)
|
||||
return _("To {0} | {1} {2}").format(self.customer_name, self.currency, self.grand_total)
|
||||
|
||||
def onload(self):
|
||||
super(SellingController, self).onload()
|
||||
@@ -64,32 +63,43 @@ class SellingController(StockController):
|
||||
|
||||
if customer:
|
||||
from erpnext.accounts.party import _get_party_details
|
||||
|
||||
fetch_payment_terms_template = False
|
||||
if (self.get("__islocal") or
|
||||
self.company != frappe.db.get_value(self.doctype, self.name, 'company')):
|
||||
if self.get("__islocal") or self.company != frappe.db.get_value(
|
||||
self.doctype, self.name, "company"
|
||||
):
|
||||
fetch_payment_terms_template = True
|
||||
|
||||
party_details = _get_party_details(customer,
|
||||
party_details = _get_party_details(
|
||||
customer,
|
||||
ignore_permissions=self.flags.ignore_permissions,
|
||||
doctype=self.doctype, company=self.company,
|
||||
posting_date=self.get('posting_date'),
|
||||
doctype=self.doctype,
|
||||
company=self.company,
|
||||
posting_date=self.get("posting_date"),
|
||||
fetch_payment_terms_template=fetch_payment_terms_template,
|
||||
party_address=self.customer_address, shipping_address=self.shipping_address_name,
|
||||
company_address=self.get('company_address'))
|
||||
party_address=self.customer_address,
|
||||
shipping_address=self.shipping_address_name,
|
||||
company_address=self.get("company_address"),
|
||||
)
|
||||
if not self.meta.get_field("sales_team"):
|
||||
party_details.pop("sales_team")
|
||||
self.update_if_missing(party_details)
|
||||
|
||||
elif lead:
|
||||
from erpnext.crm.doctype.lead.lead import get_lead_details
|
||||
self.update_if_missing(get_lead_details(lead,
|
||||
posting_date=self.get('transaction_date') or self.get('posting_date'),
|
||||
company=self.company))
|
||||
|
||||
if self.get('taxes_and_charges') and not self.get('taxes') and not for_validate:
|
||||
taxes = get_taxes_and_charges('Sales Taxes and Charges Template', self.taxes_and_charges)
|
||||
self.update_if_missing(
|
||||
get_lead_details(
|
||||
lead,
|
||||
posting_date=self.get("transaction_date") or self.get("posting_date"),
|
||||
company=self.company,
|
||||
)
|
||||
)
|
||||
|
||||
if self.get("taxes_and_charges") and not self.get("taxes") and not for_validate:
|
||||
taxes = get_taxes_and_charges("Sales Taxes and Charges Template", self.taxes_and_charges)
|
||||
for tax in taxes:
|
||||
self.append('taxes', tax)
|
||||
self.append("taxes", tax)
|
||||
|
||||
def set_price_list_and_item_details(self, for_validate=False):
|
||||
self.set_price_list_currency("Selling")
|
||||
@@ -98,12 +108,15 @@ class SellingController(StockController):
|
||||
def remove_shipping_charge(self):
|
||||
if self.shipping_rule:
|
||||
shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule)
|
||||
existing_shipping_charge = self.get("taxes", {
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"charge_type": "Actual",
|
||||
"account_head": shipping_rule.account,
|
||||
"cost_center": shipping_rule.cost_center
|
||||
})
|
||||
existing_shipping_charge = self.get(
|
||||
"taxes",
|
||||
{
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"charge_type": "Actual",
|
||||
"account_head": shipping_rule.account,
|
||||
"cost_center": shipping_rule.cost_center,
|
||||
},
|
||||
)
|
||||
if existing_shipping_charge:
|
||||
self.get("taxes").remove(existing_shipping_charge[-1])
|
||||
self.calculate_taxes_and_totals()
|
||||
@@ -112,8 +125,9 @@ class SellingController(StockController):
|
||||
from frappe.utils import money_in_words
|
||||
|
||||
if self.meta.get_field("base_in_words"):
|
||||
base_amount = abs(self.base_grand_total
|
||||
if self.is_rounded_total_disabled() else self.base_rounded_total)
|
||||
base_amount = abs(
|
||||
self.base_grand_total if self.is_rounded_total_disabled() else self.base_rounded_total
|
||||
)
|
||||
self.base_in_words = money_in_words(base_amount, self.company_currency)
|
||||
|
||||
if self.meta.get_field("in_words"):
|
||||
@@ -124,15 +138,15 @@ class SellingController(StockController):
|
||||
if not self.meta.get_field("commission_rate"):
|
||||
return
|
||||
|
||||
self.round_floats_in(
|
||||
self, ("amount_eligible_for_commission", "commission_rate")
|
||||
)
|
||||
self.round_floats_in(self, ("amount_eligible_for_commission", "commission_rate"))
|
||||
|
||||
if not (0 <= self.commission_rate <= 100.0):
|
||||
throw("{} {}".format(
|
||||
_(self.meta.get_label("commission_rate")),
|
||||
_("must be between 0 and 100"),
|
||||
))
|
||||
throw(
|
||||
"{} {}".format(
|
||||
_(self.meta.get_label("commission_rate")),
|
||||
_("must be between 0 and 100"),
|
||||
)
|
||||
)
|
||||
|
||||
self.amount_eligible_for_commission = sum(
|
||||
item.base_net_amount for item in self.items if item.grant_commission
|
||||
@@ -140,7 +154,7 @@ class SellingController(StockController):
|
||||
|
||||
self.total_commission = flt(
|
||||
self.amount_eligible_for_commission * self.commission_rate / 100.0,
|
||||
self.precision("total_commission")
|
||||
self.precision("total_commission"),
|
||||
)
|
||||
|
||||
def calculate_contribution(self):
|
||||
@@ -154,12 +168,14 @@ class SellingController(StockController):
|
||||
|
||||
sales_person.allocated_amount = flt(
|
||||
self.amount_eligible_for_commission * sales_person.allocated_percentage / 100.0,
|
||||
self.precision("allocated_amount", sales_person))
|
||||
self.precision("allocated_amount", sales_person),
|
||||
)
|
||||
|
||||
if sales_person.commission_rate:
|
||||
sales_person.incentives = flt(
|
||||
sales_person.allocated_amount * flt(sales_person.commission_rate) / 100.0,
|
||||
self.precision("incentives", sales_person))
|
||||
self.precision("incentives", sales_person),
|
||||
)
|
||||
|
||||
total += sales_person.allocated_percentage
|
||||
|
||||
@@ -183,25 +199,29 @@ class SellingController(StockController):
|
||||
|
||||
def validate_selling_price(self):
|
||||
def throw_message(idx, item_name, rate, ref_rate_field):
|
||||
throw(_("""Row #{0}: Selling rate for item {1} is lower than its {2}.
|
||||
throw(
|
||||
_(
|
||||
"""Row #{0}: Selling rate for item {1} is lower than its {2}.
|
||||
Selling {3} should be atleast {4}.<br><br>Alternatively,
|
||||
you can disable selling price validation in {5} to bypass
|
||||
this validation.""").format(
|
||||
idx,
|
||||
bold(item_name),
|
||||
bold(ref_rate_field),
|
||||
bold("net rate"),
|
||||
bold(rate),
|
||||
get_link_to_form("Selling Settings", "Selling Settings"),
|
||||
), title=_("Invalid Selling Price"))
|
||||
this validation."""
|
||||
).format(
|
||||
idx,
|
||||
bold(item_name),
|
||||
bold(ref_rate_field),
|
||||
bold("net rate"),
|
||||
bold(rate),
|
||||
get_link_to_form("Selling Settings", "Selling Settings"),
|
||||
),
|
||||
title=_("Invalid Selling Price"),
|
||||
)
|
||||
|
||||
if (
|
||||
self.get("is_return")
|
||||
or not frappe.db.get_single_value("Selling Settings", "validate_selling_price")
|
||||
if self.get("is_return") or not frappe.db.get_single_value(
|
||||
"Selling Settings", "validate_selling_price"
|
||||
):
|
||||
return
|
||||
|
||||
is_internal_customer = self.get('is_internal_customer')
|
||||
is_internal_customer = self.get("is_internal_customer")
|
||||
valuation_rate_map = {}
|
||||
|
||||
for item in self.items:
|
||||
@@ -212,17 +232,10 @@ class SellingController(StockController):
|
||||
"Item", item.item_code, ("last_purchase_rate", "is_stock_item")
|
||||
)
|
||||
|
||||
last_purchase_rate_in_sales_uom = (
|
||||
last_purchase_rate * (item.conversion_factor or 1)
|
||||
)
|
||||
last_purchase_rate_in_sales_uom = last_purchase_rate * (item.conversion_factor or 1)
|
||||
|
||||
if flt(item.base_net_rate) < flt(last_purchase_rate_in_sales_uom):
|
||||
throw_message(
|
||||
item.idx,
|
||||
item.item_name,
|
||||
last_purchase_rate_in_sales_uom,
|
||||
"last purchase rate"
|
||||
)
|
||||
throw_message(item.idx, item.item_name, last_purchase_rate_in_sales_uom, "last purchase rate")
|
||||
|
||||
if is_internal_customer or not is_stock_item:
|
||||
continue
|
||||
@@ -238,7 +251,8 @@ class SellingController(StockController):
|
||||
for valuation_rate in valuation_rate_map
|
||||
)
|
||||
|
||||
valuation_rates = frappe.db.sql(f"""
|
||||
valuation_rates = frappe.db.sql(
|
||||
f"""
|
||||
select
|
||||
item_code, warehouse, valuation_rate
|
||||
from
|
||||
@@ -246,7 +260,9 @@ class SellingController(StockController):
|
||||
where
|
||||
({" or ".join(or_conditions)})
|
||||
and valuation_rate > 0
|
||||
""", as_dict=True)
|
||||
""",
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
for rate in valuation_rates:
|
||||
valuation_rate_map[(rate.item_code, rate.warehouse)] = rate.valuation_rate
|
||||
@@ -255,24 +271,15 @@ class SellingController(StockController):
|
||||
if not item.item_code or item.is_free_item:
|
||||
continue
|
||||
|
||||
last_valuation_rate = valuation_rate_map.get(
|
||||
(item.item_code, item.warehouse)
|
||||
)
|
||||
last_valuation_rate = valuation_rate_map.get((item.item_code, item.warehouse))
|
||||
|
||||
if not last_valuation_rate:
|
||||
continue
|
||||
|
||||
last_valuation_rate_in_sales_uom = (
|
||||
last_valuation_rate * (item.conversion_factor or 1)
|
||||
)
|
||||
last_valuation_rate_in_sales_uom = last_valuation_rate * (item.conversion_factor or 1)
|
||||
|
||||
if flt(item.base_net_rate) < flt(last_valuation_rate_in_sales_uom):
|
||||
throw_message(
|
||||
item.idx,
|
||||
item.item_name,
|
||||
last_valuation_rate_in_sales_uom,
|
||||
"valuation rate"
|
||||
)
|
||||
throw_message(item.idx, item.item_name, last_valuation_rate_in_sales_uom, "valuation rate")
|
||||
|
||||
def get_item_list(self):
|
||||
il = []
|
||||
@@ -284,68 +291,90 @@ class SellingController(StockController):
|
||||
for p in self.get("packed_items"):
|
||||
if p.parent_detail_docname == d.name and p.parent_item == d.item_code:
|
||||
# the packing details table's qty is already multiplied with parent's qty
|
||||
il.append(frappe._dict({
|
||||
'warehouse': p.warehouse or d.warehouse,
|
||||
'item_code': p.item_code,
|
||||
'qty': flt(p.qty),
|
||||
'uom': p.uom,
|
||||
'batch_no': cstr(p.batch_no).strip(),
|
||||
'serial_no': cstr(p.serial_no).strip(),
|
||||
'name': d.name,
|
||||
'target_warehouse': p.target_warehouse,
|
||||
'company': self.company,
|
||||
'voucher_type': self.doctype,
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
||||
'sales_invoice_item': d.get("sales_invoice_item"),
|
||||
'dn_detail': d.get("dn_detail"),
|
||||
'incoming_rate': p.get("incoming_rate")
|
||||
}))
|
||||
il.append(
|
||||
frappe._dict(
|
||||
{
|
||||
"warehouse": p.warehouse or d.warehouse,
|
||||
"item_code": p.item_code,
|
||||
"qty": flt(p.qty),
|
||||
"uom": p.uom,
|
||||
"batch_no": cstr(p.batch_no).strip(),
|
||||
"serial_no": cstr(p.serial_no).strip(),
|
||||
"name": d.name,
|
||||
"target_warehouse": p.target_warehouse,
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
"allow_zero_valuation": d.allow_zero_valuation_rate,
|
||||
"sales_invoice_item": d.get("sales_invoice_item"),
|
||||
"dn_detail": d.get("dn_detail"),
|
||||
"incoming_rate": p.get("incoming_rate"),
|
||||
}
|
||||
)
|
||||
)
|
||||
else:
|
||||
il.append(frappe._dict({
|
||||
'warehouse': d.warehouse,
|
||||
'item_code': d.item_code,
|
||||
'qty': d.stock_qty,
|
||||
'uom': d.uom,
|
||||
'stock_uom': d.stock_uom,
|
||||
'conversion_factor': d.conversion_factor,
|
||||
'batch_no': cstr(d.get("batch_no")).strip(),
|
||||
'serial_no': cstr(d.get("serial_no")).strip(),
|
||||
'name': d.name,
|
||||
'target_warehouse': d.target_warehouse,
|
||||
'company': self.company,
|
||||
'voucher_type': self.doctype,
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
||||
'sales_invoice_item': d.get("sales_invoice_item"),
|
||||
'dn_detail': d.get("dn_detail"),
|
||||
'incoming_rate': d.get("incoming_rate")
|
||||
}))
|
||||
il.append(
|
||||
frappe._dict(
|
||||
{
|
||||
"warehouse": d.warehouse,
|
||||
"item_code": d.item_code,
|
||||
"qty": d.stock_qty,
|
||||
"uom": d.uom,
|
||||
"stock_uom": d.stock_uom,
|
||||
"conversion_factor": d.conversion_factor,
|
||||
"batch_no": cstr(d.get("batch_no")).strip(),
|
||||
"serial_no": cstr(d.get("serial_no")).strip(),
|
||||
"name": d.name,
|
||||
"target_warehouse": d.target_warehouse,
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
"allow_zero_valuation": d.allow_zero_valuation_rate,
|
||||
"sales_invoice_item": d.get("sales_invoice_item"),
|
||||
"dn_detail": d.get("dn_detail"),
|
||||
"incoming_rate": d.get("incoming_rate"),
|
||||
}
|
||||
)
|
||||
)
|
||||
return il
|
||||
|
||||
def has_product_bundle(self, item_code):
|
||||
return frappe.db.sql("""select name from `tabProduct Bundle`
|
||||
where new_item_code=%s and docstatus != 2""", item_code)
|
||||
return frappe.db.sql(
|
||||
"""select name from `tabProduct Bundle`
|
||||
where new_item_code=%s and docstatus != 2""",
|
||||
item_code,
|
||||
)
|
||||
|
||||
def get_already_delivered_qty(self, current_docname, so, so_detail):
|
||||
delivered_via_dn = frappe.db.sql("""select sum(qty) from `tabDelivery Note Item`
|
||||
delivered_via_dn = frappe.db.sql(
|
||||
"""select sum(qty) from `tabDelivery Note Item`
|
||||
where so_detail = %s and docstatus = 1
|
||||
and against_sales_order = %s
|
||||
and parent != %s""", (so_detail, so, current_docname))
|
||||
and parent != %s""",
|
||||
(so_detail, so, current_docname),
|
||||
)
|
||||
|
||||
delivered_via_si = frappe.db.sql("""select sum(si_item.qty)
|
||||
delivered_via_si = frappe.db.sql(
|
||||
"""select sum(si_item.qty)
|
||||
from `tabSales Invoice Item` si_item, `tabSales Invoice` si
|
||||
where si_item.parent = si.name and si.update_stock = 1
|
||||
and si_item.so_detail = %s and si.docstatus = 1
|
||||
and si_item.sales_order = %s
|
||||
and si.name != %s""", (so_detail, so, current_docname))
|
||||
and si.name != %s""",
|
||||
(so_detail, so, current_docname),
|
||||
)
|
||||
|
||||
total_delivered_qty = (flt(delivered_via_dn[0][0]) if delivered_via_dn else 0) \
|
||||
+ (flt(delivered_via_si[0][0]) if delivered_via_si else 0)
|
||||
total_delivered_qty = (flt(delivered_via_dn[0][0]) if delivered_via_dn else 0) + (
|
||||
flt(delivered_via_si[0][0]) if delivered_via_si else 0
|
||||
)
|
||||
|
||||
return total_delivered_qty
|
||||
|
||||
def get_so_qty_and_warehouse(self, so_detail):
|
||||
so_item = frappe.db.sql("""select qty, warehouse from `tabSales Order Item`
|
||||
where name = %s and docstatus = 1""", so_detail, as_dict=1)
|
||||
so_item = frappe.db.sql(
|
||||
"""select qty, warehouse from `tabSales Order Item`
|
||||
where name = %s and docstatus = 1""",
|
||||
so_detail,
|
||||
as_dict=1,
|
||||
)
|
||||
so_qty = so_item and flt(so_item[0]["qty"]) or 0.0
|
||||
so_warehouse = so_item and so_item[0]["warehouse"] or ""
|
||||
return so_qty, so_warehouse
|
||||
@@ -371,8 +400,9 @@ class SellingController(StockController):
|
||||
sales_order = frappe.get_doc("Sales Order", so)
|
||||
|
||||
if sales_order.status in ["Closed", "Cancelled"]:
|
||||
frappe.throw(_("{0} {1} is cancelled or closed").format(_("Sales Order"), so),
|
||||
frappe.InvalidStatusError)
|
||||
frappe.throw(
|
||||
_("{0} {1} is cancelled or closed").format(_("Sales Order"), so), frappe.InvalidStatusError
|
||||
)
|
||||
|
||||
sales_order.update_reserved_qty(so_item_rows)
|
||||
|
||||
@@ -384,42 +414,51 @@ class SellingController(StockController):
|
||||
for d in items:
|
||||
if not self.get("return_against"):
|
||||
# Get incoming rate based on original item cost based on valuation method
|
||||
qty = flt(d.get('stock_qty') or d.get('actual_qty'))
|
||||
qty = flt(d.get("stock_qty") or d.get("actual_qty"))
|
||||
|
||||
if not (self.get("is_return") and d.incoming_rate):
|
||||
d.incoming_rate = get_incoming_rate({
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.warehouse,
|
||||
"posting_date": self.get('posting_date') or self.get('transaction_date'),
|
||||
"posting_time": self.get('posting_time') or nowtime(),
|
||||
"qty": qty if cint(self.get("is_return")) else (-1 * qty),
|
||||
"serial_no": d.get('serial_no'),
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"allow_zero_valuation": d.get("allow_zero_valuation")
|
||||
}, raise_error_if_no_rate=False)
|
||||
d.incoming_rate = get_incoming_rate(
|
||||
{
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.warehouse,
|
||||
"posting_date": self.get("posting_date") or self.get("transaction_date"),
|
||||
"posting_time": self.get("posting_time") or nowtime(),
|
||||
"qty": qty if cint(self.get("is_return")) else (-1 * qty),
|
||||
"serial_no": d.get("serial_no"),
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"allow_zero_valuation": d.get("allow_zero_valuation"),
|
||||
},
|
||||
raise_error_if_no_rate=False,
|
||||
)
|
||||
|
||||
# For internal transfers use incoming rate as the valuation rate
|
||||
if self.is_internal_transfer():
|
||||
if d.doctype == "Packed Item":
|
||||
incoming_rate = flt(d.incoming_rate * d.conversion_factor, d.precision('incoming_rate'))
|
||||
incoming_rate = flt(d.incoming_rate * d.conversion_factor, d.precision("incoming_rate"))
|
||||
if d.incoming_rate != incoming_rate:
|
||||
d.incoming_rate = incoming_rate
|
||||
else:
|
||||
rate = flt(d.incoming_rate * d.conversion_factor, d.precision('rate'))
|
||||
rate = flt(d.incoming_rate * d.conversion_factor, d.precision("rate"))
|
||||
if d.rate != rate:
|
||||
d.rate = rate
|
||||
|
||||
d.discount_percentage = 0
|
||||
d.discount_amount = 0
|
||||
frappe.msgprint(_("Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer")
|
||||
.format(d.idx), alert=1)
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
|
||||
).format(d.idx),
|
||||
alert=1,
|
||||
)
|
||||
|
||||
elif self.get("return_against"):
|
||||
# Get incoming rate of return entry from reference document
|
||||
# based on original item cost as per valuation method
|
||||
d.incoming_rate = get_rate_for_return(self.doctype, self.name, d.item_code, self.return_against, item_row=d)
|
||||
d.incoming_rate = get_rate_for_return(
|
||||
self.doctype, self.name, d.item_code, self.return_against, item_row=d
|
||||
)
|
||||
|
||||
def update_stock_ledger(self):
|
||||
self.update_reserved_qty()
|
||||
@@ -428,63 +467,66 @@ class SellingController(StockController):
|
||||
# Loop over items and packed items table
|
||||
for d in self.get_item_list():
|
||||
if frappe.get_cached_value("Item", d.item_code, "is_stock_item") == 1 and flt(d.qty):
|
||||
if flt(d.conversion_factor)==0.0:
|
||||
d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0
|
||||
if flt(d.conversion_factor) == 0.0:
|
||||
d.conversion_factor = (
|
||||
get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0
|
||||
)
|
||||
|
||||
# On cancellation or return entry submission, make stock ledger entry for
|
||||
# target warehouse first, to update serial no values properly
|
||||
|
||||
if d.warehouse and ((not cint(self.is_return) and self.docstatus==1)
|
||||
or (cint(self.is_return) and self.docstatus==2)):
|
||||
sl_entries.append(self.get_sle_for_source_warehouse(d))
|
||||
if d.warehouse and (
|
||||
(not cint(self.is_return) and self.docstatus == 1)
|
||||
or (cint(self.is_return) and self.docstatus == 2)
|
||||
):
|
||||
sl_entries.append(self.get_sle_for_source_warehouse(d))
|
||||
|
||||
if d.target_warehouse:
|
||||
sl_entries.append(self.get_sle_for_target_warehouse(d))
|
||||
|
||||
if d.warehouse and ((not cint(self.is_return) and self.docstatus==2)
|
||||
or (cint(self.is_return) and self.docstatus==1)):
|
||||
sl_entries.append(self.get_sle_for_source_warehouse(d))
|
||||
if d.warehouse and (
|
||||
(not cint(self.is_return) and self.docstatus == 2)
|
||||
or (cint(self.is_return) and self.docstatus == 1)
|
||||
):
|
||||
sl_entries.append(self.get_sle_for_source_warehouse(d))
|
||||
|
||||
self.make_sl_entries(sl_entries)
|
||||
|
||||
def get_sle_for_source_warehouse(self, item_row):
|
||||
sle = self.get_sl_entries(item_row, {
|
||||
"actual_qty": -1*flt(item_row.qty),
|
||||
"incoming_rate": item_row.incoming_rate,
|
||||
"recalculate_rate": cint(self.is_return)
|
||||
})
|
||||
sle = self.get_sl_entries(
|
||||
item_row,
|
||||
{
|
||||
"actual_qty": -1 * flt(item_row.qty),
|
||||
"incoming_rate": item_row.incoming_rate,
|
||||
"recalculate_rate": cint(self.is_return),
|
||||
},
|
||||
)
|
||||
if item_row.target_warehouse and not cint(self.is_return):
|
||||
sle.dependant_sle_voucher_detail_no = item_row.name
|
||||
|
||||
return sle
|
||||
|
||||
def get_sle_for_target_warehouse(self, item_row):
|
||||
sle = self.get_sl_entries(item_row, {
|
||||
"actual_qty": flt(item_row.qty),
|
||||
"warehouse": item_row.target_warehouse
|
||||
})
|
||||
sle = self.get_sl_entries(
|
||||
item_row, {"actual_qty": flt(item_row.qty), "warehouse": item_row.target_warehouse}
|
||||
)
|
||||
|
||||
if self.docstatus == 1:
|
||||
if not cint(self.is_return):
|
||||
sle.update({
|
||||
"incoming_rate": item_row.incoming_rate,
|
||||
"recalculate_rate": 1
|
||||
})
|
||||
sle.update({"incoming_rate": item_row.incoming_rate, "recalculate_rate": 1})
|
||||
else:
|
||||
sle.update({
|
||||
"outgoing_rate": item_row.incoming_rate
|
||||
})
|
||||
sle.update({"outgoing_rate": item_row.incoming_rate})
|
||||
if item_row.warehouse:
|
||||
sle.dependant_sle_voucher_detail_no = item_row.name
|
||||
|
||||
return sle
|
||||
|
||||
def set_po_nos(self, for_validate=False):
|
||||
if self.doctype == 'Sales Invoice' and hasattr(self, "items"):
|
||||
if self.doctype == "Sales Invoice" and hasattr(self, "items"):
|
||||
if for_validate and self.po_no:
|
||||
return
|
||||
self.set_pos_for_sales_invoice()
|
||||
if self.doctype == 'Delivery Note' and hasattr(self, "items"):
|
||||
if self.doctype == "Delivery Note" and hasattr(self, "items"):
|
||||
if for_validate and self.po_no:
|
||||
return
|
||||
self.set_pos_for_delivery_note()
|
||||
@@ -493,34 +535,39 @@ class SellingController(StockController):
|
||||
po_nos = []
|
||||
if self.po_no:
|
||||
po_nos.append(self.po_no)
|
||||
self.get_po_nos('Sales Order', 'sales_order', po_nos)
|
||||
self.get_po_nos('Delivery Note', 'delivery_note', po_nos)
|
||||
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
|
||||
self.get_po_nos("Sales Order", "sales_order", po_nos)
|
||||
self.get_po_nos("Delivery Note", "delivery_note", po_nos)
|
||||
self.po_no = ", ".join(list(set(x.strip() for x in ",".join(po_nos).split(","))))
|
||||
|
||||
def set_pos_for_delivery_note(self):
|
||||
po_nos = []
|
||||
if self.po_no:
|
||||
po_nos.append(self.po_no)
|
||||
self.get_po_nos('Sales Order', 'against_sales_order', po_nos)
|
||||
self.get_po_nos('Sales Invoice', 'against_sales_invoice', po_nos)
|
||||
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
|
||||
self.get_po_nos("Sales Order", "against_sales_order", po_nos)
|
||||
self.get_po_nos("Sales Invoice", "against_sales_invoice", po_nos)
|
||||
self.po_no = ", ".join(list(set(x.strip() for x in ",".join(po_nos).split(","))))
|
||||
|
||||
def get_po_nos(self, ref_doctype, ref_fieldname, po_nos):
|
||||
doc_list = list(set(d.get(ref_fieldname) for d in self.items if d.get(ref_fieldname)))
|
||||
if doc_list:
|
||||
po_nos += [d.po_no for d in frappe.get_all(ref_doctype, 'po_no', filters = {'name': ('in', doc_list)}) if d.get('po_no')]
|
||||
po_nos += [
|
||||
d.po_no
|
||||
for d in frappe.get_all(ref_doctype, "po_no", filters={"name": ("in", doc_list)})
|
||||
if d.get("po_no")
|
||||
]
|
||||
|
||||
def set_gross_profit(self):
|
||||
if self.doctype in ["Sales Order", "Quotation"]:
|
||||
for item in self.items:
|
||||
item.gross_profit = flt(((item.base_rate - item.valuation_rate) * item.stock_qty), self.precision("amount", item))
|
||||
|
||||
item.gross_profit = flt(
|
||||
((item.base_rate - item.valuation_rate) * item.stock_qty), self.precision("amount", item)
|
||||
)
|
||||
|
||||
def set_customer_address(self):
|
||||
address_dict = {
|
||||
'customer_address': 'address_display',
|
||||
'shipping_address_name': 'shipping_address',
|
||||
'company_address': 'company_address_display'
|
||||
"customer_address": "address_display",
|
||||
"shipping_address_name": "shipping_address",
|
||||
"company_address": "company_address_display",
|
||||
}
|
||||
|
||||
for address_field, address_display_field in address_dict.items():
|
||||
@@ -536,15 +583,31 @@ class SellingController(StockController):
|
||||
if self.doctype == "POS Invoice":
|
||||
return
|
||||
|
||||
for d in self.get('items'):
|
||||
for d in self.get("items"):
|
||||
if self.doctype == "Sales Invoice":
|
||||
stock_items = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or '']
|
||||
stock_items = [
|
||||
d.item_code,
|
||||
d.description,
|
||||
d.warehouse,
|
||||
d.sales_order or d.delivery_note,
|
||||
d.batch_no or "",
|
||||
]
|
||||
non_stock_items = [d.item_code, d.description, d.sales_order or d.delivery_note]
|
||||
elif self.doctype == "Delivery Note":
|
||||
stock_items = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or '']
|
||||
non_stock_items = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice]
|
||||
stock_items = [
|
||||
d.item_code,
|
||||
d.description,
|
||||
d.warehouse,
|
||||
d.against_sales_order or d.against_sales_invoice,
|
||||
d.batch_no or "",
|
||||
]
|
||||
non_stock_items = [
|
||||
d.item_code,
|
||||
d.description,
|
||||
d.against_sales_order or d.against_sales_invoice,
|
||||
]
|
||||
elif self.doctype in ["Sales Order", "Quotation"]:
|
||||
stock_items = [d.item_code, d.description, d.warehouse, '']
|
||||
stock_items = [d.item_code, d.description, d.warehouse, ""]
|
||||
non_stock_items = [d.item_code, d.description]
|
||||
|
||||
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
|
||||
@@ -552,7 +615,7 @@ class SellingController(StockController):
|
||||
duplicate_items_msg += "<br><br>"
|
||||
duplicate_items_msg += _("Please enable {} in {} to allow same item in multiple rows").format(
|
||||
frappe.bold("Allow Item to Be Added Multiple Times in a Transaction"),
|
||||
get_link_to_form("Selling Settings", "Selling Settings")
|
||||
get_link_to_form("Selling Settings", "Selling Settings"),
|
||||
)
|
||||
if stock_items in check_list:
|
||||
frappe.throw(duplicate_items_msg)
|
||||
@@ -570,22 +633,26 @@ class SellingController(StockController):
|
||||
for d in items:
|
||||
if d.get("target_warehouse") and d.get("warehouse") == d.get("target_warehouse"):
|
||||
warehouse = frappe.bold(d.get("target_warehouse"))
|
||||
frappe.throw(_("Row {0}: Delivery Warehouse ({1}) and Customer Warehouse ({2}) can not be same")
|
||||
.format(d.idx, warehouse, warehouse))
|
||||
frappe.throw(
|
||||
_("Row {0}: Delivery Warehouse ({1}) and Customer Warehouse ({2}) can not be same").format(
|
||||
d.idx, warehouse, warehouse
|
||||
)
|
||||
)
|
||||
|
||||
if not self.get("is_internal_customer") and any(d.get("target_warehouse") for d in items):
|
||||
msg = _("Target Warehouse is set for some items but the customer is not an internal customer.")
|
||||
msg += " " + _("This {} will be treated as material transfer.").format(_(self.doctype))
|
||||
frappe.msgprint(msg, title="Internal Transfer", alert=True)
|
||||
|
||||
|
||||
def validate_items(self):
|
||||
# validate items to see if they have is_sales_item enabled
|
||||
from erpnext.controllers.buying_controller import validate_item_type
|
||||
|
||||
validate_item_type(self, "is_sales_item", "sales")
|
||||
|
||||
|
||||
def set_default_income_account_for_item(obj):
|
||||
for d in obj.get("items"):
|
||||
if d.item_code:
|
||||
if getattr(d, "income_account", None):
|
||||
set_item_default(d.item_code, obj.company, 'income_account', d.income_account)
|
||||
set_item_default(d.item_code, obj.company, "income_account", d.income_account)
|
||||
|
||||
@@ -8,12 +8,15 @@ from frappe.model.document import Document
|
||||
from frappe.utils import comma_or, flt, getdate, now, nowdate
|
||||
|
||||
|
||||
class OverAllowanceError(frappe.ValidationError): pass
|
||||
class OverAllowanceError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
def validate_status(status, options):
|
||||
if status not in options:
|
||||
frappe.throw(_("Status must be one of {0}").format(comma_or(options)))
|
||||
|
||||
|
||||
status_map = {
|
||||
"Lead": [
|
||||
["Lost Quotation", "has_lost_quotation"],
|
||||
@@ -26,7 +29,7 @@ status_map = {
|
||||
["Lost", "has_lost_quotation"],
|
||||
["Quotation", "has_active_quotation"],
|
||||
["Converted", "has_ordered_quotation"],
|
||||
["Closed", "eval:self.status=='Closed'"]
|
||||
["Closed", "eval:self.status=='Closed'"],
|
||||
],
|
||||
"Quotation": [
|
||||
["Draft", None],
|
||||
@@ -37,20 +40,41 @@ status_map = {
|
||||
],
|
||||
"Sales Order": [
|
||||
["Draft", None],
|
||||
["To Deliver and Bill", "eval:self.per_delivered < 100 and self.per_billed < 100 and self.docstatus == 1"],
|
||||
["To Bill", "eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed < 100 and self.docstatus == 1"],
|
||||
["To Deliver", "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1 and not self.skip_delivery_note"],
|
||||
["Completed", "eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1"],
|
||||
[
|
||||
"To Deliver and Bill",
|
||||
"eval:self.per_delivered < 100 and self.per_billed < 100 and self.docstatus == 1",
|
||||
],
|
||||
[
|
||||
"To Bill",
|
||||
"eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed < 100 and self.docstatus == 1",
|
||||
],
|
||||
[
|
||||
"To Deliver",
|
||||
"eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1 and not self.skip_delivery_note",
|
||||
],
|
||||
[
|
||||
"Completed",
|
||||
"eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1",
|
||||
],
|
||||
["Cancelled", "eval:self.docstatus==2"],
|
||||
["Closed", "eval:self.status=='Closed'"],
|
||||
["On Hold", "eval:self.status=='On Hold'"],
|
||||
],
|
||||
"Purchase Order": [
|
||||
["Draft", None],
|
||||
["To Receive and Bill", "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1"],
|
||||
[
|
||||
"To Receive and Bill",
|
||||
"eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1",
|
||||
],
|
||||
["To Bill", "eval:self.per_received >= 100 and self.per_billed < 100 and self.docstatus == 1"],
|
||||
["To Receive", "eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1"],
|
||||
["Completed", "eval:self.per_received >= 100 and self.per_billed == 100 and self.docstatus == 1"],
|
||||
[
|
||||
"To Receive",
|
||||
"eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1",
|
||||
],
|
||||
[
|
||||
"Completed",
|
||||
"eval:self.per_received >= 100 and self.per_billed == 100 and self.docstatus == 1",
|
||||
],
|
||||
["Delivered", "eval:self.status=='Delivered'"],
|
||||
["Cancelled", "eval:self.docstatus==2"],
|
||||
["On Hold", "eval:self.status=='On Hold'"],
|
||||
@@ -77,18 +101,39 @@ status_map = {
|
||||
["Stopped", "eval:self.status == 'Stopped'"],
|
||||
["Cancelled", "eval:self.docstatus == 2"],
|
||||
["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"],
|
||||
["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
|
||||
["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"],
|
||||
["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"],
|
||||
["Received", "eval:self.status != 'Stopped' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
|
||||
["Partially Received", "eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
|
||||
["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"],
|
||||
["Manufactured", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'"]
|
||||
[
|
||||
"Ordered",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'",
|
||||
],
|
||||
[
|
||||
"Transferred",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'",
|
||||
],
|
||||
[
|
||||
"Issued",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'",
|
||||
],
|
||||
[
|
||||
"Received",
|
||||
"eval:self.status != 'Stopped' and self.per_received == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'",
|
||||
],
|
||||
[
|
||||
"Partially Received",
|
||||
"eval:self.status != 'Stopped' and self.per_received > 0 and self.per_received < 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'",
|
||||
],
|
||||
[
|
||||
"Partially Ordered",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1",
|
||||
],
|
||||
[
|
||||
"Manufactured",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'",
|
||||
],
|
||||
],
|
||||
"Bank Transaction": [
|
||||
["Unreconciled", "eval:self.docstatus == 1 and self.unallocated_amount>0"],
|
||||
["Reconciled", "eval:self.docstatus == 1 and self.unallocated_amount<=0"],
|
||||
["Cancelled", "eval:self.docstatus == 2"]
|
||||
["Cancelled", "eval:self.docstatus == 2"],
|
||||
],
|
||||
"POS Opening Entry": [
|
||||
["Draft", None],
|
||||
@@ -106,15 +151,16 @@ status_map = {
|
||||
"Transaction Deletion Record": [
|
||||
["Draft", None],
|
||||
["Completed", "eval:self.docstatus == 1"],
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
class StatusUpdater(Document):
|
||||
"""
|
||||
Updates the status of the calling records
|
||||
Delivery Note: Update Delivered Qty, Update Percent and Validate over delivery
|
||||
Sales Invoice: Update Billed Amt, Update Percent and Validate over billing
|
||||
Installation Note: Update Installed Qty, Update Percent Qty and Validate over installation
|
||||
Updates the status of the calling records
|
||||
Delivery Note: Update Delivered Qty, Update Percent and Validate over delivery
|
||||
Sales Invoice: Update Billed Amt, Update Percent and Validate over billing
|
||||
Installation Note: Update Installed Qty, Update Percent Qty and Validate over installation
|
||||
"""
|
||||
|
||||
def update_prevdoc_status(self):
|
||||
@@ -123,8 +169,8 @@ class StatusUpdater(Document):
|
||||
|
||||
def set_status(self, update=False, status=None, update_modified=True):
|
||||
if self.is_new():
|
||||
if self.get('amended_from'):
|
||||
self.status = 'Draft'
|
||||
if self.get("amended_from"):
|
||||
self.status = "Draft"
|
||||
return
|
||||
|
||||
if self.doctype in status_map:
|
||||
@@ -139,20 +185,33 @@ class StatusUpdater(Document):
|
||||
self.status = s[0]
|
||||
break
|
||||
elif s[1].startswith("eval:"):
|
||||
if frappe.safe_eval(s[1][5:], None, { "self": self.as_dict(), "getdate": getdate,
|
||||
"nowdate": nowdate, "get_value": frappe.db.get_value }):
|
||||
if frappe.safe_eval(
|
||||
s[1][5:],
|
||||
None,
|
||||
{
|
||||
"self": self.as_dict(),
|
||||
"getdate": getdate,
|
||||
"nowdate": nowdate,
|
||||
"get_value": frappe.db.get_value,
|
||||
},
|
||||
):
|
||||
self.status = s[0]
|
||||
break
|
||||
elif getattr(self, s[1])():
|
||||
self.status = s[0]
|
||||
break
|
||||
|
||||
if self.status != _status and self.status not in ("Cancelled", "Partially Ordered",
|
||||
"Ordered", "Issued", "Transferred"):
|
||||
if self.status != _status and self.status not in (
|
||||
"Cancelled",
|
||||
"Partially Ordered",
|
||||
"Ordered",
|
||||
"Issued",
|
||||
"Transferred",
|
||||
):
|
||||
self.add_comment("Label", _(self.status))
|
||||
|
||||
if update:
|
||||
self.db_set('status', self.status, update_modified = update_modified)
|
||||
self.db_set("status", self.status, update_modified=update_modified)
|
||||
|
||||
def validate_qty(self):
|
||||
"""Validates qty at row level"""
|
||||
@@ -167,57 +226,78 @@ class StatusUpdater(Document):
|
||||
|
||||
# get unique transactions to update
|
||||
for d in self.get_all_children():
|
||||
if hasattr(d, 'qty') and d.qty < 0 and not self.get('is_return'):
|
||||
if hasattr(d, "qty") and d.qty < 0 and not self.get("is_return"):
|
||||
frappe.throw(_("For an item {0}, quantity must be positive number").format(d.item_code))
|
||||
|
||||
if hasattr(d, 'qty') and d.qty > 0 and self.get('is_return'):
|
||||
if hasattr(d, "qty") and d.qty > 0 and self.get("is_return"):
|
||||
frappe.throw(_("For an item {0}, quantity must be negative number").format(d.item_code))
|
||||
|
||||
if d.doctype == args['source_dt'] and d.get(args["join_field"]):
|
||||
args['name'] = d.get(args['join_field'])
|
||||
if d.doctype == args["source_dt"] and d.get(args["join_field"]):
|
||||
args["name"] = d.get(args["join_field"])
|
||||
|
||||
# get all qty where qty > target_field
|
||||
item = frappe.db.sql("""select item_code, `{target_ref_field}`,
|
||||
item = frappe.db.sql(
|
||||
"""select item_code, `{target_ref_field}`,
|
||||
`{target_field}`, parenttype, parent from `tab{target_dt}`
|
||||
where `{target_ref_field}` < `{target_field}`
|
||||
and name=%s and docstatus=1""".format(**args),
|
||||
args['name'], as_dict=1)
|
||||
and name=%s and docstatus=1""".format(
|
||||
**args
|
||||
),
|
||||
args["name"],
|
||||
as_dict=1,
|
||||
)
|
||||
if item:
|
||||
item = item[0]
|
||||
item['idx'] = d.idx
|
||||
item['target_ref_field'] = args['target_ref_field'].replace('_', ' ')
|
||||
item["idx"] = d.idx
|
||||
item["target_ref_field"] = args["target_ref_field"].replace("_", " ")
|
||||
|
||||
# if not item[args['target_ref_field']]:
|
||||
# msgprint(_("Note: System will not check over-delivery and over-booking for Item {0} as quantity or amount is 0").format(item.item_code))
|
||||
if args.get('no_allowance'):
|
||||
item['reduce_by'] = item[args['target_field']] - item[args['target_ref_field']]
|
||||
if item['reduce_by'] > .01:
|
||||
if args.get("no_allowance"):
|
||||
item["reduce_by"] = item[args["target_field"]] - item[args["target_ref_field"]]
|
||||
if item["reduce_by"] > 0.01:
|
||||
self.limits_crossed_error(args, item, "qty")
|
||||
|
||||
elif item[args['target_ref_field']]:
|
||||
elif item[args["target_ref_field"]]:
|
||||
self.check_overflow_with_allowance(item, args)
|
||||
|
||||
def check_overflow_with_allowance(self, item, args):
|
||||
"""
|
||||
Checks if there is overflow condering a relaxation allowance
|
||||
Checks if there is overflow condering a relaxation allowance
|
||||
"""
|
||||
qty_or_amount = "qty" if "qty" in args['target_ref_field'] else "amount"
|
||||
qty_or_amount = "qty" if "qty" in args["target_ref_field"] else "amount"
|
||||
|
||||
# check if overflow is within allowance
|
||||
allowance, self.item_allowance, self.global_qty_allowance, self.global_amount_allowance = \
|
||||
get_allowance_for(item['item_code'], self.item_allowance,
|
||||
self.global_qty_allowance, self.global_amount_allowance, qty_or_amount)
|
||||
(
|
||||
allowance,
|
||||
self.item_allowance,
|
||||
self.global_qty_allowance,
|
||||
self.global_amount_allowance,
|
||||
) = get_allowance_for(
|
||||
item["item_code"],
|
||||
self.item_allowance,
|
||||
self.global_qty_allowance,
|
||||
self.global_amount_allowance,
|
||||
qty_or_amount,
|
||||
)
|
||||
|
||||
role_allowed_to_over_deliver_receive = frappe.db.get_single_value('Stock Settings', 'role_allowed_to_over_deliver_receive')
|
||||
role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
|
||||
role = role_allowed_to_over_deliver_receive if qty_or_amount == 'qty' else role_allowed_to_over_bill
|
||||
role_allowed_to_over_deliver_receive = frappe.db.get_single_value(
|
||||
"Stock Settings", "role_allowed_to_over_deliver_receive"
|
||||
)
|
||||
role_allowed_to_over_bill = frappe.db.get_single_value(
|
||||
"Accounts Settings", "role_allowed_to_over_bill"
|
||||
)
|
||||
role = (
|
||||
role_allowed_to_over_deliver_receive if qty_or_amount == "qty" else role_allowed_to_over_bill
|
||||
)
|
||||
|
||||
overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
|
||||
item[args['target_ref_field']]) * 100
|
||||
overflow_percent = (
|
||||
(item[args["target_field"]] - item[args["target_ref_field"]]) / item[args["target_ref_field"]]
|
||||
) * 100
|
||||
|
||||
if overflow_percent - allowance > 0.01:
|
||||
item['max_allowed'] = flt(item[args['target_ref_field']] * (100+allowance)/100)
|
||||
item['reduce_by'] = item[args['target_field']] - item['max_allowed']
|
||||
item["max_allowed"] = flt(item[args["target_ref_field"]] * (100 + allowance) / 100)
|
||||
item["reduce_by"] = item[args["target_field"]] - item["max_allowed"]
|
||||
|
||||
if role not in frappe.get_roles():
|
||||
self.limits_crossed_error(args, item, qty_or_amount)
|
||||
@@ -225,45 +305,55 @@ class StatusUpdater(Document):
|
||||
self.warn_about_bypassing_with_role(item, qty_or_amount, role)
|
||||
|
||||
def limits_crossed_error(self, args, item, qty_or_amount):
|
||||
'''Raise exception for limits crossed'''
|
||||
"""Raise exception for limits crossed"""
|
||||
if qty_or_amount == "qty":
|
||||
action_msg = _('To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.')
|
||||
action_msg = _(
|
||||
'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.'
|
||||
)
|
||||
else:
|
||||
action_msg = _('To allow over billing, update "Over Billing Allowance" in Accounts Settings or the Item.')
|
||||
action_msg = _(
|
||||
'To allow over billing, update "Over Billing Allowance" in Accounts Settings or the Item.'
|
||||
)
|
||||
|
||||
frappe.throw(_('This document is over limit by {0} {1} for item {4}. Are you making another {3} against the same {2}?')
|
||||
.format(
|
||||
frappe.throw(
|
||||
_(
|
||||
"This document is over limit by {0} {1} for item {4}. Are you making another {3} against the same {2}?"
|
||||
).format(
|
||||
frappe.bold(_(item["target_ref_field"].title())),
|
||||
frappe.bold(item["reduce_by"]),
|
||||
frappe.bold(_(args.get('target_dt'))),
|
||||
frappe.bold(_(args.get("target_dt"))),
|
||||
frappe.bold(_(self.doctype)),
|
||||
frappe.bold(item.get('item_code'))
|
||||
) + '<br><br>' + action_msg, OverAllowanceError, title = _('Limit Crossed'))
|
||||
frappe.bold(item.get("item_code")),
|
||||
)
|
||||
+ "<br><br>"
|
||||
+ action_msg,
|
||||
OverAllowanceError,
|
||||
title=_("Limit Crossed"),
|
||||
)
|
||||
|
||||
def warn_about_bypassing_with_role(self, item, qty_or_amount, role):
|
||||
action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling")
|
||||
|
||||
msg = (_("{} of {} {} ignored for item {} because you have {} role.")
|
||||
.format(
|
||||
action,
|
||||
_(item["target_ref_field"].title()),
|
||||
frappe.bold(item["reduce_by"]),
|
||||
frappe.bold(item.get('item_code')),
|
||||
role)
|
||||
)
|
||||
msg = _("{} of {} {} ignored for item {} because you have {} role.").format(
|
||||
action,
|
||||
_(item["target_ref_field"].title()),
|
||||
frappe.bold(item["reduce_by"]),
|
||||
frappe.bold(item.get("item_code")),
|
||||
role,
|
||||
)
|
||||
frappe.msgprint(msg, indicator="orange", alert=True)
|
||||
|
||||
def update_qty(self, update_modified=True):
|
||||
"""Updates qty or amount at row level
|
||||
|
||||
:param update_modified: If true, updates `modified` and `modified_by` for target parent doc
|
||||
:param update_modified: If true, updates `modified` and `modified_by` for target parent doc
|
||||
"""
|
||||
for args in self.status_updater:
|
||||
# condition to include current record (if submit or no if cancel)
|
||||
if self.docstatus == 1:
|
||||
args['cond'] = ' or parent="%s"' % self.name.replace('"', '\"')
|
||||
args["cond"] = ' or parent="%s"' % self.name.replace('"', '"')
|
||||
else:
|
||||
args['cond'] = ' and parent!="%s"' % self.name.replace('"', '\"')
|
||||
args["cond"] = ' and parent!="%s"' % self.name.replace('"', '"')
|
||||
|
||||
self._update_children(args, update_modified)
|
||||
|
||||
@@ -273,56 +363,73 @@ class StatusUpdater(Document):
|
||||
def _update_children(self, args, update_modified):
|
||||
"""Update quantities or amount in child table"""
|
||||
for d in self.get_all_children():
|
||||
if d.doctype != args['source_dt']:
|
||||
if d.doctype != args["source_dt"]:
|
||||
continue
|
||||
|
||||
self._update_modified(args, update_modified)
|
||||
|
||||
# updates qty in the child table
|
||||
args['detail_id'] = d.get(args['join_field'])
|
||||
args["detail_id"] = d.get(args["join_field"])
|
||||
|
||||
args['second_source_condition'] = ""
|
||||
if args.get('second_source_dt') and args.get('second_source_field') \
|
||||
and args.get('second_join_field'):
|
||||
args["second_source_condition"] = ""
|
||||
if (
|
||||
args.get("second_source_dt")
|
||||
and args.get("second_source_field")
|
||||
and args.get("second_join_field")
|
||||
):
|
||||
if not args.get("second_source_extra_cond"):
|
||||
args["second_source_extra_cond"] = ""
|
||||
|
||||
args['second_source_condition'] = frappe.db.sql(""" select ifnull((select sum(%(second_source_field)s)
|
||||
args["second_source_condition"] = frappe.db.sql(
|
||||
""" select ifnull((select sum(%(second_source_field)s)
|
||||
from `tab%(second_source_dt)s`
|
||||
where `%(second_join_field)s`="%(detail_id)s"
|
||||
and (`tab%(second_source_dt)s`.docstatus=1)
|
||||
%(second_source_extra_cond)s), 0) """ % args)[0][0]
|
||||
%(second_source_extra_cond)s), 0) """
|
||||
% args
|
||||
)[0][0]
|
||||
|
||||
if args['detail_id']:
|
||||
if not args.get("extra_cond"): args["extra_cond"] = ""
|
||||
if args["detail_id"]:
|
||||
if not args.get("extra_cond"):
|
||||
args["extra_cond"] = ""
|
||||
|
||||
args["source_dt_value"] = frappe.db.sql("""
|
||||
args["source_dt_value"] = (
|
||||
frappe.db.sql(
|
||||
"""
|
||||
(select ifnull(sum(%(source_field)s), 0)
|
||||
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
|
||||
and (docstatus=1 %(cond)s) %(extra_cond)s)
|
||||
""" % args)[0][0] or 0.0
|
||||
"""
|
||||
% args
|
||||
)[0][0]
|
||||
or 0.0
|
||||
)
|
||||
|
||||
if args['second_source_condition']:
|
||||
args["source_dt_value"] += flt(args['second_source_condition'])
|
||||
if args["second_source_condition"]:
|
||||
args["source_dt_value"] += flt(args["second_source_condition"])
|
||||
|
||||
frappe.db.sql("""update `tab%(target_dt)s`
|
||||
frappe.db.sql(
|
||||
"""update `tab%(target_dt)s`
|
||||
set %(target_field)s = %(source_dt_value)s %(update_modified)s
|
||||
where name='%(detail_id)s'""" % args)
|
||||
where name='%(detail_id)s'"""
|
||||
% args
|
||||
)
|
||||
|
||||
def _update_percent_field_in_targets(self, args, update_modified=True):
|
||||
"""Update percent field in parent transaction"""
|
||||
if args.get('percent_join_field_parent'):
|
||||
if args.get("percent_join_field_parent"):
|
||||
# if reference to target doc where % is to be updated, is
|
||||
# in source doc's parent form, consider percent_join_field_parent
|
||||
args['name'] = self.get(args['percent_join_field_parent'])
|
||||
args["name"] = self.get(args["percent_join_field_parent"])
|
||||
self._update_percent_field(args, update_modified)
|
||||
else:
|
||||
distinct_transactions = set(d.get(args['percent_join_field'])
|
||||
for d in self.get_all_children(args['source_dt']))
|
||||
distinct_transactions = set(
|
||||
d.get(args["percent_join_field"]) for d in self.get_all_children(args["source_dt"])
|
||||
)
|
||||
|
||||
for name in distinct_transactions:
|
||||
if name:
|
||||
args['name'] = name
|
||||
args["name"] = name
|
||||
self._update_percent_field(args, update_modified)
|
||||
|
||||
def _update_percent_field(self, args, update_modified=True):
|
||||
@@ -330,23 +437,29 @@ class StatusUpdater(Document):
|
||||
|
||||
self._update_modified(args, update_modified)
|
||||
|
||||
if args.get('target_parent_field'):
|
||||
frappe.db.sql("""update `tab%(target_parent_dt)s`
|
||||
if args.get("target_parent_field"):
|
||||
frappe.db.sql(
|
||||
"""update `tab%(target_parent_dt)s`
|
||||
set %(target_parent_field)s = round(
|
||||
ifnull((select
|
||||
ifnull(sum(if(abs(%(target_ref_field)s) > abs(%(target_field)s), abs(%(target_field)s), abs(%(target_ref_field)s))), 0)
|
||||
/ sum(abs(%(target_ref_field)s)) * 100
|
||||
from `tab%(target_dt)s` where parent="%(name)s" having sum(abs(%(target_ref_field)s)) > 0), 0), 6)
|
||||
%(update_modified)s
|
||||
where name='%(name)s'""" % args)
|
||||
where name='%(name)s'"""
|
||||
% args
|
||||
)
|
||||
|
||||
# update field
|
||||
if args.get('status_field'):
|
||||
frappe.db.sql("""update `tab%(target_parent_dt)s`
|
||||
if args.get("status_field"):
|
||||
frappe.db.sql(
|
||||
"""update `tab%(target_parent_dt)s`
|
||||
set %(status_field)s = if(%(target_parent_field)s<0.001,
|
||||
'Not %(keyword)s', if(%(target_parent_field)s>=99.999999,
|
||||
'Fully %(keyword)s', 'Partly %(keyword)s'))
|
||||
where name='%(name)s'""" % args)
|
||||
where name='%(name)s'"""
|
||||
% args
|
||||
)
|
||||
|
||||
if update_modified:
|
||||
target = frappe.get_doc(args["target_parent_dt"], args["name"])
|
||||
@@ -355,22 +468,24 @@ class StatusUpdater(Document):
|
||||
|
||||
def _update_modified(self, args, update_modified):
|
||||
if not update_modified:
|
||||
args['update_modified'] = ''
|
||||
args["update_modified"] = ""
|
||||
return
|
||||
|
||||
args['update_modified'] = ', modified = {0}, modified_by = {1}'.format(
|
||||
frappe.db.escape(now()),
|
||||
frappe.db.escape(frappe.session.user)
|
||||
args["update_modified"] = ", modified = {0}, modified_by = {1}".format(
|
||||
frappe.db.escape(now()), frappe.db.escape(frappe.session.user)
|
||||
)
|
||||
|
||||
def update_billing_status_for_zero_amount_refdoc(self, ref_dt):
|
||||
ref_fieldname = frappe.scrub(ref_dt)
|
||||
|
||||
ref_docs = [item.get(ref_fieldname) for item in (self.get('items') or []) if item.get(ref_fieldname)]
|
||||
ref_docs = [
|
||||
item.get(ref_fieldname) for item in (self.get("items") or []) if item.get(ref_fieldname)
|
||||
]
|
||||
if not ref_docs:
|
||||
return
|
||||
|
||||
zero_amount_refdocs = frappe.db.sql_list("""
|
||||
zero_amount_refdocs = frappe.db.sql_list(
|
||||
"""
|
||||
SELECT
|
||||
name
|
||||
from
|
||||
@@ -379,21 +494,34 @@ class StatusUpdater(Document):
|
||||
docstatus = 1
|
||||
and base_net_total = 0
|
||||
and name in %(ref_docs)s
|
||||
""".format(ref_dt=ref_dt), {
|
||||
'ref_docs': ref_docs
|
||||
})
|
||||
""".format(
|
||||
ref_dt=ref_dt
|
||||
),
|
||||
{"ref_docs": ref_docs},
|
||||
)
|
||||
|
||||
if zero_amount_refdocs:
|
||||
self.update_billing_status(zero_amount_refdocs, ref_dt, ref_fieldname)
|
||||
|
||||
def update_billing_status(self, zero_amount_refdoc, ref_dt, ref_fieldname):
|
||||
for ref_dn in zero_amount_refdoc:
|
||||
ref_doc_qty = flt(frappe.db.sql("""select ifnull(sum(qty), 0) from `tab%s Item`
|
||||
where parent=%s""" % (ref_dt, '%s'), (ref_dn))[0][0])
|
||||
ref_doc_qty = flt(
|
||||
frappe.db.sql(
|
||||
"""select ifnull(sum(qty), 0) from `tab%s Item`
|
||||
where parent=%s"""
|
||||
% (ref_dt, "%s"),
|
||||
(ref_dn),
|
||||
)[0][0]
|
||||
)
|
||||
|
||||
billed_qty = flt(frappe.db.sql("""select ifnull(sum(qty), 0)
|
||||
from `tab%s Item` where %s=%s and docstatus=1""" %
|
||||
(self.doctype, ref_fieldname, '%s'), (ref_dn))[0][0])
|
||||
billed_qty = flt(
|
||||
frappe.db.sql(
|
||||
"""select ifnull(sum(qty), 0)
|
||||
from `tab%s Item` where %s=%s and docstatus=1"""
|
||||
% (self.doctype, ref_fieldname, "%s"),
|
||||
(ref_dn),
|
||||
)[0][0]
|
||||
)
|
||||
|
||||
per_billed = (min(ref_doc_qty, billed_qty) / ref_doc_qty) * 100
|
||||
|
||||
@@ -402,7 +530,7 @@ class StatusUpdater(Document):
|
||||
ref_doc.db_set("per_billed", per_billed)
|
||||
|
||||
# set billling status
|
||||
if hasattr(ref_doc, 'billing_status'):
|
||||
if hasattr(ref_doc, "billing_status"):
|
||||
if ref_doc.per_billed < 0.001:
|
||||
ref_doc.db_set("billing_status", "Not Billed")
|
||||
elif ref_doc.per_billed > 99.999999:
|
||||
@@ -412,29 +540,51 @@ class StatusUpdater(Document):
|
||||
|
||||
ref_doc.set_status(update=True)
|
||||
|
||||
def get_allowance_for(item_code, item_allowance=None, global_qty_allowance=None, global_amount_allowance=None, qty_or_amount="qty"):
|
||||
|
||||
def get_allowance_for(
|
||||
item_code,
|
||||
item_allowance=None,
|
||||
global_qty_allowance=None,
|
||||
global_amount_allowance=None,
|
||||
qty_or_amount="qty",
|
||||
):
|
||||
"""
|
||||
Returns the allowance for the item, if not set, returns global allowance
|
||||
Returns the allowance for the item, if not set, returns global allowance
|
||||
"""
|
||||
if item_allowance is None:
|
||||
item_allowance = {}
|
||||
if qty_or_amount == "qty":
|
||||
if item_allowance.get(item_code, frappe._dict()).get("qty"):
|
||||
return item_allowance[item_code].qty, item_allowance, global_qty_allowance, global_amount_allowance
|
||||
return (
|
||||
item_allowance[item_code].qty,
|
||||
item_allowance,
|
||||
global_qty_allowance,
|
||||
global_amount_allowance,
|
||||
)
|
||||
else:
|
||||
if item_allowance.get(item_code, frappe._dict()).get("amount"):
|
||||
return item_allowance[item_code].amount, item_allowance, global_qty_allowance, global_amount_allowance
|
||||
return (
|
||||
item_allowance[item_code].amount,
|
||||
item_allowance,
|
||||
global_qty_allowance,
|
||||
global_amount_allowance,
|
||||
)
|
||||
|
||||
qty_allowance, over_billing_allowance = \
|
||||
frappe.db.get_value('Item', item_code, ['over_delivery_receipt_allowance', 'over_billing_allowance'])
|
||||
qty_allowance, over_billing_allowance = frappe.db.get_value(
|
||||
"Item", item_code, ["over_delivery_receipt_allowance", "over_billing_allowance"]
|
||||
)
|
||||
|
||||
if qty_or_amount == "qty" and not qty_allowance:
|
||||
if global_qty_allowance == None:
|
||||
global_qty_allowance = flt(frappe.db.get_single_value('Stock Settings', 'over_delivery_receipt_allowance'))
|
||||
global_qty_allowance = flt(
|
||||
frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance")
|
||||
)
|
||||
qty_allowance = global_qty_allowance
|
||||
elif qty_or_amount == "amount" and not over_billing_allowance:
|
||||
if global_amount_allowance == None:
|
||||
global_amount_allowance = flt(frappe.db.get_single_value('Accounts Settings', 'over_billing_allowance'))
|
||||
global_amount_allowance = flt(
|
||||
frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
|
||||
)
|
||||
over_billing_allowance = global_amount_allowance
|
||||
|
||||
if qty_or_amount == "qty":
|
||||
|
||||
@@ -21,14 +21,22 @@ from erpnext.stock import get_warehouse_account_map
|
||||
from erpnext.stock.stock_ledger import get_items_to_be_repost
|
||||
|
||||
|
||||
class QualityInspectionRequiredError(frappe.ValidationError): pass
|
||||
class QualityInspectionRejectedError(frappe.ValidationError): pass
|
||||
class QualityInspectionNotSubmittedError(frappe.ValidationError): pass
|
||||
class QualityInspectionRequiredError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class QualityInspectionRejectedError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class QualityInspectionNotSubmittedError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class StockController(AccountsController):
|
||||
def validate(self):
|
||||
super(StockController, self).validate()
|
||||
if not self.get('is_return'):
|
||||
if not self.get("is_return"):
|
||||
self.validate_inspection()
|
||||
self.validate_serialized_batch()
|
||||
self.clean_serial_nos()
|
||||
@@ -41,44 +49,56 @@ class StockController(AccountsController):
|
||||
if self.docstatus == 2:
|
||||
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
||||
|
||||
provisional_accounting_for_non_stock_items = \
|
||||
cint(frappe.db.get_value('Company', self.company, 'enable_provisional_accounting_for_non_stock_items'))
|
||||
provisional_accounting_for_non_stock_items = cint(
|
||||
frappe.db.get_value(
|
||||
"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
|
||||
)
|
||||
)
|
||||
|
||||
if cint(erpnext.is_perpetual_inventory_enabled(self.company)) or provisional_accounting_for_non_stock_items:
|
||||
if (
|
||||
cint(erpnext.is_perpetual_inventory_enabled(self.company))
|
||||
or provisional_accounting_for_non_stock_items
|
||||
):
|
||||
warehouse_account = get_warehouse_account_map(self.company)
|
||||
|
||||
if self.docstatus==1:
|
||||
if self.docstatus == 1:
|
||||
if not gl_entries:
|
||||
gl_entries = self.get_gl_entries(warehouse_account)
|
||||
make_gl_entries(gl_entries, from_repost=from_repost)
|
||||
|
||||
elif self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self.docstatus == 1:
|
||||
elif self.doctype in ["Purchase Receipt", "Purchase Invoice"] and self.docstatus == 1:
|
||||
gl_entries = []
|
||||
gl_entries = self.get_asset_gl_entry(gl_entries)
|
||||
make_gl_entries(gl_entries, from_repost=from_repost)
|
||||
|
||||
def validate_serialized_batch(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
for d in self.get("items"):
|
||||
if hasattr(d, 'serial_no') and hasattr(d, 'batch_no') and d.serial_no and d.batch_no:
|
||||
serial_nos = frappe.get_all("Serial No",
|
||||
if hasattr(d, "serial_no") and hasattr(d, "batch_no") and d.serial_no and d.batch_no:
|
||||
serial_nos = frappe.get_all(
|
||||
"Serial No",
|
||||
fields=["batch_no", "name", "warehouse"],
|
||||
filters={
|
||||
"name": ("in", get_serial_nos(d.serial_no))
|
||||
}
|
||||
filters={"name": ("in", get_serial_nos(d.serial_no))},
|
||||
)
|
||||
|
||||
for row in serial_nos:
|
||||
if row.warehouse and row.batch_no != d.batch_no:
|
||||
frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}")
|
||||
.format(d.idx, row.name, d.batch_no))
|
||||
frappe.throw(
|
||||
_("Row #{0}: Serial No {1} does not belong to Batch {2}").format(
|
||||
d.idx, row.name, d.batch_no
|
||||
)
|
||||
)
|
||||
|
||||
if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
|
||||
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
|
||||
|
||||
if expiry_date and getdate(expiry_date) < getdate(self.posting_date):
|
||||
frappe.throw(_("Row #{0}: The batch {1} has already expired.")
|
||||
.format(d.idx, get_link_to_form("Batch", d.get("batch_no"))))
|
||||
frappe.throw(
|
||||
_("Row #{0}: The batch {1} has already expired.").format(
|
||||
d.idx, get_link_to_form("Batch", d.get("batch_no"))
|
||||
)
|
||||
)
|
||||
|
||||
def clean_serial_nos(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import clean_serial_no_string
|
||||
@@ -88,13 +108,14 @@ class StockController(AccountsController):
|
||||
# remove extra whitespace and store one serial no on each line
|
||||
row.serial_no = clean_serial_no_string(row.serial_no)
|
||||
|
||||
for row in self.get('packed_items') or []:
|
||||
for row in self.get("packed_items") or []:
|
||||
if hasattr(row, "serial_no") and row.serial_no:
|
||||
# remove extra whitespace and store one serial no on each line
|
||||
row.serial_no = clean_serial_no_string(row.serial_no)
|
||||
|
||||
def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
|
||||
default_cost_center=None):
|
||||
def get_gl_entries(
|
||||
self, warehouse_account=None, default_expense_account=None, default_cost_center=None
|
||||
):
|
||||
|
||||
if not warehouse_account:
|
||||
warehouse_account = get_warehouse_account_map(self.company)
|
||||
@@ -116,44 +137,61 @@ class StockController(AccountsController):
|
||||
self.check_expense_account(item_row)
|
||||
|
||||
# expense account/ target_warehouse / source_warehouse
|
||||
if item_row.get('target_warehouse'):
|
||||
warehouse = item_row.get('target_warehouse')
|
||||
if item_row.get("target_warehouse"):
|
||||
warehouse = item_row.get("target_warehouse")
|
||||
expense_account = warehouse_account[warehouse]["account"]
|
||||
else:
|
||||
expense_account = item_row.expense_account
|
||||
|
||||
gl_list.append(self.get_gl_dict({
|
||||
"account": warehouse_account[sle.warehouse]["account"],
|
||||
"against": expense_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"project": item_row.project or self.get('project'),
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": flt(sle.stock_value_difference, precision),
|
||||
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
|
||||
}, warehouse_account[sle.warehouse]["account_currency"], item=item_row))
|
||||
gl_list.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": warehouse_account[sle.warehouse]["account"],
|
||||
"against": expense_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"project": item_row.project or self.get("project"),
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": flt(sle.stock_value_difference, precision),
|
||||
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
|
||||
},
|
||||
warehouse_account[sle.warehouse]["account_currency"],
|
||||
item=item_row,
|
||||
)
|
||||
)
|
||||
|
||||
gl_list.append(self.get_gl_dict({
|
||||
"account": expense_account,
|
||||
"against": warehouse_account[sle.warehouse]["account"],
|
||||
"cost_center": item_row.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": flt(sle.stock_value_difference, precision),
|
||||
"project": item_row.get("project") or self.get("project"),
|
||||
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No"
|
||||
}, item=item_row))
|
||||
gl_list.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against": warehouse_account[sle.warehouse]["account"],
|
||||
"cost_center": item_row.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": flt(sle.stock_value_difference, precision),
|
||||
"project": item_row.get("project") or self.get("project"),
|
||||
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
|
||||
},
|
||||
item=item_row,
|
||||
)
|
||||
)
|
||||
elif sle.warehouse not in warehouse_with_no_account:
|
||||
warehouse_with_no_account.append(sle.warehouse)
|
||||
|
||||
if warehouse_with_no_account:
|
||||
for wh in warehouse_with_no_account:
|
||||
if frappe.db.get_value("Warehouse", wh, "company"):
|
||||
frappe.throw(_("Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}.").format(wh, self.company))
|
||||
frappe.throw(
|
||||
_(
|
||||
"Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}."
|
||||
).format(wh, self.company)
|
||||
)
|
||||
|
||||
return process_gl_map(gl_list, precision=precision)
|
||||
|
||||
def get_debit_field_precision(self):
|
||||
if not frappe.flags.debit_field_precision:
|
||||
frappe.flags.debit_field_precision = frappe.get_precision("GL Entry", "debit_in_account_currency")
|
||||
frappe.flags.debit_field_precision = frappe.get_precision(
|
||||
"GL Entry", "debit_in_account_currency"
|
||||
)
|
||||
|
||||
return frappe.flags.debit_field_precision
|
||||
|
||||
@@ -163,12 +201,16 @@ class StockController(AccountsController):
|
||||
is_opening = "Yes" if reconciliation_purpose == "Opening Stock" else "No"
|
||||
details = []
|
||||
for voucher_detail_no in sle_map:
|
||||
details.append(frappe._dict({
|
||||
"name": voucher_detail_no,
|
||||
"expense_account": default_expense_account,
|
||||
"cost_center": default_cost_center,
|
||||
"is_opening": is_opening
|
||||
}))
|
||||
details.append(
|
||||
frappe._dict(
|
||||
{
|
||||
"name": voucher_detail_no,
|
||||
"expense_account": default_expense_account,
|
||||
"cost_center": default_cost_center,
|
||||
"is_opening": is_opening,
|
||||
}
|
||||
)
|
||||
)
|
||||
return details
|
||||
else:
|
||||
details = self.get("items")
|
||||
@@ -207,7 +249,8 @@ class StockController(AccountsController):
|
||||
|
||||
def get_stock_ledger_details(self):
|
||||
stock_ledger = {}
|
||||
stock_ledger_entries = frappe.db.sql("""
|
||||
stock_ledger_entries = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
name, warehouse, stock_value_difference, valuation_rate,
|
||||
voucher_detail_no, item_code, posting_date, posting_time,
|
||||
@@ -216,110 +259,154 @@ class StockController(AccountsController):
|
||||
`tabStock Ledger Entry`
|
||||
where
|
||||
voucher_type=%s and voucher_no=%s and is_cancelled = 0
|
||||
""", (self.doctype, self.name), as_dict=True)
|
||||
""",
|
||||
(self.doctype, self.name),
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
for sle in stock_ledger_entries:
|
||||
stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
|
||||
return stock_ledger
|
||||
|
||||
def make_batches(self, warehouse_field):
|
||||
'''Create batches if required. Called before submit'''
|
||||
"""Create batches if required. Called before submit"""
|
||||
for d in self.items:
|
||||
if d.get(warehouse_field) and not d.batch_no:
|
||||
has_batch_no, create_new_batch = frappe.db.get_value('Item', d.item_code, ['has_batch_no', 'create_new_batch'])
|
||||
has_batch_no, create_new_batch = frappe.db.get_value(
|
||||
"Item", d.item_code, ["has_batch_no", "create_new_batch"]
|
||||
)
|
||||
if has_batch_no and create_new_batch:
|
||||
d.batch_no = frappe.get_doc(dict(
|
||||
doctype='Batch',
|
||||
item=d.item_code,
|
||||
supplier=getattr(self, 'supplier', None),
|
||||
reference_doctype=self.doctype,
|
||||
reference_name=self.name)).insert().name
|
||||
d.batch_no = (
|
||||
frappe.get_doc(
|
||||
dict(
|
||||
doctype="Batch",
|
||||
item=d.item_code,
|
||||
supplier=getattr(self, "supplier", None),
|
||||
reference_doctype=self.doctype,
|
||||
reference_name=self.name,
|
||||
)
|
||||
)
|
||||
.insert()
|
||||
.name
|
||||
)
|
||||
|
||||
def check_expense_account(self, item):
|
||||
if not item.get("expense_account"):
|
||||
msg = _("Please set an Expense Account in the Items table")
|
||||
frappe.throw(_("Row #{0}: Expense Account not set for the Item {1}. {2}")
|
||||
.format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing"))
|
||||
frappe.throw(
|
||||
_("Row #{0}: Expense Account not set for the Item {1}. {2}").format(
|
||||
item.idx, frappe.bold(item.item_code), msg
|
||||
),
|
||||
title=_("Expense Account Missing"),
|
||||
)
|
||||
|
||||
else:
|
||||
is_expense_account = frappe.get_cached_value("Account",
|
||||
item.get("expense_account"), "report_type")=="Profit and Loss"
|
||||
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Stock Reconciliation", "Stock Entry") and not is_expense_account:
|
||||
frappe.throw(_("Expense / Difference account ({0}) must be a 'Profit or Loss' account")
|
||||
.format(item.get("expense_account")))
|
||||
is_expense_account = (
|
||||
frappe.get_cached_value("Account", item.get("expense_account"), "report_type")
|
||||
== "Profit and Loss"
|
||||
)
|
||||
if (
|
||||
self.doctype
|
||||
not in ("Purchase Receipt", "Purchase Invoice", "Stock Reconciliation", "Stock Entry")
|
||||
and not is_expense_account
|
||||
):
|
||||
frappe.throw(
|
||||
_("Expense / Difference account ({0}) must be a 'Profit or Loss' account").format(
|
||||
item.get("expense_account")
|
||||
)
|
||||
)
|
||||
if is_expense_account and not item.get("cost_center"):
|
||||
frappe.throw(_("{0} {1}: Cost Center is mandatory for Item {2}").format(
|
||||
_(self.doctype), self.name, item.get("item_code")))
|
||||
frappe.throw(
|
||||
_("{0} {1}: Cost Center is mandatory for Item {2}").format(
|
||||
_(self.doctype), self.name, item.get("item_code")
|
||||
)
|
||||
)
|
||||
|
||||
def delete_auto_created_batches(self):
|
||||
for d in self.items:
|
||||
if not d.batch_no: continue
|
||||
if not d.batch_no:
|
||||
continue
|
||||
|
||||
frappe.db.set_value("Serial No", {"batch_no": d.batch_no, "status": "Inactive"}, "batch_no", None)
|
||||
frappe.db.set_value(
|
||||
"Serial No", {"batch_no": d.batch_no, "status": "Inactive"}, "batch_no", None
|
||||
)
|
||||
|
||||
d.batch_no = None
|
||||
d.db_set("batch_no", None)
|
||||
|
||||
for data in frappe.get_all("Batch",
|
||||
{'reference_name': self.name, 'reference_doctype': self.doctype}):
|
||||
for data in frappe.get_all(
|
||||
"Batch", {"reference_name": self.name, "reference_doctype": self.doctype}
|
||||
):
|
||||
frappe.delete_doc("Batch", data.name)
|
||||
|
||||
def get_sl_entries(self, d, args):
|
||||
sl_dict = frappe._dict({
|
||||
"item_code": d.get("item_code", None),
|
||||
"warehouse": d.get("warehouse", None),
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
'fiscal_year': get_fiscal_year(self.posting_date, company=self.company)[0],
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"voucher_detail_no": d.name,
|
||||
"actual_qty": (self.docstatus==1 and 1 or -1)*flt(d.get("stock_qty")),
|
||||
"stock_uom": frappe.db.get_value("Item", args.get("item_code") or d.get("item_code"), "stock_uom"),
|
||||
"incoming_rate": 0,
|
||||
"company": self.company,
|
||||
"batch_no": cstr(d.get("batch_no")).strip(),
|
||||
"serial_no": d.get("serial_no"),
|
||||
"project": d.get("project") or self.get('project'),
|
||||
"is_cancelled": 1 if self.docstatus==2 else 0
|
||||
})
|
||||
sl_dict = frappe._dict(
|
||||
{
|
||||
"item_code": d.get("item_code", None),
|
||||
"warehouse": d.get("warehouse", None),
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"fiscal_year": get_fiscal_year(self.posting_date, company=self.company)[0],
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"voucher_detail_no": d.name,
|
||||
"actual_qty": (self.docstatus == 1 and 1 or -1) * flt(d.get("stock_qty")),
|
||||
"stock_uom": frappe.db.get_value(
|
||||
"Item", args.get("item_code") or d.get("item_code"), "stock_uom"
|
||||
),
|
||||
"incoming_rate": 0,
|
||||
"company": self.company,
|
||||
"batch_no": cstr(d.get("batch_no")).strip(),
|
||||
"serial_no": d.get("serial_no"),
|
||||
"project": d.get("project") or self.get("project"),
|
||||
"is_cancelled": 1 if self.docstatus == 2 else 0,
|
||||
}
|
||||
)
|
||||
|
||||
sl_dict.update(args)
|
||||
return sl_dict
|
||||
|
||||
def make_sl_entries(self, sl_entries, allow_negative_stock=False,
|
||||
via_landed_cost_voucher=False):
|
||||
def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
|
||||
from erpnext.stock.stock_ledger import make_sl_entries
|
||||
|
||||
make_sl_entries(sl_entries, allow_negative_stock, via_landed_cost_voucher)
|
||||
|
||||
def make_gl_entries_on_cancel(self):
|
||||
if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s
|
||||
and voucher_no=%s""", (self.doctype, self.name)):
|
||||
self.make_gl_entries()
|
||||
if frappe.db.sql(
|
||||
"""select name from `tabGL Entry` where voucher_type=%s
|
||||
and voucher_no=%s""",
|
||||
(self.doctype, self.name),
|
||||
):
|
||||
self.make_gl_entries()
|
||||
|
||||
def get_serialized_items(self):
|
||||
serialized_items = []
|
||||
item_codes = list(set(d.item_code for d in self.get("items")))
|
||||
if item_codes:
|
||||
serialized_items = frappe.db.sql_list("""select name from `tabItem`
|
||||
where has_serial_no=1 and name in ({})""".format(", ".join(["%s"]*len(item_codes))),
|
||||
tuple(item_codes))
|
||||
serialized_items = frappe.db.sql_list(
|
||||
"""select name from `tabItem`
|
||||
where has_serial_no=1 and name in ({})""".format(
|
||||
", ".join(["%s"] * len(item_codes))
|
||||
),
|
||||
tuple(item_codes),
|
||||
)
|
||||
|
||||
return serialized_items
|
||||
|
||||
def validate_warehouse(self):
|
||||
from erpnext.stock.utils import validate_disabled_warehouse, validate_warehouse_company
|
||||
|
||||
warehouses = list(set(d.warehouse for d in
|
||||
self.get("items") if getattr(d, "warehouse", None)))
|
||||
warehouses = list(set(d.warehouse for d in self.get("items") if getattr(d, "warehouse", None)))
|
||||
|
||||
target_warehouses = list(set([d.target_warehouse for d in
|
||||
self.get("items") if getattr(d, "target_warehouse", None)]))
|
||||
target_warehouses = list(
|
||||
set([d.target_warehouse for d in self.get("items") if getattr(d, "target_warehouse", None)])
|
||||
)
|
||||
|
||||
warehouses.extend(target_warehouses)
|
||||
|
||||
from_warehouse = list(set([d.from_warehouse for d in
|
||||
self.get("items") if getattr(d, "from_warehouse", None)]))
|
||||
from_warehouse = list(
|
||||
set([d.from_warehouse for d in self.get("items") if getattr(d, "from_warehouse", None)])
|
||||
)
|
||||
|
||||
warehouses.extend(from_warehouse)
|
||||
|
||||
@@ -332,14 +419,17 @@ class StockController(AccountsController):
|
||||
if self.doctype == "Delivery Note":
|
||||
target_ref_field = "amount - (returned_qty * rate)"
|
||||
|
||||
self._update_percent_field({
|
||||
"target_dt": self.doctype + " Item",
|
||||
"target_parent_dt": self.doctype,
|
||||
"target_parent_field": "per_billed",
|
||||
"target_ref_field": target_ref_field,
|
||||
"target_field": "billed_amt",
|
||||
"name": self.name,
|
||||
}, update_modified)
|
||||
self._update_percent_field(
|
||||
{
|
||||
"target_dt": self.doctype + " Item",
|
||||
"target_parent_dt": self.doctype,
|
||||
"target_parent_field": "per_billed",
|
||||
"target_ref_field": target_ref_field,
|
||||
"target_field": "billed_amt",
|
||||
"name": self.name,
|
||||
},
|
||||
update_modified,
|
||||
)
|
||||
|
||||
def validate_inspection(self):
|
||||
"""Checks if quality inspection is set/ is valid for Items that require inspection."""
|
||||
@@ -347,24 +437,28 @@ class StockController(AccountsController):
|
||||
"Purchase Receipt": "inspection_required_before_purchase",
|
||||
"Purchase Invoice": "inspection_required_before_purchase",
|
||||
"Sales Invoice": "inspection_required_before_delivery",
|
||||
"Delivery Note": "inspection_required_before_delivery"
|
||||
"Delivery Note": "inspection_required_before_delivery",
|
||||
}
|
||||
inspection_required_fieldname = inspection_fieldname_map.get(self.doctype)
|
||||
|
||||
# return if inspection is not required on document level
|
||||
if ((not inspection_required_fieldname and self.doctype != "Stock Entry") or
|
||||
(self.doctype == "Stock Entry" and not self.inspection_required) or
|
||||
(self.doctype in ["Sales Invoice", "Purchase Invoice"] and not self.update_stock)):
|
||||
return
|
||||
if (
|
||||
(not inspection_required_fieldname and self.doctype != "Stock Entry")
|
||||
or (self.doctype == "Stock Entry" and not self.inspection_required)
|
||||
or (self.doctype in ["Sales Invoice", "Purchase Invoice"] and not self.update_stock)
|
||||
):
|
||||
return
|
||||
|
||||
for row in self.get('items'):
|
||||
for row in self.get("items"):
|
||||
qi_required = False
|
||||
if (inspection_required_fieldname and frappe.db.get_value("Item", row.item_code, inspection_required_fieldname)):
|
||||
if inspection_required_fieldname and frappe.db.get_value(
|
||||
"Item", row.item_code, inspection_required_fieldname
|
||||
):
|
||||
qi_required = True
|
||||
elif self.doctype == "Stock Entry" and row.t_warehouse:
|
||||
qi_required = True # inward stock needs inspection
|
||||
qi_required = True # inward stock needs inspection
|
||||
|
||||
if qi_required: # validate row only if inspection is required on item level
|
||||
if qi_required: # validate row only if inspection is required on item level
|
||||
self.validate_qi_presence(row)
|
||||
if self.docstatus == 1:
|
||||
self.validate_qi_submission(row)
|
||||
@@ -381,12 +475,16 @@ class StockController(AccountsController):
|
||||
|
||||
def validate_qi_submission(self, row):
|
||||
"""Check if QI is submitted on row level, during submission"""
|
||||
action = frappe.db.get_single_value("Stock Settings", "action_if_quality_inspection_is_not_submitted")
|
||||
action = frappe.db.get_single_value(
|
||||
"Stock Settings", "action_if_quality_inspection_is_not_submitted"
|
||||
)
|
||||
qa_docstatus = frappe.db.get_value("Quality Inspection", row.quality_inspection, "docstatus")
|
||||
|
||||
if not qa_docstatus == 1:
|
||||
link = frappe.utils.get_link_to_form('Quality Inspection', row.quality_inspection)
|
||||
msg = f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}"
|
||||
link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection)
|
||||
msg = (
|
||||
f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}"
|
||||
)
|
||||
if action == "Stop":
|
||||
frappe.throw(_(msg), title=_("Inspection Submission"), exc=QualityInspectionNotSubmittedError)
|
||||
else:
|
||||
@@ -398,7 +496,7 @@ class StockController(AccountsController):
|
||||
qa_status = frappe.db.get_value("Quality Inspection", row.quality_inspection, "status")
|
||||
|
||||
if qa_status == "Rejected":
|
||||
link = frappe.utils.get_link_to_form('Quality Inspection', row.quality_inspection)
|
||||
link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection)
|
||||
msg = f"Row #{row.idx}: Quality Inspection {link} was rejected for item {row.item_code}"
|
||||
if action == "Stop":
|
||||
frappe.throw(_(msg), title=_("Inspection Rejected"), exc=QualityInspectionRejectedError)
|
||||
@@ -411,48 +509,71 @@ class StockController(AccountsController):
|
||||
frappe.get_doc("Blanket Order", blanket_order).update_ordered_qty()
|
||||
|
||||
def validate_customer_provided_item(self):
|
||||
for d in self.get('items'):
|
||||
for d in self.get("items"):
|
||||
# Customer Provided parts will have zero valuation rate
|
||||
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
|
||||
if frappe.db.get_value("Item", d.item_code, "is_customer_provided_item"):
|
||||
d.allow_zero_valuation_rate = 1
|
||||
|
||||
def set_rate_of_stock_uom(self):
|
||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice", "Purchase Order", "Sales Invoice", "Sales Order", "Delivery Note", "Quotation"]:
|
||||
if self.doctype in [
|
||||
"Purchase Receipt",
|
||||
"Purchase Invoice",
|
||||
"Purchase Order",
|
||||
"Sales Invoice",
|
||||
"Sales Order",
|
||||
"Delivery Note",
|
||||
"Quotation",
|
||||
]:
|
||||
for d in self.get("items"):
|
||||
d.stock_uom_rate = d.rate / (d.conversion_factor or 1)
|
||||
|
||||
def validate_internal_transfer(self):
|
||||
if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \
|
||||
and self.is_internal_transfer():
|
||||
if (
|
||||
self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt")
|
||||
and self.is_internal_transfer()
|
||||
):
|
||||
self.validate_in_transit_warehouses()
|
||||
self.validate_multi_currency()
|
||||
self.validate_packed_items()
|
||||
|
||||
def validate_in_transit_warehouses(self):
|
||||
if (self.doctype == 'Sales Invoice' and self.get('update_stock')) or self.doctype == 'Delivery Note':
|
||||
for item in self.get('items'):
|
||||
if (
|
||||
self.doctype == "Sales Invoice" and self.get("update_stock")
|
||||
) or self.doctype == "Delivery Note":
|
||||
for item in self.get("items"):
|
||||
if not item.target_warehouse:
|
||||
frappe.throw(_("Row {0}: Target Warehouse is mandatory for internal transfers").format(item.idx))
|
||||
frappe.throw(
|
||||
_("Row {0}: Target Warehouse is mandatory for internal transfers").format(item.idx)
|
||||
)
|
||||
|
||||
if (self.doctype == 'Purchase Invoice' and self.get('update_stock')) or self.doctype == 'Purchase Receipt':
|
||||
for item in self.get('items'):
|
||||
if (
|
||||
self.doctype == "Purchase Invoice" and self.get("update_stock")
|
||||
) or self.doctype == "Purchase Receipt":
|
||||
for item in self.get("items"):
|
||||
if not item.from_warehouse:
|
||||
frappe.throw(_("Row {0}: From Warehouse is mandatory for internal transfers").format(item.idx))
|
||||
frappe.throw(
|
||||
_("Row {0}: From Warehouse is mandatory for internal transfers").format(item.idx)
|
||||
)
|
||||
|
||||
def validate_multi_currency(self):
|
||||
if self.currency != self.company_currency:
|
||||
frappe.throw(_("Internal transfers can only be done in company's default currency"))
|
||||
|
||||
def validate_packed_items(self):
|
||||
if self.doctype in ('Sales Invoice', 'Delivery Note Item') and self.get('packed_items'):
|
||||
if self.doctype in ("Sales Invoice", "Delivery Note Item") and self.get("packed_items"):
|
||||
frappe.throw(_("Packed Items cannot be transferred internally"))
|
||||
|
||||
def validate_putaway_capacity(self):
|
||||
# if over receipt is attempted while 'apply putaway rule' is disabled
|
||||
# and if rule was applied on the transaction, validate it.
|
||||
from erpnext.stock.doctype.putaway_rule.putaway_rule import get_available_putaway_capacity
|
||||
valid_doctype = self.doctype in ("Purchase Receipt", "Stock Entry", "Purchase Invoice",
|
||||
"Stock Reconciliation")
|
||||
|
||||
valid_doctype = self.doctype in (
|
||||
"Purchase Receipt",
|
||||
"Stock Entry",
|
||||
"Purchase Invoice",
|
||||
"Stock Reconciliation",
|
||||
)
|
||||
|
||||
if self.doctype == "Purchase Invoice" and self.get("update_stock") == 0:
|
||||
valid_doctype = False
|
||||
@@ -461,14 +582,15 @@ class StockController(AccountsController):
|
||||
rule_map = defaultdict(dict)
|
||||
for item in self.get("items"):
|
||||
warehouse_field = "t_warehouse" if self.doctype == "Stock Entry" else "warehouse"
|
||||
rule = frappe.db.get_value("Putaway Rule",
|
||||
{
|
||||
"item_code": item.get("item_code"),
|
||||
"warehouse": item.get(warehouse_field)
|
||||
},
|
||||
["name", "disable"], as_dict=True)
|
||||
rule = frappe.db.get_value(
|
||||
"Putaway Rule",
|
||||
{"item_code": item.get("item_code"), "warehouse": item.get(warehouse_field)},
|
||||
["name", "disable"],
|
||||
as_dict=True,
|
||||
)
|
||||
if rule:
|
||||
if rule.get("disabled"): continue # dont validate for disabled rule
|
||||
if rule.get("disabled"):
|
||||
continue # dont validate for disabled rule
|
||||
|
||||
if self.doctype == "Stock Reconciliation":
|
||||
stock_qty = flt(item.qty)
|
||||
@@ -489,46 +611,55 @@ class StockController(AccountsController):
|
||||
frappe.throw(msg=message, title=_("Over Receipt"))
|
||||
|
||||
def prepare_over_receipt_message(self, rule, values):
|
||||
message = _("{0} qty of Item {1} is being received into Warehouse {2} with capacity {3}.") \
|
||||
.format(
|
||||
frappe.bold(values["qty_put"]), frappe.bold(values["item"]),
|
||||
frappe.bold(values["warehouse"]), frappe.bold(values["capacity"])
|
||||
)
|
||||
message = _(
|
||||
"{0} qty of Item {1} is being received into Warehouse {2} with capacity {3}."
|
||||
).format(
|
||||
frappe.bold(values["qty_put"]),
|
||||
frappe.bold(values["item"]),
|
||||
frappe.bold(values["warehouse"]),
|
||||
frappe.bold(values["capacity"]),
|
||||
)
|
||||
message += "<br><br>"
|
||||
rule_link = frappe.utils.get_link_to_form("Putaway Rule", rule)
|
||||
message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link)
|
||||
return message
|
||||
|
||||
def repost_future_sle_and_gle(self):
|
||||
args = frappe._dict({
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"company": self.company
|
||||
})
|
||||
args = frappe._dict(
|
||||
{
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"company": self.company,
|
||||
}
|
||||
)
|
||||
|
||||
if future_sle_exists(args) or repost_required_for_queue(self):
|
||||
item_based_reposting = cint(frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"))
|
||||
item_based_reposting = cint(
|
||||
frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting")
|
||||
)
|
||||
if item_based_reposting:
|
||||
create_item_wise_repost_entries(voucher_type=self.doctype, voucher_no=self.name)
|
||||
else:
|
||||
create_repost_item_valuation_entry(args)
|
||||
|
||||
|
||||
def repost_required_for_queue(doc: StockController) -> bool:
|
||||
"""check if stock document contains repeated item-warehouse with queue based valuation.
|
||||
|
||||
if queue exists for repeated items then SLEs need to reprocessed in background again.
|
||||
"""
|
||||
|
||||
consuming_sles = frappe.db.get_all("Stock Ledger Entry",
|
||||
consuming_sles = frappe.db.get_all(
|
||||
"Stock Ledger Entry",
|
||||
filters={
|
||||
"voucher_type": doc.doctype,
|
||||
"voucher_no": doc.name,
|
||||
"actual_qty": ("<", 0),
|
||||
"is_cancelled": 0
|
||||
"is_cancelled": 0,
|
||||
},
|
||||
fields=["item_code", "warehouse", "stock_queue"]
|
||||
fields=["item_code", "warehouse", "stock_queue"],
|
||||
)
|
||||
item_warehouses = [(sle.item_code, sle.warehouse) for sle in consuming_sles]
|
||||
|
||||
@@ -551,32 +682,41 @@ def make_quality_inspections(doctype, docname, items):
|
||||
inspections = []
|
||||
for item in items:
|
||||
if flt(item.get("sample_size")) > flt(item.get("qty")):
|
||||
frappe.throw(_("{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})").format(
|
||||
item_name=item.get("item_name"),
|
||||
sample_size=item.get("sample_size"),
|
||||
accepted_quantity=item.get("qty")
|
||||
))
|
||||
frappe.throw(
|
||||
_(
|
||||
"{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})"
|
||||
).format(
|
||||
item_name=item.get("item_name"),
|
||||
sample_size=item.get("sample_size"),
|
||||
accepted_quantity=item.get("qty"),
|
||||
)
|
||||
)
|
||||
|
||||
quality_inspection = frappe.get_doc({
|
||||
"doctype": "Quality Inspection",
|
||||
"inspection_type": "Incoming",
|
||||
"inspected_by": frappe.session.user,
|
||||
"reference_type": doctype,
|
||||
"reference_name": docname,
|
||||
"item_code": item.get("item_code"),
|
||||
"description": item.get("description"),
|
||||
"sample_size": flt(item.get("sample_size")),
|
||||
"item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None,
|
||||
"batch_no": item.get("batch_no")
|
||||
}).insert()
|
||||
quality_inspection = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Quality Inspection",
|
||||
"inspection_type": "Incoming",
|
||||
"inspected_by": frappe.session.user,
|
||||
"reference_type": doctype,
|
||||
"reference_name": docname,
|
||||
"item_code": item.get("item_code"),
|
||||
"description": item.get("description"),
|
||||
"sample_size": flt(item.get("sample_size")),
|
||||
"item_serial_no": item.get("serial_no").split("\n")[0] if item.get("serial_no") else None,
|
||||
"batch_no": item.get("batch_no"),
|
||||
}
|
||||
).insert()
|
||||
quality_inspection.save()
|
||||
inspections.append(quality_inspection.name)
|
||||
|
||||
return inspections
|
||||
|
||||
|
||||
def is_reposting_pending():
|
||||
return frappe.db.exists("Repost Item Valuation",
|
||||
{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
|
||||
return frappe.db.exists(
|
||||
"Repost Item Valuation", {"docstatus": 1, "status": ["in", ["Queued", "In Progress"]]}
|
||||
)
|
||||
|
||||
|
||||
def future_sle_exists(args, sl_entries=None):
|
||||
key = (args.voucher_type, args.voucher_no)
|
||||
@@ -593,7 +733,8 @@ def future_sle_exists(args, sl_entries=None):
|
||||
|
||||
or_conditions = get_conditions_to_validate_future_sle(sl_entries)
|
||||
|
||||
data = frappe.db.sql("""
|
||||
data = frappe.db.sql(
|
||||
"""
|
||||
select item_code, warehouse, count(name) as total_row
|
||||
from `tabStock Ledger Entry` force index (item_warehouse)
|
||||
where
|
||||
@@ -604,43 +745,55 @@ def future_sle_exists(args, sl_entries=None):
|
||||
and is_cancelled = 0
|
||||
GROUP BY
|
||||
item_code, warehouse
|
||||
""".format(" or ".join(or_conditions)), args, as_dict=1)
|
||||
""".format(
|
||||
" or ".join(or_conditions)
|
||||
),
|
||||
args,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
for d in data:
|
||||
frappe.local.future_sle[key][(d.item_code, d.warehouse)] = d.total_row
|
||||
|
||||
return len(data)
|
||||
|
||||
def validate_future_sle_not_exists(args, key, sl_entries=None):
|
||||
item_key = ''
|
||||
if args.get('item_code'):
|
||||
item_key = (args.get('item_code'), args.get('warehouse'))
|
||||
|
||||
if not sl_entries and hasattr(frappe.local, 'future_sle'):
|
||||
if (not frappe.local.future_sle.get(key) or
|
||||
(item_key and item_key not in frappe.local.future_sle.get(key))):
|
||||
def validate_future_sle_not_exists(args, key, sl_entries=None):
|
||||
item_key = ""
|
||||
if args.get("item_code"):
|
||||
item_key = (args.get("item_code"), args.get("warehouse"))
|
||||
|
||||
if not sl_entries and hasattr(frappe.local, "future_sle"):
|
||||
if not frappe.local.future_sle.get(key) or (
|
||||
item_key and item_key not in frappe.local.future_sle.get(key)
|
||||
):
|
||||
return True
|
||||
|
||||
|
||||
def get_cached_data(args, key):
|
||||
if not hasattr(frappe.local, 'future_sle'):
|
||||
if not hasattr(frappe.local, "future_sle"):
|
||||
frappe.local.future_sle = {}
|
||||
|
||||
if key not in frappe.local.future_sle:
|
||||
frappe.local.future_sle[key] = frappe._dict({})
|
||||
|
||||
if args.get('item_code'):
|
||||
item_key = (args.get('item_code'), args.get('warehouse'))
|
||||
if args.get("item_code"):
|
||||
item_key = (args.get("item_code"), args.get("warehouse"))
|
||||
count = frappe.local.future_sle[key].get(item_key)
|
||||
|
||||
return True if (count or count == 0) else False
|
||||
else:
|
||||
return frappe.local.future_sle[key]
|
||||
|
||||
|
||||
def get_sle_entries_against_voucher(args):
|
||||
return frappe.get_all("Stock Ledger Entry",
|
||||
return frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
filters={"voucher_type": args.voucher_type, "voucher_no": args.voucher_no},
|
||||
fields=["item_code", "warehouse"],
|
||||
order_by="creation asc")
|
||||
order_by="creation asc",
|
||||
)
|
||||
|
||||
|
||||
def get_conditions_to_validate_future_sle(sl_entries):
|
||||
warehouse_items_map = {}
|
||||
@@ -654,16 +807,18 @@ def get_conditions_to_validate_future_sle(sl_entries):
|
||||
for warehouse, items in warehouse_items_map.items():
|
||||
or_conditions.append(
|
||||
f"""warehouse = {frappe.db.escape(warehouse)}
|
||||
and item_code in ({', '.join(frappe.db.escape(item) for item in items)})""")
|
||||
and item_code in ({', '.join(frappe.db.escape(item) for item in items)})"""
|
||||
)
|
||||
|
||||
return or_conditions
|
||||
|
||||
|
||||
def create_repost_item_valuation_entry(args):
|
||||
args = frappe._dict(args)
|
||||
repost_entry = frappe.new_doc("Repost Item Valuation")
|
||||
repost_entry.based_on = args.based_on
|
||||
if not args.based_on:
|
||||
repost_entry.based_on = 'Transaction' if args.voucher_no else "Item and Warehouse"
|
||||
repost_entry.based_on = "Transaction" if args.voucher_no else "Item and Warehouse"
|
||||
repost_entry.voucher_type = args.voucher_type
|
||||
repost_entry.voucher_no = args.voucher_no
|
||||
repost_entry.item_code = args.item_code
|
||||
|
||||
@@ -8,9 +8,9 @@ from frappe.utils import cint, flt, get_link_to_form
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
|
||||
class Subcontracting():
|
||||
class Subcontracting:
|
||||
def set_materials_for_subcontracted_items(self, raw_material_table):
|
||||
if self.doctype == 'Purchase Invoice' and not self.update_stock:
|
||||
if self.doctype == "Purchase Invoice" and not self.update_stock:
|
||||
return
|
||||
|
||||
self.raw_material_table = raw_material_table
|
||||
@@ -33,13 +33,14 @@ class Subcontracting():
|
||||
self.__get_backflush_based_on()
|
||||
|
||||
def __get_backflush_based_on(self):
|
||||
self.backflush_based_on = frappe.db.get_single_value("Buying Settings",
|
||||
"backflush_raw_materials_of_subcontract_based_on")
|
||||
self.backflush_based_on = frappe.db.get_single_value(
|
||||
"Buying Settings", "backflush_raw_materials_of_subcontract_based_on"
|
||||
)
|
||||
|
||||
def __get_purchase_orders(self):
|
||||
self.purchase_orders = []
|
||||
|
||||
if self.doctype == 'Purchase Order':
|
||||
if self.doctype == "Purchase Order":
|
||||
return
|
||||
|
||||
self.purchase_orders = [d.purchase_order for d in self.items if d.purchase_order]
|
||||
@@ -48,7 +49,7 @@ class Subcontracting():
|
||||
self.__changed_name = []
|
||||
self.__reference_name = []
|
||||
|
||||
if self.doctype == 'Purchase Order' or self.is_new():
|
||||
if self.doctype == "Purchase Order" or self.is_new():
|
||||
self.set(self.raw_material_table, [])
|
||||
return
|
||||
|
||||
@@ -68,20 +69,20 @@ class Subcontracting():
|
||||
|
||||
def __get_data_before_save(self):
|
||||
item_dict = {}
|
||||
if self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self._doc_before_save:
|
||||
for row in self._doc_before_save.get('items'):
|
||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and self._doc_before_save:
|
||||
for row in self._doc_before_save.get("items"):
|
||||
item_dict[row.name] = (row.item_code, row.qty)
|
||||
|
||||
return item_dict
|
||||
|
||||
def get_available_materials(self):
|
||||
''' Get the available raw materials which has been transferred to the supplier.
|
||||
available_materials = {
|
||||
(item_code, subcontracted_item, purchase_order): {
|
||||
'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details
|
||||
}
|
||||
}
|
||||
'''
|
||||
"""Get the available raw materials which has been transferred to the supplier.
|
||||
available_materials = {
|
||||
(item_code, subcontracted_item, purchase_order): {
|
||||
'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details
|
||||
}
|
||||
}
|
||||
"""
|
||||
if not self.purchase_orders:
|
||||
return
|
||||
|
||||
@@ -89,8 +90,17 @@ class Subcontracting():
|
||||
key = (row.rm_item_code, row.main_item_code, row.purchase_order)
|
||||
|
||||
if key not in self.available_materials:
|
||||
self.available_materials.setdefault(key, frappe._dict({'qty': 0, 'serial_no': [],
|
||||
'batch_no': defaultdict(float), 'item_details': row, 'po_details': []})
|
||||
self.available_materials.setdefault(
|
||||
key,
|
||||
frappe._dict(
|
||||
{
|
||||
"qty": 0,
|
||||
"serial_no": [],
|
||||
"batch_no": defaultdict(float),
|
||||
"item_details": row,
|
||||
"po_details": [],
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
details = self.available_materials[key]
|
||||
@@ -106,17 +116,17 @@ class Subcontracting():
|
||||
self.__set_alternative_item_details(row)
|
||||
|
||||
self.__transferred_items = copy.deepcopy(self.available_materials)
|
||||
for doctype in ['Purchase Receipt', 'Purchase Invoice']:
|
||||
for doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
self.__update_consumed_materials(doctype)
|
||||
|
||||
def __update_consumed_materials(self, doctype, return_consumed_items=False):
|
||||
'''Deduct the consumed materials from the available materials.'''
|
||||
"""Deduct the consumed materials from the available materials."""
|
||||
|
||||
pr_items = self.__get_received_items(doctype)
|
||||
if not pr_items:
|
||||
return ([], {}) if return_consumed_items else None
|
||||
|
||||
pr_items = {d.name: d.get(self.get('po_field') or 'purchase_order') for d in pr_items}
|
||||
pr_items = {d.name: d.get(self.get("po_field") or "purchase_order") for d in pr_items}
|
||||
consumed_materials = self.__get_consumed_items(doctype, pr_items.keys())
|
||||
|
||||
if return_consumed_items:
|
||||
@@ -127,97 +137,153 @@ class Subcontracting():
|
||||
if not self.available_materials.get(key):
|
||||
continue
|
||||
|
||||
self.available_materials[key]['qty'] -= row.consumed_qty
|
||||
self.available_materials[key]["qty"] -= row.consumed_qty
|
||||
if row.serial_no:
|
||||
self.available_materials[key]['serial_no'] = list(
|
||||
set(self.available_materials[key]['serial_no']) - set(get_serial_nos(row.serial_no))
|
||||
self.available_materials[key]["serial_no"] = list(
|
||||
set(self.available_materials[key]["serial_no"]) - set(get_serial_nos(row.serial_no))
|
||||
)
|
||||
|
||||
if row.batch_no:
|
||||
self.available_materials[key]['batch_no'][row.batch_no] -= row.consumed_qty
|
||||
self.available_materials[key]["batch_no"][row.batch_no] -= row.consumed_qty
|
||||
|
||||
def __get_transferred_items(self):
|
||||
fields = ['`tabStock Entry`.`purchase_order`']
|
||||
alias_dict = {'item_code': 'rm_item_code', 'subcontracted_item': 'main_item_code', 'basic_rate': 'rate'}
|
||||
fields = ["`tabStock Entry`.`purchase_order`"]
|
||||
alias_dict = {
|
||||
"item_code": "rm_item_code",
|
||||
"subcontracted_item": "main_item_code",
|
||||
"basic_rate": "rate",
|
||||
}
|
||||
|
||||
child_table_fields = ['item_code', 'item_name', 'description', 'qty', 'basic_rate', 'amount',
|
||||
'serial_no', 'uom', 'subcontracted_item', 'stock_uom', 'batch_no', 'conversion_factor',
|
||||
's_warehouse', 't_warehouse', 'item_group', 'po_detail']
|
||||
child_table_fields = [
|
||||
"item_code",
|
||||
"item_name",
|
||||
"description",
|
||||
"qty",
|
||||
"basic_rate",
|
||||
"amount",
|
||||
"serial_no",
|
||||
"uom",
|
||||
"subcontracted_item",
|
||||
"stock_uom",
|
||||
"batch_no",
|
||||
"conversion_factor",
|
||||
"s_warehouse",
|
||||
"t_warehouse",
|
||||
"item_group",
|
||||
"po_detail",
|
||||
]
|
||||
|
||||
if self.backflush_based_on == 'BOM':
|
||||
child_table_fields.append('original_item')
|
||||
if self.backflush_based_on == "BOM":
|
||||
child_table_fields.append("original_item")
|
||||
|
||||
for field in child_table_fields:
|
||||
fields.append(f'`tabStock Entry Detail`.`{field}` As {alias_dict.get(field, field)}')
|
||||
fields.append(f"`tabStock Entry Detail`.`{field}` As {alias_dict.get(field, field)}")
|
||||
|
||||
filters = [['Stock Entry', 'docstatus', '=', 1], ['Stock Entry', 'purpose', '=', 'Send to Subcontractor'],
|
||||
['Stock Entry', 'purchase_order', 'in', self.purchase_orders]]
|
||||
filters = [
|
||||
["Stock Entry", "docstatus", "=", 1],
|
||||
["Stock Entry", "purpose", "=", "Send to Subcontractor"],
|
||||
["Stock Entry", "purchase_order", "in", self.purchase_orders],
|
||||
]
|
||||
|
||||
return frappe.get_all('Stock Entry', fields = fields, filters=filters)
|
||||
return frappe.get_all("Stock Entry", fields=fields, filters=filters)
|
||||
|
||||
def __get_received_items(self, doctype):
|
||||
fields = []
|
||||
self.po_field = 'purchase_order'
|
||||
self.po_field = "purchase_order"
|
||||
|
||||
for field in ['name', self.po_field, 'parent']:
|
||||
fields.append(f'`tab{doctype} Item`.`{field}`')
|
||||
for field in ["name", self.po_field, "parent"]:
|
||||
fields.append(f"`tab{doctype} Item`.`{field}`")
|
||||
|
||||
filters = [[doctype, 'docstatus', '=', 1], [f'{doctype} Item', self.po_field, 'in', self.purchase_orders]]
|
||||
if doctype == 'Purchase Invoice':
|
||||
filters.append(['Purchase Invoice', 'update_stock', "=", 1])
|
||||
filters = [
|
||||
[doctype, "docstatus", "=", 1],
|
||||
[f"{doctype} Item", self.po_field, "in", self.purchase_orders],
|
||||
]
|
||||
if doctype == "Purchase Invoice":
|
||||
filters.append(["Purchase Invoice", "update_stock", "=", 1])
|
||||
|
||||
return frappe.get_all(f'{doctype}', fields = fields, filters = filters)
|
||||
return frappe.get_all(f"{doctype}", fields=fields, filters=filters)
|
||||
|
||||
def __get_consumed_items(self, doctype, pr_items):
|
||||
return frappe.get_all('Purchase Receipt Item Supplied',
|
||||
fields = ['serial_no', 'rm_item_code', 'reference_name', 'batch_no', 'consumed_qty', 'main_item_code'],
|
||||
filters = {'docstatus': 1, 'reference_name': ('in', list(pr_items)), 'parenttype': doctype})
|
||||
return frappe.get_all(
|
||||
"Purchase Receipt Item Supplied",
|
||||
fields=[
|
||||
"serial_no",
|
||||
"rm_item_code",
|
||||
"reference_name",
|
||||
"batch_no",
|
||||
"consumed_qty",
|
||||
"main_item_code",
|
||||
],
|
||||
filters={"docstatus": 1, "reference_name": ("in", list(pr_items)), "parenttype": doctype},
|
||||
)
|
||||
|
||||
def __set_alternative_item_details(self, row):
|
||||
if row.get('original_item'):
|
||||
self.alternative_item_details[row.get('original_item')] = row
|
||||
if row.get("original_item"):
|
||||
self.alternative_item_details[row.get("original_item")] = row
|
||||
|
||||
def __get_pending_qty_to_receive(self):
|
||||
'''Get qty to be received against the purchase order.'''
|
||||
"""Get qty to be received against the purchase order."""
|
||||
|
||||
self.qty_to_be_received = defaultdict(float)
|
||||
|
||||
if self.doctype != 'Purchase Order' and self.backflush_based_on != 'BOM' and self.purchase_orders:
|
||||
for row in frappe.get_all('Purchase Order Item',
|
||||
fields = ['item_code', '(qty - received_qty) as qty', 'parent', 'name'],
|
||||
filters = {'docstatus': 1, 'parent': ('in', self.purchase_orders)}):
|
||||
if (
|
||||
self.doctype != "Purchase Order" and self.backflush_based_on != "BOM" and self.purchase_orders
|
||||
):
|
||||
for row in frappe.get_all(
|
||||
"Purchase Order Item",
|
||||
fields=["item_code", "(qty - received_qty) as qty", "parent", "name"],
|
||||
filters={"docstatus": 1, "parent": ("in", self.purchase_orders)},
|
||||
):
|
||||
|
||||
self.qty_to_be_received[(row.item_code, row.parent)] += row.qty
|
||||
|
||||
def __get_materials_from_bom(self, item_code, bom_no, exploded_item=0):
|
||||
doctype = 'BOM Item' if not exploded_item else 'BOM Explosion Item'
|
||||
fields = [f'`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit']
|
||||
doctype = "BOM Item" if not exploded_item else "BOM Explosion Item"
|
||||
fields = [f"`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit"]
|
||||
|
||||
alias_dict = {'item_code': 'rm_item_code', 'name': 'bom_detail_no', 'source_warehouse': 'reserve_warehouse'}
|
||||
for field in ['item_code', 'name', 'rate', 'stock_uom',
|
||||
'source_warehouse', 'description', 'item_name', 'stock_uom']:
|
||||
fields.append(f'`tab{doctype}`.`{field}` As {alias_dict.get(field, field)}')
|
||||
alias_dict = {
|
||||
"item_code": "rm_item_code",
|
||||
"name": "bom_detail_no",
|
||||
"source_warehouse": "reserve_warehouse",
|
||||
}
|
||||
for field in [
|
||||
"item_code",
|
||||
"name",
|
||||
"rate",
|
||||
"stock_uom",
|
||||
"source_warehouse",
|
||||
"description",
|
||||
"item_name",
|
||||
"stock_uom",
|
||||
]:
|
||||
fields.append(f"`tab{doctype}`.`{field}` As {alias_dict.get(field, field)}")
|
||||
|
||||
filters = [[doctype, 'parent', '=', bom_no], [doctype, 'docstatus', '=', 1],
|
||||
['BOM', 'item', '=', item_code], [doctype, 'sourced_by_supplier', '=', 0]]
|
||||
filters = [
|
||||
[doctype, "parent", "=", bom_no],
|
||||
[doctype, "docstatus", "=", 1],
|
||||
["BOM", "item", "=", item_code],
|
||||
[doctype, "sourced_by_supplier", "=", 0],
|
||||
]
|
||||
|
||||
return frappe.get_all('BOM', fields = fields, filters=filters, order_by = f'`tab{doctype}`.`idx`') or []
|
||||
return (
|
||||
frappe.get_all("BOM", fields=fields, filters=filters, order_by=f"`tab{doctype}`.`idx`") or []
|
||||
)
|
||||
|
||||
def __remove_changed_rows(self):
|
||||
if not self.__changed_name:
|
||||
return
|
||||
|
||||
i=1
|
||||
i = 1
|
||||
self.set(self.raw_material_table, [])
|
||||
for d in self._doc_before_save.supplied_items:
|
||||
if d.reference_name in self.__changed_name:
|
||||
continue
|
||||
|
||||
if (d.reference_name not in self.__reference_name):
|
||||
if d.reference_name not in self.__reference_name:
|
||||
continue
|
||||
|
||||
d.idx = i
|
||||
self.append('supplied_items', d)
|
||||
self.append("supplied_items", d)
|
||||
|
||||
i += 1
|
||||
|
||||
@@ -226,31 +292,35 @@ class Subcontracting():
|
||||
|
||||
has_supplied_items = True if self.get(self.raw_material_table) else False
|
||||
for row in self.items:
|
||||
if (self.doctype != 'Purchase Order' and ((self.__changed_name and row.name not in self.__changed_name)
|
||||
or (has_supplied_items and not self.__changed_name))):
|
||||
if self.doctype != "Purchase Order" and (
|
||||
(self.__changed_name and row.name not in self.__changed_name)
|
||||
or (has_supplied_items and not self.__changed_name)
|
||||
):
|
||||
continue
|
||||
|
||||
if self.doctype == 'Purchase Order' or self.backflush_based_on == 'BOM':
|
||||
for bom_item in self.__get_materials_from_bom(row.item_code, row.bom, row.get('include_exploded_items')):
|
||||
qty = (flt(bom_item.qty_consumed_per_unit) * flt(row.qty) * row.conversion_factor)
|
||||
if self.doctype == "Purchase Order" or self.backflush_based_on == "BOM":
|
||||
for bom_item in self.__get_materials_from_bom(
|
||||
row.item_code, row.bom, row.get("include_exploded_items")
|
||||
):
|
||||
qty = flt(bom_item.qty_consumed_per_unit) * flt(row.qty) * row.conversion_factor
|
||||
bom_item.main_item_code = row.item_code
|
||||
self.__update_reserve_warehouse(bom_item, row)
|
||||
self.__set_alternative_item(bom_item)
|
||||
self.__add_supplied_item(row, bom_item, qty)
|
||||
|
||||
elif self.backflush_based_on != 'BOM':
|
||||
elif self.backflush_based_on != "BOM":
|
||||
for key, transfer_item in self.available_materials.items():
|
||||
if (key[1], key[2]) == (row.item_code, row.purchase_order) and transfer_item.qty > 0:
|
||||
qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0
|
||||
transfer_item.qty -= qty
|
||||
self.__add_supplied_item(row, transfer_item.get('item_details'), qty)
|
||||
self.__add_supplied_item(row, transfer_item.get("item_details"), qty)
|
||||
|
||||
if self.qty_to_be_received:
|
||||
self.qty_to_be_received[(row.item_code, row.purchase_order)] -= row.qty
|
||||
|
||||
def __update_reserve_warehouse(self, row, item):
|
||||
if self.doctype == 'Purchase Order':
|
||||
row.reserve_warehouse = (self.set_reserve_warehouse or item.warehouse)
|
||||
if self.doctype == "Purchase Order":
|
||||
row.reserve_warehouse = self.set_reserve_warehouse or item.warehouse
|
||||
|
||||
def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
|
||||
key = (item_row.item_code, item_row.purchase_order)
|
||||
@@ -262,8 +332,9 @@ class Subcontracting():
|
||||
qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0))
|
||||
transfer_item.item_details.required_qty = transfer_item.qty
|
||||
|
||||
if (transfer_item.serial_no or frappe.get_cached_value('UOM',
|
||||
transfer_item.item_details.stock_uom, 'must_be_whole_number')):
|
||||
if transfer_item.serial_no or frappe.get_cached_value(
|
||||
"UOM", transfer_item.item_details.stock_uom, "must_be_whole_number"
|
||||
):
|
||||
return frappe.utils.ceil(qty)
|
||||
|
||||
return qty
|
||||
@@ -277,7 +348,7 @@ class Subcontracting():
|
||||
rm_obj = self.append(self.raw_material_table, bom_item)
|
||||
rm_obj.reference_name = item_row.name
|
||||
|
||||
if self.doctype == 'Purchase Order':
|
||||
if self.doctype == "Purchase Order":
|
||||
rm_obj.required_qty = qty
|
||||
else:
|
||||
rm_obj.consumed_qty = 0
|
||||
@@ -287,12 +358,12 @@ class Subcontracting():
|
||||
def __set_batch_nos(self, bom_item, item_row, rm_obj, qty):
|
||||
key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order)
|
||||
|
||||
if (self.available_materials.get(key) and self.available_materials[key]['batch_no']):
|
||||
if self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
|
||||
new_rm_obj = None
|
||||
for batch_no, batch_qty in self.available_materials[key]['batch_no'].items():
|
||||
for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
|
||||
if batch_qty >= qty:
|
||||
self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
|
||||
self.available_materials[key]['batch_no'][batch_no] -= qty
|
||||
self.available_materials[key]["batch_no"][batch_no] -= qty
|
||||
return
|
||||
|
||||
elif qty > 0 and batch_qty > 0:
|
||||
@@ -300,7 +371,7 @@ class Subcontracting():
|
||||
new_rm_obj = self.append(self.raw_material_table, bom_item)
|
||||
new_rm_obj.reference_name = item_row.name
|
||||
self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty)
|
||||
self.available_materials[key]['batch_no'][batch_no] = 0
|
||||
self.available_materials[key]["batch_no"][batch_no] = 0
|
||||
|
||||
if abs(qty) > 0 and not new_rm_obj:
|
||||
self.__set_consumed_qty(rm_obj, qty)
|
||||
@@ -313,29 +384,35 @@ class Subcontracting():
|
||||
rm_obj.consumed_qty = consumed_qty
|
||||
|
||||
def __set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty):
|
||||
rm_obj.update({'consumed_qty': qty, 'batch_no': batch_no,
|
||||
'required_qty': qty, 'purchase_order': item_row.purchase_order})
|
||||
rm_obj.update(
|
||||
{
|
||||
"consumed_qty": qty,
|
||||
"batch_no": batch_no,
|
||||
"required_qty": qty,
|
||||
"purchase_order": item_row.purchase_order,
|
||||
}
|
||||
)
|
||||
|
||||
self.__set_serial_nos(item_row, rm_obj)
|
||||
|
||||
def __set_serial_nos(self, item_row, rm_obj):
|
||||
key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order)
|
||||
if (self.available_materials.get(key) and self.available_materials[key]['serial_no']):
|
||||
used_serial_nos = self.available_materials[key]['serial_no'][0: cint(rm_obj.consumed_qty)]
|
||||
rm_obj.serial_no = '\n'.join(used_serial_nos)
|
||||
if self.available_materials.get(key) and self.available_materials[key]["serial_no"]:
|
||||
used_serial_nos = self.available_materials[key]["serial_no"][0 : cint(rm_obj.consumed_qty)]
|
||||
rm_obj.serial_no = "\n".join(used_serial_nos)
|
||||
|
||||
# Removed the used serial nos from the list
|
||||
for sn in used_serial_nos:
|
||||
self.available_materials[key]['serial_no'].remove(sn)
|
||||
self.available_materials[key]["serial_no"].remove(sn)
|
||||
|
||||
def set_consumed_qty_in_po(self):
|
||||
# Update consumed qty back in the purchase order
|
||||
if self.is_subcontracted != 'Yes':
|
||||
if self.is_subcontracted != "Yes":
|
||||
return
|
||||
|
||||
self.__get_purchase_orders()
|
||||
itemwise_consumed_qty = defaultdict(float)
|
||||
for doctype in ['Purchase Receipt', 'Purchase Invoice']:
|
||||
for doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
consumed_items, pr_items = self.__update_consumed_materials(doctype, return_consumed_items=True)
|
||||
|
||||
for row in consumed_items:
|
||||
@@ -345,10 +422,12 @@ class Subcontracting():
|
||||
self.__update_consumed_qty_in_po(itemwise_consumed_qty)
|
||||
|
||||
def __update_consumed_qty_in_po(self, itemwise_consumed_qty):
|
||||
fields = ['main_item_code', 'rm_item_code', 'parent', 'supplied_qty', 'name']
|
||||
filters = {'docstatus': 1, 'parent': ('in', self.purchase_orders)}
|
||||
fields = ["main_item_code", "rm_item_code", "parent", "supplied_qty", "name"]
|
||||
filters = {"docstatus": 1, "parent": ("in", self.purchase_orders)}
|
||||
|
||||
for row in frappe.get_all('Purchase Order Item Supplied', fields = fields, filters=filters, order_by='idx'):
|
||||
for row in frappe.get_all(
|
||||
"Purchase Order Item Supplied", fields=fields, filters=filters, order_by="idx"
|
||||
):
|
||||
key = (row.rm_item_code, row.main_item_code, row.parent)
|
||||
consumed_qty = itemwise_consumed_qty.get(key, 0)
|
||||
|
||||
@@ -356,10 +435,10 @@ class Subcontracting():
|
||||
consumed_qty = row.supplied_qty
|
||||
|
||||
itemwise_consumed_qty[key] -= consumed_qty
|
||||
frappe.db.set_value('Purchase Order Item Supplied', row.name, 'consumed_qty', consumed_qty)
|
||||
frappe.db.set_value("Purchase Order Item Supplied", row.name, "consumed_qty", consumed_qty)
|
||||
|
||||
def __validate_supplied_items(self):
|
||||
if self.doctype not in ['Purchase Invoice', 'Purchase Receipt']:
|
||||
if self.doctype not in ["Purchase Invoice", "Purchase Receipt"]:
|
||||
return
|
||||
|
||||
for row in self.get(self.raw_material_table):
|
||||
@@ -371,18 +450,20 @@ class Subcontracting():
|
||||
self.__validate_serial_no(row, key)
|
||||
|
||||
def __validate_batch_no(self, row, key):
|
||||
if row.get('batch_no') and row.get('batch_no') not in self.__transferred_items.get(key).get('batch_no'):
|
||||
link = get_link_to_form('Purchase Order', row.purchase_order)
|
||||
if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get(
|
||||
"batch_no"
|
||||
):
|
||||
link = get_link_to_form("Purchase Order", row.purchase_order)
|
||||
msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the Purchase Order {link}'
|
||||
frappe.throw(_(msg), title=_("Incorrect Batch Consumed"))
|
||||
|
||||
def __validate_serial_no(self, row, key):
|
||||
if row.get('serial_no'):
|
||||
serial_nos = get_serial_nos(row.get('serial_no'))
|
||||
incorrect_sn = set(serial_nos).difference(self.__transferred_items.get(key).get('serial_no'))
|
||||
if row.get("serial_no"):
|
||||
serial_nos = get_serial_nos(row.get("serial_no"))
|
||||
incorrect_sn = set(serial_nos).difference(self.__transferred_items.get(key).get("serial_no"))
|
||||
|
||||
if incorrect_sn:
|
||||
incorrect_sn = "\n".join(incorrect_sn)
|
||||
link = get_link_to_form('Purchase Order', row.purchase_order)
|
||||
msg = f'The Serial Nos {incorrect_sn} has not supplied against the Purchase Order {link}'
|
||||
link = get_link_to_form("Purchase Order", row.purchase_order)
|
||||
msg = f"The Serial Nos {incorrect_sn} has not supplied against the Purchase Order {link}"
|
||||
frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed"))
|
||||
|
||||
@@ -59,23 +59,23 @@ class calculate_taxes_and_totals(object):
|
||||
self.calculate_total_net_weight()
|
||||
|
||||
def validate_item_tax_template(self):
|
||||
for item in self.doc.get('items'):
|
||||
if item.item_code and item.get('item_tax_template'):
|
||||
for item in self.doc.get("items"):
|
||||
if item.item_code and item.get("item_tax_template"):
|
||||
item_doc = frappe.get_cached_doc("Item", item.item_code)
|
||||
args = {
|
||||
'net_rate': item.net_rate or item.rate,
|
||||
'tax_category': self.doc.get('tax_category'),
|
||||
'posting_date': self.doc.get('posting_date'),
|
||||
'bill_date': self.doc.get('bill_date'),
|
||||
'transaction_date': self.doc.get('transaction_date'),
|
||||
'company': self.doc.get('company')
|
||||
"net_rate": item.net_rate or item.rate,
|
||||
"tax_category": self.doc.get("tax_category"),
|
||||
"posting_date": self.doc.get("posting_date"),
|
||||
"bill_date": self.doc.get("bill_date"),
|
||||
"transaction_date": self.doc.get("transaction_date"),
|
||||
"company": self.doc.get("company"),
|
||||
}
|
||||
|
||||
item_group = item_doc.item_group
|
||||
item_group_taxes = []
|
||||
|
||||
while item_group:
|
||||
item_group_doc = frappe.get_cached_doc('Item Group', item_group)
|
||||
item_group_doc = frappe.get_cached_doc("Item Group", item_group)
|
||||
item_group_taxes += item_group_doc.taxes or []
|
||||
item_group = item_group_doc.parent_item_group
|
||||
|
||||
@@ -90,9 +90,11 @@ class calculate_taxes_and_totals(object):
|
||||
if taxes:
|
||||
if item.item_tax_template not in taxes:
|
||||
item.item_tax_template = taxes[0]
|
||||
frappe.msgprint(_("Row {0}: Item Tax template updated as per validity and rate applied").format(
|
||||
item.idx, frappe.bold(item.item_code)
|
||||
))
|
||||
frappe.msgprint(
|
||||
_("Row {0}: Item Tax template updated as per validity and rate applied").format(
|
||||
item.idx, frappe.bold(item.item_code)
|
||||
)
|
||||
)
|
||||
|
||||
def validate_conversion_rate(self):
|
||||
# validate conversion rate
|
||||
@@ -101,13 +103,17 @@ class calculate_taxes_and_totals(object):
|
||||
self.doc.currency = company_currency
|
||||
self.doc.conversion_rate = 1.0
|
||||
else:
|
||||
validate_conversion_rate(self.doc.currency, self.doc.conversion_rate,
|
||||
self.doc.meta.get_label("conversion_rate"), self.doc.company)
|
||||
validate_conversion_rate(
|
||||
self.doc.currency,
|
||||
self.doc.conversion_rate,
|
||||
self.doc.meta.get_label("conversion_rate"),
|
||||
self.doc.company,
|
||||
)
|
||||
|
||||
self.doc.conversion_rate = flt(self.doc.conversion_rate)
|
||||
|
||||
def calculate_item_values(self):
|
||||
if self.doc.get('is_consolidated'):
|
||||
if self.doc.get("is_consolidated"):
|
||||
return
|
||||
|
||||
if not self.discount_amount_applied:
|
||||
@@ -118,19 +124,30 @@ class calculate_taxes_and_totals(object):
|
||||
item.rate = 0.0
|
||||
elif item.price_list_rate:
|
||||
if not item.rate or (item.pricing_rules and item.discount_percentage > 0):
|
||||
item.rate = flt(item.price_list_rate *
|
||||
(1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
|
||||
item.rate = flt(
|
||||
item.price_list_rate * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")
|
||||
)
|
||||
|
||||
item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
|
||||
|
||||
elif item.discount_amount and item.pricing_rules:
|
||||
item.rate = item.price_list_rate - item.discount_amount
|
||||
item.rate = item.price_list_rate - item.discount_amount
|
||||
|
||||
if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item',
|
||||
'POS Invoice Item', 'Purchase Invoice Item', 'Purchase Order Item', 'Purchase Receipt Item']:
|
||||
if item.doctype in [
|
||||
"Quotation Item",
|
||||
"Sales Order Item",
|
||||
"Delivery Note Item",
|
||||
"Sales Invoice Item",
|
||||
"POS Invoice Item",
|
||||
"Purchase Invoice Item",
|
||||
"Purchase Order Item",
|
||||
"Purchase Receipt Item",
|
||||
]:
|
||||
item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
|
||||
if flt(item.rate_with_margin) > 0:
|
||||
item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
|
||||
item.rate = flt(
|
||||
item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")
|
||||
)
|
||||
|
||||
if item.discount_amount and not item.discount_percentage:
|
||||
item.rate = item.rate_with_margin - item.discount_amount
|
||||
@@ -149,18 +166,22 @@ class calculate_taxes_and_totals(object):
|
||||
elif not item.qty and self.doc.get("is_debit_note"):
|
||||
item.amount = flt(item.rate, item.precision("amount"))
|
||||
else:
|
||||
item.amount = flt(item.rate * item.qty, item.precision("amount"))
|
||||
item.amount = flt(item.rate * item.qty, item.precision("amount"))
|
||||
|
||||
item.net_amount = item.amount
|
||||
|
||||
self._set_in_company_currency(item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"])
|
||||
self._set_in_company_currency(
|
||||
item, ["price_list_rate", "rate", "net_rate", "amount", "net_amount"]
|
||||
)
|
||||
|
||||
item.item_tax_amount = 0.0
|
||||
|
||||
def _set_in_company_currency(self, doc, fields):
|
||||
"""set values in base currency"""
|
||||
for f in fields:
|
||||
val = flt(flt(doc.get(f), doc.precision(f)) * self.doc.conversion_rate, doc.precision("base_" + f))
|
||||
val = flt(
|
||||
flt(doc.get(f), doc.precision(f)) * self.doc.conversion_rate, doc.precision("base_" + f)
|
||||
)
|
||||
doc.set("base_" + f, val)
|
||||
|
||||
def initialize_taxes(self):
|
||||
@@ -169,16 +190,22 @@ class calculate_taxes_and_totals(object):
|
||||
validate_taxes_and_charges(tax)
|
||||
validate_inclusive_tax(tax, self.doc)
|
||||
|
||||
if not (self.doc.get('is_consolidated') or tax.get("dont_recompute_tax")):
|
||||
if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
|
||||
tax.item_wise_tax_detail = {}
|
||||
|
||||
tax_fields = ["total", "tax_amount_after_discount_amount",
|
||||
"tax_amount_for_current_item", "grand_total_for_current_item",
|
||||
"tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
|
||||
tax_fields = [
|
||||
"total",
|
||||
"tax_amount_after_discount_amount",
|
||||
"tax_amount_for_current_item",
|
||||
"grand_total_for_current_item",
|
||||
"tax_fraction_for_current_item",
|
||||
"grand_total_fraction_for_current_item",
|
||||
]
|
||||
|
||||
if tax.charge_type != "Actual" and \
|
||||
not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
|
||||
tax_fields.append("tax_amount")
|
||||
if tax.charge_type != "Actual" and not (
|
||||
self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total"
|
||||
):
|
||||
tax_fields.append("tax_amount")
|
||||
|
||||
for fieldname in tax_fields:
|
||||
tax.set(fieldname, 0.0)
|
||||
@@ -194,25 +221,32 @@ class calculate_taxes_and_totals(object):
|
||||
cumulated_tax_fraction = 0
|
||||
total_inclusive_tax_amount_per_qty = 0
|
||||
for i, tax in enumerate(self.doc.get("taxes")):
|
||||
tax.tax_fraction_for_current_item, inclusive_tax_amount_per_qty = self.get_current_tax_fraction(tax, item_tax_map)
|
||||
(
|
||||
tax.tax_fraction_for_current_item,
|
||||
inclusive_tax_amount_per_qty,
|
||||
) = self.get_current_tax_fraction(tax, item_tax_map)
|
||||
|
||||
if i==0:
|
||||
if i == 0:
|
||||
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
|
||||
else:
|
||||
tax.grand_total_fraction_for_current_item = \
|
||||
self.doc.get("taxes")[i-1].grand_total_fraction_for_current_item \
|
||||
tax.grand_total_fraction_for_current_item = (
|
||||
self.doc.get("taxes")[i - 1].grand_total_fraction_for_current_item
|
||||
+ tax.tax_fraction_for_current_item
|
||||
)
|
||||
|
||||
cumulated_tax_fraction += tax.tax_fraction_for_current_item
|
||||
total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty)
|
||||
|
||||
if not self.discount_amount_applied and item.qty and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty):
|
||||
if (
|
||||
not self.discount_amount_applied
|
||||
and item.qty
|
||||
and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty)
|
||||
):
|
||||
amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
|
||||
|
||||
item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
|
||||
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
|
||||
item.discount_percentage = flt(item.discount_percentage,
|
||||
item.precision("discount_percentage"))
|
||||
item.discount_percentage = flt(item.discount_percentage, item.precision("discount_percentage"))
|
||||
|
||||
self._set_in_company_currency(item, ["net_rate", "net_amount"])
|
||||
|
||||
@@ -221,8 +255,8 @@ class calculate_taxes_and_totals(object):
|
||||
|
||||
def get_current_tax_fraction(self, tax, item_tax_map):
|
||||
"""
|
||||
Get tax fraction for calculating tax exclusive amount
|
||||
from tax inclusive amount
|
||||
Get tax fraction for calculating tax exclusive amount
|
||||
from tax inclusive amount
|
||||
"""
|
||||
current_tax_fraction = 0
|
||||
inclusive_tax_amount_per_qty = 0
|
||||
@@ -234,12 +268,14 @@ class calculate_taxes_and_totals(object):
|
||||
current_tax_fraction = tax_rate / 100.0
|
||||
|
||||
elif tax.charge_type == "On Previous Row Amount":
|
||||
current_tax_fraction = (tax_rate / 100.0) * \
|
||||
self.doc.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item
|
||||
current_tax_fraction = (tax_rate / 100.0) * self.doc.get("taxes")[
|
||||
cint(tax.row_id) - 1
|
||||
].tax_fraction_for_current_item
|
||||
|
||||
elif tax.charge_type == "On Previous Row Total":
|
||||
current_tax_fraction = (tax_rate / 100.0) * \
|
||||
self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
|
||||
current_tax_fraction = (tax_rate / 100.0) * self.doc.get("taxes")[
|
||||
cint(tax.row_id) - 1
|
||||
].grand_total_fraction_for_current_item
|
||||
|
||||
elif tax.charge_type == "On Item Quantity":
|
||||
inclusive_tax_amount_per_qty = flt(tax_rate)
|
||||
@@ -257,7 +293,9 @@ class calculate_taxes_and_totals(object):
|
||||
return tax.rate
|
||||
|
||||
def calculate_net_total(self):
|
||||
self.doc.total_qty = self.doc.total = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0
|
||||
self.doc.total_qty = (
|
||||
self.doc.total
|
||||
) = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0
|
||||
|
||||
for item in self.doc.get("items"):
|
||||
self.doc.total += item.amount
|
||||
@@ -276,13 +314,20 @@ class calculate_taxes_and_totals(object):
|
||||
self._calculate()
|
||||
|
||||
def calculate_taxes(self):
|
||||
rounding_adjustment_computed = self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment')
|
||||
rounding_adjustment_computed = self.doc.get("is_consolidated") and self.doc.get(
|
||||
"rounding_adjustment"
|
||||
)
|
||||
if not rounding_adjustment_computed:
|
||||
self.doc.rounding_adjustment = 0
|
||||
|
||||
# maintain actual tax rate based on idx
|
||||
actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
|
||||
for tax in self.doc.get("taxes") if tax.charge_type == "Actual"])
|
||||
actual_tax_dict = dict(
|
||||
[
|
||||
[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
|
||||
for tax in self.doc.get("taxes")
|
||||
if tax.charge_type == "Actual"
|
||||
]
|
||||
)
|
||||
|
||||
for n, item in enumerate(self.doc.get("items")):
|
||||
item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
|
||||
@@ -297,9 +342,10 @@ class calculate_taxes_and_totals(object):
|
||||
current_tax_amount += actual_tax_dict[tax.idx]
|
||||
|
||||
# accumulate tax amount into tax.tax_amount
|
||||
if tax.charge_type != "Actual" and \
|
||||
not (self.discount_amount_applied and self.doc.apply_discount_on=="Grand Total"):
|
||||
tax.tax_amount += current_tax_amount
|
||||
if tax.charge_type != "Actual" and not (
|
||||
self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total"
|
||||
):
|
||||
tax.tax_amount += current_tax_amount
|
||||
|
||||
# store tax_amount for current item as it will be used for
|
||||
# charge type = 'On Previous Row Amount'
|
||||
@@ -312,17 +358,17 @@ class calculate_taxes_and_totals(object):
|
||||
|
||||
# note: grand_total_for_current_item contains the contribution of
|
||||
# item's amount, previously applied tax and the current tax on that item
|
||||
if i==0:
|
||||
if i == 0:
|
||||
tax.grand_total_for_current_item = flt(item.net_amount + current_tax_amount)
|
||||
else:
|
||||
tax.grand_total_for_current_item = \
|
||||
flt(self.doc.get("taxes")[i-1].grand_total_for_current_item + current_tax_amount)
|
||||
tax.grand_total_for_current_item = flt(
|
||||
self.doc.get("taxes")[i - 1].grand_total_for_current_item + current_tax_amount
|
||||
)
|
||||
|
||||
# set precision in the last item iteration
|
||||
if n == len(self.doc.get("items")) - 1:
|
||||
self.round_off_totals(tax)
|
||||
self._set_in_company_currency(tax,
|
||||
["tax_amount", "tax_amount_after_discount_amount"])
|
||||
self._set_in_company_currency(tax, ["tax_amount", "tax_amount_after_discount_amount"])
|
||||
|
||||
self.round_off_base_values(tax)
|
||||
self.set_cumulative_total(i, tax)
|
||||
@@ -330,20 +376,29 @@ class calculate_taxes_and_totals(object):
|
||||
self._set_in_company_currency(tax, ["total"])
|
||||
|
||||
# adjust Discount Amount loss in last tax iteration
|
||||
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
|
||||
and self.doc.discount_amount \
|
||||
and self.doc.apply_discount_on == "Grand Total" \
|
||||
and not rounding_adjustment_computed:
|
||||
self.doc.rounding_adjustment = flt(self.doc.grand_total
|
||||
- flt(self.doc.discount_amount) - tax.total,
|
||||
self.doc.precision("rounding_adjustment"))
|
||||
if (
|
||||
i == (len(self.doc.get("taxes")) - 1)
|
||||
and self.discount_amount_applied
|
||||
and self.doc.discount_amount
|
||||
and self.doc.apply_discount_on == "Grand Total"
|
||||
and not rounding_adjustment_computed
|
||||
):
|
||||
self.doc.rounding_adjustment = flt(
|
||||
self.doc.grand_total - flt(self.doc.discount_amount) - tax.total,
|
||||
self.doc.precision("rounding_adjustment"),
|
||||
)
|
||||
|
||||
def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
|
||||
# if just for valuation, do not add the tax amount in total
|
||||
# if tax/charges is for deduction, multiply by -1
|
||||
if getattr(tax, "category", None):
|
||||
tax_amount = 0.0 if (tax.category == "Valuation") else tax_amount
|
||||
if self.doc.doctype in ["Purchase Order", "Purchase Invoice", "Purchase Receipt", "Supplier Quotation"]:
|
||||
if self.doc.doctype in [
|
||||
"Purchase Order",
|
||||
"Purchase Invoice",
|
||||
"Purchase Receipt",
|
||||
"Supplier Quotation",
|
||||
]:
|
||||
tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
|
||||
return tax_amount
|
||||
|
||||
@@ -354,7 +409,7 @@ class calculate_taxes_and_totals(object):
|
||||
if row_idx == 0:
|
||||
tax.total = flt(self.doc.net_total + tax_amount, tax.precision("total"))
|
||||
else:
|
||||
tax.total = flt(self.doc.get("taxes")[row_idx-1].total + tax_amount, tax.precision("total"))
|
||||
tax.total = flt(self.doc.get("taxes")[row_idx - 1].total + tax_amount, tax.precision("total"))
|
||||
|
||||
def get_current_tax_amount(self, item, tax, item_tax_map):
|
||||
tax_rate = self._get_tax_rate(tax, item_tax_map)
|
||||
@@ -363,16 +418,20 @@ class calculate_taxes_and_totals(object):
|
||||
if tax.charge_type == "Actual":
|
||||
# distribute the tax amount proportionally to each item row
|
||||
actual = flt(tax.tax_amount, tax.precision("tax_amount"))
|
||||
current_tax_amount = item.net_amount*actual / self.doc.net_total if self.doc.net_total else 0.0
|
||||
current_tax_amount = (
|
||||
item.net_amount * actual / self.doc.net_total if self.doc.net_total else 0.0
|
||||
)
|
||||
|
||||
elif tax.charge_type == "On Net Total":
|
||||
current_tax_amount = (tax_rate / 100.0) * item.net_amount
|
||||
elif tax.charge_type == "On Previous Row Amount":
|
||||
current_tax_amount = (tax_rate / 100.0) * \
|
||||
self.doc.get("taxes")[cint(tax.row_id) - 1].tax_amount_for_current_item
|
||||
current_tax_amount = (tax_rate / 100.0) * self.doc.get("taxes")[
|
||||
cint(tax.row_id) - 1
|
||||
].tax_amount_for_current_item
|
||||
elif tax.charge_type == "On Previous Row Total":
|
||||
current_tax_amount = (tax_rate / 100.0) * \
|
||||
self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item
|
||||
current_tax_amount = (tax_rate / 100.0) * self.doc.get("taxes")[
|
||||
cint(tax.row_id) - 1
|
||||
].grand_total_for_current_item
|
||||
elif tax.charge_type == "On Item Quantity":
|
||||
current_tax_amount = tax_rate * item.qty
|
||||
|
||||
@@ -384,11 +443,11 @@ class calculate_taxes_and_totals(object):
|
||||
def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
|
||||
# store tax breakup for each item
|
||||
key = item.item_code or item.item_name
|
||||
item_wise_tax_amount = current_tax_amount*self.doc.conversion_rate
|
||||
item_wise_tax_amount = current_tax_amount * self.doc.conversion_rate
|
||||
if tax.item_wise_tax_detail.get(key):
|
||||
item_wise_tax_amount += tax.item_wise_tax_detail[key][1]
|
||||
|
||||
tax.item_wise_tax_detail[key] = [tax_rate,flt(item_wise_tax_amount)]
|
||||
tax.item_wise_tax_detail[key] = [tax_rate, flt(item_wise_tax_amount)]
|
||||
|
||||
def round_off_totals(self, tax):
|
||||
if tax.account_head in frappe.flags.round_off_applicable_accounts:
|
||||
@@ -396,8 +455,9 @@ class calculate_taxes_and_totals(object):
|
||||
tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, 0)
|
||||
|
||||
tax.tax_amount = flt(tax.tax_amount, tax.precision("tax_amount"))
|
||||
tax.tax_amount_after_discount_amount = flt(tax.tax_amount_after_discount_amount,
|
||||
tax.precision("tax_amount"))
|
||||
tax.tax_amount_after_discount_amount = flt(
|
||||
tax.tax_amount_after_discount_amount, tax.precision("tax_amount")
|
||||
)
|
||||
|
||||
def round_off_base_values(self, tax):
|
||||
# Round off to nearest integer based on regional settings
|
||||
@@ -409,11 +469,15 @@ class calculate_taxes_and_totals(object):
|
||||
# if fully inclusive taxes and diff
|
||||
if self.doc.get("taxes") and any(cint(t.included_in_print_rate) for t in self.doc.get("taxes")):
|
||||
last_tax = self.doc.get("taxes")[-1]
|
||||
non_inclusive_tax_amount = sum(flt(d.tax_amount_after_discount_amount)
|
||||
for d in self.doc.get("taxes") if not d.included_in_print_rate)
|
||||
non_inclusive_tax_amount = sum(
|
||||
flt(d.tax_amount_after_discount_amount)
|
||||
for d in self.doc.get("taxes")
|
||||
if not d.included_in_print_rate
|
||||
)
|
||||
|
||||
diff = self.doc.total + non_inclusive_tax_amount \
|
||||
- flt(last_tax.total, last_tax.precision("total"))
|
||||
diff = (
|
||||
self.doc.total + non_inclusive_tax_amount - flt(last_tax.total, last_tax.precision("total"))
|
||||
)
|
||||
|
||||
# If discount amount applied, deduct the discount amount
|
||||
# because self.doc.total is always without discount, but last_tax.total is after discount
|
||||
@@ -422,7 +486,7 @@ class calculate_taxes_and_totals(object):
|
||||
|
||||
diff = flt(diff, self.doc.precision("rounding_adjustment"))
|
||||
|
||||
if diff and abs(diff) <= (5.0 / 10**last_tax.precision("tax_amount")):
|
||||
if diff and abs(diff) <= (5.0 / 10 ** last_tax.precision("tax_amount")):
|
||||
self.doc.rounding_adjustment = diff
|
||||
|
||||
def calculate_totals(self):
|
||||
@@ -432,16 +496,27 @@ class calculate_taxes_and_totals(object):
|
||||
self.doc.grand_total = flt(self.doc.net_total)
|
||||
|
||||
if self.doc.get("taxes"):
|
||||
self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total
|
||||
- flt(self.doc.rounding_adjustment), self.doc.precision("total_taxes_and_charges"))
|
||||
self.doc.total_taxes_and_charges = flt(
|
||||
self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment),
|
||||
self.doc.precision("total_taxes_and_charges"),
|
||||
)
|
||||
else:
|
||||
self.doc.total_taxes_and_charges = 0.0
|
||||
|
||||
self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"])
|
||||
|
||||
if self.doc.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"]:
|
||||
self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total")) \
|
||||
if self.doc.total_taxes_and_charges else self.doc.base_net_total
|
||||
if self.doc.doctype in [
|
||||
"Quotation",
|
||||
"Sales Order",
|
||||
"Delivery Note",
|
||||
"Sales Invoice",
|
||||
"POS Invoice",
|
||||
]:
|
||||
self.doc.base_grand_total = (
|
||||
flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total"))
|
||||
if self.doc.total_taxes_and_charges
|
||||
else self.doc.base_net_total
|
||||
)
|
||||
else:
|
||||
self.doc.taxes_and_charges_added = self.doc.taxes_and_charges_deducted = 0.0
|
||||
for tax in self.doc.get("taxes"):
|
||||
@@ -453,26 +528,29 @@ class calculate_taxes_and_totals(object):
|
||||
|
||||
self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
|
||||
|
||||
self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \
|
||||
if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted) \
|
||||
self.doc.base_grand_total = (
|
||||
flt(self.doc.grand_total * self.doc.conversion_rate)
|
||||
if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted)
|
||||
else self.doc.base_net_total
|
||||
)
|
||||
|
||||
self._set_in_company_currency(self.doc,
|
||||
["taxes_and_charges_added", "taxes_and_charges_deducted"])
|
||||
self._set_in_company_currency(
|
||||
self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"]
|
||||
)
|
||||
|
||||
self.doc.round_floats_in(self.doc, ["grand_total", "base_grand_total"])
|
||||
|
||||
self.set_rounded_total()
|
||||
|
||||
def calculate_total_net_weight(self):
|
||||
if self.doc.meta.get_field('total_net_weight'):
|
||||
if self.doc.meta.get_field("total_net_weight"):
|
||||
self.doc.total_net_weight = 0.0
|
||||
for d in self.doc.items:
|
||||
if d.total_weight:
|
||||
self.doc.total_net_weight += d.total_weight
|
||||
|
||||
def set_rounded_total(self):
|
||||
if self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment'):
|
||||
if self.doc.get("is_consolidated") and self.doc.get("rounding_adjustment"):
|
||||
return
|
||||
|
||||
if self.doc.meta.get_field("rounded_total"):
|
||||
@@ -480,33 +558,40 @@ class calculate_taxes_and_totals(object):
|
||||
self.doc.rounded_total = self.doc.base_rounded_total = 0
|
||||
return
|
||||
|
||||
self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
|
||||
self.doc.currency, self.doc.precision("rounded_total"))
|
||||
self.doc.rounded_total = round_based_on_smallest_currency_fraction(
|
||||
self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total")
|
||||
)
|
||||
|
||||
#if print_in_rate is set, we would have already calculated rounding adjustment
|
||||
self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
|
||||
self.doc.precision("rounding_adjustment"))
|
||||
# if print_in_rate is set, we would have already calculated rounding adjustment
|
||||
self.doc.rounding_adjustment += flt(
|
||||
self.doc.rounded_total - self.doc.grand_total, self.doc.precision("rounding_adjustment")
|
||||
)
|
||||
|
||||
self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
|
||||
|
||||
def _cleanup(self):
|
||||
if not self.doc.get('is_consolidated'):
|
||||
if not self.doc.get("is_consolidated"):
|
||||
for tax in self.doc.get("taxes"):
|
||||
if not tax.get("dont_recompute_tax"):
|
||||
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
|
||||
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(",", ":"))
|
||||
|
||||
def set_discount_amount(self):
|
||||
if self.doc.additional_discount_percentage:
|
||||
self.doc.discount_amount = flt(flt(self.doc.get(scrub(self.doc.apply_discount_on)))
|
||||
* self.doc.additional_discount_percentage / 100, self.doc.precision("discount_amount"))
|
||||
self.doc.discount_amount = flt(
|
||||
flt(self.doc.get(scrub(self.doc.apply_discount_on)))
|
||||
* self.doc.additional_discount_percentage
|
||||
/ 100,
|
||||
self.doc.precision("discount_amount"),
|
||||
)
|
||||
|
||||
def apply_discount_amount(self):
|
||||
if self.doc.discount_amount:
|
||||
if not self.doc.apply_discount_on:
|
||||
frappe.throw(_("Please select Apply Discount On"))
|
||||
|
||||
self.doc.base_discount_amount = flt(self.doc.discount_amount * self.doc.conversion_rate,
|
||||
self.doc.precision("base_discount_amount"))
|
||||
self.doc.base_discount_amount = flt(
|
||||
self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
|
||||
)
|
||||
|
||||
total_for_discount_amount = self.get_total_for_discount_amount()
|
||||
taxes = self.doc.get("taxes")
|
||||
@@ -515,20 +600,24 @@ class calculate_taxes_and_totals(object):
|
||||
if total_for_discount_amount:
|
||||
# calculate item amount after Discount Amount
|
||||
for i, item in enumerate(self.doc.get("items")):
|
||||
distributed_amount = flt(self.doc.discount_amount) * \
|
||||
item.net_amount / total_for_discount_amount
|
||||
distributed_amount = (
|
||||
flt(self.doc.discount_amount) * item.net_amount / total_for_discount_amount
|
||||
)
|
||||
|
||||
item.net_amount = flt(item.net_amount - distributed_amount, item.precision("net_amount"))
|
||||
net_total += item.net_amount
|
||||
|
||||
# discount amount rounding loss adjustment if no taxes
|
||||
if (self.doc.apply_discount_on == "Net Total" or not taxes or total_for_discount_amount==self.doc.net_total) \
|
||||
and i == len(self.doc.get("items")) - 1:
|
||||
discount_amount_loss = flt(self.doc.net_total - net_total - self.doc.discount_amount,
|
||||
self.doc.precision("net_total"))
|
||||
if (
|
||||
self.doc.apply_discount_on == "Net Total"
|
||||
or not taxes
|
||||
or total_for_discount_amount == self.doc.net_total
|
||||
) and i == len(self.doc.get("items")) - 1:
|
||||
discount_amount_loss = flt(
|
||||
self.doc.net_total - net_total - self.doc.discount_amount, self.doc.precision("net_total")
|
||||
)
|
||||
|
||||
item.net_amount = flt(item.net_amount + discount_amount_loss,
|
||||
item.precision("net_amount"))
|
||||
item.net_amount = flt(item.net_amount + discount_amount_loss, item.precision("net_amount"))
|
||||
|
||||
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate")) if item.qty else 0
|
||||
|
||||
@@ -553,34 +642,44 @@ class calculate_taxes_and_totals(object):
|
||||
actual_tax_amount = flt(actual_taxes_dict.get(tax.row_id, 0)) * flt(tax.rate) / 100
|
||||
actual_taxes_dict.setdefault(tax.idx, actual_tax_amount)
|
||||
|
||||
return flt(self.doc.grand_total - sum(actual_taxes_dict.values()),
|
||||
self.doc.precision("grand_total"))
|
||||
|
||||
return flt(
|
||||
self.doc.grand_total - sum(actual_taxes_dict.values()), self.doc.precision("grand_total")
|
||||
)
|
||||
|
||||
def calculate_total_advance(self):
|
||||
if self.doc.docstatus < 2:
|
||||
total_allocated_amount = sum(flt(adv.allocated_amount, adv.precision("allocated_amount"))
|
||||
for adv in self.doc.get("advances"))
|
||||
total_allocated_amount = sum(
|
||||
flt(adv.allocated_amount, adv.precision("allocated_amount"))
|
||||
for adv in self.doc.get("advances")
|
||||
)
|
||||
|
||||
self.doc.total_advance = flt(total_allocated_amount, self.doc.precision("total_advance"))
|
||||
|
||||
grand_total = self.doc.rounded_total or self.doc.grand_total
|
||||
|
||||
if self.doc.party_account_currency == self.doc.currency:
|
||||
invoice_total = flt(grand_total - flt(self.doc.write_off_amount),
|
||||
self.doc.precision("grand_total"))
|
||||
invoice_total = flt(
|
||||
grand_total - flt(self.doc.write_off_amount), self.doc.precision("grand_total")
|
||||
)
|
||||
else:
|
||||
base_write_off_amount = flt(flt(self.doc.write_off_amount) * self.doc.conversion_rate,
|
||||
self.doc.precision("base_write_off_amount"))
|
||||
invoice_total = flt(grand_total * self.doc.conversion_rate,
|
||||
self.doc.precision("grand_total")) - base_write_off_amount
|
||||
base_write_off_amount = flt(
|
||||
flt(self.doc.write_off_amount) * self.doc.conversion_rate,
|
||||
self.doc.precision("base_write_off_amount"),
|
||||
)
|
||||
invoice_total = (
|
||||
flt(grand_total * self.doc.conversion_rate, self.doc.precision("grand_total"))
|
||||
- base_write_off_amount
|
||||
)
|
||||
|
||||
if invoice_total > 0 and self.doc.total_advance > invoice_total:
|
||||
frappe.throw(_("Advance amount cannot be greater than {0} {1}")
|
||||
.format(self.doc.party_account_currency, invoice_total))
|
||||
frappe.throw(
|
||||
_("Advance amount cannot be greater than {0} {1}").format(
|
||||
self.doc.party_account_currency, invoice_total
|
||||
)
|
||||
)
|
||||
|
||||
if self.doc.docstatus == 0:
|
||||
if self.doc.get('write_off_outstanding_amount_automatically'):
|
||||
if self.doc.get("write_off_outstanding_amount_automatically"):
|
||||
self.doc.write_off_amount = 0
|
||||
|
||||
self.calculate_outstanding_amount()
|
||||
@@ -588,11 +687,11 @@ class calculate_taxes_and_totals(object):
|
||||
|
||||
def is_internal_invoice(self):
|
||||
"""
|
||||
Checks if its an internal transfer invoice
|
||||
and decides if to calculate any out standing amount or not
|
||||
Checks if its an internal transfer invoice
|
||||
and decides if to calculate any out standing amount or not
|
||||
"""
|
||||
|
||||
if self.doc.doctype in ('Sales Invoice', 'Purchase Invoice') and self.doc.is_internal_transfer():
|
||||
if self.doc.doctype in ("Sales Invoice", "Purchase Invoice") and self.doc.is_internal_transfer():
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -604,42 +703,61 @@ class calculate_taxes_and_totals(object):
|
||||
if self.doc.doctype == "Sales Invoice":
|
||||
self.calculate_paid_amount()
|
||||
|
||||
if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos') or \
|
||||
self.is_internal_invoice(): return
|
||||
if (
|
||||
self.doc.is_return
|
||||
and self.doc.return_against
|
||||
and not self.doc.get("is_pos")
|
||||
or self.is_internal_invoice()
|
||||
):
|
||||
return
|
||||
|
||||
self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
|
||||
self._set_in_company_currency(self.doc, ['write_off_amount'])
|
||||
self._set_in_company_currency(self.doc, ["write_off_amount"])
|
||||
|
||||
if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
grand_total = self.doc.rounded_total or self.doc.grand_total
|
||||
base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
|
||||
|
||||
if self.doc.party_account_currency == self.doc.currency:
|
||||
total_amount_to_pay = flt(grand_total - self.doc.total_advance
|
||||
- flt(self.doc.write_off_amount), self.doc.precision("grand_total"))
|
||||
total_amount_to_pay = flt(
|
||||
grand_total - self.doc.total_advance - flt(self.doc.write_off_amount),
|
||||
self.doc.precision("grand_total"),
|
||||
)
|
||||
else:
|
||||
total_amount_to_pay = flt(flt(base_grand_total, self.doc.precision("base_grand_total")) - self.doc.total_advance
|
||||
- flt(self.doc.base_write_off_amount), self.doc.precision("base_grand_total"))
|
||||
total_amount_to_pay = flt(
|
||||
flt(base_grand_total, self.doc.precision("base_grand_total"))
|
||||
- self.doc.total_advance
|
||||
- flt(self.doc.base_write_off_amount),
|
||||
self.doc.precision("base_grand_total"),
|
||||
)
|
||||
|
||||
self.doc.round_floats_in(self.doc, ["paid_amount"])
|
||||
change_amount = 0
|
||||
|
||||
if self.doc.doctype == "Sales Invoice" and not self.doc.get('is_return'):
|
||||
if self.doc.doctype == "Sales Invoice" and not self.doc.get("is_return"):
|
||||
self.calculate_change_amount()
|
||||
change_amount = self.doc.change_amount \
|
||||
if self.doc.party_account_currency == self.doc.currency else self.doc.base_change_amount
|
||||
change_amount = (
|
||||
self.doc.change_amount
|
||||
if self.doc.party_account_currency == self.doc.currency
|
||||
else self.doc.base_change_amount
|
||||
)
|
||||
|
||||
paid_amount = self.doc.paid_amount \
|
||||
if self.doc.party_account_currency == self.doc.currency else self.doc.base_paid_amount
|
||||
paid_amount = (
|
||||
self.doc.paid_amount
|
||||
if self.doc.party_account_currency == self.doc.currency
|
||||
else self.doc.base_paid_amount
|
||||
)
|
||||
|
||||
self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) + flt(change_amount),
|
||||
self.doc.precision("outstanding_amount"))
|
||||
self.doc.outstanding_amount = flt(
|
||||
total_amount_to_pay - flt(paid_amount) + flt(change_amount),
|
||||
self.doc.precision("outstanding_amount"),
|
||||
)
|
||||
|
||||
if (
|
||||
self.doc.doctype == 'Sales Invoice'
|
||||
and self.doc.get('is_pos')
|
||||
and self.doc.get('is_return')
|
||||
and not self.doc.get('is_consolidated')
|
||||
self.doc.doctype == "Sales Invoice"
|
||||
and self.doc.get("is_pos")
|
||||
and self.doc.get("is_return")
|
||||
and not self.doc.get("is_consolidated")
|
||||
):
|
||||
self.set_total_amount_to_default_mop(total_amount_to_pay)
|
||||
self.calculate_paid_amount()
|
||||
@@ -649,17 +767,17 @@ class calculate_taxes_and_totals(object):
|
||||
paid_amount = base_paid_amount = 0.0
|
||||
|
||||
if self.doc.is_pos:
|
||||
for payment in self.doc.get('payments'):
|
||||
for payment in self.doc.get("payments"):
|
||||
payment.amount = flt(payment.amount)
|
||||
payment.base_amount = payment.amount * flt(self.doc.conversion_rate)
|
||||
paid_amount += payment.amount
|
||||
base_paid_amount += payment.base_amount
|
||||
elif not self.doc.is_return:
|
||||
self.doc.set('payments', [])
|
||||
self.doc.set("payments", [])
|
||||
|
||||
if self.doc.redeem_loyalty_points and self.doc.loyalty_amount:
|
||||
base_paid_amount += self.doc.loyalty_amount
|
||||
paid_amount += (self.doc.loyalty_amount / flt(self.doc.conversion_rate))
|
||||
paid_amount += self.doc.loyalty_amount / flt(self.doc.conversion_rate)
|
||||
|
||||
self.doc.paid_amount = flt(paid_amount, self.doc.precision("paid_amount"))
|
||||
self.doc.base_paid_amount = flt(base_paid_amount, self.doc.precision("base_paid_amount"))
|
||||
@@ -670,21 +788,30 @@ class calculate_taxes_and_totals(object):
|
||||
grand_total = self.doc.rounded_total or self.doc.grand_total
|
||||
base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
|
||||
|
||||
if self.doc.doctype == "Sales Invoice" \
|
||||
and self.doc.paid_amount > grand_total and not self.doc.is_return \
|
||||
and any(d.type == "Cash" for d in self.doc.payments):
|
||||
if (
|
||||
self.doc.doctype == "Sales Invoice"
|
||||
and self.doc.paid_amount > grand_total
|
||||
and not self.doc.is_return
|
||||
and any(d.type == "Cash" for d in self.doc.payments)
|
||||
):
|
||||
|
||||
self.doc.change_amount = flt(self.doc.paid_amount - grand_total,
|
||||
self.doc.precision("change_amount"))
|
||||
self.doc.change_amount = flt(
|
||||
self.doc.paid_amount - grand_total, self.doc.precision("change_amount")
|
||||
)
|
||||
|
||||
self.doc.base_change_amount = flt(self.doc.base_paid_amount - base_grand_total,
|
||||
self.doc.precision("base_change_amount"))
|
||||
self.doc.base_change_amount = flt(
|
||||
self.doc.base_paid_amount - base_grand_total, self.doc.precision("base_change_amount")
|
||||
)
|
||||
|
||||
def calculate_write_off_amount(self):
|
||||
if self.doc.get('write_off_outstanding_amount_automatically'):
|
||||
self.doc.write_off_amount = flt(self.doc.outstanding_amount, self.doc.precision("write_off_amount"))
|
||||
self.doc.base_write_off_amount = flt(self.doc.write_off_amount * self.doc.conversion_rate,
|
||||
self.doc.precision("base_write_off_amount"))
|
||||
if self.doc.get("write_off_outstanding_amount_automatically"):
|
||||
self.doc.write_off_amount = flt(
|
||||
self.doc.outstanding_amount, self.doc.precision("write_off_amount")
|
||||
)
|
||||
self.doc.base_write_off_amount = flt(
|
||||
self.doc.write_off_amount * self.doc.conversion_rate,
|
||||
self.doc.precision("base_write_off_amount"),
|
||||
)
|
||||
|
||||
self.calculate_outstanding_amount()
|
||||
|
||||
@@ -695,10 +822,15 @@ class calculate_taxes_and_totals(object):
|
||||
if item.pricing_rules and not self.doc.ignore_pricing_rule:
|
||||
has_margin = False
|
||||
for d in get_applied_pricing_rules(item.pricing_rules):
|
||||
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
|
||||
pricing_rule = frappe.get_cached_doc("Pricing Rule", d)
|
||||
|
||||
if pricing_rule.margin_rate_or_amount and ((pricing_rule.currency == self.doc.currency and
|
||||
pricing_rule.margin_type in ['Amount', 'Percentage']) or pricing_rule.margin_type == 'Percentage'):
|
||||
if pricing_rule.margin_rate_or_amount and (
|
||||
(
|
||||
pricing_rule.currency == self.doc.currency
|
||||
and pricing_rule.margin_type in ["Amount", "Percentage"]
|
||||
)
|
||||
or pricing_rule.margin_type == "Percentage"
|
||||
):
|
||||
item.margin_type = pricing_rule.margin_type
|
||||
item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
|
||||
has_margin = True
|
||||
@@ -709,12 +841,17 @@ class calculate_taxes_and_totals(object):
|
||||
|
||||
if not item.pricing_rules and flt(item.rate) > flt(item.price_list_rate):
|
||||
item.margin_type = "Amount"
|
||||
item.margin_rate_or_amount = flt(item.rate - item.price_list_rate,
|
||||
item.precision("margin_rate_or_amount"))
|
||||
item.margin_rate_or_amount = flt(
|
||||
item.rate - item.price_list_rate, item.precision("margin_rate_or_amount")
|
||||
)
|
||||
item.rate_with_margin = item.rate
|
||||
|
||||
elif item.margin_type and item.margin_rate_or_amount:
|
||||
margin_value = item.margin_rate_or_amount if item.margin_type == 'Amount' else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100
|
||||
margin_value = (
|
||||
item.margin_rate_or_amount
|
||||
if item.margin_type == "Amount"
|
||||
else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100
|
||||
)
|
||||
rate_with_margin = flt(item.price_list_rate) + flt(margin_value)
|
||||
base_rate_with_margin = flt(rate_with_margin) * flt(self.doc.conversion_rate)
|
||||
|
||||
@@ -724,16 +861,24 @@ class calculate_taxes_and_totals(object):
|
||||
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
|
||||
|
||||
def set_total_amount_to_default_mop(self, total_amount_to_pay):
|
||||
default_mode_of_payment = frappe.db.get_value('POS Payment Method',
|
||||
{'parent': self.doc.pos_profile, 'default': 1}, ['mode_of_payment'], as_dict=1)
|
||||
default_mode_of_payment = frappe.db.get_value(
|
||||
"POS Payment Method",
|
||||
{"parent": self.doc.pos_profile, "default": 1},
|
||||
["mode_of_payment"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
if default_mode_of_payment:
|
||||
self.doc.payments = []
|
||||
self.doc.append('payments', {
|
||||
'mode_of_payment': default_mode_of_payment.mode_of_payment,
|
||||
'amount': total_amount_to_pay,
|
||||
'default': 1
|
||||
})
|
||||
self.doc.append(
|
||||
"payments",
|
||||
{
|
||||
"mode_of_payment": default_mode_of_payment.mode_of_payment,
|
||||
"amount": total_amount_to_pay,
|
||||
"default": 1,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def get_itemised_tax_breakup_html(doc):
|
||||
if not doc.taxes:
|
||||
@@ -743,7 +888,7 @@ def get_itemised_tax_breakup_html(doc):
|
||||
# get headers
|
||||
tax_accounts = []
|
||||
for tax in doc.taxes:
|
||||
if getattr(tax, "category", None) and tax.category=="Valuation":
|
||||
if getattr(tax, "category", None) and tax.category == "Valuation":
|
||||
continue
|
||||
if tax.description not in tax_accounts:
|
||||
tax_accounts.append(tax.description)
|
||||
@@ -759,34 +904,40 @@ def get_itemised_tax_breakup_html(doc):
|
||||
frappe.flags.company = None
|
||||
|
||||
return frappe.render_template(
|
||||
"templates/includes/itemised_tax_breakup.html", dict(
|
||||
"templates/includes/itemised_tax_breakup.html",
|
||||
dict(
|
||||
headers=headers,
|
||||
itemised_tax=itemised_tax,
|
||||
itemised_taxable_amount=itemised_taxable_amount,
|
||||
tax_accounts=tax_accounts,
|
||||
doc=doc
|
||||
)
|
||||
doc=doc,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_round_off_applicable_accounts(company, account_list):
|
||||
account_list = get_regional_round_off_accounts(company, account_list)
|
||||
|
||||
return account_list
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def get_regional_round_off_accounts(company, account_list):
|
||||
pass
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def update_itemised_tax_data(doc):
|
||||
#Don't delete this method, used for localization
|
||||
# Don't delete this method, used for localization
|
||||
pass
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
|
||||
return [_("Item"), _("Taxable Amount")] + tax_accounts
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def get_itemised_tax_breakup_data(doc):
|
||||
itemised_tax = get_itemised_tax(doc.taxes)
|
||||
@@ -795,10 +946,11 @@ def get_itemised_tax_breakup_data(doc):
|
||||
|
||||
return itemised_tax, itemised_taxable_amount
|
||||
|
||||
|
||||
def get_itemised_tax(taxes, with_tax_account=False):
|
||||
itemised_tax = {}
|
||||
for tax in taxes:
|
||||
if getattr(tax, "category", None) and tax.category=="Valuation":
|
||||
if getattr(tax, "category", None) and tax.category == "Valuation":
|
||||
continue
|
||||
|
||||
item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
|
||||
@@ -815,16 +967,16 @@ def get_itemised_tax(taxes, with_tax_account=False):
|
||||
else:
|
||||
tax_rate = flt(tax_data)
|
||||
|
||||
itemised_tax[item_code][tax.description] = frappe._dict(dict(
|
||||
tax_rate = tax_rate,
|
||||
tax_amount = tax_amount
|
||||
))
|
||||
itemised_tax[item_code][tax.description] = frappe._dict(
|
||||
dict(tax_rate=tax_rate, tax_amount=tax_amount)
|
||||
)
|
||||
|
||||
if with_tax_account:
|
||||
itemised_tax[item_code][tax.description].tax_account = tax.account_head
|
||||
|
||||
return itemised_tax
|
||||
|
||||
|
||||
def get_itemised_taxable_amount(items):
|
||||
itemised_taxable_amount = frappe._dict()
|
||||
for item in items:
|
||||
@@ -834,16 +986,18 @@ def get_itemised_taxable_amount(items):
|
||||
|
||||
return itemised_taxable_amount
|
||||
|
||||
|
||||
def get_rounded_tax_amount(itemised_tax, precision):
|
||||
# Rounding based on tax_amount precision
|
||||
for taxes in itemised_tax.values():
|
||||
for tax_account in taxes:
|
||||
taxes[tax_account]["tax_amount"] = flt(taxes[tax_account]["tax_amount"], precision)
|
||||
|
||||
|
||||
class init_landed_taxes_and_totals(object):
|
||||
def __init__(self, doc):
|
||||
self.doc = doc
|
||||
self.tax_field = 'taxes' if self.doc.doctype == 'Landed Cost Voucher' else 'additional_costs'
|
||||
self.tax_field = "taxes" if self.doc.doctype == "Landed Cost Voucher" else "additional_costs"
|
||||
self.set_account_currency()
|
||||
self.set_exchange_rate()
|
||||
self.set_amounts_in_company_currency()
|
||||
@@ -852,7 +1006,7 @@ class init_landed_taxes_and_totals(object):
|
||||
company_currency = erpnext.get_company_currency(self.doc.company)
|
||||
for d in self.doc.get(self.tax_field):
|
||||
if not d.account_currency:
|
||||
account_currency = frappe.db.get_value('Account', d.expense_account, 'account_currency')
|
||||
account_currency = frappe.db.get_value("Account", d.expense_account, "account_currency")
|
||||
d.account_currency = account_currency or company_currency
|
||||
|
||||
def set_exchange_rate(self):
|
||||
@@ -861,8 +1015,12 @@ class init_landed_taxes_and_totals(object):
|
||||
if d.account_currency == company_currency:
|
||||
d.exchange_rate = 1
|
||||
elif not d.exchange_rate:
|
||||
d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
|
||||
account_currency=d.account_currency, company=self.doc.company)
|
||||
d.exchange_rate = get_exchange_rate(
|
||||
self.doc.posting_date,
|
||||
account=d.expense_account,
|
||||
account_currency=d.account_currency,
|
||||
company=self.doc.company,
|
||||
)
|
||||
|
||||
if not d.exchange_rate:
|
||||
frappe.throw(_("Row {0}: Exchange Rate is mandatory").format(d.idx))
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import json
|
||||
import unittest
|
||||
|
||||
@@ -14,11 +13,12 @@ from erpnext.stock.doctype.quality_inspection.test_quality_inspection import (
|
||||
|
||||
class TestItemVariant(unittest.TestCase):
|
||||
def test_tables_in_template_copied_to_variant(self):
|
||||
fields = [{'field_name': 'quality_inspection_template'}]
|
||||
fields = [{"field_name": "quality_inspection_template"}]
|
||||
set_item_variant_settings(fields)
|
||||
variant = make_item_variant()
|
||||
self.assertEqual(variant.get("quality_inspection_template"), "_Test QC Template")
|
||||
|
||||
|
||||
def create_variant_with_tables(item, args):
|
||||
if isinstance(args, string_types):
|
||||
args = json.loads(args)
|
||||
@@ -29,14 +29,11 @@ def create_variant_with_tables(item, args):
|
||||
template.save()
|
||||
|
||||
variant = frappe.new_doc("Item")
|
||||
variant.variant_based_on = 'Item Attribute'
|
||||
variant.variant_based_on = "Item Attribute"
|
||||
variant_attributes = []
|
||||
|
||||
for d in template.attributes:
|
||||
variant_attributes.append({
|
||||
"attribute": d.attribute,
|
||||
"attribute_value": args.get(d.attribute)
|
||||
})
|
||||
variant_attributes.append({"attribute": d.attribute, "attribute_value": args.get(d.attribute)})
|
||||
|
||||
variant.set("attributes", variant_attributes)
|
||||
copy_attributes_to_variant(template, variant)
|
||||
@@ -44,6 +41,7 @@ def create_variant_with_tables(item, args):
|
||||
|
||||
return variant
|
||||
|
||||
|
||||
def make_item_variant():
|
||||
frappe.delete_doc_if_exists("Item", "_Test Variant Item-XSL", force=1)
|
||||
variant = create_variant_with_tables("_Test Variant Item", '{"Test Size": "Extra Small"}')
|
||||
@@ -52,6 +50,7 @@ def make_item_variant():
|
||||
variant.save()
|
||||
return variant
|
||||
|
||||
|
||||
def make_quality_inspection_template():
|
||||
qc_template = "_Test QC Template"
|
||||
if frappe.db.exists("Quality Inspection Template", qc_template):
|
||||
@@ -61,10 +60,13 @@ def make_quality_inspection_template():
|
||||
qc.quality_inspection_template_name = qc_template
|
||||
|
||||
create_quality_inspection_parameter("Moisture")
|
||||
qc.append('item_quality_inspection_parameter', {
|
||||
"specification": "Moisture",
|
||||
"value": "< 5%",
|
||||
})
|
||||
qc.append(
|
||||
"item_quality_inspection_parameter",
|
||||
{
|
||||
"specification": "Moisture",
|
||||
"value": "< 5%",
|
||||
},
|
||||
)
|
||||
|
||||
qc.insert()
|
||||
return qc.name
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import json
|
||||
import unittest
|
||||
|
||||
@@ -11,14 +10,14 @@ from frappe.utils import add_months, nowdate
|
||||
|
||||
class TestMapper(unittest.TestCase):
|
||||
def test_map_docs(self):
|
||||
'''Test mapping of multiple source docs on a single target doc'''
|
||||
"""Test mapping of multiple source docs on a single target doc"""
|
||||
|
||||
make_test_records("Item")
|
||||
items = ['_Test Item', '_Test Item 2', '_Test FG Item']
|
||||
items = ["_Test Item", "_Test Item 2", "_Test FG Item"]
|
||||
|
||||
# Make source docs (quotations) and a target doc (sales order)
|
||||
qtn1, item_list_1 = self.make_quotation(items, '_Test Customer')
|
||||
qtn2, item_list_2 = self.make_quotation(items, '_Test Customer')
|
||||
qtn1, item_list_1 = self.make_quotation(items, "_Test Customer")
|
||||
qtn2, item_list_2 = self.make_quotation(items, "_Test Customer")
|
||||
so, item_list_3 = self.make_sales_order()
|
||||
|
||||
# Map source docs to target with corresponding mapper method
|
||||
@@ -27,20 +26,20 @@ class TestMapper(unittest.TestCase):
|
||||
|
||||
# Assert that all inserted items are present in updated sales order
|
||||
src_items = item_list_1 + item_list_2 + item_list_3
|
||||
self.assertEqual(set(d for d in src_items),
|
||||
set(d.item_code for d in updated_so.items))
|
||||
|
||||
self.assertEqual(set(d for d in src_items), set(d.item_code for d in updated_so.items))
|
||||
|
||||
def make_quotation(self, item_list, customer):
|
||||
|
||||
qtn = frappe.get_doc({
|
||||
"doctype": "Quotation",
|
||||
"quotation_to": "Customer",
|
||||
"party_name": customer,
|
||||
"order_type": "Sales",
|
||||
"transaction_date" : nowdate(),
|
||||
"valid_till" : add_months(nowdate(), 1)
|
||||
})
|
||||
qtn = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Quotation",
|
||||
"quotation_to": "Customer",
|
||||
"party_name": customer,
|
||||
"order_type": "Sales",
|
||||
"transaction_date": nowdate(),
|
||||
"valid_till": add_months(nowdate(), 1),
|
||||
}
|
||||
)
|
||||
for item in item_list:
|
||||
qtn.append("items", {"qty": "2", "item_code": item})
|
||||
|
||||
@@ -48,21 +47,23 @@ class TestMapper(unittest.TestCase):
|
||||
return qtn, item_list
|
||||
|
||||
def make_sales_order(self):
|
||||
item = frappe.get_doc({
|
||||
"base_amount": 1000.0,
|
||||
"base_rate": 100.0,
|
||||
"description": "CPU",
|
||||
"doctype": "Sales Order Item",
|
||||
"item_code": "_Test Item",
|
||||
"item_name": "CPU",
|
||||
"parentfield": "items",
|
||||
"qty": 10.0,
|
||||
"rate": 100.0,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"stock_uom": "_Test UOM",
|
||||
"conversion_factor": 1.0,
|
||||
"uom": "_Test UOM"
|
||||
})
|
||||
so = frappe.get_doc(frappe.get_test_records('Sales Order')[0])
|
||||
item = frappe.get_doc(
|
||||
{
|
||||
"base_amount": 1000.0,
|
||||
"base_rate": 100.0,
|
||||
"description": "CPU",
|
||||
"doctype": "Sales Order Item",
|
||||
"item_code": "_Test Item",
|
||||
"item_name": "CPU",
|
||||
"parentfield": "items",
|
||||
"qty": 10.0,
|
||||
"rate": 100.0,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"stock_uom": "_Test UOM",
|
||||
"conversion_factor": 1.0,
|
||||
"uom": "_Test UOM",
|
||||
}
|
||||
)
|
||||
so = frappe.get_doc(frappe.get_test_records("Sales Order")[0])
|
||||
so.insert(ignore_permissions=True)
|
||||
return so, [item.item_code]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import unittest
|
||||
from uuid import uuid4 as _uuid4
|
||||
|
||||
@@ -6,104 +5,131 @@ import frappe
|
||||
|
||||
|
||||
def uuid4():
|
||||
return str(_uuid4())
|
||||
return str(_uuid4())
|
||||
|
||||
|
||||
class TestTaxes(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.company = frappe.get_doc({
|
||||
'doctype': 'Company',
|
||||
'company_name': uuid4(),
|
||||
'abbr': ''.join(s[0] for s in uuid4().split('-')),
|
||||
'default_currency': 'USD',
|
||||
'country': 'United States',
|
||||
}).insert()
|
||||
self.account = frappe.get_doc({
|
||||
'doctype': 'Account',
|
||||
'account_name': uuid4(),
|
||||
'account_type': 'Tax',
|
||||
'company': self.company.name,
|
||||
'parent_account': 'Duties and Taxes - {self.company.abbr}'.format(self=self)
|
||||
}).insert()
|
||||
self.item_group = frappe.get_doc({
|
||||
'doctype': 'Item Group',
|
||||
'item_group_name': uuid4(),
|
||||
'parent_item_group': 'All Item Groups',
|
||||
}).insert()
|
||||
self.item_tax_template = frappe.get_doc({
|
||||
'doctype': 'Item Tax Template',
|
||||
'title': uuid4(),
|
||||
'company': self.company.name,
|
||||
'taxes': [
|
||||
{
|
||||
'tax_type': self.account.name,
|
||||
'tax_rate': 2,
|
||||
}
|
||||
]
|
||||
}).insert()
|
||||
self.item = frappe.get_doc({
|
||||
'doctype': 'Item',
|
||||
'item_code': uuid4(),
|
||||
'item_group': self.item_group.name,
|
||||
'is_stock_item': 0,
|
||||
'taxes': [
|
||||
{
|
||||
'item_tax_template': self.item_tax_template.name,
|
||||
'tax_category': '',
|
||||
}
|
||||
],
|
||||
}).insert()
|
||||
self.customer = frappe.get_doc({
|
||||
'doctype': 'Customer',
|
||||
'customer_name': uuid4(),
|
||||
'customer_group': 'All Customer Groups',
|
||||
}).insert()
|
||||
self.supplier = frappe.get_doc({
|
||||
'doctype': 'Supplier',
|
||||
'supplier_name': uuid4(),
|
||||
'supplier_group': 'All Supplier Groups',
|
||||
}).insert()
|
||||
def setUp(self):
|
||||
self.company = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Company",
|
||||
"company_name": uuid4(),
|
||||
"abbr": "".join(s[0] for s in uuid4().split("-")),
|
||||
"default_currency": "USD",
|
||||
"country": "United States",
|
||||
}
|
||||
).insert()
|
||||
self.account = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Account",
|
||||
"account_name": uuid4(),
|
||||
"account_type": "Tax",
|
||||
"company": self.company.name,
|
||||
"parent_account": "Duties and Taxes - {self.company.abbr}".format(self=self),
|
||||
}
|
||||
).insert()
|
||||
self.item_group = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item Group",
|
||||
"item_group_name": uuid4(),
|
||||
"parent_item_group": "All Item Groups",
|
||||
}
|
||||
).insert()
|
||||
self.item_tax_template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item Tax Template",
|
||||
"title": uuid4(),
|
||||
"company": self.company.name,
|
||||
"taxes": [
|
||||
{
|
||||
"tax_type": self.account.name,
|
||||
"tax_rate": 2,
|
||||
}
|
||||
],
|
||||
}
|
||||
).insert()
|
||||
self.item = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item",
|
||||
"item_code": uuid4(),
|
||||
"item_group": self.item_group.name,
|
||||
"is_stock_item": 0,
|
||||
"taxes": [
|
||||
{
|
||||
"item_tax_template": self.item_tax_template.name,
|
||||
"tax_category": "",
|
||||
}
|
||||
],
|
||||
}
|
||||
).insert()
|
||||
self.customer = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Customer",
|
||||
"customer_name": uuid4(),
|
||||
"customer_group": "All Customer Groups",
|
||||
}
|
||||
).insert()
|
||||
self.supplier = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": uuid4(),
|
||||
"supplier_group": "All Supplier Groups",
|
||||
}
|
||||
).insert()
|
||||
|
||||
def test_taxes(self):
|
||||
self.created_docs = []
|
||||
for dt in ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice',
|
||||
'Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']:
|
||||
doc = frappe.get_doc({
|
||||
'doctype': dt,
|
||||
'company': self.company.name,
|
||||
'supplier': self.supplier.name,
|
||||
'currency': "USD",
|
||||
'schedule_date': frappe.utils.nowdate(),
|
||||
'delivery_date': frappe.utils.nowdate(),
|
||||
'customer': self.customer.name,
|
||||
'buying_price_list' if dt.startswith('Purchase') else 'selling_price_list'
|
||||
: 'Standard Buying' if dt.startswith('Purchase') else 'Standard Selling',
|
||||
'items': [
|
||||
{
|
||||
'item_code': self.item.name,
|
||||
'qty': 300,
|
||||
'rate': 100,
|
||||
}
|
||||
],
|
||||
'taxes': [
|
||||
{
|
||||
'charge_type': 'On Item Quantity',
|
||||
'account_head': self.account.name,
|
||||
'description': 'N/A',
|
||||
'rate': 0,
|
||||
},
|
||||
],
|
||||
})
|
||||
doc.run_method('set_missing_values')
|
||||
doc.run_method('calculate_taxes_and_totals')
|
||||
doc.insert()
|
||||
self.assertEqual(doc.taxes[0].tax_amount, 600)
|
||||
self.created_docs.append(doc)
|
||||
def test_taxes(self):
|
||||
self.created_docs = []
|
||||
for dt in [
|
||||
"Purchase Order",
|
||||
"Purchase Receipt",
|
||||
"Purchase Invoice",
|
||||
"Quotation",
|
||||
"Sales Order",
|
||||
"Delivery Note",
|
||||
"Sales Invoice",
|
||||
]:
|
||||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": dt,
|
||||
"company": self.company.name,
|
||||
"supplier": self.supplier.name,
|
||||
"currency": "USD",
|
||||
"schedule_date": frappe.utils.nowdate(),
|
||||
"delivery_date": frappe.utils.nowdate(),
|
||||
"customer": self.customer.name,
|
||||
"buying_price_list"
|
||||
if dt.startswith("Purchase")
|
||||
else "selling_price_list": "Standard Buying"
|
||||
if dt.startswith("Purchase")
|
||||
else "Standard Selling",
|
||||
"items": [
|
||||
{
|
||||
"item_code": self.item.name,
|
||||
"qty": 300,
|
||||
"rate": 100,
|
||||
}
|
||||
],
|
||||
"taxes": [
|
||||
{
|
||||
"charge_type": "On Item Quantity",
|
||||
"account_head": self.account.name,
|
||||
"description": "N/A",
|
||||
"rate": 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
doc.run_method("set_missing_values")
|
||||
doc.run_method("calculate_taxes_and_totals")
|
||||
doc.insert()
|
||||
self.assertEqual(doc.taxes[0].tax_amount, 600)
|
||||
self.created_docs.append(doc)
|
||||
|
||||
def tearDown(self):
|
||||
for doc in self.created_docs:
|
||||
doc.delete()
|
||||
self.item.delete()
|
||||
self.item_group.delete()
|
||||
self.item_tax_template.delete()
|
||||
self.account.delete()
|
||||
self.company.delete()
|
||||
def tearDown(self):
|
||||
for doc in self.created_docs:
|
||||
doc.delete()
|
||||
self.item.delete()
|
||||
self.item_group.delete()
|
||||
self.item_tax_template.delete()
|
||||
self.account.delete()
|
||||
self.company.delete()
|
||||
|
||||
@@ -5,18 +5,28 @@ import frappe
|
||||
|
||||
class TestUtils(unittest.TestCase):
|
||||
def test_reset_default_field_value(self):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Purchase Receipt",
|
||||
"set_warehouse": "Warehouse 1",
|
||||
})
|
||||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Purchase Receipt",
|
||||
"set_warehouse": "Warehouse 1",
|
||||
}
|
||||
)
|
||||
|
||||
# Same values
|
||||
doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 1"}]
|
||||
doc.items = [
|
||||
{"warehouse": "Warehouse 1"},
|
||||
{"warehouse": "Warehouse 1"},
|
||||
{"warehouse": "Warehouse 1"},
|
||||
]
|
||||
doc.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
self.assertEqual(doc.set_warehouse, "Warehouse 1")
|
||||
|
||||
# Mixed values
|
||||
doc.items = [{"warehouse": "Warehouse 1"}, {"warehouse": "Warehouse 2"}, {"warehouse": "Warehouse 1"}]
|
||||
doc.items = [
|
||||
{"warehouse": "Warehouse 1"},
|
||||
{"warehouse": "Warehouse 2"},
|
||||
{"warehouse": "Warehouse 1"},
|
||||
]
|
||||
doc.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
self.assertEqual(doc.set_warehouse, None)
|
||||
|
||||
@@ -30,9 +40,13 @@ class TestUtils(unittest.TestCase):
|
||||
from_warehouse="_Test Warehouse - _TC",
|
||||
to_warehouse="_Test Warehouse 1 - _TC",
|
||||
items=[
|
||||
frappe._dict(item_code="_Test Item", qty=1, basic_rate=200, s_warehouse="_Test Warehouse - _TC"),
|
||||
frappe._dict(item_code="_Test FG Item", qty=4, t_warehouse="_Test Warehouse 1 - _TC", is_finished_item=1)
|
||||
]
|
||||
frappe._dict(
|
||||
item_code="_Test Item", qty=1, basic_rate=200, s_warehouse="_Test Warehouse - _TC"
|
||||
),
|
||||
frappe._dict(
|
||||
item_code="_Test FG Item", qty=4, t_warehouse="_Test Warehouse 1 - _TC", is_finished_item=1
|
||||
),
|
||||
],
|
||||
)
|
||||
se.save()
|
||||
|
||||
@@ -43,18 +57,20 @@ class TestUtils(unittest.TestCase):
|
||||
se.delete()
|
||||
|
||||
def test_reset_default_field_value_in_transfer_stock_entry(self):
|
||||
doc = frappe.get_doc({
|
||||
"doctype": "Stock Entry",
|
||||
"purpose": "Material Receipt",
|
||||
"from_warehouse": "Warehouse 1",
|
||||
"to_warehouse": "Warehouse 2",
|
||||
})
|
||||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Stock Entry",
|
||||
"purpose": "Material Receipt",
|
||||
"from_warehouse": "Warehouse 1",
|
||||
"to_warehouse": "Warehouse 2",
|
||||
}
|
||||
)
|
||||
|
||||
# Same values
|
||||
doc.items = [
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"}
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
|
||||
]
|
||||
|
||||
doc.reset_default_field_value("from_warehouse", "items", "s_warehouse")
|
||||
@@ -66,10 +82,10 @@ class TestUtils(unittest.TestCase):
|
||||
doc.items = [
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
|
||||
{"s_warehouse": "Warehouse 3", "t_warehouse": "Warehouse 2"},
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"}
|
||||
{"s_warehouse": "Warehouse 1", "t_warehouse": "Warehouse 2"},
|
||||
]
|
||||
|
||||
doc.reset_default_field_value("from_warehouse", "items", "s_warehouse")
|
||||
doc.reset_default_field_value("to_warehouse", "items", "t_warehouse")
|
||||
self.assertEqual(doc.from_warehouse, None)
|
||||
self.assertEqual(doc.to_warehouse, "Warehouse 2")
|
||||
self.assertEqual(doc.to_warehouse, "Warehouse 2")
|
||||
|
||||
@@ -17,17 +17,33 @@ def get_columns(filters, trans):
|
||||
# get conditions for grouping filter cond
|
||||
group_by_cols = group_wise_column(filters.get("group_by"))
|
||||
|
||||
columns = based_on_details["based_on_cols"] + period_cols + [_("Total(Qty)") + ":Float:120", _("Total(Amt)") + ":Currency:120"]
|
||||
columns = (
|
||||
based_on_details["based_on_cols"]
|
||||
+ period_cols
|
||||
+ [_("Total(Qty)") + ":Float:120", _("Total(Amt)") + ":Currency:120"]
|
||||
)
|
||||
if group_by_cols:
|
||||
columns = based_on_details["based_on_cols"] + group_by_cols + period_cols + \
|
||||
[_("Total(Qty)") + ":Float:120", _("Total(Amt)") + ":Currency:120"]
|
||||
columns = (
|
||||
based_on_details["based_on_cols"]
|
||||
+ group_by_cols
|
||||
+ period_cols
|
||||
+ [_("Total(Qty)") + ":Float:120", _("Total(Amt)") + ":Currency:120"]
|
||||
)
|
||||
|
||||
conditions = {"based_on_select": based_on_details["based_on_select"], "period_wise_select": period_select,
|
||||
"columns": columns, "group_by": based_on_details["based_on_group_by"], "grbc": group_by_cols, "trans": trans,
|
||||
"addl_tables": based_on_details["addl_tables"], "addl_tables_relational_cond": based_on_details.get("addl_tables_relational_cond", "")}
|
||||
conditions = {
|
||||
"based_on_select": based_on_details["based_on_select"],
|
||||
"period_wise_select": period_select,
|
||||
"columns": columns,
|
||||
"group_by": based_on_details["based_on_group_by"],
|
||||
"grbc": group_by_cols,
|
||||
"trans": trans,
|
||||
"addl_tables": based_on_details["addl_tables"],
|
||||
"addl_tables_relational_cond": based_on_details.get("addl_tables_relational_cond", ""),
|
||||
}
|
||||
|
||||
return conditions
|
||||
|
||||
|
||||
def validate_filters(filters):
|
||||
for f in ["Fiscal Year", "Based On", "Period", "Company"]:
|
||||
if not filters.get(f.lower().replace(" ", "_")):
|
||||
@@ -39,153 +55,231 @@ def validate_filters(filters):
|
||||
if filters.get("based_on") == filters.get("group_by"):
|
||||
frappe.throw(_("'Based On' and 'Group By' can not be same"))
|
||||
|
||||
|
||||
def get_data(filters, conditions):
|
||||
data = []
|
||||
inc, cond= '',''
|
||||
query_details = conditions["based_on_select"] + conditions["period_wise_select"]
|
||||
inc, cond = "", ""
|
||||
query_details = conditions["based_on_select"] + conditions["period_wise_select"]
|
||||
|
||||
posting_date = 't1.transaction_date'
|
||||
if conditions.get('trans') in ['Sales Invoice', 'Purchase Invoice', 'Purchase Receipt', 'Delivery Note']:
|
||||
posting_date = 't1.posting_date'
|
||||
posting_date = "t1.transaction_date"
|
||||
if conditions.get("trans") in [
|
||||
"Sales Invoice",
|
||||
"Purchase Invoice",
|
||||
"Purchase Receipt",
|
||||
"Delivery Note",
|
||||
]:
|
||||
posting_date = "t1.posting_date"
|
||||
if filters.period_based_on:
|
||||
posting_date = 't1.'+filters.period_based_on
|
||||
posting_date = "t1." + filters.period_based_on
|
||||
|
||||
if conditions["based_on_select"] in ["t1.project,", "t2.project,"]:
|
||||
cond = ' and '+ conditions["based_on_select"][:-1] +' IS Not NULL'
|
||||
if conditions.get('trans') in ['Sales Order', 'Purchase Order']:
|
||||
cond = " and " + conditions["based_on_select"][:-1] + " IS Not NULL"
|
||||
if conditions.get("trans") in ["Sales Order", "Purchase Order"]:
|
||||
cond += " and t1.status != 'Closed'"
|
||||
|
||||
if conditions.get('trans') == 'Quotation' and filters.get("group_by") == 'Customer':
|
||||
if conditions.get("trans") == "Quotation" and filters.get("group_by") == "Customer":
|
||||
cond += " and t1.quotation_to = 'Customer'"
|
||||
|
||||
year_start_date, year_end_date = frappe.db.get_value("Fiscal Year",
|
||||
filters.get('fiscal_year'), ["year_start_date", "year_end_date"])
|
||||
year_start_date, year_end_date = frappe.db.get_value(
|
||||
"Fiscal Year", filters.get("fiscal_year"), ["year_start_date", "year_end_date"]
|
||||
)
|
||||
|
||||
if filters.get("group_by"):
|
||||
sel_col = ''
|
||||
sel_col = ""
|
||||
ind = conditions["columns"].index(conditions["grbc"][0])
|
||||
|
||||
if filters.get("group_by") == 'Item':
|
||||
sel_col = 't2.item_code'
|
||||
elif filters.get("group_by") == 'Customer':
|
||||
sel_col = 't1.party_name' if conditions.get('trans') == 'Quotation' else 't1.customer'
|
||||
elif filters.get("group_by") == 'Supplier':
|
||||
sel_col = 't1.supplier'
|
||||
if filters.get("group_by") == "Item":
|
||||
sel_col = "t2.item_code"
|
||||
elif filters.get("group_by") == "Customer":
|
||||
sel_col = "t1.party_name" if conditions.get("trans") == "Quotation" else "t1.customer"
|
||||
elif filters.get("group_by") == "Supplier":
|
||||
sel_col = "t1.supplier"
|
||||
|
||||
if filters.get('based_on') in ['Item','Customer','Supplier']:
|
||||
if filters.get("based_on") in ["Item", "Customer", "Supplier"]:
|
||||
inc = 2
|
||||
else :
|
||||
else:
|
||||
inc = 1
|
||||
data1 = frappe.db.sql(""" select %s from `tab%s` t1, `tab%s Item` t2 %s
|
||||
data1 = frappe.db.sql(
|
||||
""" select %s from `tab%s` t1, `tab%s Item` t2 %s
|
||||
where t2.parent = t1.name and t1.company = %s and %s between %s and %s and
|
||||
t1.docstatus = 1 %s %s
|
||||
group by %s
|
||||
""" % (query_details, conditions["trans"], conditions["trans"], conditions["addl_tables"], "%s",
|
||||
posting_date, "%s", "%s", conditions.get("addl_tables_relational_cond"), cond, conditions["group_by"]), (filters.get("company"),
|
||||
year_start_date, year_end_date),as_list=1)
|
||||
"""
|
||||
% (
|
||||
query_details,
|
||||
conditions["trans"],
|
||||
conditions["trans"],
|
||||
conditions["addl_tables"],
|
||||
"%s",
|
||||
posting_date,
|
||||
"%s",
|
||||
"%s",
|
||||
conditions.get("addl_tables_relational_cond"),
|
||||
cond,
|
||||
conditions["group_by"],
|
||||
),
|
||||
(filters.get("company"), year_start_date, year_end_date),
|
||||
as_list=1,
|
||||
)
|
||||
|
||||
for d in range(len(data1)):
|
||||
#to add blanck column
|
||||
# to add blanck column
|
||||
dt = data1[d]
|
||||
dt.insert(ind,'')
|
||||
dt.insert(ind, "")
|
||||
data.append(dt)
|
||||
|
||||
#to get distinct value of col specified by group_by in filter
|
||||
row = frappe.db.sql("""select DISTINCT(%s) from `tab%s` t1, `tab%s Item` t2 %s
|
||||
# to get distinct value of col specified by group_by in filter
|
||||
row = frappe.db.sql(
|
||||
"""select DISTINCT(%s) from `tab%s` t1, `tab%s Item` t2 %s
|
||||
where t2.parent = t1.name and t1.company = %s and %s between %s and %s
|
||||
and t1.docstatus = 1 and %s = %s %s %s
|
||||
""" %
|
||||
(sel_col, conditions["trans"], conditions["trans"], conditions["addl_tables"],
|
||||
"%s", posting_date, "%s", "%s", conditions["group_by"], "%s", conditions.get("addl_tables_relational_cond"), cond),
|
||||
(filters.get("company"), year_start_date, year_end_date, data1[d][0]), as_list=1)
|
||||
"""
|
||||
% (
|
||||
sel_col,
|
||||
conditions["trans"],
|
||||
conditions["trans"],
|
||||
conditions["addl_tables"],
|
||||
"%s",
|
||||
posting_date,
|
||||
"%s",
|
||||
"%s",
|
||||
conditions["group_by"],
|
||||
"%s",
|
||||
conditions.get("addl_tables_relational_cond"),
|
||||
cond,
|
||||
),
|
||||
(filters.get("company"), year_start_date, year_end_date, data1[d][0]),
|
||||
as_list=1,
|
||||
)
|
||||
|
||||
for i in range(len(row)):
|
||||
des = ['' for q in range(len(conditions["columns"]))]
|
||||
des = ["" for q in range(len(conditions["columns"]))]
|
||||
|
||||
#get data for group_by filter
|
||||
row1 = frappe.db.sql(""" select %s , %s from `tab%s` t1, `tab%s Item` t2 %s
|
||||
# get data for group_by filter
|
||||
row1 = frappe.db.sql(
|
||||
""" select %s , %s from `tab%s` t1, `tab%s Item` t2 %s
|
||||
where t2.parent = t1.name and t1.company = %s and %s between %s and %s
|
||||
and t1.docstatus = 1 and %s = %s and %s = %s %s %s
|
||||
""" %
|
||||
(sel_col, conditions["period_wise_select"], conditions["trans"],
|
||||
conditions["trans"], conditions["addl_tables"], "%s", posting_date, "%s","%s", sel_col,
|
||||
"%s", conditions["group_by"], "%s", conditions.get("addl_tables_relational_cond"), cond),
|
||||
(filters.get("company"), year_start_date, year_end_date, row[i][0],
|
||||
data1[d][0]), as_list=1)
|
||||
"""
|
||||
% (
|
||||
sel_col,
|
||||
conditions["period_wise_select"],
|
||||
conditions["trans"],
|
||||
conditions["trans"],
|
||||
conditions["addl_tables"],
|
||||
"%s",
|
||||
posting_date,
|
||||
"%s",
|
||||
"%s",
|
||||
sel_col,
|
||||
"%s",
|
||||
conditions["group_by"],
|
||||
"%s",
|
||||
conditions.get("addl_tables_relational_cond"),
|
||||
cond,
|
||||
),
|
||||
(filters.get("company"), year_start_date, year_end_date, row[i][0], data1[d][0]),
|
||||
as_list=1,
|
||||
)
|
||||
|
||||
des[ind] = row[i][0]
|
||||
|
||||
for j in range(1,len(conditions["columns"])-inc):
|
||||
des[j+inc] = row1[0][j]
|
||||
for j in range(1, len(conditions["columns"]) - inc):
|
||||
des[j + inc] = row1[0][j]
|
||||
|
||||
data.append(des)
|
||||
else:
|
||||
data = frappe.db.sql(""" select %s from `tab%s` t1, `tab%s Item` t2 %s
|
||||
data = frappe.db.sql(
|
||||
""" select %s from `tab%s` t1, `tab%s Item` t2 %s
|
||||
where t2.parent = t1.name and t1.company = %s and %s between %s and %s and
|
||||
t1.docstatus = 1 %s %s
|
||||
group by %s
|
||||
""" %
|
||||
(query_details, conditions["trans"], conditions["trans"], conditions["addl_tables"],
|
||||
"%s", posting_date, "%s", "%s", cond, conditions.get("addl_tables_relational_cond", ""), conditions["group_by"]),
|
||||
(filters.get("company"), year_start_date, year_end_date), as_list=1)
|
||||
"""
|
||||
% (
|
||||
query_details,
|
||||
conditions["trans"],
|
||||
conditions["trans"],
|
||||
conditions["addl_tables"],
|
||||
"%s",
|
||||
posting_date,
|
||||
"%s",
|
||||
"%s",
|
||||
cond,
|
||||
conditions.get("addl_tables_relational_cond", ""),
|
||||
conditions["group_by"],
|
||||
),
|
||||
(filters.get("company"), year_start_date, year_end_date),
|
||||
as_list=1,
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_mon(dt):
|
||||
return getdate(dt).strftime("%b")
|
||||
|
||||
|
||||
def period_wise_columns_query(filters, trans):
|
||||
query_details = ''
|
||||
query_details = ""
|
||||
pwc = []
|
||||
bet_dates = get_period_date_ranges(filters.get("period"), filters.get("fiscal_year"))
|
||||
|
||||
if trans in ['Purchase Receipt', 'Delivery Note', 'Purchase Invoice', 'Sales Invoice']:
|
||||
trans_date = 'posting_date'
|
||||
if trans in ["Purchase Receipt", "Delivery Note", "Purchase Invoice", "Sales Invoice"]:
|
||||
trans_date = "posting_date"
|
||||
if filters.period_based_on:
|
||||
trans_date = filters.period_based_on
|
||||
else:
|
||||
trans_date = 'transaction_date'
|
||||
trans_date = "transaction_date"
|
||||
|
||||
if filters.get("period") != 'Yearly':
|
||||
if filters.get("period") != "Yearly":
|
||||
for dt in bet_dates:
|
||||
get_period_wise_columns(dt, filters.get("period"), pwc)
|
||||
query_details = get_period_wise_query(dt, trans_date, query_details)
|
||||
else:
|
||||
pwc = [_(filters.get("fiscal_year")) + " ("+_("Qty") + "):Float:120",
|
||||
_(filters.get("fiscal_year")) + " ("+ _("Amt") + "):Currency:120"]
|
||||
pwc = [
|
||||
_(filters.get("fiscal_year")) + " (" + _("Qty") + "):Float:120",
|
||||
_(filters.get("fiscal_year")) + " (" + _("Amt") + "):Currency:120",
|
||||
]
|
||||
query_details = " SUM(t2.stock_qty), SUM(t2.base_net_amount),"
|
||||
|
||||
query_details += 'SUM(t2.stock_qty), SUM(t2.base_net_amount)'
|
||||
query_details += "SUM(t2.stock_qty), SUM(t2.base_net_amount)"
|
||||
return pwc, query_details
|
||||
|
||||
|
||||
def get_period_wise_columns(bet_dates, period, pwc):
|
||||
if period == 'Monthly':
|
||||
pwc += [_(get_mon(bet_dates[0])) + " (" + _("Qty") + "):Float:120",
|
||||
_(get_mon(bet_dates[0])) + " (" + _("Amt") + "):Currency:120"]
|
||||
if period == "Monthly":
|
||||
pwc += [
|
||||
_(get_mon(bet_dates[0])) + " (" + _("Qty") + "):Float:120",
|
||||
_(get_mon(bet_dates[0])) + " (" + _("Amt") + "):Currency:120",
|
||||
]
|
||||
else:
|
||||
pwc += [_(get_mon(bet_dates[0])) + "-" + _(get_mon(bet_dates[1])) + " (" + _("Qty") + "):Float:120",
|
||||
_(get_mon(bet_dates[0])) + "-" + _(get_mon(bet_dates[1])) + " (" + _("Amt") + "):Currency:120"]
|
||||
pwc += [
|
||||
_(get_mon(bet_dates[0])) + "-" + _(get_mon(bet_dates[1])) + " (" + _("Qty") + "):Float:120",
|
||||
_(get_mon(bet_dates[0])) + "-" + _(get_mon(bet_dates[1])) + " (" + _("Amt") + "):Currency:120",
|
||||
]
|
||||
|
||||
|
||||
def get_period_wise_query(bet_dates, trans_date, query_details):
|
||||
query_details += """SUM(IF(t1.%(trans_date)s BETWEEN '%(sd)s' AND '%(ed)s', t2.stock_qty, NULL)),
|
||||
SUM(IF(t1.%(trans_date)s BETWEEN '%(sd)s' AND '%(ed)s', t2.base_net_amount, NULL)),
|
||||
""" % {"trans_date": trans_date, "sd": bet_dates[0],"ed": bet_dates[1]}
|
||||
""" % {
|
||||
"trans_date": trans_date,
|
||||
"sd": bet_dates[0],
|
||||
"ed": bet_dates[1],
|
||||
}
|
||||
return query_details
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_period_date_ranges(period, fiscal_year=None, year_start_date=None):
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
if not year_start_date:
|
||||
year_start_date, year_end_date = frappe.db.get_value("Fiscal Year",
|
||||
fiscal_year, ["year_start_date", "year_end_date"])
|
||||
year_start_date, year_end_date = frappe.db.get_value(
|
||||
"Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"]
|
||||
)
|
||||
|
||||
increment = {
|
||||
"Monthly": 1,
|
||||
"Quarterly": 3,
|
||||
"Half-Yearly": 6,
|
||||
"Yearly": 12
|
||||
}.get(period)
|
||||
increment = {"Monthly": 1, "Quarterly": 3, "Half-Yearly": 6, "Yearly": 12}.get(period)
|
||||
|
||||
period_date_ranges = []
|
||||
for i in range(1, 13, increment):
|
||||
@@ -199,8 +293,10 @@ def get_period_date_ranges(period, fiscal_year=None, year_start_date=None):
|
||||
|
||||
return period_date_ranges
|
||||
|
||||
|
||||
def get_period_month_ranges(period, fiscal_year):
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
period_month_ranges = []
|
||||
|
||||
for start_date, end_date in get_period_date_ranges(period, fiscal_year):
|
||||
@@ -212,6 +308,7 @@ def get_period_month_ranges(period, fiscal_year):
|
||||
|
||||
return period_month_ranges
|
||||
|
||||
|
||||
def based_wise_columns_query(based_on, trans):
|
||||
based_on_details = {}
|
||||
|
||||
@@ -219,65 +316,74 @@ def based_wise_columns_query(based_on, trans):
|
||||
if based_on == "Item":
|
||||
based_on_details["based_on_cols"] = ["Item:Link/Item:120", "Item Name:Data:120"]
|
||||
based_on_details["based_on_select"] = "t2.item_code, t2.item_name,"
|
||||
based_on_details["based_on_group_by"] = 't2.item_code'
|
||||
based_on_details["addl_tables"] = ''
|
||||
based_on_details["based_on_group_by"] = "t2.item_code"
|
||||
based_on_details["addl_tables"] = ""
|
||||
|
||||
elif based_on == "Item Group":
|
||||
based_on_details["based_on_cols"] = ["Item Group:Link/Item Group:120"]
|
||||
based_on_details["based_on_select"] = "t2.item_group,"
|
||||
based_on_details["based_on_group_by"] = 't2.item_group'
|
||||
based_on_details["addl_tables"] = ''
|
||||
based_on_details["based_on_group_by"] = "t2.item_group"
|
||||
based_on_details["addl_tables"] = ""
|
||||
|
||||
elif based_on == "Customer":
|
||||
based_on_details["based_on_cols"] = ["Customer:Link/Customer:120", "Territory:Link/Territory:120"]
|
||||
based_on_details["based_on_cols"] = [
|
||||
"Customer:Link/Customer:120",
|
||||
"Territory:Link/Territory:120",
|
||||
]
|
||||
based_on_details["based_on_select"] = "t1.customer_name, t1.territory, "
|
||||
based_on_details["based_on_group_by"] = 't1.party_name' if trans == 'Quotation' else 't1.customer'
|
||||
based_on_details["addl_tables"] = ''
|
||||
based_on_details["based_on_group_by"] = (
|
||||
"t1.party_name" if trans == "Quotation" else "t1.customer"
|
||||
)
|
||||
based_on_details["addl_tables"] = ""
|
||||
|
||||
elif based_on == "Customer Group":
|
||||
based_on_details["based_on_cols"] = ["Customer Group:Link/Customer Group"]
|
||||
based_on_details["based_on_select"] = "t1.customer_group,"
|
||||
based_on_details["based_on_group_by"] = 't1.customer_group'
|
||||
based_on_details["addl_tables"] = ''
|
||||
based_on_details["based_on_group_by"] = "t1.customer_group"
|
||||
based_on_details["addl_tables"] = ""
|
||||
|
||||
elif based_on == 'Supplier':
|
||||
based_on_details["based_on_cols"] = ["Supplier:Link/Supplier:120", "Supplier Group:Link/Supplier Group:140"]
|
||||
elif based_on == "Supplier":
|
||||
based_on_details["based_on_cols"] = [
|
||||
"Supplier:Link/Supplier:120",
|
||||
"Supplier Group:Link/Supplier Group:140",
|
||||
]
|
||||
based_on_details["based_on_select"] = "t1.supplier, t3.supplier_group,"
|
||||
based_on_details["based_on_group_by"] = 't1.supplier'
|
||||
based_on_details["addl_tables"] = ',`tabSupplier` t3'
|
||||
based_on_details["based_on_group_by"] = "t1.supplier"
|
||||
based_on_details["addl_tables"] = ",`tabSupplier` t3"
|
||||
based_on_details["addl_tables_relational_cond"] = " and t1.supplier = t3.name"
|
||||
|
||||
elif based_on == 'Supplier Group':
|
||||
elif based_on == "Supplier Group":
|
||||
based_on_details["based_on_cols"] = ["Supplier Group:Link/Supplier Group:140"]
|
||||
based_on_details["based_on_select"] = "t3.supplier_group,"
|
||||
based_on_details["based_on_group_by"] = 't3.supplier_group'
|
||||
based_on_details["addl_tables"] = ',`tabSupplier` t3'
|
||||
based_on_details["based_on_group_by"] = "t3.supplier_group"
|
||||
based_on_details["addl_tables"] = ",`tabSupplier` t3"
|
||||
based_on_details["addl_tables_relational_cond"] = " and t1.supplier = t3.name"
|
||||
|
||||
elif based_on == "Territory":
|
||||
based_on_details["based_on_cols"] = ["Territory:Link/Territory:120"]
|
||||
based_on_details["based_on_select"] = "t1.territory,"
|
||||
based_on_details["based_on_group_by"] = 't1.territory'
|
||||
based_on_details["addl_tables"] = ''
|
||||
based_on_details["based_on_group_by"] = "t1.territory"
|
||||
based_on_details["addl_tables"] = ""
|
||||
|
||||
elif based_on == "Project":
|
||||
if trans in ['Sales Invoice', 'Delivery Note', 'Sales Order']:
|
||||
if trans in ["Sales Invoice", "Delivery Note", "Sales Order"]:
|
||||
based_on_details["based_on_cols"] = ["Project:Link/Project:120"]
|
||||
based_on_details["based_on_select"] = "t1.project,"
|
||||
based_on_details["based_on_group_by"] = 't1.project'
|
||||
based_on_details["addl_tables"] = ''
|
||||
elif trans in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
|
||||
based_on_details["based_on_group_by"] = "t1.project"
|
||||
based_on_details["addl_tables"] = ""
|
||||
elif trans in ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]:
|
||||
based_on_details["based_on_cols"] = ["Project:Link/Project:120"]
|
||||
based_on_details["based_on_select"] = "t2.project,"
|
||||
based_on_details["based_on_group_by"] = 't2.project'
|
||||
based_on_details["addl_tables"] = ''
|
||||
based_on_details["based_on_group_by"] = "t2.project"
|
||||
based_on_details["addl_tables"] = ""
|
||||
else:
|
||||
frappe.throw(_("Project-wise data is not available for Quotation"))
|
||||
|
||||
return based_on_details
|
||||
|
||||
|
||||
def group_wise_column(group_by):
|
||||
if group_by:
|
||||
return [group_by+":Link/"+group_by+":120"]
|
||||
return [group_by + ":Link/" + group_by + ":120"]
|
||||
else:
|
||||
return []
|
||||
|
||||
@@ -15,21 +15,29 @@ def get_list_context(context=None):
|
||||
return {
|
||||
"global_number_format": frappe.db.get_default("number_format") or "#,###.##",
|
||||
"currency": frappe.db.get_default("currency"),
|
||||
"currency_symbols": json.dumps(dict(frappe.db.sql("""select name, symbol
|
||||
from tabCurrency where enabled=1"""))),
|
||||
"currency_symbols": json.dumps(
|
||||
dict(
|
||||
frappe.db.sql(
|
||||
"""select name, symbol
|
||||
from tabCurrency where enabled=1"""
|
||||
)
|
||||
)
|
||||
),
|
||||
"row_template": "templates/includes/transaction_row.html",
|
||||
"get_list": get_transaction_list
|
||||
"get_list": get_transaction_list,
|
||||
}
|
||||
|
||||
|
||||
def get_webform_list_context(module):
|
||||
if get_module_app(module) != 'erpnext':
|
||||
if get_module_app(module) != "erpnext":
|
||||
return
|
||||
return {
|
||||
"get_list": get_webform_transaction_list
|
||||
}
|
||||
return {"get_list": get_webform_transaction_list}
|
||||
|
||||
def get_webform_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"):
|
||||
""" Get List of transactions for custom doctypes """
|
||||
|
||||
def get_webform_transaction_list(
|
||||
doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"
|
||||
):
|
||||
"""Get List of transactions for custom doctypes"""
|
||||
from frappe.www.list import get_list
|
||||
|
||||
if not filters:
|
||||
@@ -38,42 +46,62 @@ def get_webform_transaction_list(doctype, txt=None, filters=None, limit_start=0,
|
||||
meta = frappe.get_meta(doctype)
|
||||
|
||||
for d in meta.fields:
|
||||
if d.fieldtype == 'Link' and d.fieldname != 'amended_from':
|
||||
if d.fieldtype == "Link" and d.fieldname != "amended_from":
|
||||
allowed_docs = [d.name for d in get_transaction_list(doctype=d.options, custom=True)]
|
||||
allowed_docs.append('')
|
||||
filters.append((d.fieldname, 'in', allowed_docs))
|
||||
allowed_docs.append("")
|
||||
filters.append((d.fieldname, "in", allowed_docs))
|
||||
|
||||
return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=False,
|
||||
fields=None, order_by="modified")
|
||||
return get_list(
|
||||
doctype,
|
||||
txt,
|
||||
filters,
|
||||
limit_start,
|
||||
limit_page_length,
|
||||
ignore_permissions=False,
|
||||
fields=None,
|
||||
order_by="modified",
|
||||
)
|
||||
|
||||
def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified", custom=False):
|
||||
|
||||
def get_transaction_list(
|
||||
doctype,
|
||||
txt=None,
|
||||
filters=None,
|
||||
limit_start=0,
|
||||
limit_page_length=20,
|
||||
order_by="modified",
|
||||
custom=False,
|
||||
):
|
||||
user = frappe.session.user
|
||||
ignore_permissions = False
|
||||
|
||||
if not filters: filters = []
|
||||
if not filters:
|
||||
filters = []
|
||||
|
||||
if doctype in ['Supplier Quotation', 'Purchase Invoice']:
|
||||
filters.append((doctype, 'docstatus', '<', 2))
|
||||
if doctype in ["Supplier Quotation", "Purchase Invoice"]:
|
||||
filters.append((doctype, "docstatus", "<", 2))
|
||||
else:
|
||||
filters.append((doctype, 'docstatus', '=', 1))
|
||||
filters.append((doctype, "docstatus", "=", 1))
|
||||
|
||||
if (user != 'Guest' and is_website_user()) or doctype == 'Request for Quotation':
|
||||
parties_doctype = 'Request for Quotation Supplier' if doctype == 'Request for Quotation' else doctype
|
||||
if (user != "Guest" and is_website_user()) or doctype == "Request for Quotation":
|
||||
parties_doctype = (
|
||||
"Request for Quotation Supplier" if doctype == "Request for Quotation" else doctype
|
||||
)
|
||||
# find party for this contact
|
||||
customers, suppliers = get_customers_suppliers(parties_doctype, user)
|
||||
|
||||
if customers:
|
||||
if doctype == 'Quotation':
|
||||
filters.append(('quotation_to', '=', 'Customer'))
|
||||
filters.append(('party_name', 'in', customers))
|
||||
if doctype == "Quotation":
|
||||
filters.append(("quotation_to", "=", "Customer"))
|
||||
filters.append(("party_name", "in", customers))
|
||||
else:
|
||||
filters.append(('customer', 'in', customers))
|
||||
filters.append(("customer", "in", customers))
|
||||
elif suppliers:
|
||||
filters.append(('supplier', 'in', suppliers))
|
||||
filters.append(("supplier", "in", suppliers))
|
||||
elif not custom:
|
||||
return []
|
||||
|
||||
if doctype == 'Request for Quotation':
|
||||
if doctype == "Request for Quotation":
|
||||
parties = customers or suppliers
|
||||
return rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length)
|
||||
|
||||
@@ -84,49 +112,88 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p
|
||||
ignore_permissions = False
|
||||
filters = []
|
||||
|
||||
transactions = get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length,
|
||||
fields='name', ignore_permissions=ignore_permissions, order_by='modified desc')
|
||||
transactions = get_list_for_transactions(
|
||||
doctype,
|
||||
txt,
|
||||
filters,
|
||||
limit_start,
|
||||
limit_page_length,
|
||||
fields="name",
|
||||
ignore_permissions=ignore_permissions,
|
||||
order_by="modified desc",
|
||||
)
|
||||
|
||||
if custom:
|
||||
return transactions
|
||||
|
||||
return post_process(doctype, transactions)
|
||||
|
||||
def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20,
|
||||
ignore_permissions=False, fields=None, order_by=None):
|
||||
""" Get List of transactions like Invoices, Orders """
|
||||
|
||||
def get_list_for_transactions(
|
||||
doctype,
|
||||
txt,
|
||||
filters,
|
||||
limit_start,
|
||||
limit_page_length=20,
|
||||
ignore_permissions=False,
|
||||
fields=None,
|
||||
order_by=None,
|
||||
):
|
||||
"""Get List of transactions like Invoices, Orders"""
|
||||
from frappe.www.list import get_list
|
||||
|
||||
meta = frappe.get_meta(doctype)
|
||||
data = []
|
||||
or_filters = []
|
||||
|
||||
for d in get_list(doctype, txt, filters=filters, fields="name", limit_start=limit_start,
|
||||
limit_page_length=limit_page_length, ignore_permissions=ignore_permissions, order_by="modified desc"):
|
||||
for d in get_list(
|
||||
doctype,
|
||||
txt,
|
||||
filters=filters,
|
||||
fields="name",
|
||||
limit_start=limit_start,
|
||||
limit_page_length=limit_page_length,
|
||||
ignore_permissions=ignore_permissions,
|
||||
order_by="modified desc",
|
||||
):
|
||||
data.append(d)
|
||||
|
||||
if txt:
|
||||
if meta.get_field('items'):
|
||||
if meta.get_field('items').options:
|
||||
child_doctype = meta.get_field('items').options
|
||||
for item in frappe.get_all(child_doctype, {"item_name": ['like', "%" + txt + "%"]}):
|
||||
if meta.get_field("items"):
|
||||
if meta.get_field("items").options:
|
||||
child_doctype = meta.get_field("items").options
|
||||
for item in frappe.get_all(child_doctype, {"item_name": ["like", "%" + txt + "%"]}):
|
||||
child = frappe.get_doc(child_doctype, item.name)
|
||||
or_filters.append([doctype, "name", "=", child.parent])
|
||||
|
||||
if or_filters:
|
||||
for r in frappe.get_list(doctype, fields=fields,filters=filters, or_filters=or_filters,
|
||||
limit_start=limit_start, limit_page_length=limit_page_length,
|
||||
ignore_permissions=ignore_permissions, order_by=order_by):
|
||||
for r in frappe.get_list(
|
||||
doctype,
|
||||
fields=fields,
|
||||
filters=filters,
|
||||
or_filters=or_filters,
|
||||
limit_start=limit_start,
|
||||
limit_page_length=limit_page_length,
|
||||
ignore_permissions=ignore_permissions,
|
||||
order_by=order_by,
|
||||
):
|
||||
data.append(r)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length):
|
||||
data = frappe.db.sql("""select distinct parent as name, supplier from `tab{doctype}`
|
||||
where supplier = '{supplier}' and docstatus=1 order by modified desc limit {start}, {len}""".
|
||||
format(doctype=parties_doctype, supplier=parties[0], start=limit_start, len = limit_page_length), as_dict=1)
|
||||
data = frappe.db.sql(
|
||||
"""select distinct parent as name, supplier from `tab{doctype}`
|
||||
where supplier = '{supplier}' and docstatus=1 order by modified desc limit {start}, {len}""".format(
|
||||
doctype=parties_doctype, supplier=parties[0], start=limit_start, len=limit_page_length
|
||||
),
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
return post_process(doctype, data)
|
||||
|
||||
|
||||
def post_process(doctype, data):
|
||||
result = []
|
||||
for d in data:
|
||||
@@ -137,11 +204,15 @@ def post_process(doctype, data):
|
||||
|
||||
if doc.get("per_billed"):
|
||||
doc.status_percent += flt(doc.per_billed)
|
||||
doc.status_display.append(_("Billed") if doc.per_billed==100 else _("{0}% Billed").format(doc.per_billed))
|
||||
doc.status_display.append(
|
||||
_("Billed") if doc.per_billed == 100 else _("{0}% Billed").format(doc.per_billed)
|
||||
)
|
||||
|
||||
if doc.get("per_delivered"):
|
||||
doc.status_percent += flt(doc.per_delivered)
|
||||
doc.status_display.append(_("Delivered") if doc.per_delivered==100 else _("{0}% Delivered").format(doc.per_delivered))
|
||||
doc.status_display.append(
|
||||
_("Delivered") if doc.per_delivered == 100 else _("{0}% Delivered").format(doc.per_delivered)
|
||||
)
|
||||
|
||||
if hasattr(doc, "set_indicator"):
|
||||
doc.set_indicator()
|
||||
@@ -152,6 +223,7 @@ def post_process(doctype, data):
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_customers_suppliers(doctype, user):
|
||||
customers = []
|
||||
suppliers = []
|
||||
@@ -160,10 +232,11 @@ def get_customers_suppliers(doctype, user):
|
||||
customer_field_name = get_customer_field_name(doctype)
|
||||
|
||||
has_customer_field = meta.has_field(customer_field_name)
|
||||
has_supplier_field = meta.has_field('supplier')
|
||||
has_supplier_field = meta.has_field("supplier")
|
||||
|
||||
if has_common(["Supplier", "Customer"], frappe.get_roles(user)):
|
||||
contacts = frappe.db.sql("""
|
||||
contacts = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
`tabContact`.email_id,
|
||||
`tabDynamic Link`.link_doctype,
|
||||
@@ -172,15 +245,18 @@ def get_customers_suppliers(doctype, user):
|
||||
`tabContact`, `tabDynamic Link`
|
||||
where
|
||||
`tabContact`.name=`tabDynamic Link`.parent and `tabContact`.email_id =%s
|
||||
""", user, as_dict=1)
|
||||
customers = [c.link_name for c in contacts if c.link_doctype == 'Customer']
|
||||
suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier']
|
||||
elif frappe.has_permission(doctype, 'read', user=user):
|
||||
""",
|
||||
user,
|
||||
as_dict=1,
|
||||
)
|
||||
customers = [c.link_name for c in contacts if c.link_doctype == "Customer"]
|
||||
suppliers = [c.link_name for c in contacts if c.link_doctype == "Supplier"]
|
||||
elif frappe.has_permission(doctype, "read", user=user):
|
||||
customer_list = frappe.get_list("Customer")
|
||||
customers = suppliers = [customer.name for customer in customer_list]
|
||||
|
||||
return customers if has_customer_field else None, \
|
||||
suppliers if has_supplier_field else None
|
||||
return customers if has_customer_field else None, suppliers if has_supplier_field else None
|
||||
|
||||
|
||||
def has_website_permission(doc, ptype, user, verbose=False):
|
||||
doctype = doc.doctype
|
||||
@@ -188,25 +264,24 @@ def has_website_permission(doc, ptype, user, verbose=False):
|
||||
if customers:
|
||||
return frappe.db.exists(doctype, get_customer_filter(doc, customers))
|
||||
elif suppliers:
|
||||
fieldname = 'suppliers' if doctype == 'Request for Quotation' else 'supplier'
|
||||
return frappe.db.exists(doctype, {
|
||||
'name': doc.name,
|
||||
fieldname: ["in", suppliers]
|
||||
})
|
||||
fieldname = "suppliers" if doctype == "Request for Quotation" else "supplier"
|
||||
return frappe.db.exists(doctype, {"name": doc.name, fieldname: ["in", suppliers]})
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def get_customer_filter(doc, customers):
|
||||
doctype = doc.doctype
|
||||
filters = frappe._dict()
|
||||
filters.name = doc.name
|
||||
filters[get_customer_field_name(doctype)] = ['in', customers]
|
||||
if doctype == 'Quotation':
|
||||
filters.quotation_to = 'Customer'
|
||||
filters[get_customer_field_name(doctype)] = ["in", customers]
|
||||
if doctype == "Quotation":
|
||||
filters.quotation_to = "Customer"
|
||||
return filters
|
||||
|
||||
|
||||
def get_customer_field_name(doctype):
|
||||
if doctype == 'Quotation':
|
||||
return 'party_name'
|
||||
if doctype == "Quotation":
|
||||
return "party_name"
|
||||
else:
|
||||
return 'customer'
|
||||
return "customer"
|
||||
|
||||
Reference in New Issue
Block a user