Merge branch 'project-template-and-tasks' of https://github.com/pateljannat/erpnext into project-template-and-tasks

This commit is contained in:
pateljannat 2020-12-17 11:56:06 +05:30
commit 2f4c88b228
67 changed files with 1179 additions and 409 deletions

View File

@ -21,8 +21,8 @@ def docs_link_exists(body):
if word.startswith('http') and uri_validator(word): if word.startswith('http') and uri_validator(word):
parsed_url = urlparse(word) parsed_url = urlparse(word)
if parsed_url.netloc == "github.com": if parsed_url.netloc == "github.com":
_, org, repo, _type, ref = parsed_url.path.split('/') parts = parsed_url.path.split('/')
if org == "frappe" and repo in docs_repos: if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
return True return True

View File

@ -14,7 +14,6 @@
"column_break_9", "column_break_9",
"update_stock", "update_stock",
"ignore_pricing_rule", "ignore_pricing_rule",
"hide_unavailable_items",
"warehouse", "warehouse",
"campaign", "campaign",
"company_address", "company_address",
@ -23,6 +22,9 @@
"section_break_11", "section_break_11",
"payments", "payments",
"section_break_14", "section_break_14",
"hide_images",
"hide_unavailable_items",
"auto_add_item_to_cart",
"item_groups", "item_groups",
"column_break_16", "column_break_16",
"customer_groups", "customer_groups",
@ -124,7 +126,8 @@
}, },
{ {
"fieldname": "section_break_14", "fieldname": "section_break_14",
"fieldtype": "Section Break" "fieldtype": "Section Break",
"label": "Configuration"
}, },
{ {
"description": "Only show Items from these Item Groups", "description": "Only show Items from these Item Groups",
@ -314,13 +317,25 @@
"fieldname": "hide_unavailable_items", "fieldname": "hide_unavailable_items",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Hide Unavailable Items" "label": "Hide Unavailable Items"
},
{
"default": "0",
"fieldname": "hide_images",
"fieldtype": "Check",
"label": "Hide Images"
},
{
"default": "0",
"fieldname": "auto_add_item_to_cart",
"fieldtype": "Check",
"label": "Automatically Add Filtered Item To Cart"
} }
], ],
"icon": "icon-cog", "icon": "icon-cog",
"idx": 1, "idx": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2020-10-29 13:18:38.795925", "modified": "2020-12-10 13:59:28.877572",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Profile", "name": "POS Profile",

View File

@ -15,6 +15,16 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
return (doc.qty<=doc.received_qty) ? "green" : "orange"; return (doc.qty<=doc.received_qty) ? "green" : "orange";
}); });
} }
this.frm.set_query("unrealized_profit_loss_account", function() {
return {
filters: {
company: doc.company,
is_group: 0,
root_type: "Liability",
}
};
});
}, },
onload: function() { onload: function() {
this._super(); this._super();

View File

@ -1,6 +1,5 @@
{ {
"actions": [], "actions": [],
"allow_auto_repeat": 1,
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-05-21 16:16:39", "creation": "2013-05-21 16:16:39",
@ -127,6 +126,7 @@
"write_off_cost_center", "write_off_cost_center",
"advances_section", "advances_section",
"allocate_advances_automatically", "allocate_advances_automatically",
"adjust_advance_taxes",
"get_advances", "get_advances",
"advances", "advances",
"payment_schedule_section", "payment_schedule_section",
@ -152,9 +152,11 @@
"is_opening", "is_opening",
"against_expense_account", "against_expense_account",
"column_break_63", "column_break_63",
"unrealized_profit_loss_account",
"status", "status",
"inter_company_invoice_reference", "inter_company_invoice_reference",
"is_internal_supplier", "is_internal_supplier",
"represents_company",
"remarks", "remarks",
"subscription_section", "subscription_section",
"from_date", "from_date",
@ -1223,7 +1225,7 @@
"fieldtype": "Select", "fieldtype": "Select",
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Status", "label": "Status",
"options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled", "options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nUnpaid\nOverdue\nCancelled\nInternal Transfer",
"print_hide": 1 "print_hide": 1
}, },
{ {
@ -1330,13 +1332,37 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Project", "label": "Project",
"options": "Project" "options": "Project"
},
{
"default": "0",
"description": "Taxes paid while advance payment will be adjusted against this invoice",
"fieldname": "adjust_advance_taxes",
"fieldtype": "Check",
"label": "Adjust Advance Taxes"
},
{
"depends_on": "eval:doc.is_internal_supplier",
"description": "Unrealized Profit / Loss account for intra-company transfers",
"fieldname": "unrealized_profit_loss_account",
"fieldtype": "Link",
"label": "Unrealized Profit / Loss Account",
"options": "Account"
},
{
"depends_on": "eval:doc.is_internal_supplier",
"description": "Company which internal supplier represents",
"fetch_from": "supplier.represents_company",
"fieldname": "represents_company",
"fieldtype": "Link",
"label": "Represents Company",
"options": "Company"
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 204, "idx": 204,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-10-30 13:57:18.266978", "modified": "2020-12-11 12:46:12.796378",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@ -206,8 +206,8 @@ class PurchaseInvoice(BuyingController):
["Purchase Receipt", "purchase_receipt", "pr_detail"] ["Purchase Receipt", "purchase_receipt", "pr_detail"]
]) ])
def validate_warehouse(self): def validate_warehouse(self, for_validate=True):
if self.update_stock: if self.update_stock and for_validate:
for d in self.get('items'): for d in self.get('items'):
if not d.warehouse: if not d.warehouse:
frappe.throw(_("Warehouse required at Row No {0}, please set default warehouse for the item {1} for the company {2}"). frappe.throw(_("Warehouse required at Row No {0}, please set default warehouse for the item {1} for the company {2}").
@ -233,7 +233,7 @@ class PurchaseInvoice(BuyingController):
if self.update_stock: if self.update_stock:
self.validate_item_code() self.validate_item_code()
self.validate_warehouse() self.validate_warehouse(for_validate)
if auto_accounting_for_stock: if auto_accounting_for_stock:
warehouse_account = get_warehouse_account_map(self.company) warehouse_account = get_warehouse_account_map(self.company)
@ -449,6 +449,7 @@ class PurchaseInvoice(BuyingController):
self.get_asset_gl_entry(gl_entries) self.get_asset_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries) self.make_tax_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries)
gl_entries = make_regional_gl_entries(gl_entries, self) gl_entries = make_regional_gl_entries(gl_entries, self)
@ -457,7 +458,6 @@ class PurchaseInvoice(BuyingController):
self.make_payment_gl_entries(gl_entries) self.make_payment_gl_entries(gl_entries)
self.make_write_off_gl_entry(gl_entries) self.make_write_off_gl_entry(gl_entries)
self.make_gle_for_rounding_adjustment(gl_entries) self.make_gle_for_rounding_adjustment(gl_entries)
return gl_entries return gl_entries
def check_asset_cwip_enabled(self): def check_asset_cwip_enabled(self):
@ -474,31 +474,30 @@ class PurchaseInvoice(BuyingController):
# because rounded_total had value even before introcution of posting GLE based on rounded total # because rounded_total had value even before introcution of posting GLE based on rounded total
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
if grand_total: if grand_total and not self.is_internal_transfer():
# Didnot use base_grand_total to book rounding loss gle # Didnot use base_grand_total to book rounding loss gle
grand_total_in_company_currency = flt(grand_total * self.conversion_rate, grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
self.precision("grand_total")) self.precision("grand_total"))
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": self.credit_to, "account": self.credit_to,
"party_type": "Supplier", "party_type": "Supplier",
"party": self.supplier, "party": self.supplier,
"due_date": self.due_date, "due_date": self.due_date,
"against": self.against_expense_account, "against": self.against_expense_account,
"credit": grand_total_in_company_currency, "credit": grand_total_in_company_currency,
"credit_in_account_currency": grand_total_in_company_currency \ "credit_in_account_currency": grand_total_in_company_currency \
if self.party_account_currency==self.company_currency else grand_total, if self.party_account_currency==self.company_currency else grand_total,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"project": self.project, "project": self.project,
"cost_center": self.cost_center "cost_center": self.cost_center
}, self.party_account_currency, item=self) }, self.party_account_currency, item=self)
) )
def make_item_gl_entries(self, gl_entries): def make_item_gl_entries(self, gl_entries):
# item gl entries # item gl entries
stock_items = self.get_stock_items() stock_items = self.get_stock_items()
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
if self.update_stock and self.auto_accounting_for_stock: if self.update_stock and self.auto_accounting_for_stock:
warehouse_account = get_warehouse_account_map(self.company) warehouse_account = get_warehouse_account_map(self.company)
@ -526,7 +525,6 @@ class PurchaseInvoice(BuyingController):
item, voucher_wise_stock_value, account_currency) item, voucher_wise_stock_value, account_currency)
if item.from_warehouse: if item.from_warehouse:
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": warehouse_account[item.warehouse]['account'], "account": warehouse_account[item.warehouse]['account'],
"against": warehouse_account[item.from_warehouse]["account"], "against": warehouse_account[item.from_warehouse]["account"],
@ -546,16 +544,18 @@ class PurchaseInvoice(BuyingController):
"debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")), "debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
}, warehouse_account[item.from_warehouse]["account_currency"], item=item)) }, warehouse_account[item.from_warehouse]["account_currency"], item=item))
gl_entries.append( # Do not book expense for transfer within same company transfer
self.get_gl_dict({ if not self.is_internal_transfer():
"account": item.expense_account, gl_entries.append(
"against": self.supplier, self.get_gl_dict({
"debit": flt(item.base_net_amount, item.precision("base_net_amount")), "account": item.expense_account,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "against": self.supplier,
"cost_center": item.cost_center, "debit": flt(item.base_net_amount, item.precision("base_net_amount")),
"project": item.project "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
}, account_currency, item=item) "cost_center": item.cost_center,
) "project": item.project
}, account_currency, item=item)
)
else: else:
gl_entries.append( gl_entries.append(
@ -832,7 +832,8 @@ class PurchaseInvoice(BuyingController):
}, account_currency, item=tax) }, account_currency, item=tax)
) )
# accumulate valuation tax # accumulate valuation tax
if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount): if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount) \
and not self.is_internal_transfer():
if self.auto_accounting_for_stock and not tax.cost_center: if self.auto_accounting_for_stock and not tax.cost_center:
frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category))) frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category)))
valuation_tax.setdefault(tax.name, 0) valuation_tax.setdefault(tax.name, 0)
@ -876,8 +877,19 @@ class PurchaseInvoice(BuyingController):
"against": self.supplier, "against": self.supplier,
"credit": valuation_tax[tax.name], "credit": valuation_tax[tax.name],
"remarks": self.remarks or "Accounting Entry for Stock" "remarks": self.remarks or "Accounting Entry for Stock"
}, item=tax) }, item=tax))
)
def make_internal_transfer_gl_entries(self, gl_entries):
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
account_currency = get_account_currency(self.unrealized_profit_loss_account)
gl_entries.append(
self.get_gl_dict({
"account": self.unrealized_profit_loss_account,
"against": self.supplier,
"credit": flt(self.total_taxes_and_charges),
"credit_in_account_currency": flt(self.base_total_taxes_and_charges),
"cost_center": self.cost_center
}, account_currency, item=self))
def make_payment_gl_entries(self, gl_entries): def make_payment_gl_entries(self, gl_entries):
# Make Cash GL Entries # Make Cash GL Entries
@ -1095,7 +1107,9 @@ class PurchaseInvoice(BuyingController):
if self.docstatus == 2: if self.docstatus == 2:
status = "Cancelled" status = "Cancelled"
elif self.docstatus == 1: elif self.docstatus == 1:
if outstanding_amount > 0 and due_date < nowdate: if self.is_internal_transfer():
self.status = 'Internal Transfer'
elif outstanding_amount > 0 and due_date < nowdate:
self.status = "Overdue" self.status = "Overdue"
elif outstanding_amount > 0 and due_date >= nowdate: elif outstanding_amount > 0 and due_date >= nowdate:
self.status = "Unpaid" self.status = "Unpaid"

View File

@ -4,23 +4,25 @@
// render // render
frappe.listview_settings['Purchase Invoice'] = { frappe.listview_settings['Purchase Invoice'] = {
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company", add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
"currency", "is_return", "release_date", "on_hold"], "currency", "is_return", "release_date", "on_hold", "represents_company", "is_internal_supplier"],
get_indicator: function(doc) { get_indicator: function(doc) {
if( (flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') { if ((flt(doc.outstanding_amount) <= 0) && doc.docstatus == 1 && doc.status == 'Debit Note Issued') {
return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"]; return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<=,0"];
} else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) { } else if (flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
if(cint(doc.on_hold) && !doc.release_date) { if(cint(doc.on_hold) && !doc.release_date) {
return [__("On Hold"), "darkgrey"]; return [__("On Hold"), "darkgrey"];
} else if(cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) { } else if (cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) {
return [__("Temporarily on Hold"), "darkgrey"]; return [__("Temporarily on Hold"), "darkgrey"];
} else if(frappe.datetime.get_diff(doc.due_date) < 0) { } else if (frappe.datetime.get_diff(doc.due_date) < 0) {
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"]; return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];
} else { } else {
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"]; return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"];
} }
} else if(cint(doc.is_return)) { } else if (cint(doc.is_return)) {
return [__("Return"), "darkgrey", "is_return,=,Yes"]; return [__("Return"), "darkgrey", "is_return,=,Yes"];
} else if(flt(doc.outstanding_amount)==0 && doc.docstatus==1) { } else if (doc.company == doc.represents_company && doc.is_internal_supplier) {
return [__("Internal Transfer"), "darkgrey", "outstanding_amount,=,0"];
} else if (flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
return [__("Paid"), "green", "outstanding_amount,=,0"]; return [__("Paid"), "green", "outstanding_amount,=,0"];
} }
} }

View File

