diff --git a/netforce_clinic/layouts/clinic_cust_invoice_form.xml b/netforce_clinic/layouts/clinic_cust_invoice_form.xml
index 175ea6c..b0c7f41 100644
--- a/netforce_clinic/layouts/clinic_cust_invoice_form.xml
+++ b/netforce_clinic/layouts/clinic_cust_invoice_form.xml
@@ -4,6 +4,6 @@
-
+
diff --git a/netforce_clinic/layouts/clinic_hd_case_form.xml b/netforce_clinic/layouts/clinic_hd_case_form.xml
index 8826fa1..5c31cf4 100644
--- a/netforce_clinic/layouts/clinic_hd_case_form.xml
+++ b/netforce_clinic/layouts/clinic_hd_case_form.xml
@@ -57,6 +57,7 @@
+
@@ -110,12 +111,9 @@
-
-
-
-
+
-
+
diff --git a/netforce_clinic/models/account_invoice.py b/netforce_clinic/models/account_invoice.py
index cb48057..49c8213 100644
--- a/netforce_clinic/models/account_invoice.py
+++ b/netforce_clinic/models/account_invoice.py
@@ -76,4 +76,208 @@ class AccountInvoice(Model):
}
get_model("account.fixed.asset").create(vals)
+ def post(self,ids,context={}):
+ t0=time.time()
+ settings=get_model("settings").browse(1)
+ for obj in self.browse(ids):
+ obj.check_related()
+ if abs(obj.amount_total)<0.001:
+ raise Exception("Invoice total is zero")
+ partner=obj.partner_id
+ if obj.type=="out":
+ account_id=partner.account_receivable_id.id or settings.account_receivable_id.id
+ if not account_id:
+ raise Exception("Account receivable not found")
+ elif obj.type=="in":
+ account_id=partner.account_payable_id.id or settings.account_payable_id.id
+ if not account_id:
+ raise Exception("Account payable not found")
+ sign=obj.type=="out" and 1 or -1
+ if obj.inv_type=="credit":
+ sign*=-1
+ #XXX ratchawat
+ if not obj.patient_partner_id:
+ obj.write({"account_id": account_id})
+ if obj.type=="out":
+ desc="Sale; "+partner.name
+ elif obj.type=="in":
+ desc="Purchase; "+partner.name
+ if obj.type=="out":
+ journal_id=settings.sale_journal_id.id
+ if not journal_id:
+ raise Exception("Sales journal not found")
+ elif obj.type=="in":
+ journal_id=settings.purchase_journal_id.id
+ if not journal_id:
+ raise Exception("Purchases journal not found")
+ if obj.currency_rate:
+ currency_rate=obj.currency_rate
+ else:
+ if obj.currency_id.id==settings.currency_id.id:
+ currency_rate=1.0
+ else:
+ rate_from=obj.currency_id.get_rate(date=obj.date)
+ if not rate_from:
+ raise Exception("Missing currency rate for %s"%obj.currency_id.code)
+ rate_to=settings.currency_id.get_rate(date=obj.date)
+ if not rate_to:
+ raise Exception("Missing currency rate for %s"%settings.currency_id.code)
+ currency_rate=rate_from/rate_to
+ obj.write({"currency_rate":currency_rate})
+ move_vals={
+ "journal_id": journal_id,
+ "number": obj.number,
+ "date": obj.date,
+ "ref": obj.ref,
+ "narration": desc,
+ "related_id": "account.invoice,%s"%obj.id,
+ "company_id": obj.company_id.id,
+ }
+ lines=[]
+ taxes={}
+ tax_nos=[]
+ t01=time.time()
+ total_amt=0.0
+ total_base=0.0
+ total_tax=0.0
+ for line in obj.lines:
+ cur_amt=get_model("currency").convert(line.amount,obj.currency_id.id,settings.currency_id.id,rate=currency_rate)
+ total_amt+=cur_amt
+ tax_id=line.tax_id
+ if tax_id and obj.tax_type!="no_tax":
+ base_amt=get_model("account.tax.rate").compute_base(tax_id,cur_amt,tax_type=obj.tax_type)
+ tax_comps=get_model("account.tax.rate").compute_taxes(tax_id,base_amt,when="invoice")
+ for comp_id,tax_amt in tax_comps.items():
+ tax_vals=taxes.setdefault(comp_id,{"tax_amt":0,"base_amt":0})
+ tax_vals["tax_amt"]+=tax_amt
+ tax_vals["base_amt"]+=base_amt
+ total_tax+=tax_amt
+ else:
+ base_amt=cur_amt
+ total_base+=base_amt
+ acc_id=line.account_id.id
+ if not acc_id:
+ raise Exception("Missing line account for invoice line '%s'"%line.description)
+ amt=base_amt*sign
+ line_vals={
+ "description": line.description,
+ "account_id": acc_id,
+ "credit": amt>0 and amt or 0,
+ "debit": amt<0 and -amt or 0,
+ "track_id": line.track_id.id,
+ "track2_id": line.track2_id.id,
+ "partner_id": partner.id,
+ }
+ lines.append(line_vals)
+ for comp_id,tax_vals in taxes.items():
+ comp=get_model("account.tax.component").browse(comp_id)
+ acc_id=comp.account_id.id
+ if not acc_id:
+ raise Exception("Missing account for tax component %s"%comp.name)
+ amt=tax_vals["tax_amt"]*sign
+ line_vals={
+ "description": desc,
+ "account_id": acc_id,
+ "credit": amt>0 and amt or 0,
+ "debit": amt<0 and -amt or 0,
+ "tax_comp_id": comp_id,
+ "tax_base": tax_vals["base_amt"],
+ "partner_id": partner.id,
+ "invoice_id": obj.id,
+ }
+ if comp.type in ("vat","vat_exempt"):
+ if obj.type=="out":
+ if obj.tax_no:
+ tax_no=obj.tax_no
+ else:
+ tax_no=self.gen_tax_no(exclude=tax_nos,context={"date":obj.date})
+ tax_nos.append(tax_no)
+ obj.write({"tax_no":tax_no})
+ line_vals["tax_no"]=tax_no
+ elif obj.type=="in":
+ line_vals["tax_no"]=obj.tax_no
+ lines.append(line_vals)
+ if obj.tax_type=="tax_in":
+ rounding=total_amt-(total_base+total_tax)
+ if abs(rounding)>0.00499: # XXX
+ amt=rounding*sign
+ if not settings.rounding_account_id.id:
+ raise Exception("Missing rounding account in financial settings")
+ line_vals={
+ "description": desc,
+ "account_id": settings.rounding_account_id.id,
+ "credit": amt>0 and amt or 0,
+ "debit": amt<0 and -amt or 0,
+ "partner_id": partner.id,
+ "invoice_id": obj.id,
+ }
+ lines.append(line_vals)
+ t02=time.time()
+ dt01=(t02-t01)*1000
+ print("post dt01",dt01)
+ groups={}
+ keys=["description","account_id","track_id","tax_comp_id","partner_id","invoice_id","reconcile_id"]
+ for line in lines:
+ key_val=tuple(line.get(k) for k in keys)
+ if key_val in groups:
+ group=groups[key_val]
+ group["debit"]+=line["debit"]
+ group["credit"]+=line["credit"]
+ if line.get("tax_base"):
+ if "tax_base" not in group:
+ group["tax_base"]=0
+ group["tax_base"]+=line["tax_base"]
+ else:
+ groups[key_val]=line.copy()
+ group_lines=sorted(groups.values(),key=lambda l: (l["debit"],l["credit"]))
+ for line in group_lines:
+ amt=line["debit"]-line["credit"]
+ amt=get_model("currency").round(settings.currency_id.id,amt)
+ if amt>=0:
+ line["debit"]=amt
+ line["credit"]=0
+ else:
+ line["debit"]=0
+ line["credit"]=-amt
+ amt=0
+ for line in group_lines:
+ amt-=line["debit"]-line["credit"]
+ line_vals={
+ "description": desc,
+ "account_id": account_id,
+ "debit": amt>0 and amt or 0,
+ "credit": amt<0 and -amt or 0,
+ "due_date": obj.due_date,
+ "partner_id": partner.id,
+ }
+
+ #XXX ratchawat: get receive account from invoice directly
+ if obj.patient_partner_id:
+ line_vals['account_id']=obj.account_id.id
+
+ acc=get_model("account.account").browse(account_id)
+ if acc.currency_id.id!=settings.currency_id.id:
+ if acc.currency_id.id!=obj.currency_id.id:
+ raise Exception("Invalid account currency for this invoice: %s"%acc.code)
+ line_vals["amount_cur"]=obj.amount_total*sign
+ move_vals["lines"]=[("create",line_vals)]
+ move_vals["lines"]+=[("create",vals) for vals in group_lines]
+ t03=time.time()
+ dt02=(t03-t02)*1000
+ print("post dt02",dt02)
+ move_id=get_model("account.move").create(move_vals)
+ t04=time.time()
+ dt03=(t04-t03)*1000
+ print("post dt03",dt03)
+ get_model("account.move").post([move_id])
+ t05=time.time()
+ dt04=(t05-t04)*1000
+ print("post dt04",dt04)
+ obj.write({"move_id":move_id,"state":"waiting_payment"})
+ t06=time.time()
+ dt05=(t06-t05)*1000
+ print("post dt05",dt05)
+ t1=time.time()
+ dt=(t1-t0)*1000
+ print("invoice.post <<< %d ms"%dt)
AccountInvoice.register()
diff --git a/netforce_clinic/models/hd_case.py b/netforce_clinic/models/hd_case.py
index 8ad1c6b..987c088 100644
--- a/netforce_clinic/models/hd_case.py
+++ b/netforce_clinic/models/hd_case.py
@@ -635,7 +635,7 @@ class HDCase(Model):
else:
acc=prod_acc(prod.id,stype.id,'cash')
account_id=acc.get("ar_credit_id",None)
- ar_debit_id=acc.get("ar_debit_id",None)
+ ar_debit_id=acc.get("ar_debit_id",None) # account receiveable
if not account_id:
raise Exception("No Income Credit Account for product [%s] %s"%(prod.code, prod.name))
if not ar_debit_id:
@@ -668,58 +668,78 @@ class HDCase(Model):
patient=obj.patient_id
patient_partner=patient.partner_id
+
+ def group_invoice_line(invoice_lines):
+ invoice_vals={}
+ for mode,invoice_line in invoice_lines:
+ ar_debit_id=invoice_line['ar_debit_id']
+ if not invoice_vals.get(ar_debit_id):
+ invoice_vals[ar_debit_id]=[]
+ del invoice_line['ar_debit_id']
+ invoice_vals[ar_debit_id].append((mode,invoice_line))
+ return invoice_vals
if rmb_lines:
ptype=patient.type_id
partner=ptype.contact_id
if not partner:
raise Exception("No contact for patient type %s"%obj.ptype.name)
- vals={
- "type": "out",
- "inv_type": "invoice",
- "tax_type": "tax_in",
- 'date': obj.date,
- 'due_date': due_date,
- "ref": '%s (%s)'%(patient.name or '',patient.number or ''),
- 'department_id': obj.department_id.id,
- "related_id": "clinic.hd.case,%s"%obj.id,
- "currency_id": currency_id,
- "company_id": company_id,
- "lines": [],
- "company_id": company_id,
- }
- vals["partner_id"]=partner.id
- vals['lines']=rmb_lines
- if patient_partner:
- vals['patient_partner_id']=patient_partner.id,
+ #vals['lines']=rmb_lines
+ # reset account receiveable
+ # group invoice line by account receiveable
+ invoices=group_invoice_line(rmb_lines)
if obj.branch_id:
context['branch_id']=obj.branch_id.id
- get_model("account.invoice").create(vals,context=context)
-
+ for account_receiveable_id, lines in invoices.items():
+ vals={
+ "type": "out",
+ "inv_type": "invoice",
+ "tax_type": "tax_in",
+ 'date': obj.date,
+ 'due_date': due_date,
+ "ref": '%s (%s)'%(patient.name or '',patient.number or ''),
+ 'department_id': obj.department_id.id,
+ "related_id": "clinic.hd.case,%s"%obj.id,
+ "currency_id": currency_id,
+ "company_id": company_id,
+ "lines": [],
+ "company_id": company_id,
+ "partner_id": partner.id,
+ 'patient_partner_id':patient_partner.id,
+ 'account_id':account_receiveable_id ,
+ 'lines':lines,
+ }
+ inv_id=get_model("account.invoice").create(vals,context=context)
+ inv=get_model("account.invoice").browse(inv_id)
+
+ # click button Credit from popup of button Pay
if normb_lines and is_credit:
partner=patient.partner_id
if not partner:
raise Exception("No contact for this patient %s"%obj.partner.name)
context['branch_id']=obj.branch_id.id
- number=self._get_number_invoice_noclaim(context=context)
- vals={
- 'number': number,
- "type": "out",
- "inv_type": "invoice",
- "tax_type": "tax_in",
- 'due_date': due_date,
- "ref": '%s (%s)'%(patient.name or '',patient.number or ''),
- "related_id": "clinic.hd.case,%s"%obj.id,
- "currency_id": currency_id,
- "company_id": company_id,
- "lines": [],
- "company_id": company_id,
- 'partner_id':partner.id,
- }
- vals['lines']=normb_lines
- if patient_partner:
- vals['patient_partner_id']=patient_partner.id,
- get_model("account.invoice").create(vals,context)
+
+ invoices=group_invoice_line(normb_lines)
+ for account_receiveable_id, lines in invoices.items():
+ number=self._get_number_invoice_noclaim(context=context)
+ vals={
+ 'number': number,
+ "type": "out",
+ "inv_type": "invoice",
+ "tax_type": "tax_in",
+ 'due_date': due_date,
+ "ref": '%s (%s)'%(patient.name or '',patient.number or ''),
+ "related_id": "clinic.hd.case,%s"%obj.id,
+ "currency_id": currency_id,
+ "company_id": company_id,
+ "lines": [],
+ "company_id": company_id,
+ 'partner_id':partner.id,
+ 'patient_partner_id':patient_partner.id,
+ 'account_id': account_receiveable_id,
+ }
+ vals['lines']=lines
+ get_model("account.invoice").create(vals,context)
obj.make_pickings()
# prevent douplicate create invoice & picking