@ -580,6 +580,16 @@ frappe.ui.form.on('Sales Invoice', {
}; };
}); });
frm.set_query("unrealized_profit_loss_account", function() {
return {
filters: {
company: frm.doc.company,
is_group: 0,
root_type: "Liability",
}
};
});
frm.custom_make_buttons = { frm.custom_make_buttons = {
'Delivery Note': 'Delivery', 'Delivery Note': 'Delivery',
'Sales Invoice': 'Sales Return', 'Sales Invoice': 'Sales Return',

View File

@ -1,6 +1,5 @@
{ {
"actions": [], "actions": [],
"allow_auto_repeat": 1,
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-05-24 19:29:05", "creation": "2013-05-24 19:29:05",
@ -158,6 +157,7 @@
"more_information", "more_information",
"inter_company_invoice_reference", "inter_company_invoice_reference",
"is_internal_customer", "is_internal_customer",
"represents_company",
"customer_group", "customer_group",
"campaign", "campaign",
"is_discounted", "is_discounted",
@ -171,6 +171,7 @@
"c_form_applicable", "c_form_applicable",
"c_form_no", "c_form_no",
"column_break8", "column_break8",
"unrealized_profit_loss_account",
"remarks", "remarks",
"sales_team_section_break", "sales_team_section_break",
"sales_partner", "sales_partner",
@ -1655,7 +1656,7 @@
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Status", "label": "Status",
"no_copy": 1, "no_copy": 1,
"options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled", "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
@ -1950,13 +1951,31 @@
"fieldtype": "Data", "fieldtype": "Data",
"label": "Company Tax ID", "label": "Company Tax ID",
"read_only": 1 "read_only": 1
},
{
"depends_on": "eval:doc.is_internal_customer",
"description": "Unrealized Profit / Loss account for intra-company transfers",
"fieldname": "unrealized_profit_loss_account",
"fieldtype": "Link",
"label": "Unrealized Profit / Loss Account",
"options": "Account"
},
{
"depends_on": "eval:doc.is_internal_customer",
"description": "Company which internal customer represents",
"fetch_from": "customer.represents_company",
"fieldname": "represents_company",
"fieldtype": "Link",
"label": "Represents Company",
"options": "Company",
"read_only": 1
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 181, "idx": 181,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-10-30 13:57:45.086303", "modified": "2020-12-11 12:48:31.769958",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@ -758,6 +758,7 @@ class SalesInvoice(SellingController):
self.make_customer_gl_entry(gl_entries) self.make_customer_gl_entry(gl_entries)
self.make_tax_gl_entries(gl_entries) self.make_tax_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries)
self.make_item_gl_entries(gl_entries) self.make_item_gl_entries(gl_entries)
@ -777,7 +778,7 @@ class SalesInvoice(SellingController):
# Checked both rounding_adjustment and rounded_total # Checked both rounding_adjustment and rounded_total
# because rounded_total had value even before introcution of posting GLE based on rounded total # because rounded_total had value even before introcution of posting GLE based on rounded total
grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total grand_total = self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
if grand_total: if grand_total and not self.is_internal_transfer():
# Didnot use base_grand_total to book rounding loss gle # Didnot use base_grand_total to book rounding loss gle
grand_total_in_company_currency = flt(grand_total * self.conversion_rate, grand_total_in_company_currency = flt(grand_total * self.conversion_rate,
self.precision("grand_total")) self.precision("grand_total"))
@ -816,6 +817,18 @@ class SalesInvoice(SellingController):
}, account_currency, item=tax) }, account_currency, item=tax)
) )
def make_internal_transfer_gl_entries(self, gl_entries):
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
account_currency = get_account_currency(self.unrealized_profit_loss_account)
gl_entries.append(
self.get_gl_dict({
"account": self.unrealized_profit_loss_account,
"against": self.customer,
"debit": flt(self.total_taxes_and_charges),
"debit_in_account_currency": flt(self.base_total_taxes_and_charges),
"cost_center": self.cost_center
}, account_currency, item=self))
def make_item_gl_entries(self, gl_entries): def make_item_gl_entries(self, gl_entries):
# income account gl entries # income account gl entries
for item in self.get("items"): for item in self.get("items"):
@ -838,22 +851,24 @@ class SalesInvoice(SellingController):
asset.db_set("disposal_date", self.posting_date) asset.db_set("disposal_date", self.posting_date)
asset.set_status("Sold" if self.docstatus==1 else None) asset.set_status("Sold" if self.docstatus==1 else None)
else: else:
income_account = (item.income_account # Do not book income for transfer within same company
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account) if not self.is_internal_transfer():
income_account = (item.income_account
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
account_currency = get_account_currency(income_account) account_currency = get_account_currency(income_account)
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": income_account, "account": income_account,
"against": self.customer, "against": self.customer,
"credit": flt(item.base_net_amount, item.precision("base_net_amount")), "credit": flt(item.base_net_amount, item.precision("base_net_amount")),
"credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount")) "credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))
if account_currency==self.company_currency if account_currency==self.company_currency
else flt(item.net_amount, item.precision("net_amount"))), else flt(item.net_amount, item.precision("net_amount"))),
"cost_center": item.cost_center, "cost_center": item.cost_center,
"project": item.project or self.project "project": item.project or self.project
}, account_currency, item=item) }, account_currency, item=item)
) )
# expense account gl entries # expense account gl entries
if cint(self.update_stock) and \ if cint(self.update_stock) and \
@ -1265,7 +1280,9 @@ class SalesInvoice(SellingController):
if self.docstatus == 2: if self.docstatus == 2:
status = "Cancelled" status = "Cancelled"
elif self.docstatus == 1: elif self.docstatus == 1:
if outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed': if self.is_internal_transfer():
self.status = 'Internal Transfer'
elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed':
self.status = "Overdue and Discounted" self.status = "Overdue and Discounted"
elif outstanding_amount > 0 and due_date < nowdate: elif outstanding_amount > 0 and due_date < nowdate:
self.status = "Overdue" self.status = "Overdue"
@ -1530,9 +1547,13 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
if doctype in ["Sales Invoice", "Sales Order"]: if doctype in ["Sales Invoice", "Sales Order"]:
source_doc = frappe.get_doc(doctype, source_name) source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order" target_doctype = "Purchase Invoice" if doctype == "Sales Invoice" else "Purchase Order"
source_document_warehouse_field = 'target_warehouse'
target_document_warehouse_field = 'from_warehouse'
else: else:
source_doc = frappe.get_doc(doctype, source_name) source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order" target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order"
source_document_warehouse_field = 'from_warehouse'
target_document_warehouse_field = 'target_warehouse'
validate_inter_company_transaction(source_doc, doctype) validate_inter_company_transaction(source_doc, doctype)
details = get_inter_company_details(source_doc, doctype) details = get_inter_company_details(source_doc, doctype)
@ -1559,6 +1580,26 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
if currency: if currency:
target_doc.currency = currency target_doc.currency = currency
item_field_map = {
"doctype": target_doctype + " Item",
"field_no_map": [
"income_account",
"expense_account",
"cost_center",
"warehouse"
]
}
if source_doc.get('update_stock'):
item_field_map.update({
'field_map': {
source_document_warehouse_field: target_document_warehouse_field,
'batch_no': 'batch_no',
'serial_no': 'serial_no'
}
})
doclist = get_mapped_doc(doctype, source_name, { doclist = get_mapped_doc(doctype, source_name, {
doctype: { doctype: {
"doctype": target_doctype, "doctype": target_doctype,
@ -1567,15 +1608,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
"taxes_and_charges" "taxes_and_charges"
] ]
}, },
doctype +" Item": { doctype +" Item": item_field_map
"doctype": target_doctype + " Item",
"field_no_map": [
"income_account",
"expense_account",
"cost_center",
"warehouse"
]
}
}, target_doc, set_missing_values) }, target_doc, set_missing_values)

View File

@ -14,8 +14,8 @@ frappe.listview_settings['Sales Invoice'] = {
"Credit Note Issued": "darkgrey", "Credit Note Issued": "darkgrey",
"Unpaid and Discounted": "orange", "Unpaid and Discounted": "orange",
"Overdue and Discounted": "red", "Overdue and Discounted": "red",
"Overdue": "red" "Overdue": "red",
"Internal Transfer": "darkgrey"
}; };
return [__(doc.status), status_color[doc.status], "status,=,"+doc.status]; return [__(doc.status), status_color[doc.status], "status,=,"+doc.status];
}, },

View File

@ -1573,7 +1573,7 @@ class TestSalesInvoice(unittest.TestCase):
for gle in gl_entries: for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
def test_sales_invoice_with_project_link(self): def test_sales_invoice_with_project_link(self):
from erpnext.projects.doctype.project.test_project import make_project from erpnext.projects.doctype.project.test_project import make_project
@ -1607,9 +1607,9 @@ class TestSalesInvoice(unittest.TestCase):
debit_in_account_currency, credit_in_account_currency debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc""", sales_invoice.name, as_dict=1) order by account asc""", sales_invoice.name, as_dict=1)
self.assertTrue(gl_entries) self.assertTrue(gl_entries)
for gle in gl_entries: for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["project"], gle.project) self.assertEqual(expected_values[gle.account]["project"], gle.project)
@ -1781,6 +1781,60 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(target_doc.company, "_Test Company 1") self.assertEqual(target_doc.company, "_Test Company 1")
self.assertEqual(target_doc.supplier, "_Test Internal Supplier") self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
def test_internal_transfer_gl_entry(self):
## Create internal transfer account
account = create_account(account_name="Unrealized Profit",
parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
frappe.db.set_value('Company', '_Test Company with perpetual inventory',
'unrealized_profit_loss_account', account)
customer = create_internal_customer("_Test Internal Customer 2", "_Test Company with perpetual inventory",
"_Test Company with perpetual inventory")
create_internal_supplier("_Test Internal Supplier 2", "_Test Company with perpetual inventory",
"_Test Company with perpetual inventory")
si = create_sales_invoice(
company = "_Test Company with perpetual inventory",
customer = customer,
debit_to = "Debtors - TCP1",
warehouse = "Stores - TCP1",
income_account = "Sales - TCP1",
expense_account = "Cost of Goods Sold - TCP1",
cost_center = "Main - TCP1",
currency = "INR",
do_not_save = 1
)
si.selling_price_list = "_Test Price List Rest of the World"
si.update_stock = 1
si.items[0].target_warehouse = 'Work In Progress - TCP1'
add_taxes(si)
si.save()
si.submit()
target_doc = make_inter_company_transaction("Sales Invoice", si.name)
target_doc.company = '_Test Company with perpetual inventory'
target_doc.items[0].warehouse = 'Finished Goods - TCP1'
add_taxes(target_doc)
target_doc.save()
target_doc.submit()
si_gl_entries = [
["_Test Account Excise Duty - TCP1", 0.0, 12.0, nowdate()],
["Unrealized Profit - TCP1", 12.0, 0.0, nowdate()]
]
check_gl_entries(self, si.name, si_gl_entries, add_days(nowdate(), -1))
pi_gl_entries = [
["_Test Account Excise Duty - TCP1", 12.0 , 0.0, nowdate()],
["Unrealized Profit - TCP1", 0.0, 12.0, nowdate()]
]
check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
def test_eway_bill_json(self): def test_eway_bill_json(self):
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
address = frappe.get_doc({ address = frappe.get_doc({
@ -2039,4 +2093,57 @@ def get_taxes_and_charges():
"parentfield": "taxes", "parentfield": "taxes",
"rate": 2, "rate": 2,
"row_id": 1 "row_id": 1
}] }]
def create_internal_customer(customer_name, represents_company, allowed_to_interact_with):
if not frappe.db.exists("Customer", customer_name):
customer = frappe.get_doc({
"customer_group": "_Test Customer Group",
"customer_name": customer_name,
"customer_type": "Individual",
"doctype": "Customer",
"territory": "_Test Territory",
"is_internal_customer": 1,
"represents_company": represents_company
})
customer.append("companies", {
"company": allowed_to_interact_with
})
customer.insert()
customer_name = customer.name
else:
customer_name = frappe.db.get_value("Customer", customer_name)
return customer_name
def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
if not frappe.db.exists("Supplier", supplier_name):
supplier = frappe.get_doc({
"supplier_group": "_Test Supplier Group",
"supplier_name": supplier_name,
"doctype": "Supplier",
"is_internal_supplier": 1,
"represents_company": represents_company
})
supplier.append("companies", {
"company": allowed_to_interact_with
})
supplier.insert()
supplier_name = supplier.name
else:
supplier_name = frappe.db.exists("Supplier", supplier_name)
return supplier_name
def add_taxes(doc):
doc.append('taxes', {
'account_head': '_Test Account Excise Duty - TCP1',
"charge_type": "On Net Total",
"cost_center": "Main - TCP1",
"description": "Excise Duty",
"rate": 12
})

View File

@ -42,11 +42,13 @@
{% if(filters.show_future_payments) { %} {% if(filters.show_future_payments) { %}
{% var balance_row = data.slice(-1).pop(); {% var balance_row = data.slice(-1).pop();
var range1 = report.columns[11].label; var start = filters.based_on_payment_terms ? 13 : 11;
var range2 = report.columns[12].label; var range1 = report.columns[start].label;
var range3 = report.columns[13].label; var range2 = report.columns[start+1].label;
var range4 = report.columns[14].label; var range3 = report.columns[start+2].label;
var range5 = report.columns[15].label; var range4 = report.columns[start+3].label;
var range5 = report.columns[start+4].label;
var range6 = report.columns[start+5].label;
%} %}
{% if(balance_row) { %} {% if(balance_row) { %}
<table class="table table-bordered table-condensed"> <table class="table table-bordered table-condensed">
@ -70,20 +72,34 @@
<th>{%= __(range3) %}</th> <th>{%= __(range3) %}</th>
<th>{%= __(range4) %}</th> <th>{%= __(range4) %}</th>
<th>{%= __(range5) %}</th> <th>{%= __(range5) %}</th>
<th>{%= __(range6) %}</th>
<th>{%= __("Total") %}</th> <th>{%= __("Total") %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>{%= __("Total Outstanding") %}</td> <td>{%= __("Total Outstanding") %}</td>
<td class="text-right">{%= format_number(balance_row["range1"], null, 2) %}</td> <td class="text-right">
<td class="text-right">{%= format_currency(balance_row["range2"]) %}</td> {%= format_number(balance_row["age"], null, 2) %}
<td class="text-right">{%= format_currency(balance_row["range3"]) %}</td> </td>
<td class="text-right">{%= format_currency(balance_row["range4"]) %}</td> <td class="text-right">
<td class="text-right">{%= format_currency(balance_row["range5"]) %}</td> {%= format_currency(balance_row["range1"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range2"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range3"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range4"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range5"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right"> <td class="text-right">
{%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %} {%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %}
</td> </td>
</tr> </tr>
<td>{%= __("Future Payments") %}</td> <td>{%= __("Future Payments") %}</td>
<td></td> <td></td>
@ -91,6 +107,7 @@
<td></td> <td></td>
<td></td> <td></td>
<td></td> <td></td>
<td></td>
<td class="text-right"> <td class="text-right">
{%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %} {%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
</td> </td>
@ -101,6 +118,7 @@
<th></th> <th></th>
<th></th> <th></th>
<th></th> <th></th>
<th></th>
<th class="text-right"> <th class="text-right">
{%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th> {%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th>
</tr> </tr>
@ -218,15 +236,15 @@
<td></td> <td></td>
<td style="text-align: right"><b>{%= __("Total") %}</b></td> <td style="text-align: right"><b>{%= __("Total") %}</b></td>
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["invoiced"], data[0]["currency"] ) %}</td> {%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %}</td>
{% if(!filters.show_future_payments) { %} {% if(!filters.show_future_payments) { %}
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td> {%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %} </td> <td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} </td>
{% } %} {% } %}
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td> {%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% if(filters.show_future_payments) { %} {% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %} {% if(report.report_name === "Accounts Receivable") { %}
@ -234,8 +252,8 @@
{%= data[i]["po_no"] %}</td> {%= data[i]["po_no"] %}</td>
{% } %} {% } %}
<td style="text-align: right">{%= data[i]["future_ref"] %}</td> <td style="text-align: right">{%= data[i]["future_ref"] %}</td>
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[0]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[0]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
{% } %} {% } %}
{% } %} {% } %}
{% } else { %} {% } else { %}
@ -256,10 +274,10 @@
{% } else { %} {% } else { %}
<td><b>{%= __("Total") %}</b></td> <td><b>{%= __("Total") %}</b></td>
{% } %} {% } %}
<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[0]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% } %} {% } %}
{% } %} {% } %}
</tr> </tr>

View File

@ -8,6 +8,7 @@ from frappe.utils import flt
from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (get_tax_accounts, from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (get_tax_accounts,
get_grand_total, add_total_row, get_display_value, get_group_by_and_display_fields, add_sub_total_row, get_grand_total, add_total_row, get_display_value, get_group_by_and_display_fields, add_sub_total_row,
get_group_by_conditions) get_group_by_conditions)
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details
def execute(filters=None): def execute(filters=None):
return _execute(filters) return _execute(filters)
@ -22,7 +23,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
aii_account_map = get_aii_accounts() aii_account_map = get_aii_accounts()
if item_list: if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency, itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency,
doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges") doctype='Purchase Invoice', tax_doctype='Purchase Taxes and Charges')
po_pr_map = get_purchase_receipts_against_purchase_order(item_list) po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
@ -34,10 +35,14 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if filters.get('group_by'): if filters.get('group_by'):
grand_total = get_grand_total(filters, 'Purchase Invoice') grand_total = get_grand_total(filters, 'Purchase Invoice')
item_details = get_item_details()
for d in item_list: for d in item_list:
if not d.stock_qty: if not d.stock_qty:
continue continue
item_record = item_details.get(d.item_code)
purchase_receipt = None purchase_receipt = None
if d.purchase_receipt: if d.purchase_receipt:
purchase_receipt = d.purchase_receipt purchase_receipt = d.purchase_receipt
@ -48,8 +53,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
row = { row = {
'item_code': d.item_code, 'item_code': d.item_code,
'item_name': d.item_name, 'item_name': item_record.item_name,
'item_group': d.item_group, 'item_group': item_record.item_group,
'description': d.description, 'description': d.description,
'invoice': d.parent, 'invoice': d.parent,
'posting_date': d.posting_date, 'posting_date': d.posting_date,
@ -81,10 +86,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
for tax in tax_columns: for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {}) item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update({ row.update({
frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0), frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0), frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
}) })
total_tax += flt(item_tax.get("tax_amount")) total_tax += flt(item_tax.get('tax_amount'))
row.update({ row.update({
'total_tax': total_tax, 'total_tax': total_tax,
@ -309,8 +314,8 @@ def get_items(filters, additional_query_columns):
select select
`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`, `tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company, `tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`, `tabPurchase Invoice Item`.description, `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`, `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`, `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`, `tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,

View File

@ -8,6 +8,7 @@ from frappe.utils import flt, cstr
from frappe.model.meta import get_field_precision from frappe.model.meta import get_field_precision
from frappe.utils.xlsxutils import handle_html from frappe.utils.xlsxutils import handle_html
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details, get_customer_details
def execute(filters=None): def execute(filters=None):
return _execute(filters) return _execute(filters)
@ -16,7 +17,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if not filters: filters = {} if not filters: filters = {}
columns = get_columns(additional_table_columns, filters) columns = get_columns(additional_table_columns, filters)
company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency") company_currency = frappe.get_cached_value('Company', filters.get('company'), 'default_currency')
item_list = get_items(filters, additional_query_columns) item_list = get_items(filters, additional_query_columns)
if item_list: if item_list:
@ -33,7 +34,13 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if filters.get('group_by'): if filters.get('group_by'):
grand_total = get_grand_total(filters, 'Sales Invoice') grand_total = get_grand_total(filters, 'Sales Invoice')
customer_details = get_customer_details()
item_details = get_item_details()
for d in item_list: for d in item_list:
customer_record = customer_details.get(d.customer)
item_record = item_details.get(d.item_code)
delivery_note = None delivery_note = None
if d.delivery_note: if d.delivery_note:
delivery_note = d.delivery_note delivery_note = d.delivery_note
@ -45,14 +52,14 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
row = { row = {
'item_code': d.item_code, 'item_code': d.item_code,
'item_name': d.item_name, 'item_name': item_record.item_name,
'item_group': d.item_group, 'item_group': item_record.item_group,
'description': d.description, 'description': d.description,
'invoice': d.parent, 'invoice': d.parent,
'posting_date': d.posting_date, 'posting_date': d.posting_date,
'customer': d.customer, 'customer': d.customer,
'customer_name': d.customer_name, 'customer_name': customer_record.customer_name,
'customer_group': d.customer_group, 'customer_group': customer_record.customer_group,
} }
if additional_query_columns: if additional_query_columns:
@ -90,10 +97,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
for tax in tax_columns: for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {}) item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row.update({ row.update({
frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0), frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0), frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
}) })
total_tax += flt(item_tax.get("tax_amount")) total_tax += flt(item_tax.get('tax_amount'))
row.update({ row.update({
'total_tax': total_tax, 'total_tax': total_tax,
@ -226,7 +233,7 @@ def get_columns(additional_table_columns, filters):
if filters.get('group_by') != 'Territory': if filters.get('group_by') != 'Territory':
columns.extend([ columns.extend([
{ {
'label': _("Territory"), 'label': _('Territory'),
'fieldname': 'territory', 'fieldname': 'territory',
'fieldtype': 'Link', 'fieldtype': 'Link',
'options': 'Territory', 'options': 'Territory',
@ -374,13 +381,12 @@ def get_items(filters, additional_query_columns):
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to, `tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks, `tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total, `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.item_name, `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.item_group, `tabSales Invoice Item`.description, `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.income_account, `tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
`tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
`tabSales Invoice Item`.stock_uom, `tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
`tabSales Invoice Item`.base_net_amount, `tabSales Invoice`.customer_name, `tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
`tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0} `tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0}
from `tabSales Invoice`, `tabSales Invoice Item` from `tabSales Invoice`, `tabSales Invoice Item`
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
@ -417,14 +423,14 @@ def get_deducted_taxes():
return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'") return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'")
def get_tax_accounts(item_list, columns, company_currency, def get_tax_accounts(item_list, columns, company_currency,
doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"): doctype='Sales Invoice', tax_doctype='Sales Taxes and Charges'):
import json import json
item_row_map = {} item_row_map = {}
tax_columns = [] tax_columns = []
invoice_item_row = {} invoice_item_row = {}
itemised_tax = {} itemised_tax = {}
tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field("tax_amount"), tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field('tax_amount'),
currency=company_currency) or 2 currency=company_currency) or 2
for d in item_list: for d in item_list:
@ -469,8 +475,8 @@ def get_tax_accounts(item_list, columns, company_currency,
tax_rate = tax_data tax_rate = tax_data
tax_amount = 0 tax_amount = 0
if charge_type == "Actual" and not tax_rate: if charge_type == 'Actual' and not tax_rate:
tax_rate = "NA" tax_rate = 'NA'
item_net_amount = sum([flt(d.base_net_amount) item_net_amount = sum([flt(d.base_net_amount)
for d in item_row_map.get(parent, {}).get(item_code, [])]) for d in item_row_map.get(parent, {}).get(item_code, [])])
@ -484,17 +490,17 @@ def get_tax_accounts(item_list, columns, company_currency,
if (doctype == 'Purchase Invoice' and name in deducted_tax) else tax_value) if (doctype == 'Purchase Invoice' and name in deducted_tax) else tax_value)
itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
"tax_rate": tax_rate, 'tax_rate': tax_rate,
"tax_amount": tax_value 'tax_amount': tax_value
}) })
except ValueError: except ValueError:
continue continue
elif charge_type == "Actual" and tax_amount: elif charge_type == 'Actual' and tax_amount:
for d in invoice_item_row.get(parent, []): for d in invoice_item_row.get(parent, []):
itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
"tax_rate": "NA", 'tax_rate': 'NA',
"tax_amount": flt((tax_amount * d.base_net_amount) / d.base_net_total, 'tax_amount': flt((tax_amount * d.base_net_amount) / d.base_net_total,
tax_amount_precision) tax_amount_precision)
}) })
@ -563,7 +569,7 @@ def add_total_row(data, filters, prev_group_by_value, item, total_row_map,
}) })
total_row_map.setdefault('total_row', { total_row_map.setdefault('total_row', {
subtotal_display_field: "Total", subtotal_display_field: 'Total',
'stock_qty': 0.0, 'stock_qty': 0.0,
'amount': 0.0, 'amount': 0.0,
'bold': 1, 'bold': 1,

View File

@ -13,8 +13,8 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g
class AssetValueAdjustment(Document): class AssetValueAdjustment(Document):
def validate(self): def validate(self):
self.validate_date() self.validate_date()
self.set_difference_amount()
self.set_current_asset_value() self.set_current_asset_value()
self.set_difference_amount()
def on_submit(self): def on_submit(self):
self.make_depreciation_entry() self.make_depreciation_entry()
@ -25,7 +25,7 @@ class AssetValueAdjustment(Document):
frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry)) frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry))
self.reschedule_depreciations(self.current_asset_value) self.reschedule_depreciations(self.current_asset_value)
def validate_date(self): def validate_date(self):
asset_purchase_date = frappe.db.get_value('Asset', self.asset, 'purchase_date') asset_purchase_date = frappe.db.get_value('Asset', self.asset, 'purchase_date')
if getdate(self.date) < getdate(asset_purchase_date): if getdate(self.date) < getdate(asset_purchase_date):
@ -53,6 +53,7 @@ class AssetValueAdjustment(Document):
je.posting_date = self.date je.posting_date = self.date
je.company = self.company je.company = self.company
je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount) je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount)
je.finance_book = self.finance_book
credit_entry = { credit_entry = {
"account": accumulated_depreciation_account, "account": accumulated_depreciation_account,
@ -78,7 +79,7 @@ class AssetValueAdjustment(Document):
debit_entry.update({ debit_entry.update({
dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension') dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension')
}) })
je.append("accounts", credit_entry) je.append("accounts", credit_entry)
je.append("accounts", debit_entry) je.append("accounts", debit_entry)

View File

@ -75,24 +75,23 @@ def get_data(filters):
for asset in assets_record: for asset in assets_record:
asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \ asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \
- flt(depreciation_amount_map.get(asset.name)) - flt(depreciation_amount_map.get(asset.name))
if asset_value: row = {
row = { "asset_id": asset.asset_id,
"asset_id": asset.asset_id, "asset_name": asset.asset_name,
"asset_name": asset.asset_name, "status": asset.status,
"status": asset.status, "department": asset.department,
"department": asset.department, "cost_center": asset.cost_center,
"cost_center": asset.cost_center, "vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
"vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice), "gross_purchase_amount": asset.gross_purchase_amount,
"gross_purchase_amount": asset.gross_purchase_amount, "opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation, "depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0, "available_for_use_date": asset.available_for_use_date,
"available_for_use_date": asset.available_for_use_date, "location": asset.location,
"location": asset.location, "asset_category": asset.asset_category,
"asset_category": asset.asset_category, "purchase_date": asset.purchase_date,
"purchase_date": asset.purchase_date, "asset_value": asset_value
"asset_value": asset_value }
} data.append(row)
data.append(row)
return data return data

View File

@ -49,6 +49,12 @@ class Supplier(TransactionBase):
msgprint(_("Series is mandatory"), raise_exception=1) msgprint(_("Series is mandatory"), raise_exception=1)
validate_party_accounts(self) validate_party_accounts(self)
self.validate_internal_supplier()
def validate_internal_supplier(self):
if self.is_internal_supplier and frappe.db.get_value("Supplier", {"represents_company": self.represents_company}, "name"):
frappe.throw(_("Internal Supplier for company {0} already exists").format(
frappe.bold(self.represents_company)))
def on_trash(self): def on_trash(self):
delete_contact_and_address('Supplier', self.name) delete_contact_and_address('Supplier', self.name)

View File

@ -107,6 +107,8 @@ class AccountsController(TransactionBase):
else: else:
self.validate_deferred_start_and_end_date() self.validate_deferred_start_and_end_date()
self.set_inter_company_account()
validate_regional(self) validate_regional(self)
if self.doctype != 'Material Request': if self.doctype != 'Material Request':
apply_pricing_rule_on_transaction(self) apply_pricing_rule_on_transaction(self)
@ -932,6 +934,38 @@ class AccountsController(TransactionBase):
else: else:
return frappe.db.get_single_value("Global Defaults", "disable_rounded_total") return frappe.db.get_single_value("Global Defaults", "disable_rounded_total")
def set_inter_company_account(self):
"""
Set intercompany account for inter warehouse transactions
This account will be used in case billing company and internal customer's
representation company is same
"""
if self.is_internal_transfer() and not self.unrealized_profit_loss_account:
unrealized_profit_loss_account = frappe.db.get_value('Company', self.company, 'unrealized_profit_loss_account')
if not unrealized_profit_loss_account:
msg = _("Please select Unrealized Profit / Loss account or add default Unrealized Profit / Loss account account for company {0}").format(
frappe.bold(self.company))
frappe.throw(msg)
self.unrealized_profit_loss_account = unrealized_profit_loss_account
def is_internal_transfer(self):
"""
It will an internal transfer if its an internal customer and representation
company is same as billing company
"""
if self.doctype == 'Sales Invoice':
internal_party_field = 'is_internal_customer'
else:
internal_party_field = 'is_internal_supplier'
if self.get(internal_party_field) and (self.represents_company == self.company):
return True
return False
@frappe.whitelist() @frappe.whitelist()
def get_tax_rate(account_head): def get_tax_rate(account_head):
return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True) return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)

View File

@ -42,6 +42,7 @@ class BuyingController(StockController):
self.validate_items() self.validate_items()
self.set_qty_as_per_stock_uom() self.set_qty_as_per_stock_uom()
self.validate_stock_or_nonstock_items() self.validate_stock_or_nonstock_items()
self.update_tax_category_for_internal_transfer()
self.validate_warehouse() self.validate_warehouse()
self.validate_from_warehouse() self.validate_from_warehouse()
self.set_supplier_address() self.set_supplier_address()
@ -94,13 +95,23 @@ class BuyingController(StockController):
def validate_stock_or_nonstock_items(self): def validate_stock_or_nonstock_items(self):
if self.meta.get_field("taxes") and not self.get_stock_items() and not self.get_asset_items(): if self.meta.get_field("taxes") and not self.get_stock_items() and not self.get_asset_items():
tax_for_valuation = [d for d in self.get("taxes") msg = _('Tax Category has been changed to "Total" because all the Items are non-stock items')
self.update_tax_category(msg)
def update_tax_category_for_internal_transfer(self):
if self.doctype == 'Purchase Invoice' and self.is_internal_transfer():
msg = _('Tax Category has been changed to "Total" as its an internal purchase.')
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"]] if d.category in ["Valuation", "Valuation and Total"]]
if tax_for_valuation: if tax_for_valuation:
for d in tax_for_valuation: for d in tax_for_valuation:
d.category = 'Total' d.category = 'Total'
msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items'))
msgprint(msg)
def validate_asset_return(self): 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:

View File

@ -254,22 +254,26 @@ class StatusUpdater(Document):
if not args.get("second_source_extra_cond"): if not args.get("second_source_extra_cond"):
args["second_source_extra_cond"] = "" args["second_source_extra_cond"] = ""
args['second_source_condition'] = """ + 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` from `tab%(second_source_dt)s`
where `%(second_join_field)s`="%(detail_id)s" where `%(second_join_field)s`="%(detail_id)s"
and (`tab%(second_source_dt)s`.docstatus=1) %(second_source_extra_cond)s FOR UPDATE), 0)""" % args and (`tab%(second_source_dt)s`.docstatus=1)
%(second_source_extra_cond)s), 0) """ % args)[0][0]
if args['detail_id']: if args['detail_id']:
if not args.get("extra_cond"): args["extra_cond"] = "" if not args.get("extra_cond"): args["extra_cond"] = ""
frappe.db.sql("""update `tab%(target_dt)s` args["source_dt_value"] = frappe.db.sql("""
set %(target_field)s = (
(select ifnull(sum(%(source_field)s), 0) (select ifnull(sum(%(source_field)s), 0)
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s" from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
and (docstatus=1 %(cond)s) %(extra_cond)s) and (docstatus=1 %(cond)s) %(extra_cond)s)
%(second_source_condition)s """ % args)[0][0] or 0.0
)
%(update_modified)s if args['second_source_condition']:
args["source_dt_value"] += flt(args['second_source_condition'])
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): def _update_percent_field_in_targets(self, args, update_modified=True):

View File

@ -77,7 +77,7 @@ class StockController(AccountsController):
if sle_list: if sle_list:
for sle in sle_list: for sle in sle_list:
if warehouse_account.get(sle.warehouse): if warehouse_account.get(sle.warehouse):
# from warehouse account/ target warehouse account # from warehouse account
self.check_expense_account(item_row) self.check_expense_account(item_row)
@ -92,9 +92,16 @@ class StockController(AccountsController):
sle = self.update_stock_ledger_entries(sle) sle = self.update_stock_ledger_entries(sle)
# expense account/ target_warehouse / source_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({ gl_list.append(self.get_gl_dict({
"account": warehouse_account[sle.warehouse]["account"], "account": warehouse_account[sle.warehouse]["account"],
"against": item_row.expense_account, "against": expense_account,
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"project": item_row.project or self.get('project'), "project": item_row.project or self.get('project'),
"remarks": self.get("remarks") or "Accounting Entry for Stock", "remarks": self.get("remarks") or "Accounting Entry for Stock",
@ -102,9 +109,8 @@ class StockController(AccountsController):
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No", "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
}, warehouse_account[sle.warehouse]["account_currency"], item=item_row)) }, warehouse_account[sle.warehouse]["account_currency"], item=item_row))
# expense account
gl_list.append(self.get_gl_dict({ gl_list.append(self.get_gl_dict({
"account": item_row.expense_account, "account": expense_account,
"against": warehouse_account[sle.warehouse]["account"], "against": warehouse_account[sle.warehouse]["account"],
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"project": item_row.project or self.get('project'), "project": item_row.project or self.get('project'),

View File

@ -519,6 +519,17 @@ class calculate_taxes_and_totals(object):
if self.doc.docstatus == 0: if self.doc.docstatus == 0:
self.calculate_outstanding_amount() self.calculate_outstanding_amount()
def is_internal_invoice(self):
"""
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():
return True
return False
def calculate_outstanding_amount(self): def calculate_outstanding_amount(self):
# NOTE: # NOTE:
# write_off_amount is only for POS Invoice # write_off_amount is only for POS Invoice
@ -526,7 +537,8 @@ class calculate_taxes_and_totals(object):
if self.doc.doctype == "Sales Invoice": if self.doc.doctype == "Sales Invoice":
self.calculate_paid_amount() self.calculate_paid_amount()
if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos'): 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.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'])

View File

@ -1,23 +1,31 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
cur_frm.add_fetch("contract_template", "contract_terms", "contract_terms");
cur_frm.add_fetch("contract_template", "requires_fulfilment", "requires_fulfilment");
// Add fulfilment terms from contract template into contract
frappe.ui.form.on("Contract", { frappe.ui.form.on("Contract", {
contract_template: function (frm) { contract_template: function (frm) {
// Populate the fulfilment terms table from a contract template, if any
if (frm.doc.contract_template) { if (frm.doc.contract_template) {
frappe.model.with_doc("Contract Template", frm.doc.contract_template, function () { frappe.call({
var tabletransfer = frappe.model.get_doc("Contract Template", frm.doc.contract_template); method: 'erpnext.crm.doctype.contract_template.contract_template.get_contract_template',
args: {
frm.doc.fulfilment_terms = []; template_name: frm.doc.contract_template,
$.each(tabletransfer.fulfilment_terms, function (index, row) { doc: frm.doc
var d = frm.add_child("fulfilment_terms"); },
d.requirement = row.requirement; callback: function(r) {
frm.refresh_field("fulfilment_terms"); if (r && r.message) {
}); let contract_template = r.message.contract_template;
frm.set_value("contract_terms", r.message.contract_terms);
frm.set_value("requires_fulfilment", contract_template.requires_fulfilment);
if (frm.doc.requires_fulfilment) {
// Populate the fulfilment terms table from a contract template, if any
r.message.contract_template.fulfilment_terms.forEach(element => {
let d = frm.add_child("fulfilment_terms");
d.requirement = element.requirement;
});
frm.refresh_field("fulfilment_terms");
}
}
}
}); });
} }
} }

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"creation": "2018-04-12 06:32:04.582486", "creation": "2018-04-12 06:32:04.582486",
@ -247,7 +248,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-03-30 06:56:07.257932", "modified": "2020-12-07 11:15:58.385521",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Contract", "name": "Contract",

View File

@ -11,7 +11,9 @@
"contract_terms", "contract_terms",
"sb_fulfilment", "sb_fulfilment",
"requires_fulfilment", "requires_fulfilment",
"fulfilment_terms" "fulfilment_terms",
"section_break_6",
"contract_template_help"
], ],
"fields": [ "fields": [
{ {
@ -41,10 +43,20 @@
"fieldtype": "Table", "fieldtype": "Table",
"label": "Fulfilment Terms and Conditions", "label": "Fulfilment Terms and Conditions",
"options": "Contract Template Fulfilment Terms" "options": "Contract Template Fulfilment Terms"
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
},
{
"fieldname": "contract_template_help",
"fieldtype": "HTML",
"label": "Contract Template Help",
"options": "<h4>Contract Template Example</h4>\n\n<pre>Contract for Customer {{ party_name }}\n\n-Valid From : {{ start_date }} \n-Valid To : {{ end_date }}\n</pre>\n\n<h4>How to get fieldnames</h4>\n\n<p>The field names you can use in your Contract Template are the fields in the Contract for which you are creating the template. You can find out the fields of any documents via Setup &gt; Customize Form View and selecting the document type (e.g. Contract)</p>\n\n<h4>Templating</h4>\n\n<p>Templates are compiled using the Jinja Templating Language. To learn more about Jinja, <a class=\"strong\" href=\"http://jinja.pocoo.org/docs/dev/templates/\">read this documentation.</a></p>"
} }
], ],
"links": [], "links": [],
"modified": "2020-11-11 17:49:44.879363", "modified": "2020-12-07 10:44:22.587047",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Contract Template", "name": "Contract Template",

View File

@ -5,6 +5,27 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils.jinja import validate_template
from six import string_types
import json
class ContractTemplate(Document): class ContractTemplate(Document):
pass def validate(self):
if self.contract_terms:
validate_template(self.contract_terms)
@frappe.whitelist()
def get_contract_template(template_name, doc):
if isinstance(doc, string_types):
doc = json.loads(doc)
contract_template = frappe.get_doc("Contract Template", template_name)
contract_terms = None
if contract_template.contract_terms:
contract_terms = frappe.render_template(contract_template.contract_terms, doc)
return {
'contract_template': contract_template,
'contract_terms': contract_terms
}

View File

@ -260,6 +260,15 @@ def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings):
"""Shipping lines represents the shipping details, """Shipping lines represents the shipping details,
each such shipping detail consists of a list of tax_lines""" each such shipping detail consists of a list of tax_lines"""
for shipping_charge in shipping_lines: for shipping_charge in shipping_lines:
if shipping_charge.get("price"):
taxes.append({
"charge_type": _("Actual"),
"account_head": get_tax_account_head(shipping_charge),
"description": shipping_charge["title"],
"tax_amount": shipping_charge["price"],
"cost_center": shopify_settings.cost_center
})
for tax in shipping_charge.get("tax_lines"): for tax in shipping_charge.get("tax_lines"):
taxes.append({ taxes.append({
"charge_type": _("Actual"), "charge_type": _("Actual"),

View File

@ -30,6 +30,11 @@
"label": "Laboratory", "label": "Laboratory",
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test\",\n\t\t\"label\": \"Lab Test\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sample Collection\",\n\t\t\"label\": \"Sample Collection\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Dosage Form\",\n\t\t\"label\": \"Dosage Form\"\n\t}\n]" "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test\",\n\t\t\"label\": \"Lab Test\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sample Collection\",\n\t\t\"label\": \"Sample Collection\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Dosage Form\",\n\t\t\"label\": \"Dosage Form\"\n\t}\n]"
}, },
{
"hidden": 0,
"label": "Inpatient",
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Medication Order\",\n\t\t\"label\": \"Inpatient Medication Order\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Medication Entry\",\n\t\t\"label\": \"Inpatient Medication Entry\"\n\t}\n]"
},
{ {
"hidden": 0, "hidden": 0,
"label": "Rehabilitation and Physiotherapy", "label": "Rehabilitation and Physiotherapy",
@ -38,7 +43,7 @@
{ {
"hidden": 0, "hidden": 0,
"label": "Records and History", "label": "Records and History",
"links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]" "links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t}\n]"
}, },
{ {
"hidden": 0, "hidden": 0,
@ -64,7 +69,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Healthcare", "label": "Healthcare",
"modified": "2020-11-23 23:00:48.764377", "modified": "2020-11-26 22:09:09.164584",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Healthcare", "name": "Healthcare",

View File

@ -29,6 +29,29 @@ frappe.ui.form.on('Inpatient Medication Entry', {
} }
}; };
}); });
if (frm.doc.__islocal || frm.doc.docstatus !== 0 || !frm.doc.update_stock)
return;
frm.add_custom_button(__('Make Stock Entry'), function() {
frappe.call({
method: 'erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry.make_difference_stock_entry',
args: { docname: frm.doc.name },
freeze: true,
callback: function(r) {
if (r.message) {
var doclist = frappe.model.sync(r.message);
frappe.set_route('Form', doclist[0].doctype, doclist[0].name);
} else {
frappe.msgprint({
title: __('No Drug Shortage'),
message: __('All the drugs are available with sufficient qty to process this Inpatient Medication Entry.'),
indicator: 'green'
});
}
}
});
});
}, },
patient: function(frm) { patient: function(frm) {

View File

@ -142,25 +142,32 @@ class InpatientMedicationEntry(Document):
return orders, order_entry_map return orders, order_entry_map
def check_stock_qty(self): def check_stock_qty(self):
from erpnext.stock.stock_ledger import NegativeStockError drug_shortage = get_drug_shortage_map(self.medication_orders, self.warehouse)
drug_availability = dict() if drug_shortage:
for d in self.medication_orders: message = _('Quantity not available for the following items in warehouse {0}. ').format(frappe.bold(self.warehouse))
if not drug_availability.get(d.drug_code): message += _('Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed.')
drug_availability[d.drug_code] = 0
drug_availability[d.drug_code] += flt(d.dosage)
for drug, dosage in drug_availability.items(): formatted_item_rows = ''
available_qty = get_latest_stock_qty(drug, self.warehouse)
# validate qty for drug, shortage_qty in drug_shortage.items():
if flt(available_qty) < flt(dosage): item_link = get_link_to_form('Item', drug)
frappe.throw(_('Quantity not available for {0} in warehouse {1}').format( formatted_item_rows += """
frappe.bold(drug), frappe.bold(self.warehouse)) <td>{0}</td>
+ '<br><br>' + _('Available quantity is {0}, you need {1}').format( <td>{1}</td>
frappe.bold(available_qty), frappe.bold(dosage)) </tr>""".format(item_link, frappe.bold(shortage_qty))
+ '<br><br>' + _('Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed.'),
NegativeStockError, title=_('Insufficient Stock')) message += """
<table class='table'>
<thead>
<th>{0}</th>
<th>{1}</th>
</thead>
{2}
</table>
""".format(_('Drug Code'), _('Shortage Qty'), formatted_item_rows)
frappe.throw(message, title=_('Insufficient Stock'), is_minimizable=True, wide=True)
def make_stock_entry(self): def make_stock_entry(self):
stock_entry = frappe.new_doc('Stock Entry') stock_entry = frappe.new_doc('Stock Entry')
@ -223,7 +230,8 @@ def get_pending_medication_orders(entry):
for doc in data: for doc in data:
inpatient_record = doc.inpatient_record inpatient_record = doc.inpatient_record
doc['service_unit'] = get_current_healthcare_service_unit(inpatient_record) if inpatient_record:
doc['service_unit'] = get_current_healthcare_service_unit(inpatient_record)
if entry.service_unit and doc.service_unit != entry.service_unit: if entry.service_unit and doc.service_unit != entry.service_unit:
to_remove.append(doc) to_remove.append(doc)
@ -276,4 +284,55 @@ def get_current_healthcare_service_unit(inpatient_record):
ip_record = frappe.get_doc('Inpatient Record', inpatient_record) ip_record = frappe.get_doc('Inpatient Record', inpatient_record)
if ip_record.inpatient_occupancies: if ip_record.inpatient_occupancies:
return ip_record.inpatient_occupancies[-1].service_unit return ip_record.inpatient_occupancies[-1].service_unit
return return
def get_drug_shortage_map(medication_orders, warehouse):
"""
Returns a dict like { drug_code: shortage_qty }
"""
drug_requirement = dict()
for d in medication_orders:
if not drug_requirement.get(d.drug_code):
drug_requirement[d.drug_code] = 0
drug_requirement[d.drug_code] += flt(d.dosage)
drug_shortage = dict()
for drug, required_qty in drug_requirement.items():
available_qty = get_latest_stock_qty(drug, warehouse)
if flt(required_qty) > flt(available_qty):
drug_shortage[drug] = flt(flt(required_qty) - flt(available_qty))
return drug_shortage
@frappe.whitelist()
def make_difference_stock_entry(docname):
doc = frappe.get_doc('Inpatient Medication Entry', docname)
drug_shortage = get_drug_shortage_map(doc.medication_orders, doc.warehouse)
if not drug_shortage:
return None
stock_entry = frappe.new_doc('Stock Entry')
stock_entry.purpose = 'Material Transfer'
stock_entry.set_stock_entry_type()
stock_entry.to_warehouse = doc.warehouse
stock_entry.company = doc.company
cost_center = frappe.get_cached_value('Company', doc.company, 'cost_center')
expense_account = get_account(None, 'expense_account', 'Healthcare Settings', doc.company)
for drug, shortage_qty in drug_shortage.items():
se_child = stock_entry.append('items')
se_child.item_code = drug
se_child.item_name = frappe.db.get_value('Item', drug, 'stock_uom')
se_child.uom = frappe.db.get_value('Item', drug, 'stock_uom')
se_child.stock_uom = se_child.uom
se_child.qty = flt(shortage_qty)
se_child.t_warehouse = doc.warehouse
# in stock uom
se_child.conversion_factor = 1
se_child.cost_center = cost_center
se_child.expense_account = expense_account
return stock_entry

View File

@ -9,6 +9,7 @@ from frappe.utils import add_days, getdate, now_datetime
from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import create_patient, create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import create_patient, create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge
from erpnext.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import create_ipmo, create_ipme from erpnext.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import create_ipmo, create_ipme
from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_drug_shortage_map, make_difference_stock_entry
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_account from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_account
class TestInpatientMedicationEntry(unittest.TestCase): class TestInpatientMedicationEntry(unittest.TestCase):
@ -82,6 +83,39 @@ class TestInpatientMedicationEntry(unittest.TestCase):
self.assertEqual(stock_entry.items[0].patient, self.patient) self.assertEqual(stock_entry.items[0].patient, self.patient)
self.assertEqual(stock_entry.items[0].inpatient_medication_entry_child, ipme.medication_orders[0].name) self.assertEqual(stock_entry.items[0].inpatient_medication_entry_child, ipme.medication_orders[0].name)
def test_drug_shortage_stock_entry(self):
ipmo = create_ipmo(self.patient)
ipmo.submit()
ipmo.reload()
date = add_days(getdate(), -1)
filters = frappe._dict(
from_date=date,
to_date=date,
from_time='',
to_time='',
item_code='Dextromethorphan',
patient=self.patient
)
# check drug shortage
ipme = create_ipme(filters, update_stock=1)
ipme.warehouse = 'Finished Goods - _TC'
ipme.save()
drug_shortage = get_drug_shortage_map(ipme.medication_orders, ipme.warehouse)
self.assertEqual(drug_shortage.get('Dextromethorphan'), 3)
# check material transfer for drug shortage
make_stock_entry()
stock_entry = make_difference_stock_entry(ipme.name)
self.assertEqual(stock_entry.items[0].item_code, 'Dextromethorphan')
self.assertEqual(stock_entry.items[0].qty, 3)
stock_entry.from_warehouse = 'Stores - _TC'
stock_entry.submit()
ipme.reload()
ipme.submit()
def tearDown(self): def tearDown(self):
# cleanup - Discharge # cleanup - Discharge
schedule_discharge(frappe.as_json({'patient': self.patient})) schedule_discharge(frappe.as_json({'patient': self.patient}))
@ -94,15 +128,12 @@ class TestInpatientMedicationEntry(unittest.TestCase):
for entry in frappe.get_all('Inpatient Medication Entry'): for entry in frappe.get_all('Inpatient Medication Entry'):
doc = frappe.get_doc('Inpatient Medication Entry', entry.name) doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
doc.cancel() doc.cancel()
frappe.db.delete('Stock Entry', {'inpatient_medication_entry': doc.name})
doc.delete()
for entry in frappe.get_all('Inpatient Medication Order'): for entry in frappe.get_all('Inpatient Medication Order'):
doc = frappe.get_doc('Inpatient Medication Order', entry.name) doc = frappe.get_doc('Inpatient Medication Order', entry.name)
doc.cancel() doc.cancel()
doc.delete()
def make_stock_entry(): def make_stock_entry(warehouse=None):
frappe.db.set_value('Company', '_Test Company', { frappe.db.set_value('Company', '_Test Company', {
'stock_adjustment_account': 'Stock Adjustment - _TC', 'stock_adjustment_account': 'Stock Adjustment - _TC',
'default_inventory_account': 'Stock In Hand - _TC' 'default_inventory_account': 'Stock In Hand - _TC'
@ -110,7 +141,7 @@ def make_stock_entry():
stock_entry = frappe.new_doc('Stock Entry') stock_entry = frappe.new_doc('Stock Entry')
stock_entry.stock_entry_type = 'Material Receipt' stock_entry.stock_entry_type = 'Material Receipt'
stock_entry.company = '_Test Company' stock_entry.company = '_Test Company'
stock_entry.to_warehouse = 'Stores - _TC' stock_entry.to_warehouse = warehouse or 'Stores - _TC'
expense_account = get_account(None, 'expense_account', 'Healthcare Settings', '_Test Company') expense_account = get_account(None, 'expense_account', 'Healthcare Settings', '_Test Company')
se_child = stock_entry.append('items') se_child = stock_entry.append('items')
se_child.item_code = 'Dextromethorphan' se_child.item_code = 'Dextromethorphan'

View File

@ -18,6 +18,10 @@ def get_data():
{ {
'label': _('Billing'), 'label': _('Billing'),
'items': ['Sales Invoice'] 'items': ['Sales Invoice']
},
{
'label': _('Orders'),
'items': ['Inpatient Medication Order']
} }
] ]
} }

View File

@ -18,13 +18,18 @@ frappe.ui.form.on('Employee Advance', {
if (!frm.doc.employee) { if (!frm.doc.employee) {
frappe.msgprint(__("Please select employee first")); frappe.msgprint(__("Please select employee first"));
} }
var company_currency = erpnext.get_currency(frm.doc.company); let company_currency = erpnext.get_currency(frm.doc.company);
let currencies = [company_currency];
if (frm.doc.currency && (frm.doc.currency != company_currency)) {
currencies.push(frm.doc.currency);
}
return { return {
filters: { filters: {
"root_type": "Asset", "root_type": "Asset",
"is_group": 0, "is_group": 0,
"company": frm.doc.company, "company": frm.doc.company,
"account_currency": ["in", [frm.doc.currency, company_currency]], "account_currency": ["in", currencies],
} }
}; };
}); });
@ -181,21 +186,23 @@ frappe.ui.form.on('Employee Advance', {
}, },
currency: function(frm) { currency: function(frm) {
var from_currency = frm.doc.currency; if (frm.doc.currency) {
var company_currency; var from_currency = frm.doc.currency;
if (!frm.doc.company) { var company_currency;
company_currency = erpnext.get_currency(frappe.defaults.get_default("Company")); if (!frm.doc.company) {
} else { company_currency = erpnext.get_currency(frappe.defaults.get_default("Company"));
company_currency = erpnext.get_currency(frm.doc.company); } else {
company_currency = erpnext.get_currency(frm.doc.company);
}
if (from_currency != company_currency) {
frm.events.set_exchange_rate(frm, from_currency, company_currency);
} else {
frm.set_value("exchange_rate", 1.0);
frm.set_df_property('exchange_rate', 'hidden', 1);
frm.set_df_property("exchange_rate", "description", "" );
}
frm.refresh_fields();
} }
if (from_currency != company_currency) {
frm.events.set_exchange_rate(frm, from_currency, company_currency);
} else {
frm.set_value("exchange_rate", 1.0);
frm.set_df_property('exchange_rate', 'hidden', 1);
frm.set_df_property("exchange_rate", "description", "" );
}
frm.refresh_fields();
}, },
set_exchange_rate: function(frm, from_currency, company_currency) { set_exchange_rate: function(frm, from_currency, company_currency) {

View File

@ -8,7 +8,17 @@ frappe.views.calendar["Job Card"] = {
"allDay": "allDay", "allDay": "allDay",
"progress": "progress" "progress": "progress"
}, },
gantt: true, gantt: {
field_map: {
"start": "started_time",
"end": "started_time",
"id": "name",
"title": "subject",
"color": "color",
"allDay": "allDay",
"progress": "progress"
}
},
filters: [ filters: [
{ {
"fieldtype": "Link", "fieldtype": "Link",

View File

@ -491,6 +491,39 @@ class TestWorkOrder(unittest.TestCase):
work_order1.save() work_order1.save()
self.assertEqual(work_order1.operations[0].time_in_mins, 40.0) self.assertEqual(work_order1.operations[0].time_in_mins, 40.0)
def test_partial_material_consumption(self):
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1)
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
ste_cancel_list = []
ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0)
ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
target="_Test Warehouse - _TC", qty=20, basic_rate=1000.0)
ste_cancel_list.extend([ste1, ste2])
s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 4))
s.submit()
ste_cancel_list.append(s)
ste1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
ste1.submit()
ste_cancel_list.append(ste1)
print(wo_order.name)
ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2))
self.assertEquals(ste3.fg_completed_qty, 2)
expected_qty = {"_Test Item": 2, "_Test Item Home Desktop 100": 4}
for row in ste3.items:
self.assertEquals(row.qty, expected_qty.get(row.item_code))
for ste_doc in ste_cancel_list:
ste_doc.cancel()
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0)
def get_scrap_item_details(bom_no): def get_scrap_item_details(bom_no):
scrap_items = {} scrap_items = {}
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item` for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`

View File

@ -545,7 +545,8 @@ erpnext.work_order = {
var tbl = frm.doc.required_items || []; var tbl = frm.doc.required_items || [];
var tbl_lenght = tbl.length; var tbl_lenght = tbl.length;
for (var i = 0, len = tbl_lenght; i < len; i++) { for (var i = 0, len = tbl_lenght; i < len; i++) {
if (flt(frm.doc.required_items[i].required_qty) > flt(frm.doc.required_items[i].consumed_qty)) { let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
counter += 1; counter += 1;
} }
} }

View File

@ -59,7 +59,7 @@ class Member(Document):
frappe.msgprint(_("A customer is already linked to this Member")) frappe.msgprint(_("A customer is already linked to this Member"))
cust = create_customer(frappe._dict({ cust = create_customer(frappe._dict({
'fullname': self.member_name, 'fullname': self.member_name,
'email': self.email_id or self.user, 'email': self.email_id or self.email,
'phone': None 'phone': None
})) }))
@ -177,4 +177,4 @@ def register_member(fullname, email, rzpay_plan_id, subscription_id, pan=None, m
mobile=mobile mobile=mobile
)) ))
return member.name return member.name

View File

@ -450,7 +450,6 @@ erpnext.patches.v8_9.set_member_party_type
erpnext.patches.v9_0.add_user_to_child_table_in_pos_profile erpnext.patches.v9_0.add_user_to_child_table_in_pos_profile
erpnext.patches.v9_0.set_schedule_date_for_material_request_and_purchase_order erpnext.patches.v9_0.set_schedule_date_for_material_request_and_purchase_order
erpnext.patches.v9_0.student_admission_childtable_migrate erpnext.patches.v9_0.student_admission_childtable_migrate
erpnext.patches.v9_0.fix_subscription_next_date #2017-10-23
erpnext.patches.v9_0.add_healthcare_domain erpnext.patches.v9_0.add_healthcare_domain
erpnext.patches.v9_0.set_variant_item_description erpnext.patches.v9_0.set_variant_item_description
erpnext.patches.v9_0.set_uoms_in_variant_field erpnext.patches.v9_0.set_uoms_in_variant_field

View File

@ -5,6 +5,8 @@ from frappe.utils import nowdate
from erpnext.accounts.doctype.account.test_account import create_account from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
from erpnext.loan_management.doctype.loan.loan import make_repayment_entry from erpnext.loan_management.doctype.loan.loan import make_repayment_entry
from erpnext.loan_management.doctype.loan_repayment.loan_repayment import get_accrued_interest_entries
from frappe.model.naming import make_autoname
def execute(): def execute():
@ -18,15 +20,29 @@ def execute():
frappe.reload_doc('loan_management', 'doctype', 'loan_repayment_detail') frappe.reload_doc('loan_management', 'doctype', 'loan_repayment_detail')
frappe.reload_doc('loan_management', 'doctype', 'loan_interest_accrual') frappe.reload_doc('loan_management', 'doctype', 'loan_interest_accrual')
frappe.reload_doc('accounts', 'doctype', 'gl_entry') frappe.reload_doc('accounts', 'doctype', 'gl_entry')
frappe.reload_doc('accounts', 'doctype', 'journal_entry_account')
updated_loan_types = [] updated_loan_types = []
loans_to_close = []
# Update old loan status as closed
if frappe.db.has_column('Repayment Schedule', 'paid'):
loans_list = frappe.db.sql("""SELECT distinct parent from `tabRepayment Schedule`
where paid = 0 and docstatus = 1""", as_dict=1)
loans_to_close = [d.parent for d in loans_list]
if loans_to_close:
frappe.db.sql("UPDATE `tabLoan` set status = 'Closed' where name not in (%s)" % (', '.join(['%s'] * len(loans_to_close))), tuple(loans_to_close))
loans = frappe.get_all('Loan', fields=['name', 'loan_type', 'company', 'status', 'mode_of_payment', loans = frappe.get_all('Loan', fields=['name', 'loan_type', 'company', 'status', 'mode_of_payment',
'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account']) 'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account'],
filters={'docstatus': 1, 'status': ('!=', 'Closed')})
for loan in loans: for loan in loans:
# Update details in Loan Types and Loan # Update details in Loan Types and Loan
loan_type_company = frappe.db.get_value('Loan Type', loan.loan_type, 'company') loan_type_company = frappe.db.get_value('Loan Type', loan.loan_type, 'company')
loan_type = loan.loan_type
group_income_account = frappe.get_value('Account', {'company': loan.company, group_income_account = frappe.get_value('Account', {'company': loan.company,
'is_group': 1, 'root_type': 'Income', 'account_name': _('Indirect Income')}) 'is_group': 1, 'root_type': 'Income', 'account_name': _('Indirect Income')})
@ -38,7 +54,26 @@ def execute():
penalty_account = create_account(company=loan.company, account_type='Income Account', penalty_account = create_account(company=loan.company, account_type='Income Account',
account_name='Penalty Account', parent_account=group_income_account) account_name='Penalty Account', parent_account=group_income_account)
if not loan_type_company: # Same loan type used for multiple companies
if loan_type_company and loan_type_company != loan.company:
# get loan type for appropriate company
loan_type_name = frappe.get_value('Loan Type', {'company': loan.company,
'mode_of_payment': loan.mode_of_payment, 'loan_account': loan.loan_account,
'payment_account': loan.payment_account, 'interest_income_account': loan.interest_income_account,
'penalty_income_account': loan.penalty_income_account}, 'name')
if not loan_type_name:
loan_type_name = create_loan_type(loan, loan_type_name, penalty_account)
# update loan type in loan
frappe.db.sql("UPDATE `tabLoan` set loan_type = %s where name = %s", (loan_type_name,
loan.name))
loan_type = loan_type_name
if loan_type_name not in updated_loan_types:
updated_loan_types.append(loan_type_name)
elif not loan_type_company:
loan_type_doc = frappe.get_doc('Loan Type', loan.loan_type) loan_type_doc = frappe.get_doc('Loan Type', loan.loan_type)
loan_type_doc.is_term_loan = 1 loan_type_doc.is_term_loan = 1
loan_type_doc.company = loan.company loan_type_doc.company = loan.company
@ -49,8 +84,9 @@ def execute():
loan_type_doc.penalty_income_account = penalty_account loan_type_doc.penalty_income_account = penalty_account
loan_type_doc.submit() loan_type_doc.submit()
updated_loan_types.append(loan.loan_type) updated_loan_types.append(loan.loan_type)
loan_type = loan.loan_type
if loan.loan_type in updated_loan_types: if loan_type in updated_loan_types:
if loan.status == 'Fully Disbursed': if loan.status == 'Fully Disbursed':
status = 'Disbursed' status = 'Disbursed'
elif loan.status == 'Repaid/Closed': elif loan.status == 'Repaid/Closed':
@ -64,25 +100,48 @@ def execute():
'status': status 'status': status
}) })
process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan.loan_type, process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan_type,
loan=loan.name) loan=loan.name)
payments = frappe.db.sql(''' SELECT j.name, a.debit, a.debit_in_account_currency, j.posting_date
FROM `tabJournal Entry` j, `tabJournal Entry Account` a
WHERE a.parent = j.name and a.reference_type='Loan' and a.reference_name = %s
and account = %s
''', (loan.name, loan.loan_account), as_dict=1)
for payment in payments: if frappe.db.has_column('Repayment Schedule', 'paid'):
repayment_entry = make_repayment_entry(loan.name, loan.loan_applicant_type, loan.applicant, total_principal, total_interest = frappe.db.get_value('Repayment Schedule', {'paid': 1, 'parent': loan.name},
loan.loan_type, loan.company) ['sum(principal_amount) as total_principal', 'sum(interest_amount) as total_interest'])
repayment_entry.amount_paid = payment.debit_in_account_currency accrued_entries = get_accrued_interest_entries(loan.name)
repayment_entry.posting_date = payment.posting_date for entry in accrued_entries:
repayment_entry.save() interest_paid = 0
repayment_entry.submit() principal_paid = 0
jv = frappe.get_doc('Journal Entry', payment.name) if total_interest > entry.interest_amount:
jv.flags.ignore_links = True interest_paid = entry.interest_amount
jv.cancel() else:
interest_paid = total_interest
if total_principal > entry.payable_principal_amount:
principal_paid = entry.payable_principal_amount
else:
principal_paid = total_principal
frappe.db.sql(""" UPDATE `tabLoan Interest Accrual`
SET paid_principal_amount = `paid_principal_amount` + %s,
paid_interest_amount = `paid_interest_amount` + %s
WHERE name = %s""",
(principal_paid, interest_paid, entry.name))
total_principal -= principal_paid
total_interest -= interest_paid
def create_loan_type(loan, loan_type_name, penalty_account):
loan_type_doc = frappe.new_doc('Loan Type')
loan_type_doc.loan_name = make_autoname("Loan Type-.####")
loan_type_doc.is_term_loan = 1
loan_type_doc.company = loan.company
loan_type_doc.mode_of_payment = loan.mode_of_payment
loan_type_doc.payment_account = loan.payment_account
loan_type_doc.loan_account = loan.loan_account
loan_type_doc.interest_income_account = loan.interest_income_account
loan_type_doc.penalty_income_account = penalty_account
loan_type_doc.submit()
return loan_type_doc.name

View File

@ -1,48 +0,0 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import getdate
from frappe.automation.doctype.auto_repeat.auto_repeat import get_next_schedule_date
def execute():
frappe.reload_doc('accounts', 'doctype', 'subscription')
fields = ["name", "reference_doctype", "reference_document",
"start_date", "frequency", "repeat_on_day"]
for d in fields:
if not frappe.db.has_column('Subscription', d):
return
doctypes = ('Purchase Order', 'Sales Order', 'Purchase Invoice', 'Sales Invoice')
for data in frappe.get_all('Subscription',
fields = fields,
filters = {'reference_doctype': ('in', doctypes), 'docstatus': 1}):
recurring_id = frappe.db.get_value(data.reference_doctype, data.reference_document, "recurring_id")
if recurring_id:
frappe.db.sql("update `tab{0}` set subscription=%s where recurring_id=%s"
.format(data.reference_doctype), (data.name, recurring_id))
date_field = 'transaction_date'
if data.reference_doctype in ['Sales Invoice', 'Purchase Invoice']:
date_field = 'posting_date'
start_date = frappe.db.get_value(data.reference_doctype, data.reference_document, date_field)
if start_date and getdate(start_date) != getdate(data.start_date):
last_ref_date = frappe.db.sql("""
select {0}
from `tab{1}`
where subscription=%s and docstatus < 2
order by creation desc
limit 1
""".format(date_field, data.reference_doctype), data.name)[0][0]
next_schedule_date = get_next_schedule_date(last_ref_date, data.frequency, data.repeat_on_day)
frappe.db.set_value("Subscription", data.name, {
"start_date": start_date,
"next_schedule_date": next_schedule_date
}, None)

View File

@ -12,14 +12,6 @@ frappe.ui.form.on('Additional Salary', {
} }
}; };
}); });
if (!frm.doc.currency) return;
frm.set_query("salary_component", function() {
return {
query: "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
filters: {currency: frm.doc.currency, company: frm.doc.company}
};
});
}, },
employee: function(frm) { employee: function(frm) {

View File

@ -23,6 +23,7 @@
"employee_benefits", "employee_benefits",
"totals", "totals",
"total_amount", "total_amount",
"column_break",
"pro_rata_dispensed_amount" "pro_rata_dispensed_amount"
], ],
"fields": [ "fields": [
@ -139,11 +140,15 @@
"label": "Company", "label": "Company",
"options": "Company", "options": "Company",
"reqd": 1 "reqd": 1
},
{
"fieldname": "column_break",
"fieldtype": "Column Break"
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-11-25 11:49:05.095101", "modified": "2020-12-14 15:52:08.566418",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Employee Benefit Application", "name": "Employee Benefit Application",

View File

@ -11,11 +11,11 @@ frappe.ui.form.on('Employee Incentive', {
}; };
}); });
if (!frm.doc.currency) return; if (!frm.doc.company) return;
frm.set_query("salary_component", function() { frm.set_query("salary_component", function() {
return { return {
query: "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components", query: "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
filters: {type: "earning", currency: frm.doc.currency, company: frm.doc.company} filters: {type: "earning", company: frm.doc.company}
}; };
}); });

View File

@ -4,9 +4,13 @@
frappe.ui.form.on('Retention Bonus', { frappe.ui.form.on('Retention Bonus', {
setup: function(frm) { setup: function(frm) {
frm.set_query("employee", function() { frm.set_query("employee", function() {
if (!frm.doc.company) {
frappe.msgprint(__("Please Select Company First"));
}
return { return {
filters: { filters: {
"status": "Active" "status": "Active",
"company": frm.doc.company
} }
}; };
}); });

View File

@ -55,17 +55,17 @@ frappe.ui.form.on('Salary Structure', {
}, },
set_earning_deduction_component: function(frm) { set_earning_deduction_component: function(frm) {
if(!frm.doc.currency && !frm.doc.company) return; if(!frm.doc.company) return;
frm.set_query("salary_component", "earnings", function() { frm.set_query("salary_component", "earnings", function() {
return { return {
query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components", query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
filters: {type: "earning", currency: frm.doc.currency, company: frm.doc.company} filters: {type: "earning", company: frm.doc.company}
}; };
}); });
frm.set_query("salary_component", "deductions", function() { frm.set_query("salary_component", "deductions", function() {
return { return {
query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components", query : "erpnext.payroll.doctype.salary_structure.salary_structure.get_earning_deduction_components",
filters: {type: "deduction", currency: frm.doc.currency, company: frm.doc.company} filters: {type: "deduction", company: frm.doc.company}
}; };
}); });
}, },
@ -74,7 +74,6 @@ frappe.ui.form.on('Salary Structure', {
currency: function(frm) { currency: function(frm) {
calculate_totals(frm.doc); calculate_totals(frm.doc);
frm.trigger("set_dynamic_labels") frm.trigger("set_dynamic_labels")
frm.trigger('set_earning_deduction_component');
frm.refresh() frm.refresh()
}, },

View File

@ -210,7 +210,7 @@ def get_employees(salary_structure):
@frappe.whitelist() @frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs @frappe.validate_and_sanitize_search_inputs
def get_earning_deduction_components(doctype, txt, searchfield, start, page_len, filters): def get_earning_deduction_components(doctype, txt, searchfield, start, page_len, filters):
if len(filters) < 3: if len(filters) < 2:
return {} return {}
return frappe.db.sql(""" return frappe.db.sql("""

View File

@ -609,6 +609,15 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
this.calculate_outstanding_amount(update_paid_amount); this.calculate_outstanding_amount(update_paid_amount);
}, },
is_internal_invoice: function() {
if (['Sales Invoice', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
if (this.frm.doc.company === this.frm.doc.represents_company) {
return true;
}
}
return false;
},
calculate_outstanding_amount: function(update_paid_amount) { calculate_outstanding_amount: function(update_paid_amount) {
// NOTE: // NOTE:
// paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice // paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice
@ -617,7 +626,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
this.calculate_paid_amount(); this.calculate_paid_amount();
} }
if(this.frm.doc.is_return || this.frm.doc.docstatus > 0) return; if (this.frm.doc.is_return || (this.frm.doc.docstatus > 0) || this.is_internal_invoice()) return;
frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]); frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]);

View File

@ -408,7 +408,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
show_description(row_to_modify.idx, row_to_modify.item_code); show_description(row_to_modify.idx, row_to_modify.item_code);
this.frm.from_barcode = true; this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1;
frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, { frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, {
item_code: data.item_code, item_code: data.item_code,
qty: (row_to_modify.qty || 0) + 1 qty: (row_to_modify.qty || 0) + 1
@ -492,7 +492,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
d.item_code = ""; d.item_code = "";
} }
this.frm.from_barcode = true; this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1;
this.item_code(doc, cdt, cdn); this.item_code(doc, cdt, cdn);
}, },
@ -509,11 +509,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
show_batch_dialog = 1; show_batch_dialog = 1;
} }
// clear barcode if setting item (else barcode will take priority) // clear barcode if setting item (else barcode will take priority)
if(!this.frm.from_barcode) { if (this.frm.from_barcode == 0) {
item.barcode = null; item.barcode = null;
} }
this.frm.from_barcode = this.frm.from_barcode - 1 >= 0 ? this.frm.from_barcode - 1 : 0;
this.frm.from_barcode = false;
if(item.item_code || item.barcode || item.serial_no) { if(item.item_code || item.barcode || item.serial_no) {
if(!this.validate_company_and_party()) { if(!this.validate_company_and_party()) {
this.frm.fields_dict["items"].grid.grid_rows[item.idx - 1].remove(); this.frm.fields_dict["items"].grid.grid_rows[item.idx - 1].remove();

View File

@ -20,4 +20,4 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlData.extend( {
}); });
} }
} }
}); });

View File

@ -12,6 +12,9 @@ erpnext.setup_auto_gst_taxation = (doctype) => {
tax_category: function(frm) { tax_category: function(frm) {
frm.trigger('get_tax_template'); frm.trigger('get_tax_template');
}, },
customer_address: function(frm) {
frm.trigger('get_tax_template');
},
get_tax_template: function(frm) { get_tax_template: function(frm) {
if (!frm.doc.company) return; if (!frm.doc.company) return;

View File

@ -151,6 +151,7 @@ class Gstr1Report(object):
{select_columns} {select_columns}
from `tab{doctype}` from `tab{doctype}`
where docstatus = 1 {where_conditions} where docstatus = 1 {where_conditions}
and is_opening = 'No'
order by posting_date desc order by posting_date desc
""".format(select_columns=self.select_columns, doctype=self.doctype, """.format(select_columns=self.select_columns, doctype=self.doctype,
where_conditions=conditions), self.filters, as_dict=1) where_conditions=conditions), self.filters, as_dict=1)

View File

@ -58,6 +58,7 @@ class Customer(TransactionBase):
self.set_loyalty_program() self.set_loyalty_program()
self.check_customer_group_change() self.check_customer_group_change()
self.validate_default_bank_account() self.validate_default_bank_account()
self.validate_internal_customer()
# set loyalty program tier # set loyalty program tier
if frappe.db.exists('Customer', self.name): if frappe.db.exists('Customer', self.name):
@ -82,6 +83,11 @@ class Customer(TransactionBase):
if not is_company_account: if not is_company_account:
frappe.throw(_("{0} is not a company bank account").format(frappe.bold(self.default_bank_account))) frappe.throw(_("{0} is not a company bank account").format(frappe.bold(self.default_bank_account)))
def validate_internal_customer(self):
if self.is_internal_customer and frappe.db.get_value('Customer', {"represents_company": self.represents_company}, "name"):
frappe.throw(_("Internal Customer for company {0} already exists").format(
frappe.bold(self.represents_company)))
def on_update(self): def on_update(self):
self.validate_name_with_customer_group() self.validate_name_with_customer_group()
self.create_primary_contact() self.create_primary_contact()
@ -398,7 +404,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
# form a list of emails and names to show to the user # form a list of emails and names to show to the user
credit_controller_users_formatted = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users] credit_controller_users_formatted = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users]
if not credit_controller_users_formatted: if not credit_controller_users_formatted:
frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.".format(customer))) frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.").format(customer))
message = """Please contact any of the following users to extend the credit limits for {0}: message = """Please contact any of the following users to extend the credit limits for {0}:
<br><br><ul><li>{1}</li></ul>""".format(customer, '<li>'.join(credit_controller_users_formatted)) <br><br><ul><li>{1}</li></ul>""".format(customer, '<li>'.join(credit_controller_users_formatted))

View File

@ -14,7 +14,6 @@ from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty
from frappe.desk.notifications import clear_doctype_notifications from frappe.desk.notifications import clear_doctype_notifications
from frappe.contacts.doctype.address.address import get_company_address from frappe.contacts.doctype.address.address import get_company_address
from erpnext.controllers.selling_controller import SellingController from erpnext.controllers.selling_controller import SellingController
from frappe.automation.doctype.auto_repeat.auto_repeat import get_next_schedule_date
from erpnext.selling.doctype.customer.customer import check_credit_limit from erpnext.selling.doctype.customer.customer import check_credit_limit
from erpnext.stock.doctype.item.item import get_item_defaults from erpnext.stock.doctype.item.item import get_item_defaults
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
@ -418,8 +417,7 @@ class SalesOrder(SellingController):
def on_recurring(self, reference_doc, auto_repeat_doc): def on_recurring(self, reference_doc, auto_repeat_doc):
def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date): def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date):
delivery_date = get_next_schedule_date(ref_doc_delivery_date, delivery_date = auto_repeat_doc.get_next_schedule_date(schedule_date=ref_doc_delivery_date)
auto_repeat_doc.frequency, auto_repeat_doc.start_date, cint(auto_repeat_doc.repeat_on_day))
if delivery_date <= transaction_date: if delivery_date <= transaction_date:
delivery_date_diff = frappe.utils.date_diff(ref_doc_delivery_date, red_doc_transaction_date) delivery_date_diff = frappe.utils.date_diff(ref_doc_delivery_date, red_doc_transaction_date)

View File

@ -111,24 +111,24 @@ erpnext.PointOfSale.Controller = class {
dialog.show(); dialog.show();
} }
prepare_app_defaults(data) { async prepare_app_defaults(data) {
this.pos_opening = data.name; this.pos_opening = data.name;
this.company = data.company; this.company = data.company;
this.pos_profile = data.pos_profile; this.pos_profile = data.pos_profile;
this.pos_opening_time = data.period_start_date; this.pos_opening_time = data.period_start_date;
this.item_stock_map = {};
this.settings = {};
frappe.db.get_value('Stock Settings', undefined, 'allow_negative_stock').then(({ message }) => { frappe.db.get_value('Stock Settings', undefined, 'allow_negative_stock').then(({ message }) => {
this.allow_negative_stock = flt(message.allow_negative_stock) || false; this.allow_negative_stock = flt(message.allow_negative_stock) || false;
}); });
frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => { frappe.db.get_doc("POS Profile", this.pos_profile).then((profile) => {
this.customer_groups = profile.customer_groups.map(group => group.customer_group); this.settings.customer_groups = profile.customer_groups.map(group => group.customer_group);
this.cart.make_customer_selector(); this.settings.hide_images = profile.hide_images;
this.settings.auto_add_item_to_cart = profile.auto_add_item_to_cart;
this.make_app();
}); });
this.item_stock_map = {};
this.make_app();
} }
set_opening_entry_status() { set_opening_entry_status() {
@ -238,12 +238,11 @@ erpnext.PointOfSale.Controller = class {
this.item_selector = new erpnext.PointOfSale.ItemSelector({ this.item_selector = new erpnext.PointOfSale.ItemSelector({
wrapper: this.$components_wrapper, wrapper: this.$components_wrapper,
pos_profile: this.pos_profile, pos_profile: this.pos_profile,
settings: this.settings,
events: { events: {
item_selected: args => this.on_cart_update(args), item_selected: args => this.on_cart_update(args),
get_frm: () => this.frm || {}, get_frm: () => this.frm || {}
get_allowed_item_group: () => this.item_groups
} }
}) })
} }
@ -251,6 +250,7 @@ erpnext.PointOfSale.Controller = class {
init_item_cart() { init_item_cart() {
this.cart = new erpnext.PointOfSale.ItemCart({ this.cart = new erpnext.PointOfSale.ItemCart({
wrapper: this.$components_wrapper, wrapper: this.$components_wrapper,
settings: this.settings,
events: { events: {
get_frm: () => this.frm, get_frm: () => this.frm,
@ -273,9 +273,7 @@ erpnext.PointOfSale.Controller = class {
this.customer_details = details; this.customer_details = details;
// will add/remove LP payment method // will add/remove LP payment method
this.payment.render_loyalty_points_payment_mode(); this.payment.render_loyalty_points_payment_mode();
}, }
get_allowed_customer_group: () => this.customer_groups
} }
}) })
} }

View File

@ -1,8 +1,10 @@
erpnext.PointOfSale.ItemCart = class { erpnext.PointOfSale.ItemCart = class {
constructor({ wrapper, events }) { constructor({ wrapper, events, settings }) {
this.wrapper = wrapper; this.wrapper = wrapper;
this.events = events; this.events = events;
this.customer_info = undefined; this.customer_info = undefined;
this.hide_images = settings.hide_images;
this.allowed_customer_groups = settings.customer_groups;
this.init_component(); this.init_component();
} }
@ -32,6 +34,7 @@ erpnext.PointOfSale.ItemCart = class {
`<div class="customer-section rounded flex flex-col m-8 mb-0"></div>` `<div class="customer-section rounded flex flex-col m-8 mb-0"></div>`
) )
this.$customer_section = this.$component.find('.customer-section'); this.$customer_section = this.$component.find('.customer-section');
this.make_customer_selector();
} }
reset_customer_selector() { reset_customer_selector() {
@ -302,7 +305,7 @@ erpnext.PointOfSale.ItemCart = class {
this.$customer_section.html(`<div class="customer-search-field flex flex-1 items-center"></div>`); this.$customer_section.html(`<div class="customer-search-field flex flex-1 items-center"></div>`);
const me = this; const me = this;
const query = { query: 'erpnext.controllers.queries.customer_query' }; const query = { query: 'erpnext.controllers.queries.customer_query' };
const allowed_customer_group = this.events.get_allowed_customer_group() || []; const allowed_customer_group = this.allowed_customer_groups || [];
if (allowed_customer_group.length) { if (allowed_customer_group.length) {
query.filters = { query.filters = {
customer_group: ['in', allowed_customer_group] customer_group: ['in', allowed_customer_group]
@ -423,6 +426,7 @@ erpnext.PointOfSale.ItemCart = class {
} }
update_customer_section() { update_customer_section() {
const me = this;
const { customer, email_id='', mobile_no='', image } = this.customer_info || {}; const { customer, email_id='', mobile_no='', image } = this.customer_info || {};
if (customer) { if (customer) {
@ -460,7 +464,7 @@ erpnext.PointOfSale.ItemCart = class {
} }
function get_customer_image() { function get_customer_image() {
if (image) { if (!me.hide_images && image) {
return `<div class="icon flex items-center justify-center w-12 h-12 rounded bg-light-grey mr-4 text-grey-200"> return `<div class="icon flex items-center justify-center w-12 h-12 rounded bg-light-grey mr-4 text-grey-200">
<img class="h-full" src="${image}" alt="${image}" style="object-fit: cover;"> <img class="h-full" src="${image}" alt="${image}" style="object-fit: cover;">
</div>` </div>`

View File

@ -1,8 +1,10 @@
erpnext.PointOfSale.ItemSelector = class { erpnext.PointOfSale.ItemSelector = class {
constructor({ frm, wrapper, events, pos_profile }) { constructor({ frm, wrapper, events, pos_profile, settings }) {
this.wrapper = wrapper; this.wrapper = wrapper;
this.events = events; this.events = events;
this.pos_profile = pos_profile; this.pos_profile = pos_profile;
this.hide_images = settings.hide_images;
this.auto_add_item = settings.auto_add_item_to_cart;
this.inti_component(); this.inti_component();
} }
@ -26,13 +28,14 @@ erpnext.PointOfSale.ItemSelector = class {
<div class="flex flex-1 flex-col p-8 pt-2"> <div class="flex flex-1 flex-col p-8 pt-2">
<div class="text-grey mb-6">ALL ITEMS</div> <div class="text-grey mb-6">ALL ITEMS</div>
<div class="items-container grid grid-cols-4 gap-8"> <div class="items-container grid grid-cols-4 gap-8">
</div> </div>
</div> </div>
</div> </div>
</section>` </section>`
); );
this.$component = this.wrapper.find('.items-selector'); this.$component = this.wrapper.find('.items-selector');
this.$items_container = this.$component.find('.items-container');
} }
async load_items_data() { async load_items_data() {
@ -65,7 +68,6 @@ erpnext.PointOfSale.ItemSelector = class {
render_item_list(items) { render_item_list(items) {
this.$items_container = this.$component.find('.items-container');
this.$items_container.html(''); this.$items_container.html('');
items.forEach(item => { items.forEach(item => {
@ -75,11 +77,12 @@ erpnext.PointOfSale.ItemSelector = class {
} }
get_item_html(item) { get_item_html(item) {
const me = this;
const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item; const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom } = item;
const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange"; const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange";
function get_item_image_html() { function get_item_image_html() {
if (item_image) { if (!me.hide_images && item_image) {
return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100"> return `<div class="flex items-center justify-center h-32 border-b-grey text-6xl text-grey-100">
<img class="h-full" src="${item_image}" alt="${frappe.get_abbr(item.item_name)}" style="object-fit: cover;"> <img class="h-full" src="${item_image}" alt="${frappe.get_abbr(item.item_name)}" style="object-fit: cover;">
</div>` </div>`
@ -203,6 +206,7 @@ erpnext.PointOfSale.ItemSelector = class {
ignore_inputs: true, ignore_inputs: true,
page: cur_page.page.page page: cur_page.page.page
}); });
// for selecting the last filtered item on search // for selecting the last filtered item on search
frappe.ui.keys.on("enter", () => { frappe.ui.keys.on("enter", () => {
const selector_is_visible = this.$component.is(':visible'); const selector_is_visible = this.$component.is(':visible');
@ -235,6 +239,7 @@ erpnext.PointOfSale.ItemSelector = class {
const items = this.search_index[search_term]; const items = this.search_index[search_term];
this.items = items; this.items = items;
this.render_item_list(items); this.render_item_list(items);
this.auto_add_item && this.items.length == 1 && this.add_filtered_item_to_cart();
return; return;
} }
} }
@ -247,8 +252,13 @@ erpnext.PointOfSale.ItemSelector = class {
} }
this.items = items; this.items = items;
this.render_item_list(items); this.render_item_list(items);
this.auto_add_item && this.items.length == 1 && this.add_filtered_item_to_cart();
}); });
} }
add_filtered_item_to_cart() {
this.$items_container.find(".item-wrapper").click();
}
resize_selector(minimize) { resize_selector(minimize) {
minimize ? minimize ?

View File

@ -10,8 +10,8 @@ from frappe.utils.nestedset import get_descendants_of
def execute(filters=None): def execute(filters=None):
filters = frappe._dict(filters or {}) filters = frappe._dict(filters or {})
if filters.from_date > filters.to_date: if filters.from_date > filters.to_date:
frappe.throw(_('From Date cannot be greater than To Date')) frappe.throw(_("From Date cannot be greater than To Date"))
columns = get_columns(filters) columns = get_columns(filters)
data = get_data(filters) data = get_data(filters)
@ -148,14 +148,16 @@ def get_data(filters):
company_list.append(filters.get("company")) company_list.append(filters.get("company"))
customer_details = get_customer_details() customer_details = get_customer_details()
item_details = get_item_details()
sales_order_records = get_sales_order_details(company_list, filters) sales_order_records = get_sales_order_details(company_list, filters)
for record in sales_order_records: for record in sales_order_records:
customer_record = customer_details.get(record.customer) customer_record = customer_details.get(record.customer)
item_record = item_details.get(record.item_code)
row = { row = {
"item_code": record.item_code, "item_code": record.item_code,
"item_name": record.item_name, "item_name": item_record.item_name,
"item_group": record.item_group, "item_group": item_record.item_group,
"description": record.description, "description": record.description,
"quantity": record.qty, "quantity": record.qty,
"uom": record.uom, "uom": record.uom,
@ -196,8 +198,8 @@ def get_conditions(filters):
return conditions return conditions
def get_customer_details(): def get_customer_details():
details = frappe.get_all('Customer', details = frappe.get_all("Customer",
fields=['name', 'customer_name', "customer_group"]) fields=["name", "customer_name", "customer_group"])
customer_details = {} customer_details = {}
for d in details: for d in details:
customer_details.setdefault(d.name, frappe._dict({ customer_details.setdefault(d.name, frappe._dict({
@ -206,15 +208,25 @@ def get_customer_details():
})) }))
return customer_details return customer_details
def get_item_details():
details = frappe.db.get_all("Item",
fields=["item_code", "item_name", "item_group"])
item_details = {}
for d in details:
item_details.setdefault(d.item_code, frappe._dict({
"item_name": d.item_name,
"item_group": d.item_group
}))
return item_details
def get_sales_order_details(company_list, filters): def get_sales_order_details(company_list, filters):
conditions = get_conditions(filters) conditions = get_conditions(filters)
return frappe.db.sql(""" return frappe.db.sql("""
SELECT SELECT
so_item.item_code, so_item.item_name, so_item.item_group, so_item.item_code, so_item.description, so_item.qty,
so_item.description, so_item.qty, so_item.uom, so_item.uom, so_item.base_rate, so_item.base_amount,
so_item.base_rate, so_item.base_amount, so.name, so.name, so.transaction_date, so.customer,so.territory,
so.transaction_date, so.customer, so.territory,
so.project, so_item.delivered_qty, so.project, so_item.delivered_qty,
so_item.billed_amt, so.company so_item.billed_amt, so.company
FROM FROM

View File

@ -274,7 +274,8 @@ erpnext.company.setup_queries = function(frm) {
["default_employee_advance_account", {"root_type": "Asset"}], ["default_employee_advance_account", {"root_type": "Asset"}],
["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}], ["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}],
["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}], ["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}],
["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}] ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}],
["unrealized_profit_loss_account", {"root_type": "Liability"}]
], function(i, v) { ], function(i, v) {
erpnext.company.set_custom_query(frm, v); erpnext.company.set_custom_query(frm, v);
}); });

View File

@ -46,10 +46,9 @@
"round_off_account", "round_off_account",
"round_off_cost_center", "round_off_cost_center",
"write_off_account", "write_off_account",
"discount_allowed_account",
"discount_received_account",
"exchange_gain_loss_account", "exchange_gain_loss_account",
"unrealized_exchange_gain_loss_account", "unrealized_exchange_gain_loss_account",
"unrealized_profit_loss_account",
"column_break0", "column_break0",
"allow_account_creation_against_child_company", "allow_account_creation_against_child_company",
"default_payable_account", "default_payable_account",
@ -261,14 +260,14 @@
{ {
"fieldname": "create_chart_of_accounts_based_on", "fieldname": "create_chart_of_accounts_based_on",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Create Chart of Accounts Based on", "label": "Create Chart Of Accounts Based On",
"options": "\nStandard Template\nExisting Company" "options": "\nStandard Template\nExisting Company"
}, },
{ {
"depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"", "depends_on": "eval:doc.create_chart_of_accounts_based_on===\"Standard Template\"",
"fieldname": "chart_of_accounts", "fieldname": "chart_of_accounts",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Chart of Accounts Template", "label": "Chart Of Accounts Template",
"no_copy": 1 "no_copy": 1
}, },
{ {
@ -345,18 +344,6 @@
"label": "Write Off Account", "label": "Write Off Account",
"options": "Account" "options": "Account"
}, },
{
"fieldname": "discount_allowed_account",
"fieldtype": "Link",
"label": "Discount Allowed Account",
"options": "Account"
},
{
"fieldname": "discount_received_account",
"fieldtype": "Link",
"label": "Discount Received Account",
"options": "Account"
},
{ {
"fieldname": "exchange_gain_loss_account", "fieldname": "exchange_gain_loss_account",
"fieldtype": "Link", "fieldtype": "Link",
@ -740,6 +727,12 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Default In Transit Warehouse", "label": "Default In Transit Warehouse",
"options": "Warehouse" "options": "Warehouse"
},
{
"fieldname": "unrealized_profit_loss_account",
"fieldtype": "Link",
"label": "Unrealized Profit / Loss Account",
"options": "Account"
} }
], ],
"icon": "fa fa-building", "icon": "fa fa-building",
@ -747,7 +740,7 @@
"image_field": "company_logo", "image_field": "company_logo",
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2020-08-06 00:38:08.311216", "modified": "2020-12-03 12:27:27.085094",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Company", "name": "Company",
@ -808,4 +801,4 @@
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"track_changes": 1 "track_changes": 1
} }

View File

@ -841,6 +841,10 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
} }
}, },
fg_completed_qty: function() {
this.get_items();
},
get_items: function() { get_items: function() {
var me = this; var me = this;
if(!this.frm.doc.fg_completed_qty || !this.frm.doc.bom_no) if(!this.frm.doc.fg_completed_qty || !this.frm.doc.bom_no)
@ -850,6 +854,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
// if work order / bom is mentioned, get items // if work order / bom is mentioned, get items
return this.frm.call({ return this.frm.call({
doc: me.frm.doc, doc: me.frm.doc,
freeze: true,
method: "get_items", method: "get_items",
callback: function(r) { callback: function(r) {
if(!r.exc) refresh_field("items"); if(!r.exc) refresh_field("items");

View File

@ -120,6 +120,7 @@ class StockEntry(StockController):
self.update_transferred_qty() self.update_transferred_qty()
self.update_quality_inspection() self.update_quality_inspection()
self.delete_auto_created_batches() self.delete_auto_created_batches()
self.delete_linked_stock_entry()
if self.purpose == 'Material Transfer' and self.add_to_transit: if self.purpose == 'Material Transfer' and self.add_to_transit:
self.set_material_request_transfer_status('Not Started') self.set_material_request_transfer_status('Not Started')
@ -152,6 +153,12 @@ class StockEntry(StockController):
frappe.throw(_("For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry") frappe.throw(_("For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry")
.format(self.job_card)) .format(self.job_card))
def delete_linked_stock_entry(self):
if self.purpose == "Send to Warehouse":
for d in frappe.get_all("Stock Entry", filters={"docstatus": 0,
"outgoing_stock_entry": self.name, "purpose": "Receive at Warehouse"}):
frappe.delete_doc("Stock Entry", d.name)
def set_transfer_qty(self): def set_transfer_qty(self):
for item in self.get("items"): for item in self.get("items"):
if not flt(item.qty): if not flt(item.qty):
@ -1033,26 +1040,22 @@ class StockEntry(StockController):
wo = frappe.get_doc("Work Order", self.work_order) wo = frappe.get_doc("Work Order", self.work_order)
wo_items = frappe.get_all('Work Order Item', wo_items = frappe.get_all('Work Order Item',
filters={'parent': self.work_order}, filters={'parent': self.work_order},
fields=["item_code", "required_qty", "consumed_qty"] fields=["item_code", "required_qty", "consumed_qty", "transferred_qty"]
) )
work_order_qty = wo.material_transferred_for_manufacturing or wo.qty
for item in wo_items: for item in wo_items:
qty = item.required_qty
item_account_details = get_item_defaults(item.item_code, self.company) item_account_details = get_item_defaults(item.item_code, self.company)
# Take into account consumption if there are any. # Take into account consumption if there are any.
if self.purpose == 'Manufacture':
req_qty_each = flt(item.required_qty / wo.qty) wo_item_qty = item.transferred_qty or item.required_qty
if (flt(item.consumed_qty) != 0):
remaining_qty = flt(item.consumed_qty) - (flt(wo.produced_qty) * req_qty_each) req_qty_each = (
exhaust_qty = req_qty_each * wo.produced_qty (flt(wo_item_qty) - flt(item.consumed_qty)) /
if remaining_qty > exhaust_qty : (flt(work_order_qty) - flt(wo.produced_qty))
if (remaining_qty/(req_qty_each * flt(self.fg_completed_qty))) >= 1: )
qty =0
else: qty = req_qty_each * flt(self.fg_completed_qty)
qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty
else:
qty = req_qty_each * flt(self.fg_completed_qty)
if qty > 0: if qty > 0:
self.add_to_stock_entry_detail({ self.add_to_stock_entry_detail({
@ -1134,13 +1137,15 @@ class StockEntry(StockController):
else: else:
qty = req_qty_each * flt(self.fg_completed_qty) qty = req_qty_each * flt(self.fg_completed_qty)
elif backflushed_materials.get(item.item_code): elif backflushed_materials.get(item.item_code):
for d in backflushed_materials.get(item.item_code): for d in backflushed_materials.get(item.item_code):
if d.get(item.warehouse): if d.get(item.warehouse):
if (qty > req_qty): if (qty > req_qty):
qty = (qty/trans_qty) * flt(self.fg_completed_qty) qty = (qty/trans_qty) * flt(self.fg_completed_qty)
if consumed_qty:
qty -= consumed_qty
if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')): if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')):
qty = frappe.utils.ceil(qty) qty = frappe.utils.ceil(qty)

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestVoiceCallSettings(unittest.TestCase):
pass

View File

@ -0,0 +1,8 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Voice Call Settings', {
// refresh: function(frm) {
// }
});

View File

@ -0,0 +1,124 @@
{
"actions": [],
"autoname": "field:user",
"creation": "2020-12-08 16:52:40.590146",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"user",
"call_receiving_device",
"column_break_3",
"greeting_message",
"agent_busy_message",
"agent_unavailable_message"
],
"fields": [
{
"fieldname": "user",
"fieldtype": "Link",
"in_list_view": 1,
"label": "User",
"options": "User",
"permlevel": 1,
"reqd": 1,
"unique": 1
},
{
"fieldname": "greeting_message",
"fieldtype": "Data",
"label": "Greeting Message"
},
{
"fieldname": "agent_busy_message",
"fieldtype": "Data",
"label": "Agent Busy Message"
},
{
"fieldname": "agent_unavailable_message",
"fieldtype": "Data",
"label": "Agent Unavailable Message"
},
{
"default": "Computer",
"fieldname": "call_receiving_device",
"fieldtype": "Select",
"label": "Call Receiving Device",
"options": "Computer\nPhone"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-12-14 18:49:34.600194",
"modified_by": "Administrator",
"module": "Telephony",
"name": "Voice Call Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"delete": 1,
"email": 1,
"export": 1,
"permlevel": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"delete": 1,
"email": 1,
"export": 1,
"permlevel": 2,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"export": 1,
"permlevel": 2,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"share": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class VoiceCallSettings(Document):
pass

View File

@ -20,10 +20,10 @@
{%- if (charge.tax_amount or doc.flags.print_taxes_with_zero_amount) and (not charge.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%} {%- if (charge.tax_amount or doc.flags.print_taxes_with_zero_amount) and (not charge.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
<div class="row"> <div class="row">
<div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}"> <div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}">
<label>{{ charge.get_formatted("description") }}</label></div> <label>{{ charge.get_formatted("description") }}</label>
</div>
<div class="col-xs-7 text-right"> <div class="col-xs-7 text-right">
{{ frappe.format_value(frappe.utils.flt(charge.tax_amount), {{ charge.get_formatted('tax_amount', doc) }}
table_meta.get_field("tax_amount"), doc, currency=doc.currency) }}
</div> </div>
</div> </div>
{%- endif -%} {%- endif -%}